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 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 only the requested host contribution as text. Codex and Claude Code
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. The resolver still
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
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.7.6",
3
+ "version": "1.7.8",
4
4
  "type": "module",
5
5
  "description": "An agent-independent prompt, multi-language code-index, cleanup, and verification companion with project agent guidance.",
6
6
  "repository": {
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 hookSessionId(environment = process.env) {
430
- if (!environment[HOST_CONTEXT_RESOLVER_ENV]) return null;
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 null;
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
- return typeof sessionId === 'string' && sessionId.trim() ? sessionId.trim() : null;
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 providerSessionId = await hookSessionId();
585
+ const nativeSession = await hookSessionInput();
578
586
  const hostContext = await resolveConfiguredHostContext({
579
587
  projectRoot,
580
- providerSessionId,
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
- return output;
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
- const root = (await gitContext(projectRoot)).repositoryRoot;
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
-