genesis-compiler 1.7.1 → 1.7.3
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/docs/prompt-integration.md +26 -0
- package/package.json +1 -1
- package/plugins/opencode/project-guidance.js +65 -18
- package/profiles/engineering/baseline.md +1 -0
- package/src/cli.js +3 -0
- package/src/index/codex-hooks.js +2 -2
- package/src/index/host-context-resolver.js +1 -1
- package/src/index/opencode-plugin.js +7 -0
- package/src/index.js +1 -0
|
@@ -90,6 +90,32 @@ transform place a nonempty turn result separately from authored user text. If
|
|
|
90
90
|
no resolver is configured, both turn paths are empty and the adapters render
|
|
91
91
|
ordinary Genesis session context exactly as before.
|
|
92
92
|
|
|
93
|
+
A host that has no turn contribution can set `GENESIS_TURN_CONTEXT_ENABLED=0`.
|
|
94
|
+
The OpenCode adapter then skips the turn subprocess, and `genesis hook turn`
|
|
95
|
+
returns empty output without calling the resolver. Session guidance remains
|
|
96
|
+
active. Otherwise, OpenCode caches the latest user message's turn result,
|
|
97
|
+
including an empty result, and clears it on compaction or deletion.
|
|
98
|
+
|
|
99
|
+
OpenCode hook failures carry `GENESIS_HOOK_FAILURE` followed by JSON containing
|
|
100
|
+
`scope`, `outcome` (`timeout`, `unavailable`, or `failed`), `elapsedMs`,
|
|
101
|
+
`timeoutMs`, `code`, `signal`, and bounded `stderr`. Known secret values from the
|
|
102
|
+
environment are redacted. The adapter's five-second deadline is independent of
|
|
103
|
+
Codex hook settings. A host can render these fields as an actionable failure
|
|
104
|
+
instead of displaying the runtime stack.
|
|
105
|
+
|
|
106
|
+
The OpenCode session adapter also includes verified native `parentSessionIds`
|
|
107
|
+
in `GENESIS_HOST_CONTEXT_INPUT` for host command shims. The resolver still
|
|
108
|
+
receives the actual child's `providerSessionId`; Genesis does not interpret a
|
|
109
|
+
host's ownership rules.
|
|
110
|
+
|
|
111
|
+
After updating Genesis, hosts can inspect the generated adapter with
|
|
112
|
+
`inspectOpenCodePlugin({ projectRoot })` and call `syncOpenCodePlugin({ projectRoot })`
|
|
113
|
+
from `genesis-compiler` under their ordinary source-write admission. It updates
|
|
114
|
+
only `.opencode/plugins/genesis-project-guidance.js`, returns `status` and
|
|
115
|
+
`changedFiles`, and leaves authored project files and other plugins alone.
|
|
116
|
+
Reload the provider instance to use an adapter that was already loaded. The
|
|
117
|
+
existing `genesis migrate` command also refreshes this generated adapter.
|
|
118
|
+
|
|
93
119
|
Tasks are `start`, `adopt`, `work`, `deslop`, `program`, `blueprint`, `describe`, and `review`.
|
|
94
120
|
`start` is the host-independent first conversation: it classifies an initialized
|
|
95
121
|
project from its selected Stack and Git-visible project paths without building
|
package/package.json
CHANGED
|
@@ -7,21 +7,45 @@ const execute = promisify(execFile);
|
|
|
7
7
|
|
|
8
8
|
const HOST_CONTEXT_INPUT_ENV = "GENESIS_HOST_CONTEXT_INPUT";
|
|
9
9
|
|
|
10
|
-
async function renderContext(projectRoot, sessionId, scope) {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
10
|
+
async function renderContext(projectRoot, sessionId, scope, parentSessionIds = []) {
|
|
11
|
+
if (scope === "turn" && process.env.GENESIS_TURN_CONTEXT_ENABLED === "0") return "";
|
|
12
|
+
const started = Date.now();
|
|
13
|
+
const timeoutMs = 5_000;
|
|
14
|
+
try {
|
|
15
|
+
const { stdout } = await execute(
|
|
16
|
+
"genesis",
|
|
17
|
+
["hook", scope, "--project-root", projectRoot],
|
|
18
|
+
{
|
|
19
|
+
cwd: projectRoot,
|
|
20
|
+
env: {
|
|
21
|
+
...process.env,
|
|
22
|
+
[HOST_CONTEXT_INPUT_ENV]: JSON.stringify({ sessionId, parentSessionIds })
|
|
23
|
+
},
|
|
24
|
+
maxBuffer: 256 * 1024,
|
|
25
|
+
timeout: timeoutMs
|
|
26
|
+
}
|
|
27
|
+
);
|
|
28
|
+
return stdout.trimEnd();
|
|
29
|
+
} catch (error) {
|
|
30
|
+
let stderr = String(error.stderr || "");
|
|
31
|
+
for (const [name, value] of Object.entries(process.env)) {
|
|
32
|
+
if (value && /(?:KEY|TOKEN|SECRET|PASSWORD|CREDENTIAL|DSN|DATABASE_URL)/iu.test(name)) {
|
|
33
|
+
stderr = stderr.replaceAll(value, "[redacted]");
|
|
34
|
+
}
|
|
22
35
|
}
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
const elapsedMs = Date.now() - started;
|
|
37
|
+
const details = {
|
|
38
|
+
scope,
|
|
39
|
+
outcome: error.killed && error.signal === "SIGTERM" && elapsedMs >= timeoutMs ? "timeout" :
|
|
40
|
+
error.code === "ENOENT" ? "unavailable" : "failed",
|
|
41
|
+
elapsedMs,
|
|
42
|
+
timeoutMs,
|
|
43
|
+
code: error.code ?? null,
|
|
44
|
+
signal: error.signal || null,
|
|
45
|
+
stderr: stderr.trim().slice(0, 1_500)
|
|
46
|
+
};
|
|
47
|
+
throw new Error(`GENESIS_HOOK_FAILURE ${JSON.stringify(details)}`);
|
|
48
|
+
}
|
|
25
49
|
}
|
|
26
50
|
|
|
27
51
|
function eventSessionId(event = {}) {
|
|
@@ -30,14 +54,26 @@ function eventSessionId(event = {}) {
|
|
|
30
54
|
).trim();
|
|
31
55
|
}
|
|
32
56
|
|
|
33
|
-
export const GenesisProjectGuidance = async ({ directory, worktree } = {}) => {
|
|
57
|
+
export const GenesisProjectGuidance = async ({ client, directory, worktree } = {}) => {
|
|
34
58
|
const projectRoot = path.resolve(directory || worktree || process.cwd());
|
|
35
59
|
const contexts = new Map();
|
|
60
|
+
const turnContexts = new Map();
|
|
36
61
|
|
|
37
62
|
function contextForSession(sessionId) {
|
|
38
63
|
let context = contexts.get(sessionId);
|
|
39
64
|
if (!context) {
|
|
40
|
-
context =
|
|
65
|
+
context = Promise.resolve().then(async () => {
|
|
66
|
+
const parents = [];
|
|
67
|
+
let id = sessionId;
|
|
68
|
+
while (client && id && parents.length < 32) {
|
|
69
|
+
const result = await client.session.get({ path: { id } });
|
|
70
|
+
if (result.error) throw new Error("Genesis could not read the native session ancestry.");
|
|
71
|
+
id = String(result.data?.parentID || "").trim();
|
|
72
|
+
if (!id || id === sessionId || parents.includes(id)) break;
|
|
73
|
+
parents.push(id);
|
|
74
|
+
}
|
|
75
|
+
return renderContext(projectRoot, sessionId, "session", parents);
|
|
76
|
+
});
|
|
41
77
|
contexts.set(sessionId, context);
|
|
42
78
|
context.catch(() => {
|
|
43
79
|
if (contexts.get(sessionId) === context) contexts.delete(sessionId);
|
|
@@ -50,7 +86,10 @@ export const GenesisProjectGuidance = async ({ directory, worktree } = {}) => {
|
|
|
50
86
|
event: async ({ event } = {}) => {
|
|
51
87
|
if (!["session.compacted", "session.deleted"].includes(event?.type)) return;
|
|
52
88
|
const sessionId = eventSessionId(event);
|
|
53
|
-
if (sessionId)
|
|
89
|
+
if (sessionId) {
|
|
90
|
+
contexts.delete(sessionId);
|
|
91
|
+
turnContexts.delete(sessionId);
|
|
92
|
+
}
|
|
54
93
|
},
|
|
55
94
|
"experimental.chat.system.transform": async (input = {}, output = {}) => {
|
|
56
95
|
const sessionId = String(input.sessionID || input.sessionId || "").trim();
|
|
@@ -67,7 +106,15 @@ export const GenesisProjectGuidance = async ({ directory, worktree } = {}) => {
|
|
|
67
106
|
const sessionId = String(message.info?.sessionID || "").trim();
|
|
68
107
|
const messageId = String(message.info?.id || "").trim();
|
|
69
108
|
if (!sessionId || !messageId || !Array.isArray(message.parts)) return;
|
|
70
|
-
|
|
109
|
+
let current = turnContexts.get(sessionId);
|
|
110
|
+
if (current?.messageId !== messageId) {
|
|
111
|
+
current = { messageId, context: renderContext(projectRoot, sessionId, "turn") };
|
|
112
|
+
turnContexts.set(sessionId, current);
|
|
113
|
+
current.context.catch(() => {
|
|
114
|
+
if (turnContexts.get(sessionId) === current) turnContexts.delete(sessionId);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
const context = await current.context;
|
|
71
118
|
if (!context || message.parts.some((part) => part?.synthetic === true && part?.text === context)) {
|
|
72
119
|
return;
|
|
73
120
|
}
|
|
@@ -6,3 +6,4 @@
|
|
|
6
6
|
- If good software requires material complexity beyond these rules, stop before writing it. Explain the requirement, why the direct design is insufficient, and the smallest added complexity proposed, then ask the user to approve or clarify the tradeoff.
|
|
7
7
|
- During implementation, run the relevant checks and batch related cases when they share setup. Follow the project's verification policy for broad or final checks; do not repeat them merely because an item finishes or a session resumes. Preserve required coverage and isolation.
|
|
8
8
|
- Keep complete test output in local artifacts and preserve exit status. Read concise counts, timings and actionable failure excerpts first, expanding evidence only when needed. Do not load full passing reports or repeatedly poll unchanged logs into agent context. Retain startup and cleanup failures; retry only after a change or new evidence justifies it.
|
|
9
|
+
- For browser verification, batch related actions and return only the relevant values or page region. Capture screenshots at meaningful visual checkpoints or failures, not after every action. Preserve required viewport, identity and security coverage; prefer existing automated cases for repeated checks.
|
package/src/cli.js
CHANGED
|
@@ -562,6 +562,9 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
562
562
|
return { kind: 'discover', ...await codexAdoptionRecommendation({ projectRoot }) };
|
|
563
563
|
}
|
|
564
564
|
const scope = operands[0];
|
|
565
|
+
if (scope === 'turn' && process.env.GENESIS_TURN_CONTEXT_ENABLED === '0') {
|
|
566
|
+
return { kind: 'turn', status: 'ready', output: '' };
|
|
567
|
+
}
|
|
565
568
|
if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
|
|
566
569
|
return { kind: 'session', status: 'ready', output: '' };
|
|
567
570
|
}
|
package/src/index/codex-hooks.js
CHANGED
|
@@ -24,7 +24,7 @@ const SESSION_HOOK = {
|
|
|
24
24
|
type: 'command',
|
|
25
25
|
command: hookCommand('session'),
|
|
26
26
|
commandWindows: 'genesis hook session',
|
|
27
|
-
timeout:
|
|
27
|
+
timeout: 30,
|
|
28
28
|
statusMessage: 'Loading Genesis guidance',
|
|
29
29
|
additionalContextLimit: 4000,
|
|
30
30
|
}],
|
|
@@ -38,7 +38,7 @@ const TURN_HOOK = {
|
|
|
38
38
|
type: 'command',
|
|
39
39
|
command: hookCommand('turn'),
|
|
40
40
|
commandWindows: 'genesis hook turn',
|
|
41
|
-
timeout:
|
|
41
|
+
timeout: 30,
|
|
42
42
|
additionalContextLimit: 512,
|
|
43
43
|
}],
|
|
44
44
|
},
|
|
@@ -8,7 +8,7 @@ export const HOST_CONTEXT_INPUT_ENV = 'GENESIS_HOST_CONTEXT_INPUT';
|
|
|
8
8
|
export const SESSION_CONTEXT_INSTALLED_ENV = 'GENESIS_SESSION_CONTEXT_INSTALLED';
|
|
9
9
|
|
|
10
10
|
const RESOLVER_OUTPUT_MAX_BYTES = 64 * 1024;
|
|
11
|
-
const RESOLVER_TIMEOUT_MS =
|
|
11
|
+
const RESOLVER_TIMEOUT_MS = 30_000;
|
|
12
12
|
|
|
13
13
|
function resolverError(message) {
|
|
14
14
|
return new GenesisError('HOST_CONTEXT_RESOLVER_FAILED', message);
|
|
@@ -26,3 +26,10 @@ export async function installOpenCodePlugin({ projectRoot } = {}) {
|
|
|
26
26
|
await writeFileAtomic(location, source);
|
|
27
27
|
return { status: 'updated', changedFiles: [OPENCODE_PLUGIN_PATH] };
|
|
28
28
|
}
|
|
29
|
+
|
|
30
|
+
export async function inspectOpenCodePlugin({ projectRoot } = {}) {
|
|
31
|
+
const root = (await gitContext(projectRoot)).repositoryRoot;
|
|
32
|
+
const current = await existingSource(path.join(root, OPENCODE_PLUGIN_PATH));
|
|
33
|
+
const source = await readFile(OPENCODE_PLUGIN_SOURCE, 'utf8');
|
|
34
|
+
return { status: current === source ? 'current' : current === null ? 'missing' : 'outdated' };
|
|
35
|
+
}
|
package/src/index.js
CHANGED