@timurproko/a1 0.2.3 → 0.2.4-dev.664

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
@@ -28,6 +28,14 @@ a1 update --develop 0.1.8-dev.107 # install that exact preview
28
28
  a1 update --models # refresh A1's model catalogs
29
29
  ```
30
30
 
31
+ If `a1 update` from `0.2.2` reports that it could not resolve npm's JavaScript entry, use the official installer once:
32
+
33
+ ```sh
34
+ npm x -y -- @timurproko/a1-install
35
+ ```
36
+
37
+ It verifies the existing installation and delegates replacement to A1's cancellation-safe updater with npm's canonical execution context. Do not delete A1 data or overwrite the package with a raw global install. After the bridge, use ordinary `a1 update` commands again.
38
+
31
39
  On interactive startup A1 checks, at most once a day, whether a newer release exists on its own channel and shows an `Update Available` notice with the command to run. It never installs anything. Turn it off with the `Update check` setting (Generic section), or set `A1_SKIP_VERSION_CHECK=1`; it is also skipped when `PI_OFFLINE` or `CI` is set, or when output is not a terminal. The check reads the public npm registry, or `npm_config_registry` when set.
32
40
 
33
41
  ## Extensions
@@ -41,6 +41,8 @@ export interface ProtectedPackageReplacementOptions {
41
41
  readonly globalRoot: string;
42
42
  /** Root used only to locate the active npm implementation; installation remains pinned to globalRoot. */
43
43
  readonly npmCliRoot?: string;
44
+ /** Canonical entry established before update lifecycle mutation and revalidated when the capsule is committed. */
45
+ readonly npmCli?: string;
44
46
  readonly packageRoot: string;
45
47
  readonly transaction: UpdateTransaction;
46
48
  readonly priorRelease: {
@@ -119,7 +119,9 @@ export async function prepareUpdateRecoveryCapsule(options) {
119
119
  const prefix = npmPrefixForGlobalRoot(canonicalGlobal, platform);
120
120
  const launcherRoot = platform === "win32" ? prefix : resolve(prefix, "bin");
121
121
  const npmCliRoot = await realpath(options.npmCliRoot ?? canonicalGlobal);
122
- const npmCli = await resolveNpmCli(npmCliRoot, options.environment ?? process.env, platform);
122
+ const npmCli = options.npmCli === undefined
123
+ ? await resolveNpmCli(npmCliRoot, options.environment ?? process.env, platform)
124
+ : await requireCanonicalNpmCli(options.npmCli);
123
125
  const capsule = {
124
126
  schema: UPDATE_RECOVERY_SCHEMA,
125
127
  launchContract: PRIVATE_LAUNCH_CONTRACT,
@@ -300,7 +302,10 @@ async function spawnRecoveryWorker(entry, manifestPath, environment) {
300
302
  child.unref();
301
303
  }
302
304
  export async function resolveNpmCli(globalRoot, environment, platform = process.platform, nodeExecutable = process.execPath) {
303
- const candidates = [environment.npm_execpath, resolve(globalRoot, "npm", "bin", "npm-cli.js")]
305
+ const declaredNpmEntry = platform === "win32"
306
+ ? windowsEnvironmentValue(environment, "npm_execpath")
307
+ : environment.npm_execpath;
308
+ const candidates = [declaredNpmEntry, resolve(globalRoot, "npm", "bin", "npm-cli.js")]
304
309
  .filter((value) => typeof value === "string" && value.length > 0);
305
310
  for (const candidate of candidates) {
306
311
  const canonical = await canonicalRegularFile(candidate);
@@ -342,12 +347,20 @@ async function windowsNpmCliFromPath(environment) {
342
347
  return null;
343
348
  }
344
349
  function windowsEnvironmentValue(environment, name) {
350
+ if (Object.hasOwn(environment, name))
351
+ return environment[name];
345
352
  const key = Object.keys(environment).find(candidate => candidate.toLowerCase() === name.toLowerCase());
346
353
  return key ? environment[key] : undefined;
347
354
  }
348
355
  function unquoteWindowsPathEntry(value) {
349
356
  return value.length >= 2 && value.startsWith('"') && value.endsWith('"') ? value.slice(1, -1) : value;
350
357
  }
358
+ async function requireCanonicalNpmCli(path) {
359
+ const canonical = await canonicalRegularFile(path);
360
+ if (!canonical)
361
+ throw new Error("preflight npm JavaScript entry is no longer a regular file");
362
+ return canonical;
363
+ }
351
364
  async function canonicalRegularFile(path) {
352
365
  const canonical = await realpath(path).catch(() => null);
353
366
  if (!canonical)
@@ -1,7 +1,7 @@
1
1
  import type { UpdateChannel } from "./types.js";
2
2
  import { type UpdateActivationPhase, type UpdateMaterializationProgress } from "./update-activation.js";
3
3
  import { type UpdateRecoveryState, type UpdateTransaction, type UpdateTransactionPhase } from "./update-transaction.js";
4
- import { type ProtectedPackageReplacementResult } from "./update-recovery.js";
4
+ import { resolveNpmCli, type ProtectedPackageReplacementResult } from "./update-recovery.js";
5
5
  export declare const PRODUCT_PACKAGE: string;
6
6
  export type { UpdateChannel } from "./types.js";
7
7
  export interface ProcessRequest {
@@ -69,6 +69,8 @@ export interface SelfUpdateOptions {
69
69
  transactionStore?: UpdateTransactionJournal;
70
70
  /** Test seam for protected global package replacement. */
71
71
  packageReplacement?: (input: UpdatePackageReplacementInput) => Promise<ProtectedPackageReplacementResult>;
72
+ /** Test seam for proving replacement-authority preflight ordering. */
73
+ npmCliResolver?: typeof resolveNpmCli;
72
74
  /** Test or embedding seam for post-activation release maintenance. */
73
75
  maintenance?: () => Promise<void>;
74
76
  progress?: boolean;
@@ -102,6 +104,7 @@ export interface UpdatePackageReplacementInput {
102
104
  readonly dataDir: string;
103
105
  readonly globalRoot: string;
104
106
  readonly npmCliRoot: string;
107
+ readonly npmCli?: string;
105
108
  readonly packageRoot: string;
106
109
  readonly transaction: UpdateTransaction;
107
110
  readonly priorRelease: {
@@ -16,7 +16,7 @@ import { materializeRelease, readMaterializedRelease } from "./release-store.js"
16
16
  import { scheduleReleaseCleanup } from "./release-gc.js";
17
17
  import { UPDATE_ACTIVATION_CONTRACT, activateInstalledRelease, delegateActivation, readActivationContracts, } from "./update-activation.js";
18
18
  import { UpdateTransactionStore } from "./update-transaction.js";
19
- import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
19
+ import { npmPrefixForGlobalRoot, removeUpdateRecoveryCapsule, resolveNpmCli, runProtectedPackageReplacement, updateLauncherPaths, updateNpmInstallArguments, } from "./update-recovery.js";
20
20
  export const PRODUCT_PACKAGE = PRODUCT_TEXT.packageName;
21
21
  const UPDATE_DIST_TAGS = { stable: "latest", next: "next" };
22
22
  const defaultFileSystem = {
@@ -520,6 +520,7 @@ export async function runSelfUpdate(options) {
520
520
  ? async () => { }
521
521
  : async () => await scheduleReleaseCleanup(paths.dataDir, paths));
522
522
  let transaction = await transactionStore.read();
523
+ let npmCli;
523
524
  try {
524
525
  if ((!transaction || transaction.status === "completed") && await lifecycle.targetIsActive(targetVersion)) {
525
526
  await maintenance();
@@ -527,7 +528,17 @@ export async function runSelfUpdate(options) {
527
528
  output.stdout(`${PRODUCT_TEXT.commandName} is up to date — no update needed.\n`);
528
529
  return 0;
529
530
  }
530
- // Rationale: the bar first appears here so a no-change run never flashes it.
531
+ const preflightRequired = options.npmCliResolver !== undefined
532
+ || options.packageReplacement === undefined && options.runner === undefined;
533
+ if (preflightRequired) {
534
+ try {
535
+ npmCli = await (options.npmCliResolver ?? resolveNpmCli)(npmCliRoot, environment);
536
+ }
537
+ catch (error) {
538
+ throw new UpdatePreflightFailure(errorMessage(error));
539
+ }
540
+ }
541
+ // Rationale: the bar first appears only after every non-mutating replacement prerequisite passes.
531
542
  progress.set(3, 15);
532
543
  const cohortStore = new CohortStateStore(paths.dataDir);
533
544
  let cohortState = await cohortStore.read();
@@ -573,6 +584,7 @@ export async function runSelfUpdate(options) {
573
584
  dataDir: paths.dataDir,
574
585
  globalRoot,
575
586
  npmCliRoot,
587
+ ...(npmCli === undefined ? {} : { npmCli }),
576
588
  packageRoot,
577
589
  transaction: replacementTransaction,
578
590
  priorRelease,
@@ -655,6 +667,10 @@ export async function runSelfUpdate(options) {
655
667
  catch (error) {
656
668
  progress.clear();
657
669
  const message = errorMessage(error);
670
+ if (error instanceof UpdatePreflightFailure) {
671
+ output.stderr(`${PRODUCT_TEXT.diagnostic(`update could not start safely: ${message}. Nothing was changed. Retry with npm x -y -- ${PRODUCT_PACKAGE}-install`)}\n`);
672
+ return 1;
673
+ }
658
674
  const rollback = options.lifecycle
659
675
  ? "previous test lifecycle retained"
660
676
  : await rollbackPriorCohort(paths.dataDir, environment, transaction?.priorActiveReleaseId ?? null).catch(rollbackError => `rollback failed: ${errorMessage(rollbackError)}`);
@@ -760,6 +776,8 @@ class UpdateFailure extends Error {
760
776
  exitCode;
761
777
  constructor(exitCode, message) { super(message); this.exitCode = exitCode; }
762
778
  }
779
+ class UpdatePreflightFailure extends Error {
780
+ }
763
781
  async function canonicalImmutableRoot(dataDir, releaseRoot) {
764
782
  try {
765
783
  const [store, selected] = await Promise.all([realpath(resolve(dataDir, "releases")), realpath(releaseRoot)]);
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-03T06:54:00.608Z",
8
+ "builtAt": "2026-10-03T10:56:57.116Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "d3979187180449cb181eb92c187d1c8d0bfc4b2ba442e2e0a7c32704ea7e9589",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-03T06:53:59.023Z",
8
+ "builtAt": "2026-10-03T10:56:40.417Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "a316d11a4aabe5aa88a360169206a87a11fe4b355e836b77cd8a7950279ab415",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-03T06:54:43.872Z",
8
+ "builtAt": "2026-10-03T10:57:08.998Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "b887732721bba6f0395e92c0a1ce9f59fe30fff50ce1d6d43b2a47a74f219cfe",
11
+ "sha256": "121e6f570287541ace2c806411da2ca836cb2acab9b1205054b0d68b809d363d",
12
12
  "size": 177152
13
13
  },
14
14
  "provenance": {
@@ -80,7 +80,7 @@ Metadata that predates contract declaration is stale, never fatal. A pre-cutover
80
80
 
81
81
  Activation after the global installation is handed over the same way. The package manifest declares `updateActivationContracts` (`activate-v1`) and ships `bin/activate.js`; an updater that serves a declared contract starts that entry with the data directory and the target version and relays its line-delimited progress events (`materializing`, `phase`, `warmup`, `completed`, `failed`) into its own journal and progress bar, while the entry materializes, certifies, warms, and supervises the tree it ships in with that tree's own code. The updater resolves nothing inside the installed tree but `package.json` and that entry; `test/repository-governance/update-activation-contract.test.ts` pins this. A tree that declares no contract the updater serves is activated in-process with the layout it had, which keeps an updater older than the contract and a downgrade to an older preview working.
82
82
 
83
- `test/foundation/release/update-predecessor.integration.test.ts` is the gate for this. It installs each of the most recent published releases and drives the candidate through **that release's own** materialization and warmup, because a fixture built from the candidate would only prove the candidate agrees with itself. No reset or migration runs automatically. If disposable state blocks installation, inspect its resolved paths and obtain separate approval before removal.
83
+ `test/foundation/release/update-predecessor.integration.test.ts` is the gate for this. It installs each of the most recent published releases and drives the candidate through **that release's own** materialization and warmup, because a fixture built from the candidate would only prove the candidate agrees with itself. On Windows it also drives the newest predecessor's protected package replacement from a user-style A1 prefix with npm beside a separate Node installation, then exercises the explicit `0.2.2` installer-context bridge. Candidate-only recovery success therefore cannot hide a target-side fix that its installed predecessor cannot reach. No reset or migration runs automatically. If disposable state blocks installation, inspect its resolved paths and obtain separate approval before removal.
84
84
 
85
85
  | State | Treatment |
86
86
  | --- | --- |
@@ -99,7 +99,7 @@ Ordinary type, architecture, unit/contract, and dist checks always run for code
99
99
 
100
100
  Integration owners declare one cadence in `config/integration-owners.json`. Impact mode selects affected `pull-request` owners: a changed production path selects its coarse owner, a changed test selects its owner, and a changed file under `test/support/` or `test/fixtures/` selects the owners of the retained tests that import it directly or through other support files (the selection lists those tests as its `shared-support` reason). A support file that no retained test imports, or a test tree the scanner cannot read, falls back to every owner the shared rule declares and records `shared-support-declared`; an invalidator, unknown operational path, or manual Development dispatch selects every `pull-request` owner. An implementation-bound PR body does not force conservative selection; it only removes the documentation-only and version-only shortcuts. The modular matrix itself is derived from the selection by `scripts/release/validation-matrix.mjs`, so inactive jobs are not scheduled at all rather than checking out and exiting early. `exhaustive` owners are never silently skipped or reported as passed: impact and aggregate evidence list them as cadence-deferred, and malformed cadence blocks selection. Full regression and nightly/stable release still execute both cadence classes. PR-attached Full regression is selected only when base policy verifies an `openspec-ci` App-authored candidate, its generated repair identity, and every qualifying failed Full regression source field in `regression-provenance.json` against GitHub's read-only workflow-run record. Release/publishing, shared-support, prerequisite, build, validation-authority, unknown-path, label, Publish-only repair, and successful-run persistent-overrun changes retain bounded ordinary validation. Planning-only generated repair drafts remain exempt; eligible implementation drafts receive native lane feedback without integration authority.
101
101
 
102
- The real three-release `update-predecessor` scenario is exhaustive because four fresh npm installations dominated recent PR critical paths, and `update-performance` is exhaustive because its assertion is wall-clock timing on a shared runner; the update path's deterministic contracts stay on pull requests through `pi-release-resume` and `package-contracts`. PR validation retains deterministic predecessor command, lifecycle, fault, fixture, materialization, warmup, package, and update contracts. This permits a real published-history incompatibility or update slowdown to reach `develop` before the next exhaustive run detects it. `.github/workflows/full-regression.yml` runs every night at `02:47 UTC` against the `develop` tip with every owner and enforced budgets, independent of publication, so such a regression shows as a failed Full regression run the next morning; nightly publication's own complete validation still blocks publication. For diagnosis on any other candidate, dispatch standalone Full regression; labels and high-risk release/update impact do not add it to PR checks or change owner cadence.
102
+ The real three-release `update-predecessor` scenario is exhaustive because its fresh npm installations and private-prefix protected replacements dominate recent PR critical paths, and `update-performance` is exhaustive because its assertion is wall-clock timing on a shared runner. On Windows the predecessor owner requires the newest published updater to replace and activate the exact candidate with A1 under a user-style prefix and npm beside a separate Node installation; it separately proves the declared `0.2.2` official-installer context bridge without relabeling that immutable build as direct-update capable. The update path's deterministic contracts stay on pull requests through `pi-release-resume` and `package-contracts`, including preflight ordering, installer npm-context delegation, failure containment, lifecycle, recovery, materialization, warmup, package, and update behavior. This permits a real published-history incompatibility or update slowdown to reach `develop` before the next exhaustive run detects it, but nightly and stable publication remain blocked by the exhaustive predecessor outcome. `.github/workflows/full-regression.yml` runs every night at `02:47 UTC` against the `develop` tip with every owner and enforced budgets, independent of publication, so such a regression shows as a failed Full regression run the next morning; nightly publication's own complete validation still blocks publication. For diagnosis on any other candidate, dispatch standalone Full regression; labels and high-risk release/update impact do not add it to PR checks or change owner cadence.
103
103
 
104
104
  Development outcomes report each owner/scope invocation separately while sharing authenticated build/package preparation. The aggregate reports setup, scope, job, aggregate-processing, total runner, and runner-critical-path durations. The acceptance targets are at most eight minutes of runner critical path and five minutes for one PR-required scope; an over-target result remains unmet without retries, timeout increases, workload reduction, or mutable installation caches. Hosted queue time is reported separately when available and is not counted as test execution.
105
105
 
@@ -270,8 +270,14 @@ the version. The retired `--approve` form remains a mutation-free usage error.
270
270
  stops preparation), and maps every later first-parent commit through one merged
271
271
  GitHub pull request. It generates Pi-style Markdown beginning
272
272
  `## [version] - YYYY-MM-DD`, followed by applicable `### Breaking Changes`,
273
- `### New Features`, `### Added`, `### Changed`, and `### Fixed` sections, and
274
- creates one source-bound **draft GitHub Release**. It then dispatches
273
+ `### New Features`, `### Added`, `### Changed`, and `### Fixed` sections. Explicit
274
+ breaking changes always remain visible. Ordinary non-breaking `chore` pull requests
275
+ and generated regression-triage repairs are omitted; a regression repair that proves
276
+ a user-facing defect must use its actual `fix(scope)` title before merge. Pi proposals
277
+ use `upgrade(pi)` and appear under Changed, while historical `chore(pi): upgrade`
278
+ titles remain compatible. A range containing only filtered maintenance says that it
279
+ has no user-facing changes. Preparation then creates one source-bound **draft GitHub
280
+ Release**. It dispatches
275
281
  `release-candidate.yml`, which stamps the stable version, packs both packages with
276
282
  the generated note, and runs the complete stable suite on Windows, Linux, and macOS
277
283
  without publishing. A running or successful validation of the same source and
@@ -1,22 +1,16 @@
1
1
  ## [0.2.3] - 2026-10-03
2
2
 
3
- ### New Features
4
-
5
- - auto-merge the verified release reopening PR ([#651](https://github.com/timurproko/a1/pull/651))
6
-
7
3
  ### Changed
8
4
 
9
5
  - upgrade pinned Pi to 0.99.1 ([#642](https://github.com/timurproko/a1/pull/642))
10
- - match changelog link decoration ([#649](https://github.com/timurproko/a1/pull/649))
11
6
  - upgrade pinned Pi to 0.99.2 ([#652](https://github.com/timurproko/a1/pull/652))
7
+ - match changelog link decoration ([#649](https://github.com/timurproko/a1/pull/649))
12
8
  - rename stable wrapper ([#655](https://github.com/timurproko/a1/pull/655))
13
9
 
14
10
  ### Fixed
15
11
 
16
- - reuse PR full-regression selection across failed-job reruns ([#650](https://github.com/timurproko/a1/pull/650))
17
12
  - restore documentation auto-merge ([#653](https://github.com/timurproko/a1/pull/653))
18
13
  - allow development publication startup ([#654](https://github.com/timurproko/a1/pull/654))
19
14
  - bound Windows backlog setup ([#656](https://github.com/timurproko/a1/pull/656))
20
15
  - resolve bundled npm entry ([#657](https://github.com/timurproko/a1/pull/657))
21
16
  - verify exact npm resources ([#659](https://github.com/timurproko/a1/pull/659))
22
- - repair the 2026-10-02 full regression failure ([#660](https://github.com/timurproko/a1/pull/660))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.3",
3
+ "version": "0.2.4-dev.664",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",