genesis-compiler 1.7.6 → 1.7.8
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 +7 -1
- package/docs/prompt-integration.md +21 -3
- package/docs/stack-components.md +7 -0
- package/package.json +1 -1
- package/src/cli.js +21 -8
- package/src/index/host-context-resolver.js +14 -1
- package/src/index/stack-section-inspection.js +16 -2
package/README.md
CHANGED
|
@@ -946,7 +946,13 @@ detailed Machine City and simple Program City projections.
|
|
|
946
946
|
public non-secret defaults, value-free missing-input diagnostics, and dotenv
|
|
947
947
|
projection paths without returning any supplied environment value.
|
|
948
948
|
`inspectStackSection()` returns one exact consumer-owned section without
|
|
949
|
-
interpreting or executing its contents.
|
|
949
|
+
interpreting or executing its contents. Its default `rootMode: "repository"`
|
|
950
|
+
uses Git discovery. For an authorized source snapshot without Git metadata,
|
|
951
|
+
hosts may pass `rootMode: "directory"` and an explicit absolute `projectRoot`.
|
|
952
|
+
This reads that exact directory, including when it sits inside another Git
|
|
953
|
+
repository. It retains the same Stack validation and performs no writes or
|
|
954
|
+
commands. Missing/invalid roots fail; this is never an automatic fallback from
|
|
955
|
+
Git ownership or discovery errors.
|
|
950
956
|
|
|
951
957
|
Hosts that already validate access to a shared worktree may wrap an operation
|
|
952
958
|
in `withTrustedGitRepository(absoluteWorktreeRoot, () => inspectEnvironment({
|
|
@@ -82,7 +82,25 @@ JSON on stdin:
|
|
|
82
82
|
```
|
|
83
83
|
|
|
84
84
|
For `genesis hook turn`, the input has the same shape with `scope: "turn"`.
|
|
85
|
-
The executable returns
|
|
85
|
+
The executable returns exactly one JSON result:
|
|
86
|
+
|
|
87
|
+
- `{"kind":"unclaimed"}`: this native conversation is not managed by the host;
|
|
88
|
+
Genesis delivers its standalone guide.
|
|
89
|
+
- `{"kind":"contribution","text":"..."}`: Genesis composes this bounded host
|
|
90
|
+
contribution with its own session guide (or bounded turn contribution).
|
|
91
|
+
- `{"kind":"delivery","text":"..."}`: the host owns the complete session
|
|
92
|
+
delivery; Genesis forwards it without adding its standalone copy. Empty text
|
|
93
|
+
means the host already delivers through a native system field. This result
|
|
94
|
+
is valid only for session scope.
|
|
95
|
+
|
|
96
|
+
Malformed results and configured-resolver failures fail the hook. An unavailable
|
|
97
|
+
claimed owner must fail rather than returning `unclaimed`. Ownership belongs to
|
|
98
|
+
the native conversation: these results do not change project hook files, so
|
|
99
|
+
independent CLI and managed conversations can use the same project. The host
|
|
100
|
+
owns durable bindings, ancestry verification and restoration across reconnection.
|
|
101
|
+
No host framework dependency is added to Genesis.
|
|
102
|
+
|
|
103
|
+
Codex and Claude Code
|
|
86
104
|
hook input and OpenCode's transform callbacks supply the native session id;
|
|
87
105
|
Genesis extracts that id but does not understand the host registry or the
|
|
88
106
|
provider's prompt-placement rules. The Codex and Claude prompt hooks and OpenCode message
|
|
@@ -104,8 +122,8 @@ Codex hook settings. A host can render these fields as an actionable failure
|
|
|
104
122
|
instead of displaying the runtime stack.
|
|
105
123
|
|
|
106
124
|
The OpenCode session adapter also includes verified native `parentSessionIds`
|
|
107
|
-
in `GENESIS_HOST_CONTEXT_INPUT` for host command shims
|
|
108
|
-
receives the actual child's `providerSessionId`; Genesis does not interpret a
|
|
125
|
+
in `GENESIS_HOST_CONTEXT_INPUT` for host command shims and in the resolver
|
|
126
|
+
request when present (at most 32 IDs). The resolver receives the actual child's `providerSessionId`; Genesis does not interpret a
|
|
109
127
|
host's ownership rules.
|
|
110
128
|
|
|
111
129
|
After updating Genesis, hosts can inspect all generated integrations with
|
package/docs/stack-components.md
CHANGED
|
@@ -177,6 +177,13 @@ The named consumer then owns all interpretation and action. This boundary works
|
|
|
177
177
|
equally for web applications, libraries, command-line tools, native programs,
|
|
178
178
|
firmware, desktop software, and future systems Genesis has never seen.
|
|
179
179
|
|
|
180
|
+
The library can inspect a source snapshot without Git metadata with
|
|
181
|
+
`inspectStackSection({ name, projectRoot: absoluteDirectory, rootMode: "directory" })`.
|
|
182
|
+
It uses the exact directory and the same Stack parser without repository
|
|
183
|
+
discovery. Default library and CLI inspection continue to use the enclosing
|
|
184
|
+
Git repository and its trust checks. Invalid modes or directory roots fail;
|
|
185
|
+
there is no automatic fallback.
|
|
186
|
+
|
|
180
187
|
## Selection and identity
|
|
181
188
|
|
|
182
189
|
`genesis stack add` records selected packages and component dependency closure,
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -426,8 +426,8 @@ function writeResult(command, result) {
|
|
|
426
426
|
line(process.stdout, `${command}: ${result.status}`);
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
-
async function
|
|
430
|
-
if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return
|
|
429
|
+
async function hookSessionInput(environment = process.env) {
|
|
430
|
+
if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return {};
|
|
431
431
|
let source = environment[HOST_CONTEXT_INPUT_ENV];
|
|
432
432
|
if (source === undefined && !process.stdin.isTTY) {
|
|
433
433
|
const chunks = [];
|
|
@@ -441,7 +441,7 @@ async function hookSessionId(environment = process.env) {
|
|
|
441
441
|
}
|
|
442
442
|
source = Buffer.concat(chunks).toString('utf8');
|
|
443
443
|
}
|
|
444
|
-
if (!String(source || '').trim()) return
|
|
444
|
+
if (!String(source || '').trim()) return {};
|
|
445
445
|
let input;
|
|
446
446
|
try {
|
|
447
447
|
input = JSON.parse(source);
|
|
@@ -449,7 +449,15 @@ async function hookSessionId(environment = process.env) {
|
|
|
449
449
|
fail('HOST_CONTEXT_INPUT_INVALID', 'The host hook input must contain JSON.');
|
|
450
450
|
}
|
|
451
451
|
const sessionId = input?.sessionId ?? input?.session_id;
|
|
452
|
-
|
|
452
|
+
const parentSessionIds = input?.parentSessionIds ?? [];
|
|
453
|
+
if (!Array.isArray(parentSessionIds) || parentSessionIds.length > 32 ||
|
|
454
|
+
parentSessionIds.some(id => typeof id !== 'string' || !id.trim())) {
|
|
455
|
+
fail('HOST_CONTEXT_INPUT_INVALID', 'Native parent session ids must be a bounded list of strings.');
|
|
456
|
+
}
|
|
457
|
+
return {
|
|
458
|
+
providerSessionId: typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null,
|
|
459
|
+
...(parentSessionIds.length ? { parentSessionIds } : {}),
|
|
460
|
+
};
|
|
453
461
|
}
|
|
454
462
|
|
|
455
463
|
async function cliStackPackages(projectRoot, supplied) {
|
|
@@ -574,19 +582,24 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
574
582
|
if (scope === 'session' && process.env[SESSION_CONTEXT_INSTALLED_ENV] === '1') {
|
|
575
583
|
return { kind: 'session', status: 'ready', output: '' };
|
|
576
584
|
}
|
|
577
|
-
const
|
|
585
|
+
const nativeSession = await hookSessionInput();
|
|
578
586
|
const hostContext = await resolveConfiguredHostContext({
|
|
579
587
|
projectRoot,
|
|
580
|
-
|
|
588
|
+
...nativeSession,
|
|
581
589
|
scope,
|
|
582
590
|
});
|
|
591
|
+
// Ownership is resolved for this native conversation, not for the project.
|
|
592
|
+
// A host-owned delivery already contains everything its runtime requires.
|
|
593
|
+
if (hostContext?.kind === 'delivery') {
|
|
594
|
+
return { kind: 'session', status: 'ready', output: hostContext.text };
|
|
595
|
+
}
|
|
583
596
|
if (scope === 'turn') {
|
|
584
597
|
return hostContext === null
|
|
585
598
|
? { kind: 'turn', status: 'ready', output: '' }
|
|
586
599
|
: {
|
|
587
600
|
kind: 'turn',
|
|
588
601
|
...await projectTurnContext({
|
|
589
|
-
hostDriver: () => hostContext,
|
|
602
|
+
hostDriver: () => hostContext.text,
|
|
590
603
|
hostDriverInput: { scope: 'turn' },
|
|
591
604
|
}),
|
|
592
605
|
};
|
|
@@ -595,7 +608,7 @@ async function execute({ command, operands, options }, { signal } = {}) {
|
|
|
595
608
|
kind: 'session',
|
|
596
609
|
...await projectSessionContext({
|
|
597
610
|
...(hostContext === null ? {} : {
|
|
598
|
-
hostDriver: () => hostContext,
|
|
611
|
+
hostDriver: () => hostContext.text,
|
|
599
612
|
hostDriverInput: { scope: 'session' },
|
|
600
613
|
}),
|
|
601
614
|
projectRoot,
|
|
@@ -55,6 +55,7 @@ export async function resolveConfiguredHostContext({
|
|
|
55
55
|
environment = process.env,
|
|
56
56
|
projectRoot,
|
|
57
57
|
providerSessionId = null,
|
|
58
|
+
parentSessionIds = [],
|
|
58
59
|
scope = 'session',
|
|
59
60
|
} = {}) {
|
|
60
61
|
const command = configuredResolver(environment);
|
|
@@ -66,8 +67,20 @@ export async function resolveConfiguredHostContext({
|
|
|
66
67
|
input: {
|
|
67
68
|
scope,
|
|
68
69
|
providerSessionId: providerSessionId || null,
|
|
70
|
+
...(parentSessionIds.length ? { parentSessionIds } : {}),
|
|
69
71
|
data: resolverData(environment),
|
|
70
72
|
},
|
|
71
73
|
});
|
|
72
|
-
|
|
74
|
+
let result;
|
|
75
|
+
try { result = JSON.parse(output); } catch {
|
|
76
|
+
throw resolverError('The host context resolver must return a JSON delivery result.');
|
|
77
|
+
}
|
|
78
|
+
if (!result || typeof result !== 'object' || Array.isArray(result) ||
|
|
79
|
+
!['unclaimed', 'contribution', 'delivery'].includes(result.kind) ||
|
|
80
|
+
Object.keys(result).some((key) => !['kind', 'text'].includes(key)) ||
|
|
81
|
+
(result.kind === 'unclaimed' ? Object.hasOwn(result, 'text') : typeof result.text !== 'string') ||
|
|
82
|
+
(result.kind === 'delivery' && scope !== 'session')) {
|
|
83
|
+
throw resolverError('Invalid host context delivery result.');
|
|
84
|
+
}
|
|
85
|
+
return result.kind === 'unclaimed' ? null : result;
|
|
73
86
|
}
|
|
@@ -1,3 +1,6 @@
|
|
|
1
|
+
import { realpath, stat } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
1
4
|
import { GENESIS_CONTRACTS } from './contracts.js';
|
|
2
5
|
import { gitContext } from './git.js';
|
|
3
6
|
import { readStack } from './stack.js';
|
|
@@ -8,10 +11,22 @@ import { sha256, stableJson } from './utils.js';
|
|
|
8
11
|
export async function inspectProjectStackSection({
|
|
9
12
|
name,
|
|
10
13
|
projectRoot,
|
|
14
|
+
rootMode = 'repository',
|
|
11
15
|
stackPackages = [],
|
|
12
16
|
} = {}) {
|
|
13
17
|
const sectionName = normalizeStackSectionName(name);
|
|
14
|
-
|
|
18
|
+
let root;
|
|
19
|
+
if (rootMode === 'repository') {
|
|
20
|
+
root = (await gitContext(projectRoot)).repositoryRoot;
|
|
21
|
+
} else if (rootMode === 'directory') {
|
|
22
|
+
if (typeof projectRoot !== 'string' || !path.isAbsolute(projectRoot)) {
|
|
23
|
+
throw new TypeError('Directory inspection requires an explicit absolute projectRoot.');
|
|
24
|
+
}
|
|
25
|
+
root = await realpath(projectRoot);
|
|
26
|
+
if (!(await stat(root)).isDirectory()) throw new TypeError('Directory inspection requires a directory.');
|
|
27
|
+
} else {
|
|
28
|
+
throw new TypeError('Stack section rootMode must be repository or directory.');
|
|
29
|
+
}
|
|
15
30
|
const stack = await readStack(root, { stackPackages });
|
|
16
31
|
const section = stack.extensions.find((candidate) => candidate.name === sectionName);
|
|
17
32
|
const diagnostics = [...(section?.diagnostics || [])];
|
|
@@ -30,4 +45,3 @@ export async function inspectProjectStackSection({
|
|
|
30
45
|
diagnostics,
|
|
31
46
|
};
|
|
32
47
|
}
|
|
33
|
-
|