@timurproko/a1 0.2.1-dev.590 → 0.2.1-dev.599

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
@@ -3,20 +3,26 @@
3
3
  ## Install
4
4
 
5
5
  ```sh
6
- npm install --global @timurproko/a1@latest
6
+ # stable release
7
+ npx -y @timurproko/a1-install
8
+
9
+ # development channel
10
+ npx -y @timurproko/a1-install --develop
11
+
12
+ # exact development version
13
+ npx -y @timurproko/a1-install --version 0.1.8-dev.107
7
14
  ```
8
15
 
9
- Any channel can also be installed or updated directly with npm, without the
10
- `a1 update` commands:
16
+ The installer keeps npm's dependency transcript out of the normal terminal output,
17
+ shows one progress row, activates the installed release, and verifies the command
18
+ before reporting success. The shorter `npx @timurproko/a1-install` form also works,
19
+ but npm may ask for first-use confirmation; `-y` avoids that prompt.
11
20
 
12
- ```sh
13
- # dev channel (next tag)
14
- npm install -g @timurproko/a1@next
21
+ Direct npm installation remains available as a fallback or manual recovery path:
15
22
 
16
- # stable release
23
+ ```sh
17
24
  npm install -g @timurproko/a1@latest
18
-
19
- # exact version
25
+ npm install -g @timurproko/a1@next
20
26
  npm install -g @timurproko/a1@0.1.8-dev.107
21
27
  ```
22
28
 
@@ -70,10 +70,6 @@ async function receive(message) {
70
70
  send({ kind: "phase", phase: "extracted", bytes });
71
71
  const text = parts.join("\n");
72
72
  parts = [];
73
- if (text.length === 0) {
74
- finish({ outcome: "failed", failure: "size" });
75
- return;
76
- }
77
73
  if (header.mode === "prepare") {
78
74
  // Protocol: this success acknowledges prepared IPC only; the owner must still await its injected writer.
79
75
  output = text;
@@ -866,12 +866,12 @@ export class OwnedUiSessionShell {
866
866
  this.root.setInputSurface(null);
867
867
  this.runtime.requestRender();
868
868
  };
869
- const select = (level, persist) => {
869
+ const select = (level) => {
870
870
  close();
871
- void this.runWorkflow({ command: "thinking", argument: "", selection: level, ...(persist ? { persist: true } : {}) });
871
+ void this.runWorkflow({ command: "thinking", argument: "", selection: level });
872
872
  };
873
873
  const { createPiShellThinkingSelector } = await import("../../integrations/pi/components/thinking-selector-dialog.js");
874
- const component = createPiShellThinkingSelector(snapshot.thinkingLevel, snapshot.availableThinkingLevels, level => select(level, false), close, level => select(level, true), snapshot.defaultThinkingLevel, this.#customViewport ? {
874
+ const component = createPiShellThinkingSelector(snapshot.thinkingLevel, snapshot.availableThinkingLevels, select, close, level => this.backend.setDefaultThinkingLevel(level), snapshot.defaultThinkingLevel, this.#customViewport ? {
875
875
  profile: "bare",
876
876
  cycleBinding: this.root.editor.keybindingConfig()["app.thinking.cycle"] ?? [],
877
877
  } : undefined);
@@ -5,4 +5,4 @@ export interface PiShellThinkingSelectorPresentation {
5
5
  readonly cycleBinding: string | readonly string[];
6
6
  }
7
7
  /** Lazily loaded because thinking-selector code does not belong to the startup graph before `/thinking` opens. */
8
- export declare function createPiShellThinkingSelector(currentLevel: ThinkingSelectorLevel, availableLevels: readonly ThinkingSelectorLevel[], onSelect: (level: string) => void, onCancel: () => void, onSelectAsDefault?: (level: string) => void, defaultLevel?: ThinkingSelectorLevel, presentation?: PiShellThinkingSelectorPresentation): PiShellComponentPort;
8
+ export declare function createPiShellThinkingSelector(currentLevel: ThinkingSelectorLevel, availableLevels: readonly ThinkingSelectorLevel[], onSelect: (level: ThinkingSelectorLevel) => void, onCancel: () => void, onSelectAsDefault?: (level: ThinkingSelectorLevel) => void, defaultLevel?: ThinkingSelectorLevel, presentation?: PiShellThinkingSelectorPresentation): PiShellComponentPort;
@@ -2,14 +2,15 @@
2
2
  * Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
3
3
  * packages/coding-agent/src/modes/interactive/components/thinking-selector.ts.
4
4
  * Modifications: Preserve the searchable thinking-level selector, current/default semantics,
5
- * selection, save, cancellation, and focus while accepting the active bare-A1 cycle-key label from the
6
- * shell, styling the title with the established bold semantic accent treatment, placing it directly
7
- * below the top rule through the shared compact padded modal frame and its muted hint directly below
8
- * it, deduplicating levels, and rendering aligned muted descriptions after adjacent active and
9
- * bracketed default markers. All list and border colors use the owned theme and its explicit color
10
- * mode, and the footer uses the shared bare-A1 modal shortcut row. The comparison profile retains the
11
- * public pinned component.
12
- * Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading.
5
+ * selection, and focus while accepting the active bare-A1 cycle-key label from the shell, styling the
6
+ * title with the established bold semantic accent treatment, placing it directly below the top rule
7
+ * through the shared compact padded modal frame and its muted hint directly below it, deduplicating
8
+ * levels, and rendering aligned muted descriptions after fixed active and bracketed default columns.
9
+ * Bare A1 persists defaults immediately on Space, closes only on Escape, and uses the shared compact
10
+ * semantic shortcut row. All list and border colors use the owned theme and its explicit color mode.
11
+ * The comparison profile retains the public pinned component.
12
+ * Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading,
13
+ * owned-thinking-selector-controls.
13
14
  */
14
15
  import { Container, type Focusable, SelectList } from "@earendil-works/pi-tui";
15
16
  export type ThinkingSelectorLevel = "off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max";
@@ -2,16 +2,17 @@
2
2
  * Provenance: @earendil-works/pi-coding-agent 0.87.1 (MIT), commit f07218c4d4bbc12bef056a7058c3dd49dfe41abe,
3
3
  * packages/coding-agent/src/modes/interactive/components/thinking-selector.ts.
4
4
  * Modifications: Preserve the searchable thinking-level selector, current/default semantics,
5
- * selection, save, cancellation, and focus while accepting the active bare-A1 cycle-key label from the
6
- * shell, styling the title with the established bold semantic accent treatment, placing it directly
7
- * below the top rule through the shared compact padded modal frame and its muted hint directly below
8
- * it, deduplicating levels, and rendering aligned muted descriptions after adjacent active and
9
- * bracketed default markers. All list and border colors use the owned theme and its explicit color
10
- * mode, and the footer uses the shared bare-A1 modal shortcut row. The comparison profile retains the
11
- * public pinned component.
12
- * Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading.
5
+ * selection, and focus while accepting the active bare-A1 cycle-key label from the shell, styling the
6
+ * title with the established bold semantic accent treatment, placing it directly below the top rule
7
+ * through the shared compact padded modal frame and its muted hint directly below it, deduplicating
8
+ * levels, and rendering aligned muted descriptions after fixed active and bracketed default columns.
9
+ * Bare A1 persists defaults immediately on Space, closes only on Escape, and uses the shared compact
10
+ * semantic shortcut row. All list and border colors use the owned theme and its explicit color mode.
11
+ * The comparison profile retains the public pinned component.
12
+ * Deviations: owned-modal-shortcut-hints, owned-level-cycle-shortcut, owned-thinking-selector-heading,
13
+ * owned-thinking-selector-controls.
13
14
  */
14
- import { Container, fuzzyFilter, getKeybindings, Input, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
15
+ import { Container, fuzzyFilter, getKeybindings, Input, Key, matchesKey, SelectList, Spacer, Text, } from "@earendil-works/pi-tui";
15
16
  import { DynamicBorder } from "../../../startup-public.js";
16
17
  import { addPiModalHeader, adoptPiModalFrame } from "../../modal-frame.js";
17
18
  import { piTheme, renderPiModalShortcutHints } from "../../theme.js";
@@ -72,9 +73,9 @@ export class OwnedThinkingSelectorComponent extends Container {
72
73
  this.addChild(this.selectListContainer);
73
74
  this.addChild(new Spacer(1));
74
75
  this.addChild(new Text(renderPiModalShortcutHints([
75
- { key: this.keyDisplayText("tui.select.confirm"), action: "to select" },
76
- { key: this.keyDisplayText("app.thinking.save"), action: "to set as default" },
77
- { key: this.keyDisplayText("tui.select.cancel"), action: "to cancel" },
76
+ { key: this.keyDisplayText("tui.select.confirm"), action: "select" },
77
+ { key: "Space", action: "default" },
78
+ { key: "Esc", action: "close" },
78
79
  ]), 0, 0));
79
80
  this.addChild(new DynamicBorder((text) => piTheme().fg("border", text)));
80
81
  adoptPiModalFrame(this, { topIndex: 0, bottomIndex: this.children.length - 1, header });
@@ -88,20 +89,15 @@ export class OwnedThinkingSelectorComponent extends Container {
88
89
  .join("/");
89
90
  }
90
91
  buildSelectList(items, preselect) {
91
- const markerWidth = (item) => (item.value === this.currentLevel ? 2 : 0) + (item.value === this.defaultThinkingLevel ? 10 : 0);
92
- const primaryWidth = this.allItems.reduce((widest, item) => {
93
- const level = item.label ?? item.value;
94
- return Math.max(widest, level.length + markerWidth(item));
95
- }, 0);
92
+ const levelWidth = this.allItems.reduce((widest, item) => Math.max(widest, (item.label ?? item.value).length), 0);
96
93
  const themedItems = items.map((item) => {
97
- const level = item.label ?? item.value;
98
- const currentMarker = item.value === this.currentLevel ? ` ${piTheme().fg("success", "✓")}` : "";
94
+ const level = (item.label ?? item.value).padEnd(levelWidth);
95
+ const currentMarker = item.value === this.currentLevel ? ` ${piTheme().fg("success", "✓")}` : " ";
99
96
  const defaultMarker = item.value === this.defaultThinkingLevel
100
97
  ? ` ${piTheme().fg("muted", "[default]")}`
101
- : "";
102
- const separator = " ".repeat(Math.max(1, primaryWidth - level.length - markerWidth(item) + 1));
98
+ : " ";
103
99
  const description = item.description ? piTheme().fg("muted", item.description) : "";
104
- return { value: item.value, label: `${level}${currentMarker}${defaultMarker}${separator}${description}` };
100
+ return { value: item.value, label: `${level}${currentMarker}${defaultMarker} ${description}` };
105
101
  });
106
102
  // Invariant: the bare selector uses the owned theme's explicit color mode, not upstream's host detection.
107
103
  const list = new SelectList(themedItems, Math.max(1, themedItems.length), {
@@ -115,7 +111,6 @@ export class OwnedThinkingSelectorComponent extends Container {
115
111
  if (currentIndex !== -1)
116
112
  list.setSelectedIndex(currentIndex);
117
113
  list.onSelect = (item) => this.onSelect(item.value);
118
- list.onCancel = () => this.onCancel();
119
114
  return list;
120
115
  }
121
116
  applyFilter(query) {
@@ -130,16 +125,22 @@ export class OwnedThinkingSelectorComponent extends Container {
130
125
  }
131
126
  handleInput(keyData) {
132
127
  const kb = getKeybindings();
133
- if (kb.matches(keyData, "app.thinking.save") && this.onSelectAsDefault) {
128
+ if (keyData === " ") {
134
129
  const item = this.selectList.getSelectedItem();
135
- if (item)
136
- this.onSelectAsDefault(item.value);
130
+ if (item) {
131
+ this.defaultThinkingLevel = item.value;
132
+ this.applyFilter(this.searchInput.getValue());
133
+ this.onSelectAsDefault?.(this.defaultThinkingLevel);
134
+ }
135
+ return;
136
+ }
137
+ if (matchesKey(keyData, Key.escape)) {
138
+ this.onCancel();
137
139
  return;
138
140
  }
139
141
  const isNav = kb.matches(keyData, "tui.select.up") ||
140
142
  kb.matches(keyData, "tui.select.down") ||
141
- kb.matches(keyData, "tui.select.confirm") ||
142
- kb.matches(keyData, "tui.select.cancel");
143
+ kb.matches(keyData, "tui.select.confirm");
143
144
  if (isNav) {
144
145
  this.selectList.handleInput(keyData);
145
146
  return;
@@ -118,6 +118,8 @@ export declare class PiEngineAdapter implements OwnedUiPromptSuggestionGenerator
118
118
  * theme's name, or a `light/dark` pair meaning "follow the terminal".
119
119
  */
120
120
  configuredTheme(): string | undefined;
121
+ /** Persist the global thinking default without changing the live session level. */
122
+ setDefaultThinkingLevel(level: PiPinnedSettingsSnapshot["defaultThinkingLevel"]): void;
121
123
  /** Settings port for the live runtime, or null before the runtime is available. */
122
124
  settingsPort(): PiSettingsBridge | null;
123
125
  /** Which settings surface this adapter serves; hidden-in-bare effects are unbindable in bare mode. */
@@ -379,6 +379,10 @@ export class PiEngineAdapter {
379
379
  configuredTheme() {
380
380
  return this.#settings.configuredTheme();
381
381
  }
382
+ /** Persist the global thinking default without changing the live session level. */
383
+ setDefaultThinkingLevel(level) {
384
+ this.#settings.setDefaultThinkingLevel(level);
385
+ }
382
386
  /** Settings port for the live runtime, or null before the runtime is available. */
383
387
  settingsPort() {
384
388
  return this.#settings.settingsPort();
@@ -170,11 +170,7 @@ export class PiEngineRuntime {
170
170
  this.#sessionBindingGeneration += 1;
171
171
  this.#session = session;
172
172
  this.#sessionCommands = new PiSessionCommandIntegration(session);
173
- this.#compactionProgress?.dispose();
174
- this.#compactionProgress = observeCompactionProgress(session, percent => {
175
- if (this.#session === session && !this.#ports.disposed())
176
- this.#ports.compactionProgress(percent);
177
- });
173
+ this.#attachCompactionProgress(session);
178
174
  // Invariant: the subscription is live before the adapter rebuilds its state so the extension
179
175
  // rebind that finishes the rebuild cannot emit an event nobody is listening to.
180
176
  this.#subscribe();
@@ -193,8 +189,14 @@ export class PiEngineRuntime {
193
189
  }
194
190
  /** Forward the current session's events again under the current generation. */
195
191
  resume() {
196
- if (this.#session === undefined)
192
+ const session = this.#session;
193
+ if (session === undefined)
197
194
  return;
195
+ // Invariant: resumed events cannot enter before observation is restored, and repeated resume
196
+ // owns only one subscription and one stream-function wrapper.
197
+ this.#unsubscribe?.();
198
+ this.#unsubscribe = undefined;
199
+ this.#attachCompactionProgress(session);
198
200
  this.#subscribe();
199
201
  }
200
202
  compactionStarted() {
@@ -342,6 +344,13 @@ export class PiEngineRuntime {
342
344
  }, this.#repositoryContextPollMs);
343
345
  this.#repositoryRefreshTimer.unref?.();
344
346
  }
347
+ #attachCompactionProgress(session) {
348
+ this.#compactionProgress?.dispose();
349
+ this.#compactionProgress = observeCompactionProgress(session, percent => {
350
+ if (this.#session === session && !this.#ports.disposed())
351
+ this.#ports.compactionProgress(percent);
352
+ });
353
+ }
345
354
  #subscribe() {
346
355
  const session = this.#session;
347
356
  if (session === undefined)
@@ -33,6 +33,8 @@ export declare class PiEngineSettings {
33
33
  * theme's name, or a `light/dark` pair meaning "follow the terminal".
34
34
  */
35
35
  configuredTheme(): string | undefined;
36
+ /** Persist the global thinking default without changing the live session level. */
37
+ setDefaultThinkingLevel(level: PiPinnedSettingsSnapshot["defaultThinkingLevel"]): void;
36
38
  /** Settings port for the live runtime, or null before the runtime is available. */
37
39
  settingsPort(): PiSettingsBridge | null;
38
40
  bindSettingsOwner(owner: AgentSettingOwner, handlers: PiSettingOwnerHandlers): () => void;
@@ -32,6 +32,16 @@ export class PiEngineSettings {
32
32
  configuredTheme() {
33
33
  return this.#ports.runtime()?.services.settingsManager.getThemeSetting();
34
34
  }
35
+ /** Persist the global thinking default without changing the live session level. */
36
+ setDefaultThinkingLevel(level) {
37
+ if (!isThinkingLevel(level))
38
+ throw new TypeError("Thinking level is invalid");
39
+ const settings = this.#ports.runtime()?.services.settingsManager;
40
+ if (!settings)
41
+ throw new Error("Settings are unavailable");
42
+ settings.setDefaultThinkingLevel(level);
43
+ this.#ports.emitView();
44
+ }
35
45
  /** Settings port for the live runtime, or null before the runtime is available. */
36
46
  settingsPort() {
37
47
  const settings = this.#ports.runtime()?.services.settingsManager;
@@ -474,7 +474,7 @@ export class PiWorkflowRunner {
474
474
  return workflowResult(request.command, "completed", "Compaction requested", undefined, "silent");
475
475
  }
476
476
  catch (error) {
477
- return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error), undefined, "silent");
477
+ return workflowResult(request.command, "failed", error instanceof Error ? error.message : String(error));
478
478
  }
479
479
  }
480
480
  case "resume": {
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-27T09:22:05.237Z",
8
+ "builtAt": "2026-09-27T15:27:01.448Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "9db1726bbe3fc2e8292f2ead1e7e9d0fd4d7d0dc9217b372582bf354b0f565dc",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-27T09:22:09.867Z",
8
+ "builtAt": "2026-09-27T15:26:48.689Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "d8cda6b0c7cb36c0cc41e90802aceebf6c02eaebbc08a83d7d963d2e918dbd7a",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-09-27T09:22:55.355Z",
8
+ "builtAt": "2026-09-27T15:27:34.555Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "68d285c38302670a03c8b58d0e387f17614070bcdcfdb5d3cead3a47d1d19763",
11
+ "sha256": "37b6952849458ba61a3238595d6075b7599f77608cc2c06e5fd485ab22743c3e",
12
12
  "size": 178176
13
13
  },
14
14
  "provenance": {
@@ -169,7 +169,7 @@ export declare class TranscriptViewport {
169
169
  releaseSelection(): boolean;
170
170
  clearSelection(): boolean;
171
171
  selectedText(): string | null;
172
- /** Captures only the selected visible frame text, never off-screen transcript history. */
172
+ /** Captures visible selected text, not off-screen history. */
173
173
  captureSelectedText(): SelectionCopySnapshot | null;
174
174
  scrollBy(lines: number, now?: number): boolean;
175
175
  scrollTo(position: number, now?: number): boolean;
@@ -179,14 +179,15 @@ export class TranscriptViewport {
179
179
  const text = textSelectionText(selection, this.#selectionRows, line => usefulTextLineContent(this.#selectionRows[line] ?? ""));
180
180
  return text.length === 0 ? null : text;
181
181
  }
182
- /** Captures only the selected visible frame text, never off-screen transcript history. */
182
+ /** Captures visible selected text, not off-screen history. */
183
183
  captureSelectedText() {
184
184
  if (!this.#copyableSelection)
185
185
  return null;
186
186
  const started = performance.now();
187
- const text = this.selectedText();
187
+ let text = this.selectedText();
188
188
  if (text === null)
189
189
  return null;
190
+ text = text.trim();
190
191
  const normalized = {
191
192
  start: { line: 0, column: 0 },
192
193
  end: { line: 0, column: Number.MAX_SAFE_INTEGER },
@@ -81,7 +81,7 @@ Presentation acceptance is the reader comparing `a1 pi` with pinned Pi. `node sc
81
81
 
82
82
  ## Publication
83
83
 
84
- One workflow publishes both channels: `.github/workflows/release.yml`. Pushes do not publish. Nightly development verification runs at `03:17 UTC`; `npm run develop` explicitly requests a numbered preview and `npm run release -- patch` explicitly requests stable publication after its version pull request is manually merged; it promotes a development version to its stable core. The following development-version pull request also requires manual merge. A preview is stamped as `-dev.<merged pull-request number>` and uses npm `next` only as an internal dist-tag. New candidates pack once and are validated on Windows, Linux, and macOS; a repeated nightly verifies the exact immutable registry tarball. All publication uses provenance from the `npm-publish` environment, a preview never changes `latest`, and the stable tag, GitHub Release, and `master` are written only after npm has the package. One global non-cancelling concurrency group serializes the final registry check.
84
+ One workflow publishes both channels: `.github/workflows/release.yml`. Pushes do not publish. Nightly development verification runs at `03:17 UTC`; `npm run develop` explicitly requests a numbered preview and `npm run release -- patch` explicitly requests stable publication after its version pull request is manually merged; it promotes a development version to its stable core. The following development-version pull request also requires manual merge. A preview is stamped as `-dev.<merged pull-request number>` and uses npm `next` only as an internal dist-tag. New candidates pack the application and dependency-free installer once each and validate their exact bytes on Windows, Linux, and macOS; a repeated nightly verifies both immutable registry tarballs. All publication uses provenance from the `npm-publish` environment, a preview never changes `latest`, and the stable tag, GitHub Release, and `master` are written only after npm has both packages. One global non-cancelling concurrency group serializes the final registry check.
85
85
 
86
86
  `docs/ci-release-runbook.md` is the operational reference.
87
87
 
@@ -166,14 +166,17 @@ The command fetches authoritative `origin/develop`, resolves the unique merged p
166
166
  request associated with that exact commit through GitHub, and derives
167
167
  `<major.minor.patch>-dev.<pull-request number>`. Thus GitHub's `develop (#107)`
168
168
  source produces `0.1.8-dev.107`. It first checks npm; if the immutable version
169
- already exists it reports that version without dispatching package work. Otherwise
170
- it dispatches GitHub Actions, waits, and reports the published version. It never
169
+ already exists for both the application and installer packages it reports that
170
+ version without dispatching package work. If either immutable artifact is missing,
171
+ it dispatches GitHub Actions, waits, and verifies both published versions. It never
171
172
  builds or uploads npm bytes from the workstation.
172
173
 
173
174
  Nightly resolves the same current `origin/develop` source. It runs one platform-independent full documentation review before the platform matrix, and the matrix records that prerequisite instead of repeating the scan four times. It always runs complete verification even if source has not changed. For a new number it packs once and
174
175
  runs the suite against that final-version tarball before publication. For an
175
- existing number it downloads the exact npm tarball and runs package/update gates
176
- against those registry bytes; publication is then a successful no-op.
176
+ existing number it downloads both exact npm tarballs and runs package/update and
177
+ installer gates against those registry bytes; publication is then a successful
178
+ no-op. A new or missing installer artifact is dependency-free, packed once, and
179
+ validated on every publication platform before the publish job receives it.
177
180
 
178
181
  Manual and nightly runs share one non-cancelling concurrency group. Their final
179
182
  registry check is serialized, so overlapping requests can produce only one publish
@@ -183,7 +186,9 @@ internal `next` dist-tag and never moves `latest`.
183
186
  Users install previews with public `develop` terminology:
184
187
 
185
188
  ```sh
186
- a1 update --develop # current development channel
189
+ npx -y @timurproko/a1-install --develop
190
+ npx -y @timurproko/a1-install --version 0.1.8-dev.107
191
+ a1 update --develop # update an existing installation
187
192
  a1 update --develop 107 # numbered preview
188
193
  a1 update --develop 0.1.8-dev.107 # exact full preview version
189
194
  ```
@@ -214,13 +219,13 @@ touching anything, and the stable version is never committed to `develop`.
214
219
  publication for that exact source with the stable version in the request. A changed source is
215
220
  an error, not permission to substitute a newer commit.
216
221
  2. The workflow stamps the requested version on the checked-out source
217
- (`npm version <x.y.z> --no-git-tag-version`) before packing, so the tarball
218
- declares `0.1.8` while the tagged commit still declares `0.1.8-dev`. Everything
219
- else about the package is byte-identical to that commit's tree; `git checkout
220
- v0.1.8 && npm version 0.1.8 --no-git-tag-version && npm pack` reproduces it.
221
- 3. Only after verified publication of `0.1.8` does the helper commit `0.1.9-dev`
222
- in an owned detached worktree beneath `.worktrees/` (only this package's manifest
223
- and root lockfile version change), open the one version PR, and wait for you to
222
+ (`npm version <x.y.z> --no-git-tag-version`) before packing the application and
223
+ stages the same exact version into the dependency-free installer before packing
224
+ it. Both tarballs therefore declare `0.1.8` while the tagged commit still
225
+ declares `0.1.8-dev`; their recorded digests bind them to the same source tree.
226
+ 3. Only after verified publication of both `0.1.8` packages does the helper commit
227
+ `0.1.9-dev` in an owned detached worktree beneath `.worktrees/` (the application
228
+ manifest, root lockfile, and installer manifest versions change), open the one version PR, and wait for you to
224
229
  **merge it manually** with a bounded 30-minute poll. The helper never merges
225
230
  PRs or enables auto-merge. Until that merge the helper reports development
226
231
  reopening as incomplete, and previews cannot be published from a `develop`
@@ -236,12 +241,23 @@ only when its branch, original HEAD, and cleanliness remain unchanged. Otherwise
236
241
  preserve local work and synchronize manually with the reported remote state.
237
242
 
238
243
  Stable publication builds the process guardian on all supported platforms, stamps
239
- the version, packs once, runs the complete suite against those exact bytes on
240
- Windows, Linux, and macOS, publishes to npm `latest` with provenance from the
241
- `npm-publish` environment, and then writes `vx.y.z` on the source commit, records
242
- the GitHub Release, and fast-forwards `master`. A push of the stable version does
244
+ the version, packs each package once, runs the complete suite and installer package
245
+ check against those exact bytes on Windows, Linux, and macOS, publishes both to npm
246
+ `latest` with provenance from the `npm-publish` environment, and exercises the exact
247
+ published installer/application pair in isolated prefixes on every release lane.
248
+ Only after those post-publication checks pass does it write `vx.y.z` on the source commit, record
249
+ the GitHub Release, and fast-forward `master`. A push of the stable version does
243
250
  not publish it, and no automation ever pushes one.
244
251
 
252
+ The installer package does not yet exist on npm, so its first publication needs a
253
+ one-time granular token in the `npm-publish` environment secret
254
+ `NPM_BOOTSTRAP_TOKEN`; the workflow still publishes the exact validated tarball
255
+ with provenance. Immediately after that first successful publication, configure
256
+ `.github/workflows/release.yml` and environment `npm-publish` as the package's npm
257
+ trusted publisher, delete the bootstrap secret, and let npm 11 authenticate future
258
+ publications through GitHub OIDC. The application package continues using its
259
+ existing trusted-publisher configuration throughout.
260
+
245
261
  Rules that do not bend:
246
262
 
247
263
  - **Never upload locally rebuilt bytes.** The publisher uploads the artifact the validation ran against and checks its digest before and after.
@@ -264,7 +280,7 @@ Rules that do not bend:
264
280
  - **A Pi upgrade proposal needs a re-run:** the sync never force-pushes over `chore/pi-<version>` once it carries a commit the bot did not author; a scheduled re-run posts its fresh verdicts as a comment headed with the version and date and rewrites only the report between the `<!-- pi-upgrade-report -->` markers of the description. To re-run the derived steps and gates on the reviewer's head, dispatch the workflow with `refresh` and the `version`; it checks out the proposal branch, skips bump, evaluation, install, and merge, and reports without pushing. A branch whose commits are all the bot's is recreated from `develop` as before.
265
281
  - **A Pi version should not be adopted:** close its proposal pull request with the `pi-upgrade-skipped` label (declared in `config/github-repository-governance.json`) and say why in the closing comment; the next run proposes the newest unskipped version newer than the pin, or nothing, and names the skipped versions in its log. A proposal closed without the label is proposed again the next night. Dispatching the workflow with a `version` ignores skips, which is how a skipped version is reconsidered. To silence the schedule through a release window, set the repository variable `PI_UPGRADE_FREEZE_UNTIL` to a date (`2026-10-01`); scheduled runs exit before proposing until it passes, manual dispatches still run.
266
282
  - **Nightly documentation review fails:** inspect the reported paths and rules, identify the introducing merge from the nightly interval, and repair the invariant before unrelated work proceeds.
267
- - **Development publication fails:** fix the cause and rerun `npm run develop`; an npm version that already exists is never overwritten.
283
+ - **Development publication fails:** inspect both package versions and digests, fix the cause, and rerun `npm run develop`; an npm version that already exists is verified and never overwritten.
268
284
  - **Registry verification times out:** a `has not propagated` failure after a successful `npm publish` means npm is still ingesting the upload; it warns that a provenance-signed package "may take a few minutes" and the publisher polls for ten minutes. Confirm the version and its shasum on `https://registry.npmjs.org/<name>/<version>`, then rerun the failed jobs: the final registry check finds the exact bytes, skips `npm publish`, and verification passes. A digest or tag mismatch is not a timeout and is never repaired by rerunning.
269
285
  - **Stable publication fails or is uncertain:** no reopening PR was prepared and `develop` still declares the open `-dev` version. Inspect the workflow run, npm, and the `v<version>` tag. When nothing was published, fix the cause and rerun the same target; the registry and tag guards refuse a version that already exists.
270
286
  - **Stable is published but reopening stopped:** the helper reports the reopening PR or retained worktree. Merge the pending `chore/release-<x.y.z>-dev` PR by hand once its CI passes, or repair the branch and open the PR yourself; never rerun the release for the published version.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.1-dev.590",
3
+ "version": "0.2.1-dev.599",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",