arcane-os 0.91.0 → 0.92.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/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.92.0
4
+
5
+ - Add opt-in `allowUnborn` Git text snapshots and `initialBranch` repository
6
+ preparation for first publication from a remote with no advertised revision.
7
+ Repository configuration now reports the actual revision and unborn state.
8
+ Preserve existing checkouts, advertised refs, complete diagnostics and real
9
+ Git failures; first publication uses the existing explicit non-force writer.
10
+ - Let the default Windows `git` process runner discover an installed Git for
11
+ Windows registration and append its command directory only to that child's
12
+ PATH. Existing lookup priority, other platforms, explicit executable paths
13
+ and custom adapters retain their behavior; no installation, global setting
14
+ change or command retry is introduced.
15
+ - Reuse the retained Windows host, Whisper and ONNX runtime archives. This
16
+ release changes shared JavaScript repository/process behavior, not those
17
+ native binaries; application adoption remains independently owned.
18
+
3
19
  ## 0.91.0
4
20
 
5
21
  - Persist complete native `modelAssets.prepare()` acquisitions across release,
package/README.md CHANGED
@@ -25,7 +25,7 @@ selected SDK projection or an explicit live SDK source mount. Both profiles
25
25
  share the theme, packaging, event, cancellation,
26
26
  and browser run contracts while retaining their selected application layout.
27
27
 
28
- This checkout defines the `0.91.0` SDK contract. Development applications declare
28
+ This checkout defines the `0.92.0` SDK contract. Development applications declare
29
29
  `arcane-os` as `latest` and retain their lockfile. Exact SDK pins begin when Roshi
30
30
  designates a production build. Follow [Consumer updates](docs/consumer-updates.md)
31
31
  to refresh and compare the installed SDK and lockfile with the published channel;
@@ -47,7 +47,7 @@ const status = await repository.status({signal});
47
47
  await repository.close();
48
48
  ```
49
49
 
50
- `createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,longPaths,cloneIdentity,gitIdentity,onEvent,run}={})`
50
+ `createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,initialBranch,longPaths,cloneIdentity,gitIdentity,onEvent,run}={})`
51
51
  returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. The `directory`
52
52
  property is the absolute selected working path.
53
53
 
@@ -63,6 +63,10 @@ property is the absolute selected working path.
63
63
  `branch`, when supplied, is passed unchanged to Git's `clone --branch` option;
64
64
  otherwise Git selects the remote's default branch. Existing checkouts keep
65
65
  their own branch, remote, tracked files, untracked files and local changes.
66
+ - Optional `initialBranch` names the branch for first publication from a new
67
+ unborn clone. It is separate from `branch`, which selects an existing remote
68
+ branch or tag. Omit `branch` when the remote has no advertised revision.
69
+ See [first publication](#first-publication-from-an-unborn-remote).
66
70
  - Supplying `remoteBase` explicitly declares this initial-clone `remote` to be
67
71
  a local filesystem locator. Its native absolute command argument is captured
68
72
  during construction. Omission leaves Git's original string interpretation
@@ -77,7 +81,11 @@ property is the absolute selected working path.
77
81
  keep Git's ordinary inheritance; existing checkouts retain their configuration.
78
82
  See [clone-local author selection](#clone-local-author-selection).
79
83
  - `onEvent` and optional `run` use the existing SDK process owner. The default
80
- adapter is `runProcess`; Git must be available on PATH. Credentials and
84
+ adapter is `runProcess`; Git can be available on PATH or through an installed
85
+ Git for Windows registration. The default Windows runner appends that
86
+ installation's `cmd` directory only to the Git child's PATH, preserving
87
+ existing command lookup priority. See [Windows Git discovery](sdk-api.md#runprocess).
88
+ Custom `run` adapters retain their own execution behavior. Credentials and
81
89
  authentication remain with Git and the native host. The SDK adds no download,
82
90
  credential store, polling, network retry or process supervisor.
83
91
 
@@ -147,6 +155,8 @@ Its result is:
147
155
  {
148
156
  repositoryRoot,
149
157
  headRef: fullSymbolicRef | null,
158
+ revision: currentCommit | null,
159
+ unborn: boolean,
150
160
  origin: {urls: [...], pushUrls: [...]},
151
161
  upstream: {remoteNames: [...], mergeRefs: [...], urls: [...], pushUrls: [...]} | null
152
162
  }
@@ -160,6 +170,16 @@ arrays contain that branch's configured `remote` and `merge` values; `urls` and
160
170
  means the local repository and has no remote URL arrays. Origin is observed
161
171
  independently, even when it is not the upstream.
162
172
 
173
+ `revision` and `unborn` come from Git's successful
174
+ [porcelain-v2 branch observation](https://git-scm.com/docs/git-status#_porcelain_format_version_2):
175
+ `(initial)` yields `revision:null, unborn:true`; a current commit yields that
176
+ opaque revision and `unborn:false`, including a commit with an empty tree and
177
+ detached HEAD. Symbolic HEAD alone does not establish an unborn branch.
178
+ The command uses `--no-optional-locks` and omits untracked-file discovery, so
179
+ this observation does not refresh the index on disk. A missing branch-state
180
+ record reports `ARCANE_REPOSITORY_HEAD_UNAVAILABLE`; unsuccessful commands
181
+ retain their actual failures rather than becoming an initial state.
182
+
163
183
  Arrays retain configured order, repeated values, complete UTF-8 strings and
164
184
  empty strings. `[]` means unset; `['']` means an explicit empty value. The
165
185
  reader honors configured includes and ordinary Git configuration precedence.
@@ -172,8 +192,8 @@ Metadata uses the shared raw-Git-text reader. Lifecycle/stderr events remain
172
192
  with the process owner, raw stdout remains parser-owned, and undecodable text
173
193
  reports `ARCANE_GIT_CONFIGURATION_NOT_TEXT`. Failures retain complete raw
174
194
  stdout, process diagnostics and causal errors using the identity reader's
175
- documented attachment behavior. One observation uses four read-only Git
176
- commands for an attached local branch, or three otherwise. Results are not a
195
+ documented attachment behavior. One observation uses five read-only Git
196
+ commands for an attached local branch, or four otherwise. Results are not a
177
197
  transaction with later work or external Git activity. The app owns any decision
178
198
  that the current checkout corresponds to its selected repository and branch.
179
199
 
@@ -212,6 +232,69 @@ observe, refresh, write and publish. See Git's [pull](https://git-scm.com/docs/g
212
232
  [push](https://git-scm.com/docs/git-push) and [configuration](https://git-scm.com/docs/git-config)
213
233
  contracts.
214
234
 
235
+ ### First publication from an unborn remote
236
+
237
+ An opt-in [snapshot refresh](git-text-snapshot.md#unborn-remotes) can report
238
+ `{revision:null,files:[],unborn:true}` after a successful complete empty remote
239
+ advertisement. This means no revision was advertised to this connection; it
240
+ does not assert that hidden refs or unreachable objects cannot exist.
241
+ The application retains that explicit state instead of inventing a commit.
242
+
243
+ For an application-selected new connection with that state, prepare its first
244
+ branch without passing the existing-branch `clone --branch` option:
245
+
246
+ ```javascript
247
+ const repository = createRepositoryWorkspace({
248
+ name: connection.name,
249
+ remote: connection.locator,
250
+ remoteBase: connection.localBaseDirectory,
251
+ initialBranch: 'main'
252
+ });
253
+ await repository.open({signal});
254
+ const observed = await repository.configuration({signal});
255
+ // The application matches observed.headRef, unborn and configured URLs to its connection.
256
+ const publication = await repository.write({
257
+ files: selectedFiles,
258
+ message: authoredCommitMessage,
259
+ target: {remote: connection.locator, remoteBase: connection.localBaseDirectory, ref: 'refs/heads/main'},
260
+ signal
261
+ });
262
+ ```
263
+
264
+ `initialBranch` is captured during construction. After a successful new clone,
265
+ the SDK observes the complete remote advertisement using the clone's original
266
+ working-directory context and captured remote argument. Only empty successful
267
+ output followed by the new checkout's explicit initial state and successful
268
+ empty `git for-each-ref --format=%(refname)` observation selects
269
+ `HEAD` as `refs/heads/<initialBranch>` through
270
+ [Git symbolic-ref](https://git-scm.com/docs/git-symbolic-ref). Git owns branch-name
271
+ validity. Advertised HEAD, branches or tags keep ordinary clone selection;
272
+ an already committed clone and every existing destination remain unchanged.
273
+ Any local ref supplied by Git's template or hook behavior is preserved and
274
+ leaves HEAD unchanged, even when the remote advertises nothing.
275
+ Omission adds no process and preserves the previous open result.
276
+
277
+ For a new clone with `initialBranch`, `open` retains its clone `stdout`/`stderr`
278
+ and adds ordered `outputs:[{operation,stdout,stderr,code,signal}]`. Operations
279
+ are `clone`, `observeRemote`, `observeHead`, `observeRefs` and `selectInitialBranch`, only as
280
+ performed. A later preparation failure preserves completed outputs in
281
+ `error.details:{directory,cloned:true,outputs}` and the original complete
282
+ failure as `cause`; cancellation retains completed observations too. A writer
283
+ that prepares first retains these processes in its existing `stage:'prepare'`
284
+ output records. Failed preparation leaves the actual directory intact and does
285
+ not retry initialization or rename an existing checkout on the next call.
286
+
287
+ There is nothing to pull before the first commit of a confirmed new unborn
288
+ connection. Use the existing exact-file `write` and explicit non-force
289
+ `HEAD:refs/heads/main` publication directly. For a committed remote, retain the
290
+ ordinary selected-branch clone and targeted pull/write composition. An existing
291
+ local checkout, different local branch or intervening remote change needs the
292
+ application's own connection decision; an unborn observation is not permission
293
+ to discard or switch it. Observations and publication are not transactional:
294
+ concurrent remote changes, authentication failures and rejected pushes remain
295
+ actual errors. No pull failure is converted into success, and no retry, force,
296
+ rollback, global setting or fabricated revision is introduced.
297
+
215
298
  ### Relative local repository locators
216
299
 
217
300
  `remoteBase` is an explicit local-path choice, not a hint for a URL parser.
@@ -4,8 +4,11 @@
4
4
  from one fetched Git revision. It uses a dedicated **bare object cache**, not a
5
5
  clone with a working checkout. It never reads uncommitted application files,
6
6
  checks out files, modifies a user checkout/index, or creates another worktree.
7
- Construction performs no I/O. The native host needs Git available on its PATH;
8
- Git owns remote access, authentication and its actual failures.
7
+ Construction performs no I/O. The native host needs Git available on its PATH
8
+ or an installed Git for Windows registration discoverable by the default
9
+ [process runner](sdk-api.md#runprocess). Discovery extends only that Git child's
10
+ PATH after its existing entries. Git owns remote access, authentication and
11
+ its actual failures; custom process adapters retain their own execution.
9
12
 
10
13
  ```js
11
14
  import {createGitTextSnapshot} from 'arcane-os';
@@ -33,7 +36,7 @@ policy stable for the lifetime of its snapshot owner.
33
36
 
34
37
  ## API and lifecycle
35
38
 
36
- `createGitTextSnapshot({cacheDirectory,remote,remoteBase,ref,selectPath,gitIdentity,onEvent,run})`
39
+ `createGitTextSnapshot({cacheDirectory,remote,remoteBase,ref,selectPath,allowUnborn=false,gitIdentity,onEvent,run})`
37
40
  returns `{refresh,close,drain,dispose}`. `run` defaults to the SDK's existing
38
41
  `runProcess`; applications normally omit it. It is the same process adapter
39
42
  contract, not a second process supervisor. `onEvent` is the ordinary SDK event
@@ -91,6 +94,47 @@ application owner; this factory does not install a daemon or cross-process lock.
91
94
  An empty cache is initialized once. An existing non-bare checkout is rejected
92
95
  without repurposing it. The cache is retained when the owner closes.
93
96
 
97
+ ## Unborn remotes
98
+
99
+ Optional `allowUnborn:true` adds an explicit no-advertised-revision state:
100
+
101
+ ```javascript
102
+ const snapshot = createGitTextSnapshot({
103
+ cacheDirectory: applicationCacheDirectory,
104
+ remote: applicationRepository,
105
+ ref: 'refs/heads/main',
106
+ selectPath: applicationSelectPath,
107
+ allowUnborn: true
108
+ });
109
+ const observed = await snapshot.refresh({signal});
110
+ // Empty advertisement: {revision: null, files: [], unborn: true}.
111
+ // Selected commit: {revision: actualCommit, files: selectedText, unborn: false}.
112
+ ```
113
+
114
+ Each opted-in acquisition first completes unfiltered
115
+ `git ls-remote --symref -- <remote>` with the same captured locator and identity.
116
+ Only successful empty stdout produces the explicit unborn result. No revision
117
+ is invented and no fetch, tree listing or blob process runs on that path.
118
+ The [Git remote advertisement](https://git-scm.com/docs/git-ls-remote) is scoped
119
+ to refs visible to that connection, not hidden refs or unreachable objects.
120
+
121
+ Any advertised HEAD, branch, tag or other output continues through the ordinary
122
+ selected-ref fetch and commit resolution. A missing selected ref on a populated
123
+ remote remains a Git failure. Failed observations, authentication/transport
124
+ errors and stderr wording never establish an unborn result. A real commit with
125
+ an empty tree or zero selected files retains its actual revision and
126
+ `unborn:false`. Omitting the boolean, or supplying `false`, preserves the
127
+ previous `{revision,files}` result and command sequence without a remote probe.
128
+
129
+ Bare-cache role discovery still precedes remote observation. Every later
130
+ refresh observes again, so prior empty or committed results cannot hide a
131
+ changed remote or a failure. Caller cancellation, shared acquisition, independent
132
+ result copies and drain remain unchanged. Opted-in completion events add
133
+ `unborn`; `revision:null` and `unborn:true` describe the explicit empty state.
134
+ This option adds one remote command per acquisition; existing revision-based
135
+ text reuse still applies to committed results. For writing the first commit,
136
+ see [first-publication composition](core-repositories.md#first-publication-from-an-unborn-remote).
137
+
94
138
  ## Complete text and observable failures
95
139
 
96
140
  Content is the exact Git blob text, including whitespace, CRLF, newlines,
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "sdkVersion": "0.90.0",
3
+ "sdkVersion": "0.91.0",
4
4
  "environment": {
5
5
  "runtime": "Node.js for Node entrypoints; browser for browser-only entrypoints",
6
6
  "minimumVersion": "22.23.2 for Node entrypoints",
@@ -311,14 +311,14 @@
311
311
  "name": "createRepositoryWorkspace",
312
312
  "displayName": "createRepositoryWorkspace()",
313
313
  "kind": "function",
314
- "signature": "createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,longPaths,cloneIdentity,gitIdentity,onEvent,run=runProcess}={})",
314
+ "signature": "createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,initialBranch,longPaths,cloneIdentity,gitIdentity,onEvent,run=runProcess}={})",
315
315
  "entrypoints": ["arcane-os/core/repositories"],
316
316
  "primaryImport": "arcane-os/core/repositories",
317
317
  "group": "Native repository workspaces",
318
318
  "summary": "Owns one selected native working checkout with ArcaneData/Repos defaults and existing Git/process lifecycle.",
319
319
  "availability": "Node with Git on Windows, Linux and macOS; Android requires host process adaptation",
320
320
  "protocol": "Git working repository and SDK process adapter",
321
- "normalization": "Returns directory, open, status, configuration, pull, push, write and identical close/drain/dispose hooks. Only missing or empty destinations clone; existing roots are preserved. Optional factory remoteBase explicitly selects the initial-clone remote as a local filesystem locator and captures path.resolve(remoteBase, remote) once at construction. A relative base, including an empty string, uses the construction-time working directory. Only the Git argument is derived; the caller's original locator remains unchanged. Omission preserves every Git remote name, URL, SCP-like address and remote-helper string without guessed parsing. Optional longPaths captures a boolean: explicit Windows true/false supplies clone-local core.longpaths before the first checkout, persisting only in the new repository under ordinary Git configuration precedence. Omission preserves configuration; existing repositories and non-Windows clone commands remain unchanged. No global setting or migration. configuration observes the existing root, symbolic HEAD, origin URL/pushURL arrays and branch remote/merge/upstream URL arrays without clone/init/fetch, preserving unset, empty and repeated values. Explicit paths remain selected. Same-directory operations serialize within the process; cancellation and shutdown join accepted process work. pull/push/write accept a captured target {remote,ref,remoteBase?} for current-branch fast-forward pull or non-force current-HEAD selected-ref push; omission retains ordinary configured behavior. An operation's own remoteBase declares local-path intent and captures its native absolute argument once at acceptance, before queueing; factory remote/remoteBase/branch remain independent clone inputs. Targeted pull/push results and writer results/failure outcomes retain the original remote, ref and supplied remoteBase, including relative or empty base text. Unrepresentable base text reports TypeError without transformation. write snapshots exact text and paths, stages/commits only literal owned paths and preserves unrelated staged files and Git filters. Complete outputs and causal failures retain confirmed local/committed/pushed and uncertain outcomes plus the supplied target. Optional gitIdentity captures name/email/username for child Git operations only; omitted fields remain inherited and identity selection writes no config file. Optional cloneIdentity captures exact name/email strings and persists only supplied fields through clone --config before the first checkout on every supported platform. Empty strings are explicit; omitted or undefined fields preserve inherited/template configuration. Existing checkouts are never reconfigured. Unrepresentable process arguments fail honestly without transforming them. This option changes no global configuration or credentials and does not identify an authenticated account; command-local gitIdentity remains independent."
321
+ "normalization": "Returns directory, open, status, configuration, pull, push, write and identical close/drain/dispose hooks. Only missing or empty destinations clone; existing roots are preserved. Optional factory remoteBase explicitly selects the initial-clone remote as a local filesystem locator and captures path.resolve(remoteBase, remote) once at construction. A relative base, including an empty string, uses the construction-time working directory. Only the Git argument is derived; the caller's original locator remains unchanged. Omission preserves every Git remote name, URL, SCP-like address and remote-helper string without guessed parsing. Optional longPaths captures a boolean: explicit Windows true/false supplies clone-local core.longpaths before the first checkout, persisting only in the new repository under ordinary Git configuration precedence. Omission preserves configuration; existing repositories and non-Windows clone commands remain unchanged. No global setting or migration. configuration adds revision and unborn from successful porcelain-v2 branch state, preserving real commits including empty trees; it observes the existing root, symbolic HEAD, origin URL/pushURL arrays and branch remote/merge/upstream URL arrays without clone/init/fetch, preserving unset, empty and repeated values. Explicit paths remain selected. Same-directory operations serialize within the process; cancellation and shutdown join accepted process work. pull/push/write accept a captured target {remote,ref,remoteBase?} for current-branch fast-forward pull or non-force current-HEAD selected-ref push; omission retains ordinary configured behavior. An operation's own remoteBase declares local-path intent and captures its native absolute argument once at acceptance, before queueing; factory remote/remoteBase/branch remain independent clone inputs. Targeted pull/push results and writer results/failure outcomes retain the original remote, ref and supplied remoteBase, including relative or empty base text. Unrepresentable base text reports TypeError without transformation. write snapshots exact text and paths, stages/commits only literal owned paths and preserves unrelated staged files and Git filters. Complete outputs and causal failures retain confirmed local/committed/pushed and uncertain outcomes plus the supplied target. Optional gitIdentity captures name/email/username for child Git operations only; omitted fields remain inherited and identity selection writes no config file. Optional cloneIdentity captures exact name/email strings and persists only supplied fields through clone --config before the first checkout on every supported platform. Empty strings are explicit; omitted or undefined fields preserve inherited/template configuration. Existing checkouts are never reconfigured. Unrepresentable process arguments fail honestly without transforming them. This option changes no global configuration or credentials and does not identify an authenticated account; command-local gitIdentity remains independent. Optional initialBranch selects refs/heads/<initialBranch> only for a newly cloned checkout with successful complete empty remote advertisement, explicit local initial state and successful empty local-ref listing; branch remains clone --branch. Any template or hook-supplied local ref preserves HEAD. Existing destinations and committed or advertised clones remain unchanged. Explicit new-clone open adds complete ordered preparation outputs, including observeRefs when performed; later preparation failures retain completed outputs and their original cause. Omission preserves commands and result shape. First publication uses existing exact-file write and explicit target without an implicit pull."
322
322
  },
323
323
  {
324
324
  "id": "core-repositories:readGitIdentity",
@@ -493,14 +493,14 @@
493
493
  "name": "createGitTextSnapshot",
494
494
  "displayName": "createGitTextSnapshot()",
495
495
  "kind": "function",
496
- "signature": "createGitTextSnapshot({cacheDirectory,remote,remoteBase,ref,selectPath,gitIdentity,onEvent,run=runProcess}={})",
496
+ "signature": "createGitTextSnapshot({cacheDirectory,remote,remoteBase,ref,selectPath,allowUnborn=false,gitIdentity,onEvent,run=runProcess}={})",
497
497
  "entrypoints": ["arcane-os"],
498
498
  "primaryImport": "arcane-os",
499
499
  "group": "Workspace, doctor, repository, and server",
500
500
  "summary": "Reads complete selected text from one fetched revision using a dedicated bare Git object cache and batched retrieval.",
501
501
  "availability": "Node with Git; Android requires a host supplying Git/process capability",
502
502
  "protocol": "Git tree and batched blob transport through the shared process owner",
503
- "normalization": "Returns {refresh,close,drain,dispose}; refresh yields {revision,files:[{path,content}]}, shares concurrent work, reuses unchanged revisions, preserves exact UTF-8 text, isolates caller cancellation and drains accepted work. Optional remoteBase explicitly selects remote as a local filesystem locator and derives only its Git fetch argument through path.resolve(remoteBase, remote), once at construction independently of the cache location and later working-directory changes. A relative base, including an empty string, uses the construction-time working directory; original locator/options remain unchanged. Omission preserves every Git remote name, URL, SCP-like address and remote-helper string without guessed parsing. Unrepresentable base text reports TypeError without transformation; existing Git failures remain errors. Optional gitIdentity captures name/email/username for child Git commands including bare init and fetch, without changing config files, payloads or shared acquisition lifetime. See git-text-snapshot.md."
503
+ "normalization": "Returns {refresh,close,drain,dispose}; refresh yields {revision,files:[{path,content}]}, shares concurrent work, reuses unchanged revisions, preserves exact UTF-8 text, isolates caller cancellation and drains accepted work. Optional allowUnborn defaults false, preserving that shape and command sequence. Explicit true first observes the complete remote advertisement; successful empty stdout yields revision:null, files:[], unborn:true. Any advertised HEAD, branch, tag or other output retains normal selected-ref fetch and actual errors; committed results add unborn:false, including a real empty tree. Every acquisition reobserves rather than returning stale empty state; cache role, identity, cancellation and drain remain unchanged. Optional remoteBase explicitly selects remote as a local filesystem locator and derives only its Git remote argument through path.resolve(remoteBase, remote), once at construction independently of the cache location and later working-directory changes. A relative base, including an empty string, uses the construction-time working directory; original locator/options remain unchanged. Omission preserves every Git remote name, URL, SCP-like address and remote-helper string without guessed parsing. Unrepresentable base text reports TypeError without transformation; existing Git failures remain errors. Optional gitIdentity captures name/email/username for child Git commands including bare init and fetch, without changing config files, payloads or shared acquisition lifetime. See git-text-snapshot.md."
504
504
  },
505
505
  {
506
506
  "id": "diarization:createDiarization",
@@ -5409,7 +5409,7 @@ a dedicated bare Git cache. It does not create or alter a working checkout.
5409
5409
  ### Signature, lifecycle, and result
5410
5410
 
5411
5411
  ```text
5412
- createGitTextSnapshot({cacheDirectory,remote,remoteBase,ref,selectPath,gitIdentity,onEvent,run=runProcess}={})
5412
+ createGitTextSnapshot({cacheDirectory,remote,remoteBase,ref,selectPath,allowUnborn=false,gitIdentity,onEvent,run=runProcess}={})
5413
5413
  ```
5414
5414
 
5415
5415
  Import from `arcane-os`. The application owns the cache location, remote, ref,
@@ -5421,6 +5421,14 @@ await accepted refresh/process cleanup. Errors remain observable instead of
5421
5421
  returning stale or partial success. See [Git text snapshots](git-text-snapshot.md)
5422
5422
  for complete inputs, text semantics, errors, events and Core service composition.
5423
5423
 
5424
+ Optional `allowUnborn:true` observes the complete remote advertisement on every
5425
+ acquisition. Only successful empty output returns
5426
+ `{revision:null,files:[],unborn:true}`; committed results add `unborn:false` and
5427
+ retain the actual commit even for an empty tree. Any advertised HEAD, branch
5428
+ or tag continues through ordinary selected-ref fetch; missing refs and actual
5429
+ Git failures remain errors. Omitted/false preserves the prior result and commands.
5430
+ See [unborn remotes](git-text-snapshot.md#unborn-remotes).
5431
+
5424
5432
  Optional `remoteBase` explicitly selects `remote` as a local filesystem locator.
5425
5433
  Only its Git fetch argument is derived using `path.resolve(remoteBase, remote)`,
5426
5434
  once at construction, independently of the cache location and later changes to
@@ -5476,6 +5484,28 @@ once and stdin remains open until cancellation or a callback/input failure
5476
5484
  requests shutdown. On Microsoft NT, `npm` and `npx` are normalized to their Node
5477
5485
  CLI entrypoints.
5478
5486
 
5487
+ For the literal `git` command on Windows, the default runner reads Git for
5488
+ Windows' registered `InstallPath` in the current-user and machine registry
5489
+ views. When that installation's `cmd/git.exe` exists, its `cmd` directory is
5490
+ appended only to this child's effective PATH. Existing entries and native
5491
+ command lookup retain priority; duplicate `Path`/`PATH` spellings follow Node's
5492
+ lexical selection. Other environment fields, explicit executable paths,
5493
+ custom process adapters, other commands and other platforms keep their existing
5494
+ behavior. No global environment or registry setting is changed, and no Git
5495
+ download, installation or command retry occurs.
5496
+
5497
+ One successful location is retained in memory for this process while its
5498
+ executable remains present. A removed executable triggers fresh discovery;
5499
+ absent, failed and cancelled lookups are not retained. Concurrent cold callers
5500
+ own independent discovery and cancellation. The standard Windows registry
5501
+ helper uses the existing process owner; its lifecycle callbacks have
5502
+ `process.git.discovery.*` event names, and its captured metadata is separate
5503
+ from the requested command's output. Missing registration leaves ordinary
5504
+ lookup unchanged. Discovery failure emits `process.git.discovery.unavailable`
5505
+ with the complete error before ordinary lookup continues. Cancellation or an
5506
+ event-callback failure stops the operation before Git starts. Normal Git
5507
+ errors and missing-working-directory errors remain actual command failures.
5508
+
5479
5509
  An `AsyncIterable` input instead feeds stdin with awaited writes and closes it
5480
5510
  when the producer finishes. Its `return()` must cooperate with cancellation of
5481
5511
  an outstanding `next()`; the process owner drains both input and output work.
@@ -8400,7 +8430,7 @@ explicit existing path. The application owns the remote, branch and Core API.
8400
8430
  ### Signature and result
8401
8431
 
8402
8432
  ```text
8403
- createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,longPaths,cloneIdentity,gitIdentity,onEvent,run=runProcess}={})
8433
+ createRepositoryWorkspace({name,directory,dataRoot,remote,remoteBase,branch,initialBranch,longPaths,cloneIdentity,gitIdentity,onEvent,run=runProcess}={})
8404
8434
  ```
8405
8435
 
8406
8436
  Returns `{directory,open,status,configuration,pull,push,write,close,drain,dispose}`. Construction does
@@ -8411,6 +8441,17 @@ work and preserves the checkout. No CLI cwd, launch state, preference, snapshot
8411
8441
  cache or existing user-data location changes. See the [complete inputs, results,
8412
8442
  errors and lifecycle](core-repositories.md#one-connected-working-checkout).
8413
8443
 
8444
+ Optional `initialBranch` selects `refs/heads/<initialBranch>` only after a new
8445
+ clone, successful complete empty remote advertisement, explicit local
8446
+ initial-state observation and a successful empty local-ref listing. Any local
8447
+ ref supplied by a Git template or hook remains unchanged. Existing destinations and committed/advertised
8448
+ clones keep their branches; `branch` still means Git's existing-branch/tag clone
8449
+ selection. New-clone `open` results with this option add complete ordered
8450
+ preparation `outputs`; subsequent failures retain completed outputs and their
8451
+ original cause. Omission adds no command or result field. See
8452
+ [first-publication composition](core-repositories.md#first-publication-from-an-unborn-remote)
8453
+ for the direct first commit/push without an implicit pull.
8454
+
8414
8455
  Optional factory `remoteBase` explicitly selects the initial-clone `remote` as
8415
8456
  a local filesystem locator. Only its Git argument is derived through the host's
8416
8457
  native `path.resolve(remoteBase, remote)`, once at construction. Relative bases,
@@ -8446,13 +8487,17 @@ omitted fields remain inherited. Username remains a credential hint, with
8446
8487
  authentication owned by ordinary Git and its configured helper.
8447
8488
 
8448
8489
  `configuration({signal})` observes an existing working root without clone,
8449
- initialization or fetch. It returns `{repositoryRoot,headRef,origin,upstream}`:
8490
+ initialization or fetch. It returns `{repositoryRoot,headRef,revision,unborn,origin,upstream}`:
8450
8491
  full symbolic HEAD (including unborn, null when detached), origin URL/pushURL
8451
8492
  arrays and current branch remote/merge/URL/pushURL arrays. Unset, empty and
8452
- repeated configuration values remain distinct. These are ordinary configured
8453
- values, not authentication or endpoint proof, and are not atomic with later
8454
- operations. Raw stdout is decoded without replacement; unrepresentable text
8455
- reports `ARCANE_GIT_CONFIGURATION_NOT_TEXT` with full diagnostics.
8493
+ repeated configuration values remain distinct. `revision:null,unborn:true`
8494
+ comes only from Git's explicit porcelain-v2 initial state; committed and
8495
+ detached states retain their current revision with `unborn:false`. Missing
8496
+ branch-state output reports `ARCANE_REPOSITORY_HEAD_UNAVAILABLE`. These are
8497
+ ordinary configured values and local branch observations, not authentication
8498
+ or endpoint proof, and are not atomic with later operations. Raw stdout is
8499
+ decoded without replacement; unrepresentable text reports
8500
+ `ARCANE_GIT_CONFIGURATION_NOT_TEXT` with full diagnostics.
8456
8501
 
8457
8502
  `pull({target,signal})`, `push({target,signal})` and
8458
8503
  `write({files:[{path,content}],message,target,signal})` accept optional
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arcane-os",
3
- "version": "0.91.0",
3
+ "version": "0.92.0",
4
4
  "description": "Arcane OS JavaScript SDK, project-local CLI, browser runtime, and repository-portable application packager.",
5
5
  "type": "module",
6
6
  "main": "./src/index.mjs",
@@ -5,7 +5,7 @@ import Is from 'strong-type';
5
5
  import {ArcaneError, throwIfAborted} from '../errors.mjs';
6
6
  import {runProcess} from '../process.mjs';
7
7
  import {captureRepositoryRemote, captureRepositoryTarget, repositoryConfiguration, pullRepositoryTarget, pushRepositoryTarget,
8
- repositoryPushArguments, repositoryStatus} from '../repository.mjs';
8
+ repositoryHeadArguments, repositoryHeadState, repositoryPushArguments, repositoryStatus} from '../repository.mjs';
9
9
  import {createGitIdentityRunner} from '../git-identity.mjs';
10
10
 
11
11
  export {readGitIdentity} from '../git-identity.mjs';
@@ -45,7 +45,7 @@ export function resolveArcaneDataPaths({dataRoot} = {}) {
45
45
 
46
46
  /** One connected checkout, composed into an application's own Core service. */
47
47
  export function createRepositoryWorkspace(
48
- {name, directory, dataRoot, remote, remoteBase, branch, longPaths, cloneIdentity = {}, gitIdentity, onEvent, run = runProcess} = {}
48
+ {name, directory, dataRoot, remote, remoteBase, branch, initialBranch, longPaths, cloneIdentity = {}, gitIdentity, onEvent, run = runProcess} = {}
49
49
  ) {
50
50
  let selected = directory;
51
51
  if (selected === undefined) {
@@ -59,6 +59,12 @@ export function createRepositoryWorkspace(
59
59
  if (remote !== undefined) requireString(remote, 'remote');
60
60
  const remoteArgument = captureRepositoryRemote(remote, remoteBase);
61
61
  if (branch !== undefined) requireString(branch, 'branch');
62
+ if (initialBranch !== undefined) {
63
+ requireString(initialBranch, 'initialBranch');
64
+ if (!initialBranch.isWellFormed() || initialBranch.includes('\0')) {
65
+ throw new TypeError('initialBranch must be text representable as a native process argument.');
66
+ }
67
+ }
62
68
  if (longPaths !== undefined && !is.boolean(longPaths)) {
63
69
  throw new TypeError('longPaths must be a boolean when supplied.');
64
70
  }
@@ -126,9 +132,60 @@ export function createRepositoryWorkspace(
126
132
  arguments_,
127
133
  {cwd: path.dirname(repositoryDirectory), signal, onEvent}
128
134
  );
135
+ let outputs;
136
+ if (initialBranch !== undefined) {
137
+ const options = {cwd: repositoryDirectory, signal, onEvent};
138
+ outputs = [];
139
+ function completed(operation, observation) {
140
+ outputs.push({operation, stdout: observation.stdout, stderr: observation.stderr,
141
+ code: observation.code, signal: observation.signal ?? null});
142
+ }
143
+ completed('clone', result);
144
+ try {
145
+ throwIfAborted(signal);
146
+ const advertised = await prepareRun('git', ['ls-remote', '--symref', '--', remoteArgument],
147
+ {cwd: path.dirname(repositoryDirectory), signal, onEvent});
148
+ completed('observeRemote', advertised);
149
+ throwIfAborted(signal);
150
+ if (advertised.code !== 0) {
151
+ throw new ArcaneError('ARCANE_OPERATION_FAILED', 'Git remote observation failed.', {details: advertised});
152
+ }
153
+ if (advertised.stdout === '') {
154
+ const state = await prepareRun('git', [...repositoryHeadArguments], options);
155
+ completed('observeHead', state);
156
+ throwIfAborted(signal);
157
+ if (state.code !== 0) {
158
+ throw new ArcaneError('ARCANE_OPERATION_FAILED', 'Git branch state observation failed.', {details: state});
159
+ }
160
+ if (repositoryHeadState(state.stdout, state).unborn) {
161
+ const references = await prepareRun('git', ['for-each-ref', '--format=%(refname)'], options);
162
+ completed('observeRefs', references);
163
+ throwIfAborted(signal);
164
+ if (references.code !== 0) {
165
+ throw new ArcaneError('ARCANE_OPERATION_FAILED', 'Git local ref observation failed.', {details: references});
166
+ }
167
+ // Git templates or hooks can supply local refs even
168
+ // when the remote and this checkout's HEAD are unborn.
169
+ if (references.stdout === '') {
170
+ const selection = await prepareRun('git', ['symbolic-ref', 'HEAD', `refs/heads/${initialBranch}`], options);
171
+ completed('selectInitialBranch', selection);
172
+ throwIfAborted(signal);
173
+ if (selection.code !== 0) {
174
+ throw new ArcaneError('ARCANE_OPERATION_FAILED', 'Git initial branch selection failed.', {details: selection});
175
+ }
176
+ }
177
+ }
178
+ }
179
+ } catch (cause) {
180
+ throw new ArcaneError(cause?.code ?? 'ARCANE_REPOSITORY_PREPARE_FAILED',
181
+ cause instanceof Error ? cause.message : String(cause),
182
+ {cause, details: {directory: repositoryDirectory, cloned: true, outputs}, exitCode: cause?.exitCode});
183
+ }
184
+ }
129
185
  throwIfAborted(signal);
130
186
  opened = true;
131
- return {directory: repositoryDirectory, cloned: true, stdout: result.stdout, stderr: result.stderr};
187
+ return {directory: repositoryDirectory, cloned: true, stdout: result.stdout, stderr: result.stderr,
188
+ ...(outputs === undefined ? {} : {outputs})};
132
189
  }
133
190
 
134
191
  function accept(operation, signal) {
@@ -15,11 +15,12 @@ const snapshotRef = 'refs/arcane/text-snapshot';
15
15
  * One application service owns each dedicated cache directory. Construction
16
16
  * performs no I/O; refresh coalesces callers and close drains accepted work.
17
17
  */
18
- export function createGitTextSnapshot({cacheDirectory, remote, remoteBase, ref, selectPath, gitIdentity, onEvent, run = runProcess} = {}) {
18
+ export function createGitTextSnapshot({cacheDirectory, remote, remoteBase, ref, selectPath, allowUnborn = false, gitIdentity, onEvent, run = runProcess} = {}) {
19
19
  for (const [name, value] of Object.entries({cacheDirectory, remote, ref})) {
20
20
  if (!is.string(value) || value === '') throw new TypeError(`${name} must be a nonempty string.`);
21
21
  }
22
22
  if (!is.function(selectPath)) throw new TypeError('selectPath must be a function.');
23
+ if (!is.boolean(allowUnborn)) throw new TypeError('allowUnborn must be a boolean.');
23
24
  const remoteArgument = captureRepositoryRemote(remote, remoteBase);
24
25
  const execute = createGitIdentityRunner(run, gitIdentity);
25
26
  const directory = path.resolve(cacheDirectory);
@@ -70,11 +71,28 @@ export function createGitTextSnapshot({cacheDirectory, remote, remoteBase, ref,
70
71
  try {
71
72
  await events.send({type: 'git.snapshot.refreshing', message: 'Refreshing the selected repository revision.', data: {ref}});
72
73
  await prepare(events);
74
+ if (allowUnborn) {
75
+ // No ref pattern or --exit-code: a successful complete empty
76
+ // advertisement is distinct from a missing selected ref.
77
+ const advertised = await git(['ls-remote', '--symref', '--', remoteArgument], {onEvent: events.send});
78
+ if (advertised.code !== 0) {
79
+ throw new ArcaneError(ERROR_CODES.operationFailed, 'Git remote observation failed.', {details: advertised});
80
+ }
81
+ if (advertised.stdout === '') {
82
+ const result = {revision: null, files: [], unborn: true};
83
+ await events.send({type: 'git.snapshot.completed', message: 'The remote advertises no revision.',
84
+ data: {revision: null, unborn: true, reused: retained?.unborn === true}});
85
+ await events.drain();
86
+ retained = result;
87
+ return result;
88
+ }
89
+ }
73
90
  await git(['fetch', '--no-tags', '--no-write-fetch-head', '--', remoteArgument, `+${ref}:${snapshotRef}`], {onEvent: events.send});
74
91
  const selected = await git(['rev-parse', '--verify', `${snapshotRef}^{commit}`], {onEvent: events.send});
75
92
  const revision = selected.stdout.trim();
76
93
  if (retained?.revision === revision) {
77
- await events.send({type: 'git.snapshot.completed', message: 'The selected repository revision is unchanged.', data: {revision, reused: true}});
94
+ await events.send({type: 'git.snapshot.completed', message: 'The selected repository revision is unchanged.',
95
+ data: {revision, reused: true, ...(allowUnborn ? {unborn: false} : {})}});
78
96
  await events.drain();
79
97
  return retained;
80
98
  }
@@ -125,8 +143,9 @@ export function createGitTextSnapshot({cacheDirectory, remote, remoteBase, ref,
125
143
  }
126
144
  });
127
145
  }
128
- const result = {revision, files: reader.finish()};
129
- await events.send({type: 'git.snapshot.completed', message: 'Repository text snapshot is ready.', data: {revision, reused: false}});
146
+ const result = {revision, files: reader.finish(), ...(allowUnborn ? {unborn: false} : {})};
147
+ await events.send({type: 'git.snapshot.completed', message: 'Repository text snapshot is ready.',
148
+ data: {revision, reused: false, ...(allowUnborn ? {unborn: false} : {})}});
130
149
  await events.drain();
131
150
  retained = result;
132
151
  return result;
@@ -156,7 +175,8 @@ export function createGitTextSnapshot({cacheDirectory, remote, remoteBase, ref,
156
175
  accepted.then(settled, settled);
157
176
  }
158
177
  return waitForSnapshot(active, signal).then(function copyResult(snapshot) {
159
- return {revision: snapshot.revision, files: snapshot.files.map(function copyFile(file) { return {...file}; })};
178
+ return {revision: snapshot.revision, files: snapshot.files.map(function copyFile(file) { return {...file}; }),
179
+ ...(allowUnborn ? {unborn: snapshot.unborn} : {})};
160
180
  });
161
181
  }
162
182
 
package/src/process.mjs CHANGED
@@ -3,6 +3,7 @@ import {spawn} from 'node:child_process';
3
3
  import path from 'node:path';
4
4
  import {ArcaneError,ERROR_CODES,throwIfAborted} from './errors.mjs';
5
5
  import {createEventQueue} from './event-queue.mjs';
6
+ import {windowsGitEnvironment} from './windows-git-executable.mjs';
6
7
 
7
8
  const is = new Is(false);
8
9
 
@@ -195,6 +196,29 @@ export async function runProcess(command,args=[],{
195
196
  });
196
197
  throwIfAborted(signal);
197
198
 
199
+ let childEnvironment=env?{...process.env,...env}:process.env;
200
+ if(process.platform==='win32'&&command==='git'){
201
+ try{
202
+ childEnvironment=await windowsGitEnvironment(childEnvironment,{
203
+ run:runProcess,signal,
204
+ onEvent:function discoveryEvent(event){
205
+ return events.send({
206
+ ...event,type:event.type.replace(/^process\./u,'process.git.discovery.')
207
+ });
208
+ }
209
+ });
210
+ }catch(error){
211
+ throwIfAborted(signal);
212
+ if(events.error)throw events.error;
213
+ await events.send({
214
+ type:'process.git.discovery.unavailable',
215
+ message:'Registered Git discovery was unavailable; using the existing command environment.',
216
+ data:{error}
217
+ });
218
+ }
219
+ throwIfAborted(signal);
220
+ }
221
+
198
222
  return new Promise((resolve,reject)=>{
199
223
  let stdout=outputEncodings.stdout!==null&&outputSelected(captureOutput,'stdout')?'':null;
200
224
  let stderr=outputEncodings.stderr!==null&&outputSelected(captureOutput,'stderr')?'':null;
@@ -369,7 +393,7 @@ export async function runProcess(command,args=[],{
369
393
  try{
370
394
  child=spawn(executable,executableArgs,{
371
395
  cwd,
372
- env:env?{...process.env,...env}:process.env,
396
+ env:childEnvironment,
373
397
  shell:false,
374
398
  detached:process.platform!=='win32',
375
399
  windowsHide:true,
@@ -85,6 +85,25 @@ export function repositoryPushArguments(selection) {
85
85
  : ['push', '--no-follow-tags', '--', selection.remoteArgument, `HEAD:${selection.target.ref}`];
86
86
  }
87
87
 
88
+ // Both configuration and new-clone preparation consume Git's explicit initial
89
+ // state. NUL framing keeps unusual working filenames outside these headers.
90
+ export const repositoryHeadArguments = [
91
+ '--no-optional-locks', 'status', '--porcelain=v2', '-z', '--branch', '--untracked-files=no'
92
+ ];
93
+
94
+ export function repositoryHeadState(output, details) {
95
+ const prefix = '# branch.oid ';
96
+ const record = output.split('\0').find(function branchCommit(value) {
97
+ return value.startsWith(prefix);
98
+ });
99
+ const revision = record?.slice(prefix.length);
100
+ if (!revision) {
101
+ throw new ArcaneError('ARCANE_REPOSITORY_HEAD_UNAVAILABLE',
102
+ 'Git did not report its current commit or initial branch state.', {details});
103
+ }
104
+ return revision === '(initial)' ? {revision: null, unborn: true} : {revision, unborn: false};
105
+ }
106
+
88
107
  /** Observe an existing workspace without initializing or refreshing it. */
89
108
  export async function repositoryConfiguration({workspaceRoot = process.cwd(), signal, onEvent, run = runProcess} = {}) {
90
109
  const repositoryRoot = path.resolve(workspaceRoot);
@@ -147,7 +166,12 @@ export async function repositoryConfiguration({workspaceRoot = process.cwd(), si
147
166
  upstream.urls = remotes.get(`remote.${remoteName}.url`) ?? [];
148
167
  upstream.pushUrls = remotes.get(`remote.${remoteName}.pushurl`) ?? [];
149
168
  }
150
- return {repositoryRoot, headRef, origin, upstream};
169
+ const state = await readGitText([...repositoryHeadArguments], options);
170
+ if (state.result.code !== 0) throw processFailure(state, 'Git branch state observation failed.');
171
+ let headState;
172
+ try { headState = repositoryHeadState(state.output, state.result); }
173
+ catch (error) { error.rawStdout = state.rawStdout; throw error; }
174
+ return {repositoryRoot, headRef, ...headState, origin, upstream};
151
175
  }
152
176
 
153
177
  export async function repositoryStatus({
@@ -0,0 +1,103 @@
1
+ import Is from 'strong-type';
2
+ import {stat} from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import {throwIfAborted} from './errors.mjs';
5
+
6
+ const is=new Is(false);
7
+ let installedDirectory=null;
8
+
9
+ // Git for Windows records its chosen location for third-party integrations.
10
+ // Read both installation scopes/views in one owned Windows process. No guessed
11
+ // installation paths or changes to the registry/environment are needed.
12
+ const READ_INSTALLATIONS=String.raw`
13
+ $ErrorActionPreference = 'Stop'
14
+ [Console]::OutputEncoding = [System.Text.UTF8Encoding]::new($false)
15
+ $installations = [System.Collections.Generic.List[string]]::new()
16
+ $views = @([Microsoft.Win32.RegistryView]::Registry32)
17
+ if ([Environment]::Is64BitOperatingSystem) {
18
+ $views = @([Microsoft.Win32.RegistryView]::Registry64, [Microsoft.Win32.RegistryView]::Registry32)
19
+ }
20
+ foreach ($hive in @([Microsoft.Win32.RegistryHive]::CurrentUser, [Microsoft.Win32.RegistryHive]::LocalMachine)) {
21
+ foreach ($view in $views) {
22
+ $base = [Microsoft.Win32.RegistryKey]::OpenBaseKey($hive, $view)
23
+ try {
24
+ $key = $base.OpenSubKey('Software\GitForWindows')
25
+ if ($null -ne $key) {
26
+ try {
27
+ $location = $key.GetValue('InstallPath')
28
+ if ($location -is [string] -and $location.Length -gt 0 -and -not $installations.Contains($location)) {
29
+ $installations.Add($location)
30
+ }
31
+ } finally { $key.Dispose() }
32
+ }
33
+ } finally { $base.Dispose() }
34
+ }
35
+ }
36
+ ConvertTo-Json -InputObject @($installations.ToArray()) -Compress
37
+ `;
38
+
39
+ function environmentKey(environment,name){
40
+ return Object.keys(environment).sort().find(function matchingKey(key){
41
+ return key.toLowerCase()===name;
42
+ });
43
+ }
44
+
45
+ export function appendWindowsGitDirectory(environment,directory){
46
+ const keys=Object.keys(environment).filter(function pathKey(key){
47
+ return key.toLowerCase()==='path';
48
+ }).sort();
49
+ const selectedKey=keys[0]??'Path';
50
+ // Node chooses the first case-insensitive key before omitting undefined.
51
+ // libuv uses the parent's PATH if the child has none; null becomes 'null'.
52
+ const selected=environment[selectedKey];
53
+ const original=selected===undefined
54
+ ?process.env[environmentKey(process.env,'path')]??'':selected;
55
+ const result={...environment};
56
+ for(const key of keys)delete result[key];
57
+ const originalPath=`${original}`;
58
+ result[selectedKey]=`${originalPath}${originalPath?';':''}"${directory}"`;
59
+ return result;
60
+ }
61
+
62
+ async function installedGitExists(directory){
63
+ try{
64
+ return (await stat(path.win32.join(directory,'git.exe'))).isFile();
65
+ }catch(error){
66
+ if(error.code==='ENOENT'||error.code==='ENOTDIR')return false;
67
+ throw error;
68
+ }
69
+ }
70
+
71
+ export async function windowsGitEnvironment(environment,{run,signal,onEvent}={}){
72
+ throwIfAborted(signal);
73
+ const retained=installedDirectory;
74
+ if(retained){
75
+ const available=await installedGitExists(retained);
76
+ throwIfAborted(signal);
77
+ if(available)return appendWindowsGitDirectory(environment,retained);
78
+ if(installedDirectory===retained)installedDirectory=null;
79
+ }
80
+
81
+ const systemRoot=process.env[environmentKey(process.env,'systemroot')];
82
+ if(!systemRoot)return environment;
83
+ const powershell=path.win32.join(systemRoot,'System32','WindowsPowerShell','v1.0','powershell.exe');
84
+ const result=await run(powershell,['-NoProfile','-NonInteractive','-Command',READ_INSTALLATIONS],{
85
+ signal,onEvent,emitOutputEvents:false
86
+ });
87
+ throwIfAborted(signal);
88
+ const locations=JSON.parse(result.stdout);
89
+ if(!is.array(locations))throw new TypeError('Git installation discovery did not return a location list.');
90
+ for(const location of locations){
91
+ if(!is.string(location)||!path.win32.isAbsolute(location))continue;
92
+ const directory=path.win32.join(location,'cmd');
93
+ const available=await installedGitExists(directory);
94
+ throwIfAborted(signal);
95
+ if(available){
96
+ // Only successful, still-present discovery is retained. Concurrent
97
+ // first callers own separate lookups and cancellation independently.
98
+ installedDirectory=directory;
99
+ return appendWindowsGitDirectory(environment,directory);
100
+ }
101
+ }
102
+ return environment;
103
+ }