genesis-compiler 1.3.2 → 1.3.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/README.md CHANGED
@@ -866,6 +866,7 @@ import {
866
866
  setCollaboration,
867
867
  setEngineeringProfile,
868
868
  verify,
869
+ withTrustedGitRepository,
869
870
  } from 'genesis-compiler';
870
871
  ```
871
872
 
@@ -894,6 +895,15 @@ projection paths without returning any supplied environment value.
894
895
  `inspectStackSection()` returns one exact consumer-owned section without
895
896
  interpreting or executing its contents.
896
897
 
898
+ Hosts that already validate access to a shared worktree may wrap an operation
899
+ in `withTrustedGitRepository(absoluteWorktreeRoot, () => inspectEnvironment({
900
+ projectRoot: absoluteWorktreeRoot }))`. This explicitly trusts that one
901
+ canonical worktree for Genesis's Git subprocesses in the callback's async
902
+ context. Concurrent and nested operations remain isolated; it does not change
903
+ global Git configuration, repository ownership, or filesystem permissions.
904
+ Without this explicit host grant, Git's ownership check remains in force and
905
+ an ownership rejection reports `GIT_REPOSITORY_UNTRUSTED`.
906
+
897
907
  Normalized results identify their stable public contract in the `contract`
898
908
  field: `genesis.collaboration.v1`, `genesis.engineering.v1`,
899
909
  `genesis.environment.v2`, `genesis.session-context.v1`,
@@ -40,3 +40,12 @@ This keeps ordinary single-user projects conventional while allowing a managed
40
40
  host to require shared-group writes and no access for other users. The host is
41
41
  responsible for establishing and verifying that umask, setgid directory, ACL,
42
42
  and cross-identity contract before Genesis or an agent starts.
43
+
44
+ A host may explicitly grant `withTrustedGitRepository()` one canonical,
45
+ absolute worktree root it has already authorized. Genesis carries that exact
46
+ `safe.directory` only in its Git subprocess arguments within the operation's
47
+ async context. It does not trust all repositories, inherit another concurrent
48
+ operation's grant, or modify Git configuration or ownership. Ordinary calls
49
+ retain Git's ownership protection. Trust is not a filesystem sandbox and does
50
+ not authorize the callback itself; selecting and authorizing the root remains
51
+ the host's responsibility.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "genesis-compiler",
3
- "version": "1.3.2",
3
+ "version": "1.3.3",
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/index/git.js CHANGED
@@ -15,7 +15,12 @@ export async function gitContext(projectRoot = process.cwd()) {
15
15
  let repositoryRoot;
16
16
  try {
17
17
  repositoryRoot = await realpath(path.resolve(await runGitText(root, ['rev-parse', '--show-toplevel'])));
18
- } catch {
18
+ } catch (error) {
19
+ if (/detected dubious ownership/iu.test(error?.details?.stderr || '')) {
20
+ fail('GIT_REPOSITORY_UNTRUSTED', `Git rejected ownership of the project worktree: ${projectRoot}. The caller must explicitly trust this shared repository.`, {
21
+ cause: error.code,
22
+ });
23
+ }
19
24
  fail('GIT_REPOSITORY_REQUIRED', `Project root is not inside a Git worktree: ${projectRoot}.`);
20
25
  }
21
26
  const relative = path.relative(repositoryRoot, root);
@@ -1,9 +1,24 @@
1
+ import { AsyncLocalStorage } from 'node:async_hooks';
1
2
  import { spawn } from 'node:child_process';
3
+ import { realpath } from 'node:fs/promises';
4
+ import path from 'node:path';
2
5
 
3
6
  import { GenesisError } from './errors.js';
4
7
 
5
8
  const MAX_DIAGNOSTIC_OUTPUT = 16_384;
6
9
  const DEFAULT_TERMINATION_GRACE_MS = 1_000;
10
+ const trustedGitRepository = new AsyncLocalStorage();
11
+
12
+ /** A host may explicitly trust one validated worktree for this async operation. */
13
+ export async function withTrustedGitRepository(repositoryRoot, operation) {
14
+ if (typeof repositoryRoot !== 'string' || !path.isAbsolute(repositoryRoot) || repositoryRoot.includes('*')) {
15
+ throw new TypeError('Git repository trust requires an explicit absolute worktree path without wildcards.');
16
+ }
17
+ if (typeof operation !== 'function') throw new TypeError('Git repository trust requires an operation.');
18
+ const root = await realpath(repositoryRoot);
19
+ if (root.includes('*')) throw new TypeError('Git repository trust does not accept wildcard paths.');
20
+ return trustedGitRepository.run(root, operation);
21
+ }
7
22
 
8
23
  function processTerminationError(code, message) {
9
24
  const error = new Error(message);
@@ -180,10 +195,12 @@ export async function runProcess(command, args, {
180
195
  }
181
196
 
182
197
  export async function runGit(cwd, args, options = {}) {
198
+ const trustedRoot = trustedGitRepository.getStore();
183
199
  return runProcess('git', [
184
200
  '-c', 'core.hooksPath=/dev/null',
185
201
  '-c', 'commit.gpgSign=false',
186
202
  '-c', 'core.fsmonitor=false',
203
+ ...(trustedRoot ? ['-c', `safe.directory=${trustedRoot}`] : []),
187
204
  ...args,
188
205
  ], {
189
206
  cwd,
package/src/index.js CHANGED
@@ -24,6 +24,7 @@ import { addStackPieces, readStack } from './index/stack.js';
24
24
  import { uniqueSorted } from './index/utils.js';
25
25
  import { verifyProject } from './index/verification.js';
26
26
  import { projectSessionContext, projectTurnContext } from './index/session-context.js';
27
+ import { withTrustedGitRepository } from './index/process.js';
27
28
  import {
28
29
  HOST_CONTEXT_RESOLVER_DATA_ENV,
29
30
  HOST_CONTEXT_RESOLVER_ENV,
@@ -37,6 +38,7 @@ export {
37
38
  SESSION_CONTEXT_INSTALLED_ENV,
38
39
  projectSessionContext,
39
40
  projectTurnContext,
41
+ withTrustedGitRepository,
40
42
  };
41
43
 
42
44
  function withIndexResult(result, index) {