@timurproko/a1 0.2.5-dev.704 → 0.2.6-dev.717

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.
@@ -5,6 +5,7 @@ import type { UiRouteHost, UiRouteInput } from "../../ui/apps/contracts.js";
5
5
  import type { TranscriptViewportFrame, TranscriptViewportFrameDescriptor } from "../../ui/components/transcript-viewport.js";
6
6
  import type { PiEngineAdapter } from "../../integrations/pi/engine/adapter.js";
7
7
  import type { PiWorkflowMessage, PiWorkflowResult } from "../../integrations/pi/engine/workflows.js";
8
+ import type { PiShellLazySelectorLoader } from "../../integrations/pi/components/lazy-selectors.js";
8
9
  import { type PiShellHotkeysPresentation } from "../../integrations/pi/components/shell-presenters-info.js";
9
10
  import { type PiShellClipboardContent, type PiShellComponentPort, type PiShellEditorPort, type PiShellExtensionRendererResolver, type PiShellHeaderOptions, type PiShellHeaderPort, type PiShellImageAssetResolver, type PiShellLoadedResourcesPort, type PiShellResourceEntry, type PiShellTranscriptComponentPort } from "../../integrations/pi/components/shell-shared-facade.js";
10
11
  import { type PiTuiInputCoordinationScheduler, type PiTuiInputSurfaceKind } from "../../integrations/pi/tui-runtime/input-presentation-coordinator.js";
@@ -116,6 +117,8 @@ export interface OwnedUiShellDiagnosticOptions {
116
117
  readonly paste?: (event: PasteEvent) => void;
117
118
  /** Test seams for isolated paste preparation: an in-process starter, or the forked helper's entry. */
118
119
  readonly pastePreparation?: Omit<PastePreparationClientOptions, "onEvent" | "spareIdleMs">;
120
+ /** Test seam for pending and failed optional selector-module preparation. */
121
+ readonly lazySelectors?: PiShellLazySelectorLoader;
119
122
  }
120
123
  /** Live prompt-image admission policy supplied only by the bare-A1 profile. */
121
124
  export interface OwnedUiShellPromptImagesOptions {
@@ -14,7 +14,7 @@ import { createPiExtensionUiBridge } from "../../integrations/pi/components/shel
14
14
  import { createPiShellEditor } from "../../integrations/pi/components/shell-editor-autocomplete.js";
15
15
  import { SKILLS_COMMAND_NAME, findSkillByArgument, rewriteSkillsTunnelSubmission, skillPrompt, skillsFromCommands, } from "../../integrations/pi/components/skills-command.js";
16
16
  import { createPiQueuedInputStatus, createPiShellFooter, createPiShellHeader, createPiShellLoadedResources, createPiShellStatus, } from "../../integrations/pi/components/shell-footer-status.js";
17
- import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellTreeSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
17
+ import { createPiShellArmin, createPiShellAuthProviderSelector, createPiShellDialog, createPiShellEarendilAnnouncement, createPiShellExtensionSelector, createPiShellLoginDialog, createPiShellModelSelector, createPiShellModelsDialog, createPiShellReloadBox, createPiShellScopedModelsSelector, createPiShellSelector, createPiShellSessionSelector, createPiShellSettingsSelector, createPiShellSkillsSelector, createPiShellTrustSelector, createPiShellUserMessageSelector, } from "../../integrations/pi/components/shell-selectors-dialogs.js";
18
18
  import { createPiShellChangelog, createPiShellHotkeys, createPiShellSessionInfo, renderPiShellPackageUpdateNotice, renderPiShellStatusText, } from "../../integrations/pi/components/shell-presenters-info.js";
19
19
  import { createPiShellTranscriptComponent, renderPiShellStartupDiagnostic, renderPiShellTranscriptBlock, } from "../../integrations/pi/components/shell-presenters-transcript.js";
20
20
  import { onPiThemeChange, piTheme } from "../../integrations/pi/components/upstream/theme/theme.js";
@@ -62,6 +62,8 @@ export class OwnedUiSessionShell {
62
62
  // Invariant: translated startup diagnostics create at most one dock notice per kind per shell.
63
63
  #startupTrustHandled = false;
64
64
  #customViewport;
65
+ #lazySelectors;
66
+ #lazySelectorsPromise;
65
67
  #responseCopy;
66
68
  // Rationale: only an executor this shell created owns a spare copy helper worth warming and disposing.
67
69
  #copyExecutor;
@@ -100,6 +102,7 @@ export class OwnedUiSessionShell {
100
102
  this.#routeHost = routeHost ?? null;
101
103
  this.#startupRoute = options.engine.startupRoute;
102
104
  this.#customViewport = sessionLayout === "custom-viewport";
105
+ this.#lazySelectors = options.diagnostics?.lazySelectors;
103
106
  this.#stopped = new Promise(resolve => {
104
107
  this.#resolveStopped = resolve;
105
108
  });
@@ -622,6 +625,7 @@ export class OwnedUiSessionShell {
622
625
  if (this.#disposed || !this.#customViewport)
623
626
  return;
624
627
  this.#presentStartupRoute();
628
+ void this.#loadLazySelectors().then(selectors => selectors.prepare()).catch(() => { });
625
629
  this.root.warmPastePreparation();
626
630
  this.#copyExecutor?.warm();
627
631
  });
@@ -900,6 +904,14 @@ export class OwnedUiSessionShell {
900
904
  this.#dialogHandle = handle;
901
905
  return handle;
902
906
  }
907
+ #loadLazySelectors() {
908
+ if (this.#lazySelectors !== undefined)
909
+ return Promise.resolve(this.#lazySelectors);
910
+ return this.#lazySelectorsPromise ??= import("../../integrations/pi/components/lazy-selectors.js").then(module => {
911
+ this.#lazySelectors = module.piShellLazySelectors;
912
+ return module.piShellLazySelectors;
913
+ });
914
+ }
903
915
  async showThinkingSelector() {
904
916
  const snapshot = this.backend.pinnedSettingsSnapshot();
905
917
  const close = () => {
@@ -911,14 +923,32 @@ export class OwnedUiSessionShell {
911
923
  close();
912
924
  void this.runWorkflow({ command: "thinking", argument: "", selection: level });
913
925
  };
914
- const { createPiShellThinkingSelector } = await import("../../integrations/pi/components/thinking-selector-dialog.js");
915
- const component = createPiShellThinkingSelector(snapshot.thinkingLevel, snapshot.availableThinkingLevels, select, close, level => this.backend.setDefaultThinkingLevel(level), snapshot.defaultThinkingLevel, this.#customViewport ? {
916
- profile: "bare",
917
- cycleBinding: this.root.editor.keybindingConfig()["app.thinking.cycle"] ?? [],
918
- } : undefined);
919
- this.root.setFooterLevel(false);
920
- this.root.setInputSurface(component);
921
- this.runtime.requestRender();
926
+ const releasePresentation = this.#customViewport ? this.runtime.beginPresentationHold() : undefined;
927
+ try {
928
+ const selectors = await this.#loadLazySelectors();
929
+ const component = await selectors.createThinking({
930
+ currentLevel: snapshot.thinkingLevel,
931
+ availableLevels: snapshot.availableThinkingLevels,
932
+ onSelect: select,
933
+ onCancel: close,
934
+ onSelectAsDefault: level => this.backend.setDefaultThinkingLevel(level),
935
+ defaultLevel: snapshot.defaultThinkingLevel,
936
+ ...(this.#customViewport ? {
937
+ presentation: {
938
+ profile: "bare",
939
+ cycleBinding: this.root.editor.keybindingConfig()["app.thinking.cycle"] ?? [],
940
+ },
941
+ } : {}),
942
+ });
943
+ if (this.#disposed)
944
+ return;
945
+ this.root.setFooterLevel(false);
946
+ this.root.setInputSurface(component);
947
+ this.runtime.requestRender();
948
+ }
949
+ finally {
950
+ releasePresentation?.();
951
+ }
922
952
  }
923
953
  /** The Skills dialog: a replacement input like the model selector, applying the chosen skill through the prompt path. */
924
954
  showSkillsSelector(skills = this.#skillSummaries()) {
@@ -1050,47 +1080,56 @@ export class OwnedUiSessionShell {
1050
1080
  this.root.setInputSurface(null);
1051
1081
  this.runtime.requestRender();
1052
1082
  };
1053
- const component = await createPiShellTreeSelector({
1054
- tree: context.tree,
1055
- currentLeafId: context.currentLeafId,
1056
- terminalHeight: this.runtime.viewport().rows,
1057
- initialFilterMode: context.filterMode,
1058
- ...(initialSelectedId === undefined ? {} : { initialSelectedId }),
1059
- onLabelChange: context.appendLabelChange,
1060
- onCopy: text => {
1061
- if (!text) {
1062
- this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: "Selected entry has no text to copy" });
1063
- this.runtime.requestRender();
1064
- return;
1065
- }
1066
- const generation = this.backend.sessionBindingGeneration;
1067
- void this.backend.copyWorkflowText(text).then(acknowledged => {
1068
- if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
1083
+ const releasePresentation = this.#customViewport ? this.runtime.beginPresentationHold() : undefined;
1084
+ try {
1085
+ const selectors = await this.#loadLazySelectors();
1086
+ const component = await selectors.createTree({
1087
+ tree: context.tree,
1088
+ currentLeafId: context.currentLeafId,
1089
+ terminalHeight: this.runtime.viewport().rows,
1090
+ initialFilterMode: context.filterMode,
1091
+ ...(initialSelectedId === undefined ? {} : { initialSelectedId }),
1092
+ onLabelChange: context.appendLabelChange,
1093
+ onCopy: text => {
1094
+ if (!text) {
1095
+ this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: "Selected entry has no text to copy" });
1096
+ this.runtime.requestRender();
1069
1097
  return;
1070
- this.root.appendWorkflowStatus(acknowledged ? "Copied selected message to clipboard" : "Submitted selected message to clipboard");
1071
- this.runtime.requestRender();
1072
- }).catch(error => {
1073
- if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
1098
+ }
1099
+ const generation = this.backend.sessionBindingGeneration;
1100
+ void this.backend.copyWorkflowText(text).then(acknowledged => {
1101
+ if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
1102
+ return;
1103
+ this.root.appendWorkflowStatus(acknowledged ? "Copied selected message to clipboard" : "Submitted selected message to clipboard");
1104
+ this.runtime.requestRender();
1105
+ }).catch(error => {
1106
+ if (this.#disposed || generation !== this.backend.sessionBindingGeneration)
1107
+ return;
1108
+ this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
1109
+ this.runtime.requestRender();
1110
+ });
1111
+ },
1112
+ onCancel: close,
1113
+ onSelect: entryId => {
1114
+ if (entryId === context.currentLeafId) {
1115
+ close();
1116
+ this.root.appendWorkflowStatus("Already at this point");
1117
+ this.runtime.requestRender();
1074
1118
  return;
1075
- this.root.appendWorkflowResult({ command: "tree", outcome: "failed", message: error instanceof Error ? error.message : String(error) });
1076
- this.runtime.requestRender();
1077
- });
1078
- },
1079
- onCancel: close,
1080
- onSelect: entryId => {
1081
- if (entryId === context.currentLeafId) {
1082
- close();
1083
- this.root.appendWorkflowStatus("Already at this point");
1084
- this.runtime.requestRender();
1085
- return;
1086
- }
1087
- if (context.skipSummaryPrompt)
1088
- close();
1089
- void this.#completeTreeSelection(entryId, context.skipSummaryPrompt);
1090
- },
1091
- });
1092
- this.root.setInputSurface(component);
1093
- this.runtime.requestRender();
1119
+ }
1120
+ if (context.skipSummaryPrompt)
1121
+ close();
1122
+ void this.#completeTreeSelection(entryId, context.skipSummaryPrompt);
1123
+ },
1124
+ });
1125
+ if (this.#disposed)
1126
+ return;
1127
+ this.root.setInputSurface(component);
1128
+ this.runtime.requestRender();
1129
+ }
1130
+ finally {
1131
+ releasePresentation?.();
1132
+ }
1094
1133
  }
1095
1134
  showLoginAuthTypeSelector() {
1096
1135
  const close = () => {
@@ -1,4 +1,4 @@
1
- import { CHANGELOG_ROUTE, CHANGELOG_TITLE, HOTKEYS_ROUTE, HOTKEYS_TITLE, } from "../features/owned-ui/reference-routes.js";
1
+ import { CHANGELOG_ROUTE, HOTKEYS_ROUTE, HOTKEYS_TITLE } from "../features/owned-ui/reference-routes.js";
2
2
  import { SETTINGS_APP_ID, SETTINGS_ROUTE } from "../features/owned-ui/settings-route.js";
3
3
  import { piTheme } from "../integrations/pi/components/upstream/theme/theme.js";
4
4
  import { faint } from "../ui/components/text.js";
@@ -15,7 +15,7 @@ export function createOwnedRouteHost(settings, references) {
15
15
  }
16
16
  const changelog = route === CHANGELOG_ROUTE;
17
17
  const session = route === "session";
18
- const title = session ? "Session Info" : changelog ? CHANGELOG_TITLE : HOTKEYS_TITLE;
18
+ const title = session ? "Session Info" : changelog ? input ? "What's New" : "Changelog" : HOTKEYS_TITLE;
19
19
  const document = session ? references.session() : changelog ? references.changelog(input) : references.hotkeys();
20
20
  return deferredSurface(route, title, () => loadReferenceSurface(settings, route, route, title, document));
21
21
  },
@@ -1,6 +1,6 @@
1
1
  export declare const CHANGELOG_APP_ID = "changelog";
2
2
  export declare const CHANGELOG_ROUTE = "changelog";
3
- export declare const CHANGELOG_TITLE = "What's New";
3
+ export declare const CHANGELOG_TITLE = "Changelog";
4
4
  export declare const HOTKEYS_APP_ID = "hotkeys";
5
5
  export declare const HOTKEYS_ROUTE = "hotkeys";
6
6
  export declare const HOTKEYS_TITLE = "Keyboard Shortcuts";
@@ -1,6 +1,6 @@
1
1
  export const CHANGELOG_APP_ID = "changelog";
2
2
  export const CHANGELOG_ROUTE = "changelog";
3
- export const CHANGELOG_TITLE = "What's New";
3
+ export const CHANGELOG_TITLE = "Changelog";
4
4
  export const HOTKEYS_APP_ID = "hotkeys";
5
5
  export const HOTKEYS_ROUTE = "hotkeys";
6
6
  export const HOTKEYS_TITLE = "Keyboard Shortcuts";
@@ -1,6 +1,10 @@
1
1
  {
2
2
  "schema": "a1-release-notes-v1",
3
3
  "releases": [
4
+ {
5
+ "version": "0.2.5",
6
+ "markdown": "## [0.2.5] - 2026-10-07\n\n### New Features\n\n- show session info full screen ([#680](https://github.com/timurproko/a1/pull/680))\n- redesign session tree dialog ([#682](https://github.com/timurproko/a1/pull/682))\n- add name command input dialog ([#694](https://github.com/timurproko/a1/pull/694))\n\n### Changed\n\n- refine animated separator ([#681](https://github.com/timurproko/a1/pull/681))\n- upgrade pinned Pi to 1.0.4 ([#683](https://github.com/timurproko/a1/pull/683))\n- unify selection treatment ([#686](https://github.com/timurproko/a1/pull/686))\n- align project trust dialogs ([#687](https://github.com/timurproko/a1/pull/687))\n- standardize share dialog ([#689](https://github.com/timurproko/a1/pull/689))\n- align Resume Session dialog ([#693](https://github.com/timurproko/a1/pull/693))\n- unify dialog close hints ([#695](https://github.com/timurproko/a1/pull/695))\n- accent model scope markers ([#696](https://github.com/timurproko/a1/pull/696))\n\n### Fixed\n\n- wrap session file after label ([#684](https://github.com/timurproko/a1/pull/684))\n- align thinking markers with models ([#685](https://github.com/timurproko/a1/pull/685))\n- place new-session notice above prompt ([#688](https://github.com/timurproko/a1/pull/688))\n- close dialogs with Ctrl+C ([#690](https://github.com/timurproko/a1/pull/690))\n- restore suggestion after Backspace ([#691](https://github.com/timurproko/a1/pull/691))\n- cycle dialog filters backward ([#692](https://github.com/timurproko/a1/pull/692))\n- avoid initial value-menu highlight ([#697](https://github.com/timurproko/a1/pull/697))\n- keep session root expanded ([#698](https://github.com/timurproko/a1/pull/698))\n- hide skill prefixes in collapsed menu ([#699](https://github.com/timurproko/a1/pull/699))\n- omit space after command completion ([#700](https://github.com/timurproko/a1/pull/700))\n- stabilize values and add undo ([#701](https://github.com/timurproko/a1/pull/701))\n- standardize typing search hints ([#702](https://github.com/timurproko/a1/pull/702))\n- show tree time toggle state ([#703](https://github.com/timurproko/a1/pull/703))\n- refine resume search hints ([#704](https://github.com/timurproko/a1/pull/704))\n"
7
+ },
4
8
  {
5
9
  "version": "0.2.4",
6
10
  "markdown": "## [0.2.4] - 2026-10-05\n\n### Changed\n\n- upgrade pinned Pi to 1.0.0 ([#658](https://github.com/timurproko/a1/pull/658))\n- upgrade pinned Pi to 1.0.2 ([#669](https://github.com/timurproko/a1/pull/669))\n- add animated mark, section art, and browser preview ([#665](https://github.com/timurproko/a1/pull/665))\n- keep the hero mark still and link only it to the site ([#666](https://github.com/timurproko/a1/pull/666))\n- pick dark-mode art via picture sources ([#668](https://github.com/timurproko/a1/pull/668))\n\n### Fixed\n\n- make self-update upgrades reliable ([#664](https://github.com/timurproko/a1/pull/664))\n- restore the terminal and print a resume hint when a1 is killed ([#667](https://github.com/timurproko/a1/pull/667))\n- align progress with Pi accent ([#671](https://github.com/timurproko/a1/pull/671))\n- support Git URL rewrites ([#673](https://github.com/timurproko/a1/pull/673))\n- support repository relocation ([#675](https://github.com/timurproko/a1/pull/675))\n- prevent bottom control gutter bleed ([#677](https://github.com/timurproko/a1/pull/677))\n"
@@ -0,0 +1,42 @@
1
+ import type { PiShellComponentPort } from "./shell-shared-facade.js";
2
+ import type { ThinkingSelectorLevel } from "./upstream/components/thinking-selector.js";
3
+ import type { FilterMode } from "./upstream/components/tree-selector.js";
4
+ type ThinkingSelectorModule = typeof import("./thinking-selector-dialog.js");
5
+ type TreeSelectorModule = typeof import("./upstream/components/tree-selector.js");
6
+ export interface PiShellLazyThinkingSelectorOptions {
7
+ readonly currentLevel: ThinkingSelectorLevel;
8
+ readonly availableLevels: readonly ThinkingSelectorLevel[];
9
+ readonly onSelect: (level: ThinkingSelectorLevel) => void;
10
+ readonly onCancel: () => void;
11
+ readonly onSelectAsDefault?: (level: ThinkingSelectorLevel) => void;
12
+ readonly defaultLevel?: ThinkingSelectorLevel;
13
+ readonly presentation?: {
14
+ readonly profile: "bare";
15
+ readonly cycleBinding: string | readonly string[];
16
+ };
17
+ }
18
+ export interface PiShellLazyTreeSelectorOptions {
19
+ readonly tree: readonly unknown[];
20
+ readonly currentLeafId: string | null;
21
+ readonly terminalHeight: number;
22
+ readonly onSelect: (id: string) => void;
23
+ readonly onCancel: () => void;
24
+ readonly onLabelChange: (entryId: string, label: string | undefined) => void;
25
+ readonly onCopy?: (text: string | undefined) => void;
26
+ readonly initialSelectedId?: string;
27
+ readonly initialFilterMode?: FilterMode;
28
+ }
29
+ export interface PiShellLazySelectorLoader {
30
+ prepare(): Promise<void>;
31
+ createThinking(options: PiShellLazyThinkingSelectorOptions): Promise<PiShellComponentPort>;
32
+ createTree(options: PiShellLazyTreeSelectorOptions): Promise<PiShellComponentPort>;
33
+ }
34
+ export interface PiShellLazySelectorImports {
35
+ readonly thinking: () => Promise<ThinkingSelectorModule>;
36
+ readonly tree: () => Promise<TreeSelectorModule>;
37
+ }
38
+ /** Builds one idempotent selector loader; fulfilled, in-flight, and rejected imports remain shared. */
39
+ export declare function createPiShellLazySelectorLoader(imports?: PiShellLazySelectorImports): PiShellLazySelectorLoader;
40
+ /** Production selector loader, dynamically reached only after the first input-ready frame. */
41
+ export declare const piShellLazySelectors: PiShellLazySelectorLoader;
42
+ export {};
@@ -0,0 +1,32 @@
1
+ import { componentPort, ensureTheme } from "./shell-shared-facade.js";
2
+ const DEFAULT_IMPORTS = {
3
+ thinking: () => import("./thinking-selector-dialog.js"),
4
+ tree: () => import("./upstream/components/tree-selector.js"),
5
+ };
6
+ /** Builds one idempotent selector loader; fulfilled, in-flight, and rejected imports remain shared. */
7
+ export function createPiShellLazySelectorLoader(imports = DEFAULT_IMPORTS) {
8
+ let thinkingSelectorModule;
9
+ let treeSelectorModule;
10
+ const loadThinkingSelector = () => thinkingSelectorModule ??= imports.thinking();
11
+ const loadTreeSelector = () => treeSelectorModule ??= imports.tree();
12
+ return {
13
+ async prepare() {
14
+ // Performance: allSettled observes background failures while retaining each rejected module for explicit route recovery.
15
+ await Promise.allSettled([loadThinkingSelector(), loadTreeSelector()]);
16
+ },
17
+ async createThinking(options) {
18
+ const { createPiShellThinkingSelector } = await loadThinkingSelector();
19
+ return createPiShellThinkingSelector(options.currentLevel, options.availableLevels, options.onSelect, options.onCancel, options.onSelectAsDefault, options.defaultLevel, options.presentation);
20
+ },
21
+ async createTree(options) {
22
+ ensureTheme();
23
+ const { TreeSelectorComponent } = await loadTreeSelector();
24
+ const selector = new TreeSelectorComponent([...options.tree], options.currentLeafId, options.terminalHeight, options.onSelect, options.onCancel, options.onLabelChange, options.initialSelectedId, options.initialFilterMode);
25
+ if (options.onCopy)
26
+ selector.onCopy = options.onCopy;
27
+ return componentPort(selector);
28
+ },
29
+ };
30
+ }
31
+ /** Production selector loader, dynamically reached only after the first input-ready frame. */
32
+ export const piShellLazySelectors = createPiShellLazySelectorLoader();
@@ -8,8 +8,8 @@
8
8
  * frame with standalone title, standard outer rules, stable filter/status row, progressive result
9
9
  * paging, aligned result columns, Session Tree selection roles, quiet in-field query syntax guidance,
10
10
  * and one-line bottom dynamic feedback and searchable-dialog shortcut footer. Selected session rows
11
- * use the blue selection surface, success-green primary title, muted metadata, and ordinary text
12
- * weight.
11
+ * use the blue selection surface while preserving title and metadata foregrounds; the active session
12
+ * title uses success green independently of keyboard selection, with ordinary text weight.
13
13
  * Deviations: owned-modal-shortcut-hints, owned-resume-session-dialog,
14
14
  * owned-standard-dialog-selection.
15
15
  */
@@ -473,19 +473,13 @@ class SessionList {
473
473
  messageColor = "error";
474
474
  }
475
475
  else if (isCurrent) {
476
- messageColor = "accent";
476
+ messageColor = "success";
477
477
  }
478
478
  else if (hasName) {
479
479
  messageColor = "warning";
480
480
  }
481
- const styledMsg = isConfirmingDelete
482
- ? theme.fg("error", truncatedMsg)
483
- : isSelected
484
- ? theme.fg("success", truncatedMsg)
485
- : messageColor
486
- ? theme.fg(messageColor, truncatedMsg)
487
- : truncatedMsg;
488
- const metadataColor = isConfirmingDelete ? "error" : isSelected ? "muted" : "dim";
481
+ const styledMsg = messageColor ? theme.fg(messageColor, truncatedMsg) : truncatedMsg;
482
+ const metadataColor = isConfirmingDelete ? "error" : "dim";
489
483
  const titleColumn = fitToWidth(cursor + theme.fg("dim", prefix) + styledMsg, titleColumnWidth, "…");
490
484
  const pathColumn = pathColumnWidth > 0
491
485
  ? `${theme.fg(metadataColor, fitPathToWidth(pathText, pathColumnWidth, this.showPath))}${" ".repeat(columnGapWidth)}`
@@ -39,6 +39,12 @@ export declare class PiTuiRuntimeAdapter {
39
39
  */
40
40
  freezePresentation(): void;
41
41
  get presentationFrozen(): boolean;
42
+ /**
43
+ * Keeps asynchronous replacement-surface construction atomic at the terminal. Renderers may
44
+ * compose while held, but their writes are dropped until the final forced repaint on release.
45
+ */
46
+ beginPresentationHold(): () => void;
47
+ get presentationHeld(): boolean;
42
48
  addPreInputListener(listener: PiTuiPreInputListener): () => void;
43
49
  addInputListener(listener: PiTuiInputListener): () => void;
44
50
  switchMode(mode: "regular" | "fullscreen"): boolean;
@@ -118,6 +118,7 @@ export class PiTuiRuntimeAdapter {
118
118
  #rootDisposed = false;
119
119
  #terminalProgress = false;
120
120
  #presentationFrozen = false;
121
+ #presentationHolds = new Set();
121
122
  constructor(options) {
122
123
  this.#root = options.root;
123
124
  this.#overlayGeometry = options.onOverlayGeometry === undefined ? undefined : new OverlayGeometryTracker(options.onOverlayGeometry);
@@ -128,7 +129,7 @@ export class PiTuiRuntimeAdapter {
128
129
  // Invariant: the pinned renderer's frames pass through this gate, while writeControl and
129
130
  // the stop sequence reach the terminal directly. Freezing drops frames without touching
130
131
  // the renderer, so a scheduled repaint cannot land on top of the quit outro.
131
- const gatedTerminal = frozenGateTerminal(this.#terminal, () => this.#presentationFrozen);
132
+ const gatedTerminal = frozenGateTerminal(this.#terminal, () => this.#presentationFrozen || this.#presentationHolds.size > 0);
132
133
  const tracedTerminal = options.inputDiagnostics === undefined
133
134
  ? gatedTerminal
134
135
  : diagnosticTerminal(gatedTerminal, phase => this.#traceRuntimePhase(phase));
@@ -320,6 +321,24 @@ export class PiTuiRuntimeAdapter {
320
321
  get presentationFrozen() {
321
322
  return this.#presentationFrozen;
322
323
  }
324
+ /**
325
+ * Keeps asynchronous replacement-surface construction atomic at the terminal. Renderers may
326
+ * compose while held, but their writes are dropped until the final forced repaint on release.
327
+ */
328
+ beginPresentationHold() {
329
+ this.#assertRunning("presentation hold");
330
+ const token = Symbol("presentation-hold");
331
+ this.#presentationHolds.add(token);
332
+ return () => {
333
+ if (!this.#presentationHolds.delete(token) || this.#presentationHolds.size > 0)
334
+ return;
335
+ if (this.active && !this.#presentationFrozen)
336
+ this.#tui.requestRender(true);
337
+ };
338
+ }
339
+ get presentationHeld() {
340
+ return this.#presentationHolds.size > 0;
341
+ }
323
342
  addPreInputListener(listener) {
324
343
  if (this.#preInputListeners.has(listener))
325
344
  throw new TypeError("Pi TUI pre-input listener is already registered");
@@ -427,6 +446,7 @@ export class PiTuiRuntimeAdapter {
427
446
  return this.#stopPromise;
428
447
  if (this.#state === "idle") {
429
448
  this.#state = "stopped";
449
+ this.#presentationHolds.clear();
430
450
  this.#clearTerminalProgress();
431
451
  this.#preInputListeners.clear();
432
452
  this.#disposeRoot();
@@ -462,6 +482,7 @@ export class PiTuiRuntimeAdapter {
462
482
  // Invariant: the gate opens only for the synchronous stop sequence, so no render
463
483
  // scheduled during the outro can slip in before the alternate screen is left.
464
484
  this.#presentationFrozen = false;
485
+ this.#presentationHolds.clear();
465
486
  this.#tui.stop(stopOptions);
466
487
  }
467
488
  catch (error) {
@@ -599,6 +620,7 @@ export class PiTuiRuntimeAdapter {
599
620
  }
600
621
  #restoreAfterFailedStart() {
601
622
  this.#presentationFrozen = false;
623
+ this.#presentationHolds.clear();
602
624
  try {
603
625
  this.#tui.stop();
604
626
  }
@@ -615,6 +637,7 @@ export class PiTuiRuntimeAdapter {
615
637
  }
616
638
  #bestEffortTerminalRestore() {
617
639
  this.#presentationFrozen = false;
640
+ this.#presentationHolds.clear();
618
641
  this.#clearTerminalProgress();
619
642
  try {
620
643
  if (this.mode === "fullscreen")
@@ -5,7 +5,7 @@
5
5
  "platform": "darwin",
6
6
  "architecture": "arm64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-07T15:28:34.230Z",
8
+ "builtAt": "2026-10-08T10:43:26.855Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "51b0b2dc1c9ae577f2586c4217066929278726c2c15308d9144eb6f538e118ff",
@@ -5,7 +5,7 @@
5
5
  "platform": "linux",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-07T15:28:19.388Z",
8
+ "builtAt": "2026-10-08T10:43:15.921Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian",
11
11
  "sha256": "ef62679e83bc1fad3d38f9f21675d9256f08d20e67fd4791571032115ca1b0e9",
@@ -5,10 +5,10 @@
5
5
  "platform": "win32",
6
6
  "architecture": "x64",
7
7
  "capability": "supported",
8
- "builtAt": "2026-10-07T15:29:15.953Z",
8
+ "builtAt": "2026-10-08T10:43:54.524Z",
9
9
  "artifact": {
10
10
  "filename": "process-guardian.exe",
11
- "sha256": "a8e1e48b23cfa2a33b4821ccf799bf703b984ac5453c08926f8b5928801c07bb",
11
+ "sha256": "4c84729bd86ecee969b71895ccf86021af1f4b4e86e0906ff9ef78dbad03e6d7",
12
12
  "size": 288256
13
13
  },
14
14
  "provenance": {
@@ -24,9 +24,17 @@ node scripts/governance/local-worktree-cleanup.mjs sweep --repo D:/Git/a1
24
24
 
25
25
  `sweep` evaluates every released registration in one pass under the queue limits (100 registrations, 500 remote requests, a durable round-robin cursor) with a 180-second elapsed budget, since each merged candidate costs three evidence loads, and completes each candidate whose PR is verified merged using exactly the `complete` safeguards. It needs no `enable` and starts no process: its authority is each candidate's release. A candidate whose PR is open, draft, or not yet finalized reports `pending`; a candidate whose PR closed without merge reports `awaiting-discard` and is never touched; blockers report their exact reason. An old stop sentinel does not prevent a sweep, but `disable` run while a sweep is executing stops it before its next destructive step. If another session holds the mutation lock the sweep reports `mutation-busy` and the agent relays it as deferred rather than waiting. The JSON report carries a `lines` array with one relayable line per candidate and pruned branch, for example `#458 close-absent-and-skewed-cleanup: removed [worktree-removed, local-ref-removed]`. Pending or blocked results never delay the new delivery.
26
26
 
27
+ ### Standalone documentation evidence
28
+
29
+ A PR without an `openspec-implementation` fence does not require corrective implementation association when its complete changed-file list is standalone documentation under the repository's existing documentation lifecycle policy. This includes ordinary documentation, the root `README.md`, canonical specification revisions, and revisions to existing active-change or archived-change documentation. Cleanup reuses the same path allowlist and immutable lifecycle checks as documentation auto-merge rather than maintaining a broader cleanup-only definition.
30
+
31
+ The PR must be non-draft, same-repository, merged into `develop`, and bound to the registered PR, head, and branch. Cleanup additionally requires successful required CI for that exact head, the merge commit to remain an ancestor of current `develop`, and the remote topic ref to be absent. Introduced active changes, implementation/code or mixed paths, release notes, acceptance records, malformed lifecycle metadata, incomplete changed-file evidence, and invalid rename shapes do not qualify. Those cases remain on their existing implementation-association or fail-closed paths.
32
+
33
+ Qualifying documentation uses the ordinary cleanup path without weakening it: exact worktree and local-ref identity, ownership, clean content, generated-content inspection, journaling, non-force Git removal, residue handling, and compare-and-delete local-ref behavior remain required. A blocker at any gate retains the worktree; agents never substitute manual deletion.
34
+
27
35
  ### Nothing left to remove
28
36
 
29
- A released entry whose evidence can never verify (for example `delivery-content-drift` after a stale-base merge, or `source-association` for a PR without an `openspec-implementation` fence) would otherwise be re-verified and reported `blocked` on every sweep. One narrow exception repairs an integrated unassociated delivery without rewriting its merged body: current `develop` must contain exactly one archived `association-repair.json` for the registered change, naming the exact original PR/head/merge and successful validation run, and its named corrective version-3 PR must itself have authorized manual merge, current exact-head CI, synchronized specs, a valid archive, and absent original/corrective remote refs. Only then does cleanup continue through all ordinary worktree identity, ownership, cleanliness, disposable-content, non-force removal, journaling, and local-ref safeguards. A mismatch remains blocked and generic merge/archive evidence never substitutes.
37
+ A released entry whose evidence can never verify (for example `delivery-content-drift` after a stale-base merge, or `source-association` for an unassociated implementation PR) would otherwise be re-verified and reported `blocked` on every sweep. One narrow exception repairs an integrated unassociated delivery without rewriting its merged body: current `develop` must contain exactly one archived `association-repair.json` for the registered change, naming the exact original PR/head/merge and successful validation run, and its named corrective version-3 PR must itself have authorized manual merge, current exact-head CI, synchronized specs, a valid archive, and absent original/corrective remote refs. Only then does cleanup continue through all ordinary worktree identity, ownership, cleanliness, disposable-content, non-force removal, journaling, and local-ref safeguards. A mismatch remains blocked and generic merge/archive evidence never substitutes.
30
38
 
31
39
  When no valid corrective association exists and such an entry's worktree path is gone, Git holds no live row for it (its own dangling row is retired), and its local topic ref is absent, the sweep and `complete` ask GitHub only whether the pull request merged into `develop` in this repository and then mark the journal `done` with `retired-nothing-left` and the original evidence reason. Nothing is deleted; a present path, row, or ref keeps the entry blocked, an unmerged PR keeps it `pending` or `awaiting-discard`, a deferred failure such as `remote-budget` is never treated as unverifiable, and preview retires nothing. For an all-absent entry whose PR is not merged, the maintainer runs the explicit form, which reads nothing from GitHub and deletes nothing:
32
40
 
@@ -64,7 +72,7 @@ node scripts/governance/local-worktree-cleanup.mjs complete \
64
72
 
65
73
  The central disposable policy is `node_modules`, `dist`, `.builds`, `.artifacts`, `native/process-guardian/target`, `native/terminal-host/target`, and the exact generated file `src/integrations/pi/engine/pi-settings-metadata.json`. The `.artifacts` root is the repository's ignored generated-artifact root (finalization and validation reports, packed candidates, agent-written logs and diffs); the two native roots contain repository-generated Cargo output; Vitest writes the metadata file for source-side settings tests and leaves it shared for overlapping test processes until worktree cleanup. Agents select the owning worktree's `.artifacts/` root for their own transient delivery files rather than OS temp or another checkout, but its disposable status never makes scratch authoritative or permits secrets to be stored there. Registrations that still name `.artifacts/openspec-archive` or `.artifacts/validation` stay valid and are widened to the current policy on the next `complete`.
66
74
 
67
- Each root itself must be an ignored non-link regular file or directory inside the exact worktree. A descendant symbolic link or Windows junction is accepted only when its canonical target stays inside that same exact disposable root. Inspection records the link's path, filesystem identity, and target; purge revalidates them and removes the link entry without traversing it before removing the root. Root-level links, escapes, drift, unresolved/cyclic targets, special files, and nested repository boundaries remain blocking. Authority is component-exact: near matches such as `.artifacts-user`, `artifacts`, or `pi-settings-metadata-user.json`, arbitrary `target` directories, and sibling native projects remain blocking. Tracked/staged/unstaged/untracked content and every unknown ignored path still block. A tracked regular `.gitmodules` file alone is ordinary content; actual nested `.git` metadata, gitlinks, configured submodules, and submodule changes block. Ordinary content and these approved generated paths are traversed under separate finite entry allowances, so a normal dependency installation does not consume the ordinary source-tree allowance; both allowances retain the same deadline and content-boundary checks. Once those checks pass, cleanup deletes the declared disposable roots or files itself with a bounded retry for transient Windows sharing violations before handing the worktree to Git, so non-force Git removal only has to delete tracked content. A disposable path that stays locked after the retry budget reports `blocked` with `disposable-path-locked` and the path; the worktree, its `.git` pointer, and its journal are untouched, so stop the process holding the handle and rerun the same command.
75
+ Each approved root must be an ignored regular file, directory, symbolic link, or Windows junction at that exact path inside the exact worktree. An exact-root link may target an external generated-content directory; cleanup records its link classification, filesystem identity, and canonical target, revalidates all three immediately before deletion, and removes only the link entry through a non-recursive primitive. It never traverses, removes, or mutates the external target. A descendant link remains stricter: its canonical target must stay inside that same exact disposable root. Escapes by descendant links, drift, replacements, unresolved/cyclic targets, special files, and nested repository boundaries remain blocking. Authority is component-exact: near matches such as `.artifacts-user`, `artifacts`, or `pi-settings-metadata-user.json`, arbitrary `target` directories, and sibling native projects remain blocking. Tracked/staged/unstaged/untracked content and every unknown ignored path still block. A tracked regular `.gitmodules` file alone is ordinary content; actual nested `.git` metadata, gitlinks, configured submodules, and submodule changes block. Ordinary content and these approved generated paths are traversed under separate finite entry allowances, so a normal dependency installation does not consume the ordinary source-tree allowance; both allowances retain the same deadline and content-boundary checks. Once those checks pass, cleanup deletes the declared disposable roots or files itself with a bounded retry for transient Windows sharing violations before handing the worktree to Git, so non-force Git removal only has to delete tracked content. A disposable path that stays locked after the retry budget reports `blocked` with `disposable-path-locked` (or `disposable-link-locked` for a link entry) and the path; the worktree, its `.git` pointer, and its journal are untouched, so stop the process holding the handle and rerun the same command.
68
76
 
69
77
  Agents do not manually remove generated content, call `git worktree remove`, or delete the local branch after delivery. The JSON result is authoritative: report success only for `removed` or verified `already-absent`; otherwise retain the worktree and report the exact blocker. A handed-off entry whose worktree was deleted by hand completes through its journal, and its branch is deleted under the accepted-ancestry rule. Legacy roles can supply separate `--source-pr`, `--candidate-pr`, and `--role` values.
70
78
 
@@ -150,13 +158,13 @@ node scripts/governance/local-worktree-cleanup.mjs register --repo D:/Git/a1 --p
150
158
 
151
159
  Registration binds the repository, exact directory/filesystem identity, candidate PR, HEAD/ref, role, and generation. It returns an ID and generation, not the owner token. The worktree stays `owned`: registration alone never authorizes removal. Supported roles:
152
160
 
153
- - `implementation`: exact merged implementation PR head; candidate PR equals source PR. This is the only version-3 role because the same PR contains implementation, acceptance manifest, synchronized specs, and archive.
161
+ - `implementation`: exact merged implementation PR head, or exact merged standalone-documentation PR head under the policy above; candidate PR equals source PR. This is the only version-3 role because the same PR contains implementation, acceptance manifest, synchronized specs, and archive.
154
162
  - `acceptance`: legacy exact verified implementation merge commit; candidate PR equals source PR.
155
163
  - `archive`: legacy exact merged generated archive PR head; candidate PR is the archive PR and source PR is the implementation PR.
156
164
 
157
165
  A CI/base/older-ancestor checkout does not qualify automatically. Version 3 has no local acceptance/archive checkout to register. No local archive checkout is required when a legacy archive was generated entirely on GitHub. Legacy version-1/version-2 linkage remains supported, but individually reviewing and registering a legacy checkout is explicit adoption, not a bulk sweep.
158
166
 
159
- Low-level `register` keeps an explicit disposable allowlist for legacy and diagnostic use. The standard `complete` command supplies the central repository policy automatically; agents do not choose flags for normal completed-delivery cleanup. Neither route permits discarding tracked, staged, unstaged, untracked, unknown ignored, linked, special, or actual nested repository/submodule content. Declaring or centrally recognizing a generated directory does not bypass ignored-path, identity, type, or traversal checks.
167
+ Low-level `register` keeps an explicit disposable allowlist for legacy and diagnostic use. The standard `complete` command supplies the central repository policy automatically; agents do not choose flags for normal completed-delivery cleanup. Neither route permits discarding tracked, staged, unstaged, untracked, unknown ignored, unapproved linked, special, or actual nested repository/submodule content. The sole link exception is the guarded exact-root or contained descendant behavior above. Declaring or centrally recognizing a generated directory does not bypass ignored-path, identity, type, or traversal checks.
160
168
 
161
169
  Before handing a completed checkout to cleanup, stop its development processes, leave its directory, and release it using the returned ID and current generation:
162
170
 
@@ -55,7 +55,7 @@ Use equivalent values in bare A1 and the Pi comparison profile. Exercise both a
55
55
  - [ ] `/settings`: selected cursor, label and value accent; unselected values muted; descriptions and hints dim; search uses `> ` with inverse cursor; scalar menus, numeric controls, warning-parts dialog, deferred notice, failed write, section spacing, clipping, and hidden-entry absence match their pinned semantic roles.
56
56
  - [ ] Transcript: toggle images and image width, thinking level/visibility, Mermaid mode, output padding, cache notices, warnings, auto-compaction, queue modes, command autocomplete, provider timeout/retry/error, and changelog collapse. Existing and streaming blocks must re-render without focus, selection, scroll, or queued-work loss.
57
57
  - [ ] Trust startup: from an undecided project, compare selected Trust/Do not trust rows, arrow navigation, Enter, Escape/Ctrl+C, clearing, cursor state, and restoration. No project extension/theme/prompt/skill may run before selection, and a fail-closed diagnostic must appear once on the restored parent rather than above a blank fullscreen frame.
58
- - [ ] Stable release note: with a built matching stable fixture from an explicitly approved draft GitHub Release snapshot and a fresh A1 profile, verify that exact note opens once after trust/other startup modals, fills the existing `What's New` screen, renders the Pi-style `## [version] - YYYY-MM-DD` heading and categorized `###` sections, scrolls by keys/wheel/rail, resizes, and closes with Escape. Relaunch to verify it stays closed, then use `/changelog` to verify version-labeled packaged history puts the latest A1 release at the top and the oldest at the bottom. A preview fixture must not auto-open or write acknowledgement state; `a1 pi` must retain Pi's oldest-first in-feed changelog so its latest release remains nearest the bottom.
58
+ - [ ] Stable release note: with a built matching stable fixture from an explicitly approved draft GitHub Release snapshot and a fresh A1 profile, verify that exact note opens once after trust/other startup modals, fills the existing `What's New` screen, renders the Pi-style `## [version] - YYYY-MM-DD` heading and categorized `###` sections, scrolls by keys/wheel/rail, resizes, and closes with Escape. Relaunch to verify it stays closed, then use `/changelog` to verify the distinct `Changelog` screen's version-labeled packaged history puts the latest A1 release at the top and the oldest at the bottom. A preview fixture must not auto-open or write acknowledgement state; `a1 pi` must retain Pi's oldest-first in-feed changelog so its latest release remains nearest the bottom.
59
59
  - [ ] Terminal lifecycle: toggle hardware cursor, clear-on-shrink, and terminal progress; resize smaller/larger; open/close selectors; select and copy transcript text; verify no duplicate rows, stale OSC progress, leaked mouse mode, misplaced cursor, or broken parent input.
60
60
  - [ ] Images: in Kitty or iTerm2 verify inline width and clipping; in Windows Terminal verify the textual fallback and absence of image protocol bytes without hiding `showImages`.
61
61
  - [ ] Bare A1 quit (`/quit` and the second `Ctrl+C`): the configured quit effect plays over the last frame on the alternate screen, the terminal is restored once, and the parent shows only its earlier scrollback plus the dim `To resume this session:` hint. No frame rows, transcript rows, editor box, or footer may remain. With `/settings` → Quit → Effect set to `off`, the leave is immediate and the parent output is identical.
@@ -1,8 +1,8 @@
1
1
  # Atomic OpenSpec delivery and legacy archival
2
2
 
3
- New OpenSpec deliveries use one ordinary development pull request from draft planning through implementation, validation, acceptance, specification synchronization, and archival. The only integration action is an authorized maintainer's manual merge. Automation never auto-merges implementation-bound work and never creates a version-3 acceptance or archive follow-up PR.
3
+ New OpenSpec deliveries use one ordinary development pull request from draft planning through implementation, validation, acceptance, specification synchronization, and archival. An authorized maintainer chooses integration by manually merging the exact validated head or personally enabling GitHub native auto-merge for that unchanged head. Agents, Apps, bots, merge queue, and repository automation never arm or invoke implementation integration and never create a version-3 acceptance or archive follow-up PR.
4
4
 
5
- Unrelated standalone documentation remains separate: a non-draft unassociated PR changing only `openspec/**`, `docs/**`, and/or root `README.md` retains the existing CI-gated automatic route. A new active change, implementation association, malformed lifecycle data, or mixed path keeps the manual hold.
5
+ Unrelated standalone documentation remains separate: a non-draft unassociated PR changing only `openspec/**`, `docs/**`, and/or root `README.md` retains the existing CI-gated automatic route. A new active change, implementation association, malformed lifecycle data, or mixed path remains outside documentation-owned integration. Before finalization or without valid human provenance, any arm is disabled; a finalized version-3 human arm remains a separate maintainer-owned route.
6
6
 
7
7
  Version-1 and version-2 deliveries and their existing comments, acceptance PRs, archive PRs, receipts, retries, and bounded catch-up remain supported under [Legacy delivery](#legacy-delivery). Do not silently convert an in-flight legacy delivery.
8
8
 
@@ -14,7 +14,7 @@ Version-1 and version-2 deliveries and their existing comments, acceptance PRs,
14
14
  4. **Plain acceptance list:** keep the body phase-free and add final `## Acceptance` with one to ten concise implementation-specific behavior-and-result bullets. Do not use checkboxes, generic review/CI/approval/archive statements, URLs, mentions, or automated-test inventory.
15
15
  5. **Ready and automated finalization:** mark the PR ready. The trusted `OpenSpec finalization` workflow reconciles current `develop`, conservatively synchronizes all deltas, moves the active change into its dated archive, stages the conditional acceptance manifest, commits that to the same branch with the archive App identity, and writes the emitted paths into the body's implementation fence. Running the [finalization command](#finalization-command) locally first is optional and yields the same bytes.
16
16
  6. **Validate:** one normal exact-head workflow validates the finalized head: implementation, synchronized specs, archive, manifest, tasks/evidence, exact PR-body list, and every selected product/governance scope before emitting the stable protected aggregate. A new commit or acceptance-list change re-finalizes automatically when needed and requires full renewed validation; no lifecycle body edit or second workflow run is required.
17
- 7. **Manual merge accepts:** after the stable protected aggregate succeeds, an authorized human reviews and manually merges the exact validated head. That single action means the listed scenarios are accepted and explicitly authorizes integration. Auto-merge, merge queue, Apps, bots, and documentation reconciliation are forbidden.
17
+ 7. **Maintainer integration accepts:** after reviewing the finalized candidate, an authorized human either manually merges the exact validated head after the stable protected aggregate succeeds or personally enables GitHub native auto-merge for that unchanged head so protected integration waits for the aggregate. That human action means the listed scenarios are accepted. Agents, Apps, bots, merge queue, automation-owned implementation arms, and documentation integration remain forbidden. A changed head, acceptance list, or finalization invalidates the earlier arm and requires the maintainer to choose again.
18
18
  8. **Verify and clean:** trusted post-merge policy derives `Archived` and reports `accepted-and-archived` from committed bytes and immutable GitHub provenance without editing the accepted PR body. It publishes no lifecycle branch or PR. Shared exact-head remote cleanup may delete the unchanged topic ref; local cleanup remains separately ownership-controlled: the agent parks the worktree with `handoff` at step 7, and the next session's `sweep` removes it once the merge, archive, and remote-ref evidence verify (see [local cleanup](local-worktree-cleanup.md)).
19
19
 
20
20
  ### Complete-regression evidence ordering
@@ -38,7 +38,7 @@ Replace the multi-PR OpenSpec handoff with one manually merged development PR wh
38
38
 
39
39
  - Keep planning and implementation in the same draft PR.
40
40
  - Integrate implementation, synchronized specs, acceptance, and archive atomically.
41
- - Preserve standalone documentation auto-merge while forbidding implementation auto-merge.
41
+ - Preserve standalone documentation auto-merge while forbidding automation-owned implementation auto-merge.
42
42
 
43
43
  ## Automation
44
44
 
@@ -75,7 +75,7 @@ Do not add a quoted phase line or a routine `Validation` section listing command
75
75
  </details>
76
76
  ```
77
77
 
78
- Keep acceptance absent during proposal review so unfinished intent is not mistaken for final acceptance criteria. The finalization workflow rewrites only the implementation fence; keep the rest of the phase-free body unchanged through exact-head validation, maintainer review, and authorized manual merge. The same workflow run validates its finalized delivery record and applicable product/governance scopes before the stable protected aggregate succeeds. Do not add a lifecycle body edit or start a second validation run. After manual merge, trusted verification derives `Archived`; do not rewrite the accepted body.
78
+ Keep acceptance absent during proposal review so unfinished intent is not mistaken for final acceptance criteria. The finalization workflow rewrites only the implementation fence; keep the rest of the phase-free body unchanged through exact-head validation, maintainer review, and authorized integration. The same workflow run validates its finalized delivery record and applicable product/governance scopes before the stable protected aggregate succeeds. Do not add a lifecycle body edit or start a second validation run. After integration, trusted verification derives `Archived`; do not rewrite the accepted body.
79
79
 
80
80
  ## Agent scratch files
81
81
 
@@ -139,7 +139,7 @@ Use plain bullets:
139
139
  - Finalized specs and archive match the implementation.
140
140
  ```
141
141
 
142
- The committed conditional manifest contains the same ordered text. Trusted policy validates membership but never checks or edits it and never claims that the scenarios passed. Manual merge is the acceptance decision; no acceptance comment, checkbox edit, review-approval requirement, JSON edit, acceptance PR, archive PR, or later command is needed.
142
+ The committed conditional manifest contains the same ordered text. Trusted policy validates membership but never checks or edits it and never claims that the scenarios passed. The authorized maintainer's manual merge or personal native auto-merge arm for the unchanged exact head is the acceptance decision; no acceptance comment, checkbox edit, review-approval requirement, JSON edit, acceptance PR, archive PR, or later command is needed.
143
143
 
144
144
  If the body list changes, candidate validation reruns and compares it with the committed manifest. If the head changes, all prior exact-head CI is stale. If only the body changes to disagree with the manifest, integration remains blocked until the list and committed candidate agree again. No lifecycle body edit is needed after green CI.
145
145
 
@@ -158,7 +158,7 @@ Because the branch gains commits from the archive App, pull before pushing. A re
158
158
 
159
159
  A missing fence is not an ordinary ready PR when immutable base/head evidence shows that the candidate introduces or restores an active change, or mixes active-change edits with code or operational paths. Base-controlled readiness and finalization both fail with `missing-implementation-association`; deleting editable body metadata cannot bypass finalization. Existing active-change documentation-only revisions and ordinary code PRs without active delivery paths retain their established routes.
160
160
 
161
- If an unassociated implementation has already integrated, do not edit its merged body or publish an archive-only follow-up. A corrective implementation-bound PR for the same still-active change must add the prevention fix and an exact `association-repair.json` record naming the original PR/head/merge/validation and the corrective PR. Normal version-3 finalization then synchronizes and archives the change; the corrective PR's authorized manual merge accepts that repair without pretending the original PR was finalized retroactively.
161
+ If an unassociated implementation has already integrated, do not edit its merged body or publish an archive-only follow-up. A corrective implementation-bound PR for the same still-active change must add the prevention fix and an exact `association-repair.json` record naming the original PR/head/merge/validation and the corrective PR. Normal version-3 finalization then synchronizes and archives the change; the corrective PR's authorized maintainer integration accepts that repair without pretending the original PR was finalized retroactively.
162
162
 
163
163
  ## Finalization command
164
164
 
@@ -200,7 +200,7 @@ Before a first finalization, canonical specs must still equal the selected targe
200
200
 
201
201
  ## Conditional acceptance and derived receipt
202
202
 
203
- `acceptance.md` truthfully says its verdict activates only when the containing exact head is manually merged by an authorized human after required current-head checks. Its `openspec-delivery` block records stable pre-merge facts:
203
+ `acceptance.md` truthfully says its verdict activates only when an authorized human manually merges the containing exact head after required current-head checks or personally arms native auto-merge for that unchanged head and GitHub integrates it after those checks. Its `openspec-delivery` block records stable pre-merge facts:
204
204
 
205
205
  - schema version, repository, change, and source PR;
206
206
  - archive and manifest paths and finalization date;
@@ -209,7 +209,7 @@ Before a first finalization, canonical specs must still equal the selected targe
209
209
  - archive, canonical-spec, task, and evidence digests; and
210
210
  - explicit known-gap dispositions.
211
211
 
212
- It does not predict source head, merge commit, merger, or merge time. The PR head contains the manifest, so required CI binds those bytes without a recursive self-hash. After merge, the shared reader combines the manifest with immutable GitHub source head, successful required run, authorized human actor, absence of automatic/queue/App provenance, merge commit/time, and `develop` ancestry. That derived result is the durable version-3 receipt used by audit and cleanup.
212
+ It does not predict source head, merge commit, merger, or merge time. The PR head contains the manifest, so required CI binds those bytes without a recursive self-hash. After merge, the shared reader combines the manifest with immutable GitHub source head, successful required run, authorized human actor and permission, integration route, merge commit/time, and `develop` ancestry. Direct manual integration requires no active automatic authority. Human-enabled native auto-merge additionally requires a matching final-head enable event and `auto_merge.enabled_by`, with no later candidate commit or disable event. Queue, App/Bot, automation-owned, stale, malformed, or differently authored authority remains invalid. A human enable followed by disable before a valid manual merge is an abandoned attempt, not contradictory provenance. That shared result is the durable version-3 receipt used by audit and cleanup.
213
213
 
214
214
  Unavailable, stale, automatic, unauthorized, conflicting, or contradictory provenance produces `invalid-provenance`/`blocked`, not acceptance. Post-merge code does not repair it by pushing `develop` or publishing another PR.
215
215
 
@@ -217,7 +217,7 @@ Unavailable, stale, automatic, unauthorized, conflicting, or contradictory prove
217
217
 
218
218
  Normal Development CI remains complete for the implementation once its reviewable candidate exists. An archive-shaped final diff does not select documentation-only validation because the authoritative version-3 association remains implementation-bound. Base-controlled readiness defers a ready head that still holds the active change without running test suites or emitting `Development validation required`. The finalization workflow pushes the finalized head and updates its implementation fence; that update starts one complete selected validation, while PR-level concurrency cancels any superseded eligible run.
219
219
 
220
- `pull_request` body edits rerun required CI. The ordinary finalized phase-free run exposes the stable protected aggregate directly; it does not wait for a lifecycle body edit. Documentation auto-merge's trusted owner also reevaluates lifecycle association and disables any armed merge. Every publication entry point explicitly refuses version 3.
220
+ `pull_request` body edits rerun required CI. The ordinary finalized phase-free run exposes the stable protected aggregate directly; it does not wait for a lifecycle body edit. Documentation auto-merge's trusted owner also reevaluates lifecycle association. It preserves only an authorized human arm for the exact finalized version-3 head, never creates or exercises that arm, and disables stale, automated, malformed, draft, edited, or otherwise ineligible arms. Every publication entry point explicitly refuses version 3.
221
221
 
222
222
  The OpenSpec archive workflow remains default-branch trusted. For version 3 it uses read-only contents, PR, and Actions access to report the integrated result; App credentials are unnecessary and are not minted for post-merge verification. For legacy candidates it retains its existing scoped App publication behavior. The OpenSpec finalization workflow is the one version-3 user of the archive App: it mints a short-lived installation token with `contents: write` and `pull_requests: write`, pushes only to the candidate's own branch under a lease, edits only the body fence, and revokes the token when the run ends.
223
223
 
@@ -229,7 +229,7 @@ Read-only inspection remains:
229
229
  GH_TOKEN="$(gh auth token)" node scripts/governance/reconcile-openspec-archive.mjs --dry-run --pr <implementation-pr>
230
230
  ```
231
231
 
232
- For version 3, status distinguishes `needs-finalization`, `ready-for-manual-merge`, `accepted-and-archived`, `closed`, `blocked`, and `invalid-provenance`. A successful workflow invocation does not imply acceptance while the PR is open. Dry run never mutates refs, PRs, body text, tasks, evidence, specs, or acceptance.
232
+ For version 3, status distinguishes `needs-finalization`, `ready-for-maintainer-integration`, `accepted-and-archived`, `closed`, `blocked`, and `invalid-provenance`. A successful workflow invocation does not imply acceptance while the PR is open. Dry run never mutates refs, PRs, body text, tasks, evidence, specs, or acceptance.
233
233
 
234
234
  Scheduled scans continue to serve legacy catch-up. Version-3 candidates are verification-only and do not consume publication slots or block the legacy queue.
235
235
 
@@ -237,7 +237,7 @@ Scheduled scans continue to serve legacy catch-up. Version-3 candidates are veri
237
237
 
238
238
  After version-3 merge, verify:
239
239
 
240
- - the source PR is merged with the exact reviewed head by an authorized human;
240
+ - the source PR is merged with the exact reviewed head through a verified authorized-human integration route;
241
241
  - required exact-head CI succeeded;
242
242
  - current `develop` descends from the merge;
243
243
  - the active change is absent;
@@ -263,16 +263,16 @@ This bootstrap policy itself finishes under deployed version-2 authority. The fi
263
263
  - draft planning remaining unmerged;
264
264
  - explicit plan approval and same-PR implementation;
265
265
  - automated current-target finalization and exact-head CI;
266
- - authorized human manual merge with plain acceptance scenarios;
266
+ - authorized human integration, including manual merge and personally enabled exact-head native auto-merge, with plain acceptance scenarios;
267
267
  - integrated canonical specs/archive and no generated acceptance/archive PR;
268
268
  - read-only `accepted-and-archived` verification and exact-head branch cleanup;
269
- - stale head/body/base, incomplete tasks/evidence, malformed manifest, and automatic-merge refusal fixtures; and
269
+ - stale head/body/base/arm, incomplete tasks/evidence, malformed manifest, and automated or unauthorized auto-merge refusal fixtures; and
270
270
  - an unrelated standalone spec/docs PR retaining auto-merge.
271
271
 
272
272
  Unit tests or API success alone are not live acceptance. Until this evidence exists, version 3 is deployable but documented as awaiting its operational canary.
273
273
 
274
274
  ## Disable or roll back
275
275
 
276
- Before a version-3 merge, disable new version-3 authoring and continue using legacy version 2. After a version-3 merge, preserve its integrated archive and provenance and use an ordinary explicit corrective PR. Never direct-push a rewritten receipt, delete historical evidence, relax branch protection, or reinterpret an automatic merge as human acceptance.
276
+ Before a version-3 merge, disable new version-3 authoring and continue using legacy version 2. After a version-3 merge, preserve its integrated archive and provenance and use an ordinary explicit corrective PR. Never direct-push a rewritten receipt, delete historical evidence, relax branch protection, or reinterpret automation-owned, stale, App/Bot, or queue-backed integration as human acceptance.
277
277
 
278
278
  Disabling the OpenSpec archive workflow stops legacy publication and version-3 post-merge reporting, but does not change existing acceptance records, documentation auto-merge, remote branch cleanup, or release authority.
@@ -0,0 +1,35 @@
1
+ ## [0.2.5] - 2026-10-07
2
+
3
+ ### New Features
4
+
5
+ - show session info full screen ([#680](https://github.com/timurproko/a1/pull/680))
6
+ - redesign session tree dialog ([#682](https://github.com/timurproko/a1/pull/682))
7
+ - add name command input dialog ([#694](https://github.com/timurproko/a1/pull/694))
8
+
9
+ ### Changed
10
+
11
+ - refine animated separator ([#681](https://github.com/timurproko/a1/pull/681))
12
+ - upgrade pinned Pi to 1.0.4 ([#683](https://github.com/timurproko/a1/pull/683))
13
+ - unify selection treatment ([#686](https://github.com/timurproko/a1/pull/686))
14
+ - align project trust dialogs ([#687](https://github.com/timurproko/a1/pull/687))
15
+ - standardize share dialog ([#689](https://github.com/timurproko/a1/pull/689))
16
+ - align Resume Session dialog ([#693](https://github.com/timurproko/a1/pull/693))
17
+ - unify dialog close hints ([#695](https://github.com/timurproko/a1/pull/695))
18
+ - accent model scope markers ([#696](https://github.com/timurproko/a1/pull/696))
19
+
20
+ ### Fixed
21
+
22
+ - wrap session file after label ([#684](https://github.com/timurproko/a1/pull/684))
23
+ - align thinking markers with models ([#685](https://github.com/timurproko/a1/pull/685))
24
+ - place new-session notice above prompt ([#688](https://github.com/timurproko/a1/pull/688))
25
+ - close dialogs with Ctrl+C ([#690](https://github.com/timurproko/a1/pull/690))
26
+ - restore suggestion after Backspace ([#691](https://github.com/timurproko/a1/pull/691))
27
+ - cycle dialog filters backward ([#692](https://github.com/timurproko/a1/pull/692))
28
+ - avoid initial value-menu highlight ([#697](https://github.com/timurproko/a1/pull/697))
29
+ - keep session root expanded ([#698](https://github.com/timurproko/a1/pull/698))
30
+ - hide skill prefixes in collapsed menu ([#699](https://github.com/timurproko/a1/pull/699))
31
+ - omit space after command completion ([#700](https://github.com/timurproko/a1/pull/700))
32
+ - stabilize values and add undo ([#701](https://github.com/timurproko/a1/pull/701))
33
+ - standardize typing search hints ([#702](https://github.com/timurproko/a1/pull/702))
34
+ - show tree time toggle state ([#703](https://github.com/timurproko/a1/pull/703))
35
+ - refine resume search hints ([#704](https://github.com/timurproko/a1/pull/704))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@timurproko/a1",
3
- "version": "0.2.5-dev.704",
3
+ "version": "0.2.6-dev.717",
4
4
  "description": "Standalone terminal workspace for supervised native and managed agents",
5
5
  "type": "module",
6
6
  "privateLaunchContract": "neutral-launch-v1",