genesis-compiler 1.2.28 → 1.3.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.
- package/README.md +84 -9
- package/docs/prompt-integration.md +108 -6
- package/package.json +4 -1
- package/plugins/genesis/.codex-plugin/plugin.json +1 -1
- package/plugins/opencode/project-guidance.js +41 -5
- package/prompts/start-existing-uninitialized.txt +8 -0
- package/prompts/start-existing.txt +23 -0
- package/prompts/start-new.txt +27 -0
- package/prompts/start.txt +3 -85
- package/src/cli.js +124 -7
- package/src/index/assets.js +3 -0
- package/src/index/check.js +10 -0
- package/src/index/codex-hooks.js +21 -12
- package/src/index/collaboration.js +371 -0
- package/src/index/context.js +3 -8
- package/src/index/contracts.js +3 -0
- package/src/index/host-context-resolver.js +73 -0
- package/src/index/init.js +4 -0
- package/src/index/migration.js +2 -0
- package/src/index/opencode-plugin.js +1 -1
- package/src/index/paths.js +2 -0
- package/src/index/project-files.js +12 -0
- package/src/index/project-format.js +3 -1
- package/src/index/prompt.js +120 -45
- package/src/index/session-context.js +115 -25
- package/src/index/stack.js +2 -2
- package/src/index.js +27 -2
package/src/cli.js
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getContext,
|
|
10
10
|
indexCodebase,
|
|
11
11
|
initialize,
|
|
12
|
+
inspectCollaboration,
|
|
12
13
|
inspectEngineering,
|
|
13
14
|
inspectEnvironment,
|
|
14
15
|
inspectStackSection,
|
|
@@ -16,6 +17,7 @@ import {
|
|
|
16
17
|
listEngineeringProfiles,
|
|
17
18
|
listStackPieces,
|
|
18
19
|
migrate,
|
|
20
|
+
setCollaboration,
|
|
19
21
|
setEngineeringProfile,
|
|
20
22
|
verify,
|
|
21
23
|
} from './index.js';
|
|
@@ -24,11 +26,17 @@ import {
|
|
|
24
26
|
} from './index/codex-hooks.js';
|
|
25
27
|
import { engineeringProfile, readEngineeringBaseline } from './index/engineering.js';
|
|
26
28
|
import { asDiagnostic, fail } from './index/errors.js';
|
|
29
|
+
import {
|
|
30
|
+
HOST_CONTEXT_INPUT_ENV,
|
|
31
|
+
HOST_CONTEXT_RESOLVER_ENV,
|
|
32
|
+
SESSION_CONTEXT_INSTALLED_ENV,
|
|
33
|
+
resolveConfiguredHostContext,
|
|
34
|
+
} from './index/host-context-resolver.js';
|
|
27
35
|
import {
|
|
28
36
|
inspectProjectFormat,
|
|
29
37
|
projectFormatDiagnostic,
|
|
30
38
|
} from './index/project-format.js';
|
|
31
|
-
import { projectSessionContext } from './index/session-context.js';
|
|
39
|
+
import { projectSessionContext, projectTurnContext } from './index/session-context.js';
|
|
32
40
|
import { installedFirstPartyStackPackages } from './index/stack-catalog.js';
|
|
33
41
|
import { readStack } from './index/stack.js';
|
|
34
42
|
|
|
@@ -36,6 +44,8 @@ const USAGE = `Usage:
|
|
|
36
44
|
genesis init
|
|
37
45
|
genesis adopt [product guidance...]
|
|
38
46
|
genesis codex install
|
|
47
|
+
genesis collaboration show
|
|
48
|
+
genesis collaboration set <tone> <response-length> <experience> <explanation-style> [project requirements...]
|
|
39
49
|
genesis engineering list
|
|
40
50
|
genesis engineering show [profile]
|
|
41
51
|
genesis engineering set <profile>
|
|
@@ -63,7 +73,7 @@ prompt to the agent you already use. Review all edits through the ordinary Git
|
|
|
63
73
|
diff, then run genesis verify for the Stack's concrete checks.
|
|
64
74
|
`;
|
|
65
75
|
|
|
66
|
-
const COMMANDS = new Set(['adopt', 'check', 'codex', 'context', 'engineering', 'hook', 'index', 'init', 'inspect', 'migrate', 'prompt', 'stack', 'verify']);
|
|
76
|
+
const COMMANDS = new Set(['adopt', 'check', 'codex', 'collaboration', 'context', 'engineering', 'hook', 'index', 'init', 'inspect', 'migrate', 'prompt', 'stack', 'verify']);
|
|
67
77
|
|
|
68
78
|
function parseCommand(argv) {
|
|
69
79
|
if (argv.length === 0 || argv.includes('--help') || argv.includes('-h') || argv[0] === 'help') {
|
|
@@ -97,7 +107,21 @@ function parseCommand(argv) {
|
|
|
97
107
|
if (options.task !== undefined && command !== 'prompt') {
|
|
98
108
|
fail('CLI_OPTION_NOT_APPLICABLE', `Option --task is not applicable to ${command}.`);
|
|
99
109
|
}
|
|
100
|
-
if (command === '
|
|
110
|
+
if (command === 'collaboration') {
|
|
111
|
+
const [action] = operands;
|
|
112
|
+
if (!['show', 'set'].includes(action)) {
|
|
113
|
+
fail('CLI_COLLABORATION_ACTION_REQUIRED', 'Command collaboration requires show or set.');
|
|
114
|
+
}
|
|
115
|
+
if (action === 'show' && operands.length !== 1) {
|
|
116
|
+
fail('CLI_EXTRA_ARGUMENT', 'Command collaboration show accepts no extra arguments.');
|
|
117
|
+
}
|
|
118
|
+
if (action === 'set' && operands.length < 5) {
|
|
119
|
+
fail(
|
|
120
|
+
'CLI_COLLABORATION_VALUES_REQUIRED',
|
|
121
|
+
'Command collaboration set requires tone, response length, experience, and explanation style.',
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
} else if (command === 'stack') {
|
|
101
125
|
if (!['list', 'add'].includes(operands[0])) {
|
|
102
126
|
fail('CLI_STACK_ACTION_REQUIRED', 'Command stack requires list or add.');
|
|
103
127
|
}
|
|
@@ -136,8 +160,8 @@ function parseCommand(argv) {
|
|
|
136
160
|
'Command inspect requires environment or section <name>.',
|
|
137
161
|
);
|
|
138
162
|
}
|
|
139
|
-
} else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session'].includes(operands[0]))) {
|
|
140
|
-
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session.');
|
|
163
|
+
} else if (command === 'hook' && (operands.length !== 1 || !['discover', 'session', 'turn'].includes(operands[0]))) {
|
|
164
|
+
fail('CLI_HOOK_ACTION_REQUIRED', 'Command hook requires exactly one of: discover, session, turn.');
|
|
141
165
|
} else if (!['adopt', 'context', 'hook', 'index', 'inspect', 'prompt'].includes(command) && operands.length > 0) {
|
|
142
166
|
fail('CLI_EXTRA_ARGUMENT', `Command ${command} accepts no arguments.`);
|
|
143
167
|
}
|
|
@@ -159,6 +183,7 @@ function writeCheck(result) {
|
|
|
159
183
|
const version = format.projectVersion === null ? format.status : format.projectVersion;
|
|
160
184
|
line(process.stdout, `Project format: ${version} (${format.status}; CLI supports ${format.supportedVersion})`);
|
|
161
185
|
line(process.stdout, `Blueprint: ${result.blueprint}`);
|
|
186
|
+
line(process.stdout, `Collaboration approach: ${result.collaboration}`);
|
|
162
187
|
line(process.stdout, `Engineering approach: ${result.engineering}`);
|
|
163
188
|
line(process.stdout, `Stack: ${result.stack}`);
|
|
164
189
|
line(process.stdout, `Agent Skills: ${result.skills}`);
|
|
@@ -174,6 +199,21 @@ function writeCheck(result) {
|
|
|
174
199
|
line(process.stdout, `Check: ${result.status}`);
|
|
175
200
|
}
|
|
176
201
|
|
|
202
|
+
function writeCollaboration(result) {
|
|
203
|
+
line(process.stdout, `Tone: ${result.tone}`);
|
|
204
|
+
line(process.stdout, `Response length: ${result.responseLength}`);
|
|
205
|
+
line(process.stdout, `Assumed experience: ${result.experience}`);
|
|
206
|
+
line(process.stdout, `Explanation style: ${result.explanationStyle}`);
|
|
207
|
+
if (result.requirements) {
|
|
208
|
+
line(process.stdout, 'Project requirements:');
|
|
209
|
+
line(process.stdout, result.requirements);
|
|
210
|
+
}
|
|
211
|
+
if (result.action === 'set') {
|
|
212
|
+
namedItems('Changed files', result.changedFiles);
|
|
213
|
+
line(process.stdout, `collaboration: ${result.status}`);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
177
217
|
function writeEngineering(result) {
|
|
178
218
|
if (result.action === 'list') {
|
|
179
219
|
for (const profile of result.profiles) {
|
|
@@ -236,6 +276,10 @@ function writeResult(command, result) {
|
|
|
236
276
|
writeEngineering(result);
|
|
237
277
|
return;
|
|
238
278
|
}
|
|
279
|
+
if (command === 'collaboration') {
|
|
280
|
+
writeCollaboration(result);
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
239
283
|
if (command === 'context') {
|
|
240
284
|
process.stdout.write(result.context.endsWith('\n') ? result.context : `${result.context}\n`);
|
|
241
285
|
return;
|
|
@@ -263,7 +307,7 @@ function writeResult(command, result) {
|
|
|
263
307
|
return;
|
|
264
308
|
}
|
|
265
309
|
if (command === 'hook') {
|
|
266
|
-
if (['discover', 'session'].includes(result.kind) && result.output) line(process.stdout, result.output);
|
|
310
|
+
if (['discover', 'session', 'turn'].includes(result.kind) && result.output) line(process.stdout, result.output);
|
|
267
311
|
return;
|
|
268
312
|
}
|
|
269
313
|
if (command === 'stack' && Array.isArray(result.pieces)) {
|
|
@@ -292,6 +336,32 @@ function writeResult(command, result) {
|
|
|
292
336
|
line(process.stdout, `${command}: ${result.status}`);
|
|
293
337
|
}
|
|
294
338
|
|
|
339
|
+
async function hookSessionId(environment = process.env) {
|
|
340
|
+
if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return null;
|
|
341
|
+
let source = environment[HOST_CONTEXT_INPUT_ENV];
|
|
342
|
+
if (source === undefined && !process.stdin.isTTY) {
|
|
343
|
+
const chunks = [];
|
|
344
|
+
let bytes = 0;
|
|
345
|
+
for await (const chunk of process.stdin) {
|
|
346
|
+
bytes += Buffer.byteLength(chunk);
|
|
347
|
+
if (bytes > 16 * 1024) {
|
|
348
|
+
fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input exceeds 16 KiB.');
|
|
349
|
+
}
|
|
350
|
+
chunks.push(chunk);
|
|
351
|
+
}
|
|
352
|
+
source = Buffer.concat(chunks).toString('utf8');
|
|
353
|
+
}
|
|
354
|
+
if (!String(source || '').trim()) return null;
|
|
355
|
+
let input;
|
|
356
|
+
try {
|
|
357
|
+
input = JSON.parse(source);
|
|
358
|
+
} catch {
|
|
359
|
+
fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input must contain JSON.');
|
|
360
|
+
}
|
|
361
|
+
const sessionId = input?.sessionId ?? input?.session_id;
|
|
362
|
+
return typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
|
|
363
|
+
}
|
|
364
|
+
|
|
295
365
|
async function cliStackPackages(projectRoot, supplied) {
|
|
296
366
|
if (supplied.length > 0) return supplied;
|
|
297
367
|
try {
|
|
@@ -312,6 +382,22 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
312
382
|
return adoptProject({ projectRoot, request: operands.join(' '), stackPackages });
|
|
313
383
|
}
|
|
314
384
|
if (command === 'codex') return installCodex();
|
|
385
|
+
if (command === 'collaboration') {
|
|
386
|
+
if (operands[0] === 'show') {
|
|
387
|
+
return { action: 'show', ...await inspectCollaboration({ projectRoot }) };
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
action: 'set',
|
|
391
|
+
...await setCollaboration({
|
|
392
|
+
projectRoot,
|
|
393
|
+
tone: operands[1],
|
|
394
|
+
responseLength: operands[2],
|
|
395
|
+
experience: operands[3],
|
|
396
|
+
explanationStyle: operands[4],
|
|
397
|
+
...(operands.length > 5 ? { requirements: operands.slice(5).join(' ') } : {}),
|
|
398
|
+
}),
|
|
399
|
+
};
|
|
400
|
+
}
|
|
315
401
|
if (command === 'engineering') {
|
|
316
402
|
if (operands[0] === 'list') {
|
|
317
403
|
return { action: 'list', status: 'ok', profiles: await listEngineeringProfiles() };
|
|
@@ -379,7 +465,38 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
379
465
|
if (operands[0] === 'discover') {
|
|
380
466
|
return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
|
|
381
467
|
}
|
|
382
|
-
|
|
468
|
+
const scope = operands[0];
|
|
469
|
+
if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
|
|
470
|
+
return { kind: 'session', status: 'ready', output: '' };
|
|
471
|
+
}
|
|
472
|
+
const providerSessionId = await hookSessionId();
|
|
473
|
+
const hostContext = await resolveConfiguredHostContext({
|
|
474
|
+
projectRoot,
|
|
475
|
+
providerSessionId,
|
|
476
|
+
scope,
|
|
477
|
+
});
|
|
478
|
+
if (scope === 'turn') {
|
|
479
|
+
return hostContext === null
|
|
480
|
+
? { kind: 'turn', status: 'ready', output: '' }
|
|
481
|
+
: {
|
|
482
|
+
kind: 'turn',
|
|
483
|
+
...await projectTurnContext({
|
|
484
|
+
hostDriver: () => hostContext,
|
|
485
|
+
hostDriverInput: { scope: 'turn' },
|
|
486
|
+
}),
|
|
487
|
+
};
|
|
488
|
+
}
|
|
489
|
+
return {
|
|
490
|
+
kind: 'session',
|
|
491
|
+
...await projectSessionContext({
|
|
492
|
+
...(hostContext === null ? {} : {
|
|
493
|
+
hostDriver: () => hostContext,
|
|
494
|
+
hostDriverInput: { scope: 'session' },
|
|
495
|
+
}),
|
|
496
|
+
projectRoot,
|
|
497
|
+
stackPackages,
|
|
498
|
+
}),
|
|
499
|
+
};
|
|
383
500
|
}
|
|
384
501
|
if (command === 'verify') {
|
|
385
502
|
return verify({
|
package/src/index/assets.js
CHANGED
|
@@ -3,6 +3,9 @@ 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
|
+
'start-new': new URL('prompts/start-new.txt', root),
|
|
7
|
+
'start-existing': new URL('prompts/start-existing.txt', root),
|
|
8
|
+
'start-existing-uninitialized': new URL('prompts/start-existing-uninitialized.txt', root),
|
|
6
9
|
adopt: new URL('prompts/adopt.txt', root),
|
|
7
10
|
work: new URL('prompts/work.txt', root),
|
|
8
11
|
describe: new URL('prompts/describe.txt', root),
|
package/src/index/check.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readBlueprint } from './blueprint.js';
|
|
2
|
+
import { readCollaboration } from './collaboration.js';
|
|
2
3
|
import { inspectProjectSkills } from './agent-skills.js';
|
|
3
4
|
import { readStack } from './stack.js';
|
|
4
5
|
import { asDiagnostic } from './errors.js';
|
|
@@ -19,6 +20,7 @@ function invalidResult(area, error, projectFormat) {
|
|
|
19
20
|
status: 'invalid',
|
|
20
21
|
projectFormat,
|
|
21
22
|
blueprint: area === 'blueprint' ? 'invalid' : 'valid',
|
|
23
|
+
collaboration: area === 'collaboration' ? 'invalid' : 'unknown',
|
|
22
24
|
engineering: area === 'engineering' ? 'invalid' : 'unknown',
|
|
23
25
|
stack: area === 'stack' ? 'invalid' : 'unknown',
|
|
24
26
|
skills: area === 'skills' ? 'invalid' : 'unknown',
|
|
@@ -43,6 +45,7 @@ function formatMismatchResult(projectFormat) {
|
|
|
43
45
|
status,
|
|
44
46
|
projectFormat,
|
|
45
47
|
blueprint: 'unknown',
|
|
48
|
+
collaboration: 'unknown',
|
|
46
49
|
engineering: 'unknown',
|
|
47
50
|
stack: 'unknown',
|
|
48
51
|
skills: 'unknown',
|
|
@@ -75,7 +78,13 @@ export async function checkProject({
|
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
let stack;
|
|
81
|
+
let collaboration;
|
|
78
82
|
let engineering;
|
|
83
|
+
try {
|
|
84
|
+
collaboration = await readCollaboration(root);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
return invalidResult('collaboration', error, projectFormat);
|
|
87
|
+
}
|
|
79
88
|
try {
|
|
80
89
|
engineering = await readEngineering(root);
|
|
81
90
|
} catch (error) {
|
|
@@ -154,6 +163,7 @@ export async function checkProject({
|
|
|
154
163
|
: needsAttention ? 'attention' : 'ok',
|
|
155
164
|
projectFormat,
|
|
156
165
|
blueprint: 'valid',
|
|
166
|
+
collaboration: collaboration.status,
|
|
157
167
|
engineering: engineering.status,
|
|
158
168
|
stack: 'valid',
|
|
159
169
|
skills: skills.status,
|
package/src/index/codex-hooks.js
CHANGED
|
@@ -3,13 +3,12 @@ import path from 'node:path';
|
|
|
3
3
|
|
|
4
4
|
import { GenesisError } from './errors.js';
|
|
5
5
|
import { gitContext } from './git.js';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import { normalizeRelative, pathState, writeFileAtomic } from './utils.js';
|
|
6
|
+
import { classifyProjectKind } from './project-files.js';
|
|
7
|
+
import { pathState, writeFileAtomic } from './utils.js';
|
|
9
8
|
|
|
10
9
|
const HOOKS_PATH = '.codex/hooks.json';
|
|
11
10
|
const LEGACY_GENESIS_HOOKS_DESCRIPTION = 'Genesis project hooks.';
|
|
12
|
-
const PROJECT_HOOK_ACTIONS = ['session', 'begin', 'stop', 'end'];
|
|
11
|
+
const PROJECT_HOOK_ACTIONS = ['session', 'turn', 'begin', 'stop', 'end'];
|
|
13
12
|
|
|
14
13
|
function hookCommand(action) {
|
|
15
14
|
const local = `const {runCli}=await import('genesis-compiler/cli');process.exitCode=await runCli(['hook','${action}'])`;
|
|
@@ -31,6 +30,19 @@ const SESSION_HOOK = {
|
|
|
31
30
|
},
|
|
32
31
|
};
|
|
33
32
|
|
|
33
|
+
const TURN_HOOK = {
|
|
34
|
+
event: 'UserPromptSubmit',
|
|
35
|
+
group: {
|
|
36
|
+
hooks: [{
|
|
37
|
+
type: 'command',
|
|
38
|
+
command: hookCommand('turn'),
|
|
39
|
+
commandWindows: 'genesis hook turn',
|
|
40
|
+
timeout: 5,
|
|
41
|
+
additionalContextLimit: 512,
|
|
42
|
+
}],
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
|
|
34
46
|
const OBSOLETE_PROJECT_HOOK_COMMANDS = new Set(PROJECT_HOOK_ACTIONS.flatMap((action) => [
|
|
35
47
|
hookCommand(action),
|
|
36
48
|
`genesis hook ${action}`,
|
|
@@ -84,26 +96,23 @@ export async function installCodexHooks({ projectRoot } = {}) {
|
|
|
84
96
|
removeGenesisProjectHooks(value.hooks);
|
|
85
97
|
const groups = value.hooks[SESSION_HOOK.event] ||= [];
|
|
86
98
|
groups.push(SESSION_HOOK.group);
|
|
99
|
+
const turnGroups = value.hooks[TURN_HOOK.event] ||= [];
|
|
100
|
+
turnGroups.push(TURN_HOOK.group);
|
|
87
101
|
const rendered = `${JSON.stringify(value, null, 2)}\n`;
|
|
88
102
|
if (source === rendered) return { status: 'unchanged', changedFiles: [] };
|
|
89
103
|
await writeFileAtomic(location, rendered);
|
|
90
104
|
return { status: 'updated', changedFiles: [HOOKS_PATH] };
|
|
91
105
|
}
|
|
92
106
|
|
|
93
|
-
function zeroPaths(buffer) {
|
|
94
|
-
return [...new Set(buffer.toString('utf8').split('\0').map(normalizeRelative).filter(Boolean))];
|
|
95
|
-
}
|
|
96
|
-
|
|
97
107
|
export async function codexAdoptionRecommendation({ projectRoot = process.cwd() } = {}) {
|
|
98
108
|
let root;
|
|
99
109
|
try { root = (await gitContext(projectRoot)).repositoryRoot; } catch { return { status: 'not-applicable', output: '' }; }
|
|
100
110
|
if ((await pathState(path.join(root, 'genesis/blueprint.md'))).exists) {
|
|
101
111
|
return { status: 'not-applicable', output: '' };
|
|
102
112
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
if (files.length === 0) return { status: 'not-applicable', output: '' };
|
|
113
|
+
if (await classifyProjectKind({ projectRoot: root }) === 'new') {
|
|
114
|
+
return { status: 'not-applicable', output: '' };
|
|
115
|
+
}
|
|
107
116
|
return {
|
|
108
117
|
status: 'ready',
|
|
109
118
|
output: [
|