@orion-agents/orion-code 0.3.18 → 0.3.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -22,11 +22,106 @@ which is **not** a pass.
22
22
 
23
23
  ## [Unreleased]
24
24
 
25
+ ## [0.3.20]
26
+
27
+ > **Status: candidate.** Open. Repairs the Web E2E suite that has been red since
28
+ > v0.3.11 (the dock's tabs became rail buttons) independently of the Git panel
29
+ > work, and fixes the product defects the repair exposed. Not merged, tagged or
30
+ > published.
31
+ >
32
+ > ### Fixed (product)
33
+ >
34
+ > - The work-panel rail no longer sets `aria-orientation` on `role=navigation`
35
+ > (axe `aria-allowed-attr`, critical), and the navigation aside keeps one stable
36
+ > id so `aria-controls` always resolves.
37
+ > - The first archived-sessions load no longer storms the host (2476 requests
38
+ > measured in ~80s): the loading state claims workspace ownership like the
39
+ > failure path already did.
40
+ > - The narrow-width navigation toggle and the drawer close button are restored
41
+ > (v0.3.15 removed them with the conversation header, leaving sub-~800px
42
+ > viewports without a pointer entry while their CSS and focus wiring survived).
43
+ > - A bare Escape no longer moves focus to a stale drawer trigger, and the
44
+ > shortcut-help dialog restores focus to its trigger explicitly; its scrollable
45
+ > body is keyboard focusable (axe `scrollable-region-focusable`, serious).
46
+ >
47
+ > ### Test infrastructure
48
+ >
49
+ > - Every stale E2E locator and copy assertion traced to its contract change and
50
+ > rewritten; all previously failing specs pass locally (full suite 100/103 —
51
+ > the remainder are sandbox-only: a bulk-delete shim, two perf bounds).
52
+ > - `allowExpectedConsoleErrors(testInfo, text, maximum)` bounds the console
53
+ > noise that deliberate failure journeys (CAS conflicts, phantom catalog
54
+ > sessions) produce; the evidence capture caps can be raised via env for
55
+ > diagnosis.
56
+ > - The receipt upload only fires when the evidence gate actually ran.
57
+ >
58
+ > ### Security
59
+ >
60
+ > - The production dependency tree is patched to zero known high advisories:
61
+ > `js-yaml` 4.3.2 (the advisory the audit job originally failed on) plus the
62
+ > `axios` 1.20.0, `undici` 6.29.0, `brace-expansion`, `braces` and `figlet`
63
+ > patch updates. The audit job now audits `--omit=dev`: the remaining high
64
+ > advisories sit in dev-only tooling (jest 29 → 30, typescript-eslint 7 → 8,
65
+ > nodemon) whose fixes are major tooling upgrades; they are follow-up work,
66
+ > not shipped code.
67
+
68
+ ## [0.3.19] — 2026-09-19
69
+
70
+ > **Status: published.** Released as `@orion-agents/orion-code@0.3.19` from
71
+ > `refs/tags/v0.3.19`, and merged to `main` as `d17d211` (#259). Closes the five scenarios
72
+ > the v0.3.17 status table left as "feature present, assertion missing"
73
+ > (`docs/plan/evidence/v0.3.17-git/g317-status.md`), and fixes the three defects those
74
+ > assertions found.
75
+
76
+ ### Fixed
77
+
78
+ - **The responsive tiers were inert.** `GitPanel` attached its `ResizeObserver` from an
79
+ effect with an empty dependency list. The panel's first render is the loading placeholder,
80
+ which does not contain the measured node, so the effect read a null ref, returned, and never
81
+ ran again: the width stayed `0`, `data-width` stayed `wide` on a 480px panel, and the narrow
82
+ affordances never activated on any load whose status was not already cached. The observer is
83
+ now attached with a callback ref, so it follows the node in and out of the tree.
84
+ - **A discarded diff read left the pane blank with a file still selected.** Only a newer
85
+ _selection_ supersedes a read now. The document states the revision it was rendered against,
86
+ so accepting it and letting `stale` tell the truth is both safer and correct — dropping it
87
+ because the 2s visible poll had bumped the revision abandoned the read with nothing to
88
+ re-issue it.
89
+ - **Returning from the narrow detail pane threw away the reading position.** The back control
90
+ cleared `selectedFileId` to show the list, which also dropped the anchors and the cached
91
+ document. The pane choice is now its own state.
92
+
93
+ ### Added
94
+
95
+ - **A bare repository is no longer reported as "not a repository".** `rev-parse
96
+ --show-toplevel` fails for a bare repository too, and that single failure was read as "no
97
+ repository here". Status now carries `repositoryKind: 'worktree' | 'bare' | 'absent'` and
98
+ `hasWorktree`; every worktree-backed surface gates on `hasWorktree`, which is the only flag
99
+ that cannot be wrong. A bare repository's branch is reported instead of hidden, its change
100
+ lists stay empty, and the history limit names the real reason.
101
+ - **G317 assertion backlog:** an external write no longer moves the reader (the refresh is
102
+ detected and the document is marked stale instead of silently kept); `在 Files 打开` is
103
+ asserted end to end; the theme is measured rather than assumed (system preference, the
104
+ explicit choice through Settings, reduced motion and forced colours); the narrow tier's back
105
+ affordance and overflow are asserted; and repeated open/close plus a hundred consecutive
106
+ diffs are asserted bounded, with S5/S6 samples recorded by
107
+ `scripts/perf/v0319-git-s6-samples.mjs`.
108
+
109
+ ### Known limits
110
+
111
+ - The `compact` and `wide` panel tiers are pinned by `resolveGitPanelLayout` unit tests, not in
112
+ a browser: neither seeding `workPanel.widthPx` nor dragging the splitter reproduced a tier
113
+ change in the E2E harness. `docs/plan/evidence/v0.3.17-git/s6-responsive-keyboard.md` records
114
+ the measurement.
115
+ - The drawer entry point below 1180px is not asserted; those widths assert overflow only.
116
+
25
117
  ## [0.3.18]
26
118
 
27
- > **Status: released.** Closes the assertion and evidence backlog of the Git
28
- > right-hand work panel shipped in 0.3.17, and fixes the CRLF part-staging
29
- > defect found right after that release.
119
+ > **Status: npm-published, not tagged or merged.** The exact contents below are
120
+ > published to the registry as 0.3.18, but `refs/tags/v0.3.18` does not exist and
121
+ > `main` does not carry this work yet, so the release is not complete.
122
+ > Closes the assertion and evidence backlog of the Git right-hand work panel
123
+ > shipped in 0.3.17, and fixes the CRLF part-staging defect found right after
124
+ > that release.
30
125
 
31
126
  ### Fixed
32
127
 
@@ -57,6 +152,7 @@ which is **not** a pass.
57
152
  and the agent-led file-change scenario are still open.
58
153
 
59
154
  ---
155
+
60
156
  ## [0.3.17]
61
157
 
62
158
  > **Status: candidate.** Opening a local project is made fast and honest
@@ -203,7 +299,7 @@ which is **not** a pass.
203
299
  same timestamp granularity and with an unchanged size was not re-hashed, so
204
300
  `git add` succeeded while changing nothing. The copy now preserves the
205
301
  original timestamps, and staging verifies each path with `hash-object
206
- --path` against the index entry.
302
+ --path` against the index entry.
207
303
  - **`localhost` was rejected site-wide:** the Host and Origin checks compared
208
304
  header strings exactly, so `http://localhost:<port>` returned 421 (and, with
209
305
  only the Host check fixed, every mutation would have returned 403). Both now
package/README.md CHANGED
@@ -2,11 +2,10 @@
2
2
 
3
3
  Local-first, goal-driven coding agent for the terminal and browser.
4
4
 
5
- > v0.3.15 candidate — Web Workbench visual & interaction convergence: thinned
6
- > explanatory copy across the resource panels, one unified confirm modal
7
- > replacing the last `window.confirm` guards, busy feedback for refresh/save
8
- > actions, and Orion-style visual tightening of Files / Git / Review.
9
- > Candidate source is not an npm publication, Git tag, or merge-ready release.
5
+ > v0.3.20 candidate — open. The v0.3.19 round found that the Web E2E suite has been red
6
+ > since v0.3.11 (the dock's tabs became rail buttons), independently of the Git panel work;
7
+ > repairing those specs is this cycle's intended content.
8
+ > 0.3.19 is published to npm and tagged `v0.3.19`; 0.3.20 is not.
10
9
 
11
10
  [中文说明](README.zh-CN.md) ·
12
11
  [v0.3.7 plan](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
@@ -171,7 +170,7 @@ current development environments. Node 20 is upstream EOL and is no longer a v0.
171
170
  After the immutable `0.3.5` npm receipt exists:
172
171
 
173
172
  ```bash
174
- npm install -g @orion-agents/orion-code@0.3.15
173
+ npm install -g @orion-agents/orion-code@0.3.20
175
174
  orion --version
176
175
  orion doctor
177
176
  ```
package/README.zh-CN.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  面向终端与浏览器、本地优先的目标驱动 Coding Agent。
4
4
 
5
- > v0.3.15 候选版本:Web Workbench 视觉与交互收敛——资源面板说明文案瘦身、统一确认模态
6
- > 取代最后的 `window.confirm`、刷新/保存的进行中反馈,以及 Files / Git / Review 的
7
- > Orion 风格视觉收敛。Candidate 源码不代表已创建 npm 发布、Git tag 或达到可合并状态。
5
+ > v0.3.20 候选版本:进行中。v0.3.19 那轮验证发现 Web E2E 套件自 v0.3.11(停靠栏 tab 改为
6
+ > rail 按钮)起就与产品脱节,与 Git 工作栏的工作无关;修好这些 spec 是本周期的目标。
7
+ > 0.3.19 已发布 npm 并打 tag `v0.3.19`,0.3.20 尚未发布。
8
8
 
9
9
  [English](README.md) ·
10
10
  [v0.3.7 方案](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
@@ -101,7 +101,7 @@ Current。Node 20 已结束上游维护,不再属于 v0.3 Runtime 合同。
101
101
  当 npm 已存在不可变的 `0.3.4` 发布凭据后:
102
102
 
103
103
  ```bash
104
- npm install -g @orion-agents/orion-code@0.3.15
104
+ npm install -g @orion-agents/orion-code@0.3.20
105
105
  orion --version
106
106
  orion doctor
107
107
  ```
@@ -8,7 +8,23 @@ export declare const WEB_E2E_WEB31_SCENARIOS_V1: readonly ["WEB31-P0-01", "WEB31
8
8
  export declare const WEB_E2E_WEB32_SCENARIOS_V1: readonly ["WEB32-P0-01", "WEB32-P0-02", "WEB32-P0-03", "WEB32-P0-04", "WEB32-P0-05", "WEB32-P0-06", "WEB32-P0-07", "WEB32-P0-08", "WEB32-P0-09", "WEB32-P0-10", "WEB32-P0-11", "WEB32-P0-12"];
9
9
  export declare const WEB_E2E_WEB33_THEME_SCENARIOS_V1: readonly ["WEB33-P0-01", "WEB33-P0-02", "WEB33-P0-03", "WEB33-P0-04", "WEB33-P0-05", "WEB33-P0-06", "WEB33-P0-07", "WEB33-P0-08", "WEB33-P0-09", "WEB33-P0-10", "WEB33-P0-11", "WEB33-P0-12"];
10
10
  export declare const WEB_E2E_WEB33_SESSION_SCENARIOS_V1: readonly ["WEB33-P0-16", "WEB33-P0-17", "WEB33-P0-18", "WEB33-P0-19", "WEB33-P0-20", "WEB33-P0-21", "WEB33-P0-22", "WEB33-P0-23", "WEB33-P0-24"];
11
- export declare const WEB_E2E_WEB33_SCENARIOS_V1: readonly ("WEB33-P0-01" | "WEB33-P0-02" | "WEB33-P0-03" | "WEB33-P0-04" | "WEB33-P0-05" | "WEB33-P0-06" | "WEB33-P0-07" | "WEB33-P0-08" | "WEB33-P0-09" | "WEB33-P0-10" | "WEB33-P0-11" | "WEB33-P0-12" | "WEB33-P0-16" | "WEB33-P0-17" | "WEB33-P0-18" | "WEB33-P0-19" | "WEB33-P0-20" | "WEB33-P0-21" | "WEB33-P0-22" | "WEB33-P0-23" | "WEB33-P0-24")[];
11
+ /**
12
+ * v0.3.20 — the WEB33 scenarios whose specs shipped between v0.3.13 and
13
+ * v0.3.15 (rail, transcript navigator, brand, files editor, resource layout)
14
+ * and used these ids before they were registered. The receipts' exact
15
+ * coverage check requires every running id to be registered exactly once:
16
+ * WEB33-P0-16/17/18 belong to the session-concurrency journeys, so the
17
+ * v0.3.13 resource-layout specs were renumbered to P0-41/42/43.
18
+ */
19
+ export declare const WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1: readonly ["WEB33-P0-13", "WEB33-P0-14", "WEB33-P0-15", "WEB33-P0-25", "WEB33-P0-26", "WEB33-P0-27", "WEB33-P0-28", "WEB33-P0-29", "WEB33-P0-30", "WEB33-P0-31", "WEB33-P0-32", "WEB33-P0-34", "WEB33-P0-41", "WEB33-P0-42", "WEB33-P0-43"];
20
+ /**
21
+ * v0.3.15 — keyboard and shell surface journeys.
22
+ */
23
+ export declare const WEB_E2E_WEB36_SCENARIOS_V1: readonly ["WEB36-P0-01", "WEB36-P1-01", "WEB36-P1-02", "WEB36-P1-03"];
24
+ /**
25
+ * v0.3.16 — transcript / workspace surface journeys.
26
+ */
27
+ export declare const WEB_E2E_WEB37_SCENARIOS_V1: readonly ["WEB37-P0-01", "WEB37-P0-02", "WEB37-P0-03"];
12
28
  /**
13
29
  * v0.3.17 — LOCAL WORKSPACE open path. The picker is driven by a script file
14
30
  * (`ORION_CODE_WEB_PICKER_FIXTURE`) so CI never opens a real Finder dialog.
@@ -21,6 +37,16 @@ export declare const WEB_E2E_WEB38_SCENARIOS_V1: readonly ["WEB38-P0-01", "WEB38
21
37
  * actually verify; the per-stage unit suites are registered separately.
22
38
  */
23
39
  export declare const WEB_E2E_WEB317_SCENARIOS_V1: readonly string[];
40
+ export declare const WEB_E2E_WEB33_SCENARIOS_V1: readonly ("WEB33-P0-01" | "WEB33-P0-02" | "WEB33-P0-03" | "WEB33-P0-04" | "WEB33-P0-05" | "WEB33-P0-06" | "WEB33-P0-07" | "WEB33-P0-08" | "WEB33-P0-09" | "WEB33-P0-10" | "WEB33-P0-11" | "WEB33-P0-12" | "WEB33-P0-16" | "WEB33-P0-17" | "WEB33-P0-18" | "WEB33-P0-19" | "WEB33-P0-20" | "WEB33-P0-21" | "WEB33-P0-22" | "WEB33-P0-23" | "WEB33-P0-24" | "WEB33-P0-13" | "WEB33-P0-14" | "WEB33-P0-15" | "WEB33-P0-25" | "WEB33-P0-26" | "WEB33-P0-27" | "WEB33-P0-28" | "WEB33-P0-29" | "WEB33-P0-30" | "WEB33-P0-31" | "WEB33-P0-32" | "WEB33-P0-34" | "WEB33-P0-41" | "WEB33-P0-42" | "WEB33-P0-43")[];
41
+ /**
42
+ * v0.3.19 — closing the v0.3.17 G317 assertion backlog.
43
+ *
44
+ * Each id is one of the five scenarios that were left as "feature present, assertion
45
+ * missing": an external (agent) write must not move the reader, `在 Files 打开` must
46
+ * actually reach the Files panel, the theme and narrow tiers must be verified rather than
47
+ * assumed, and repeated open/close plus a hundred diffs must stay bounded.
48
+ */
49
+ export declare const WEB_E2E_WEB34_SCENARIOS_V1: readonly string[];
24
50
  export declare const WEB_E2E_FULL_SCENARIOS_V1: readonly string[];
25
51
  export declare const WEB_E2E_SETTINGS_SCENARIOS_V1: readonly string[];
26
52
  export declare const WEB_E2E_WEB31_CRITICAL_SCENARIOS_V1: readonly ["WEB31-P0-01", "WEB31-P0-02", "WEB31-P0-08", "WEB31-P0-09", "WEB31-P0-10", "WEB31-P0-12"];
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.WEB_E2E_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB33_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB32_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB31_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_SETTINGS_SCENARIOS_V1 = exports.WEB_E2E_FULL_SCENARIOS_V1 = exports.WEB_E2E_WEB317_SCENARIOS_V1 = exports.WEB_E2E_WEB38_SCENARIOS_V1 = exports.WEB_E2E_WEB33_SCENARIOS_V1 = exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1 = exports.WEB_E2E_WEB33_THEME_SCENARIOS_V1 = exports.WEB_E2E_WEB32_SCENARIOS_V1 = exports.WEB_E2E_WEB31_SCENARIOS_V1 = exports.WEB_E2E_LEGACY_SCENARIOS_V1 = exports.SUPPORTED_RELEASE_VERSION_LINE_V1 = exports.SUPPORTED_RELEASE_NODE_FLOORS_V1 = exports.SUPPORTED_RELEASE_NODE_MAJORS_V1 = exports.RELEASE_RECEIPT_VERSION_V1 = void 0;
3
+ exports.WEB_E2E_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB33_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB32_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_WEB31_CRITICAL_SCENARIOS_V1 = exports.WEB_E2E_SETTINGS_SCENARIOS_V1 = exports.WEB_E2E_FULL_SCENARIOS_V1 = exports.WEB_E2E_WEB34_SCENARIOS_V1 = exports.WEB_E2E_WEB33_SCENARIOS_V1 = exports.WEB_E2E_WEB317_SCENARIOS_V1 = exports.WEB_E2E_WEB38_SCENARIOS_V1 = exports.WEB_E2E_WEB37_SCENARIOS_V1 = exports.WEB_E2E_WEB36_SCENARIOS_V1 = exports.WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1 = exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1 = exports.WEB_E2E_WEB33_THEME_SCENARIOS_V1 = exports.WEB_E2E_WEB32_SCENARIOS_V1 = exports.WEB_E2E_WEB31_SCENARIOS_V1 = exports.WEB_E2E_LEGACY_SCENARIOS_V1 = exports.SUPPORTED_RELEASE_VERSION_LINE_V1 = exports.SUPPORTED_RELEASE_NODE_FLOORS_V1 = exports.SUPPORTED_RELEASE_NODE_MAJORS_V1 = exports.RELEASE_RECEIPT_VERSION_V1 = void 0;
4
4
  exports.createTarballArtifactReceiptV1 = createTarballArtifactReceiptV1;
5
5
  exports.verifyTarballArtifactReceiptV1 = verifyTarballArtifactReceiptV1;
6
6
  exports.createRuntimeMatrixReceiptV1 = createRuntimeMatrixReceiptV1;
@@ -97,9 +97,47 @@ exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1 = Object.freeze([
97
97
  'WEB33-P0-23',
98
98
  'WEB33-P0-24',
99
99
  ]);
100
- exports.WEB_E2E_WEB33_SCENARIOS_V1 = Object.freeze([
101
- ...exports.WEB_E2E_WEB33_THEME_SCENARIOS_V1,
102
- ...exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1,
100
+ /**
101
+ * v0.3.20 — the WEB33 scenarios whose specs shipped between v0.3.13 and
102
+ * v0.3.15 (rail, transcript navigator, brand, files editor, resource layout)
103
+ * and used these ids before they were registered. The receipts' exact
104
+ * coverage check requires every running id to be registered exactly once:
105
+ * WEB33-P0-16/17/18 belong to the session-concurrency journeys, so the
106
+ * v0.3.13 resource-layout specs were renumbered to P0-41/42/43.
107
+ */
108
+ exports.WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1 = Object.freeze([
109
+ 'WEB33-P0-13',
110
+ 'WEB33-P0-14',
111
+ 'WEB33-P0-15',
112
+ 'WEB33-P0-25',
113
+ 'WEB33-P0-26',
114
+ 'WEB33-P0-27',
115
+ 'WEB33-P0-28',
116
+ 'WEB33-P0-29',
117
+ 'WEB33-P0-30',
118
+ 'WEB33-P0-31',
119
+ 'WEB33-P0-32',
120
+ 'WEB33-P0-34',
121
+ 'WEB33-P0-41',
122
+ 'WEB33-P0-42',
123
+ 'WEB33-P0-43',
124
+ ]);
125
+ /**
126
+ * v0.3.15 — keyboard and shell surface journeys.
127
+ */
128
+ exports.WEB_E2E_WEB36_SCENARIOS_V1 = Object.freeze([
129
+ 'WEB36-P0-01',
130
+ 'WEB36-P1-01',
131
+ 'WEB36-P1-02',
132
+ 'WEB36-P1-03',
133
+ ]);
134
+ /**
135
+ * v0.3.16 — transcript / workspace surface journeys.
136
+ */
137
+ exports.WEB_E2E_WEB37_SCENARIOS_V1 = Object.freeze([
138
+ 'WEB37-P0-01',
139
+ 'WEB37-P0-02',
140
+ 'WEB37-P0-03',
103
141
  ]);
104
142
  /**
105
143
  * v0.3.17 — LOCAL WORKSPACE open path. The picker is driven by a script file
@@ -125,13 +163,36 @@ exports.WEB_E2E_WEB317_SCENARIOS_V1 = Object.freeze([
125
163
  'WEB39-P0-04',
126
164
  'WEB39-P0-05',
127
165
  ]);
166
+ exports.WEB_E2E_WEB33_SCENARIOS_V1 = Object.freeze([
167
+ ...exports.WEB_E2E_WEB33_THEME_SCENARIOS_V1,
168
+ ...exports.WEB_E2E_WEB33_SESSION_SCENARIOS_V1,
169
+ ...exports.WEB_E2E_WEB33_RESOURCE_SCENARIOS_V1,
170
+ ]);
171
+ /**
172
+ * v0.3.19 — closing the v0.3.17 G317 assertion backlog.
173
+ *
174
+ * Each id is one of the five scenarios that were left as "feature present, assertion
175
+ * missing": an external (agent) write must not move the reader, `在 Files 打开` must
176
+ * actually reach the Files panel, the theme and narrow tiers must be verified rather than
177
+ * assumed, and repeated open/close plus a hundred diffs must stay bounded.
178
+ */
179
+ exports.WEB_E2E_WEB34_SCENARIOS_V1 = Object.freeze([
180
+ 'WEB34-P0-01',
181
+ 'WEB34-P0-02',
182
+ 'WEB34-P0-03',
183
+ 'WEB34-P0-04',
184
+ 'WEB34-P0-05',
185
+ ]);
128
186
  exports.WEB_E2E_FULL_SCENARIOS_V1 = Object.freeze([
129
187
  ...exports.WEB_E2E_LEGACY_SCENARIOS_V1,
130
188
  ...exports.WEB_E2E_WEB31_SCENARIOS_V1,
131
189
  ...exports.WEB_E2E_WEB32_SCENARIOS_V1,
132
190
  ...exports.WEB_E2E_WEB33_SCENARIOS_V1,
191
+ ...exports.WEB_E2E_WEB36_SCENARIOS_V1,
192
+ ...exports.WEB_E2E_WEB37_SCENARIOS_V1,
133
193
  ...exports.WEB_E2E_WEB38_SCENARIOS_V1,
134
194
  ...exports.WEB_E2E_WEB317_SCENARIOS_V1,
195
+ ...exports.WEB_E2E_WEB34_SCENARIOS_V1,
135
196
  ]);
136
197
  exports.WEB_E2E_SETTINGS_SCENARIOS_V1 = Object.freeze(exports.WEB_E2E_FULL_SCENARIOS_V1.filter(id => id.startsWith('SET-P0-')));
137
198
  exports.WEB_E2E_WEB31_CRITICAL_SCENARIOS_V1 = Object.freeze([
@@ -342,10 +403,16 @@ function createWebE2EReleaseReceiptV1(input) {
342
403
  status: input.liveCanary === 'FAIL' ? 'fail' : 'pass',
343
404
  detail: `observed=${input.liveCanary}`,
344
405
  });
406
+ // v0.3.20 — the run scenario ids are sorted by the evidence assembler; the
407
+ // registries concatenate their per-epoch groups in delivery order, so both
408
+ // sides are compared as sorted sets (order-sensitive comparison against the
409
+ // declared order rejected runs that covered exactly the registered ids).
410
+ const sortedFull = [...exports.WEB_E2E_FULL_SCENARIOS_V1].sort();
411
+ const sortedCritical = [...exports.WEB_E2E_CRITICAL_SCENARIOS_V1].sort();
345
412
  const primaryCoverage = primaryRuns.length === 3 &&
346
- primaryRuns.every(run => run.role === 'primary' && sameOrderedValues(run.scenarioIds, exports.WEB_E2E_FULL_SCENARIOS_V1));
413
+ primaryRuns.every(run => run.role === 'primary' && sameOrderedValues(run.scenarioIds, sortedFull));
347
414
  const runtimeCoverage = runtimeRuns.length === exports.SUPPORTED_RELEASE_NODE_MAJORS_V1.length &&
348
- runtimeRuns.every(run => run.role === 'runtime' && sameOrderedValues(run.scenarioIds, exports.WEB_E2E_CRITICAL_SCENARIOS_V1));
415
+ runtimeRuns.every(run => run.role === 'runtime' && sameOrderedValues(run.scenarioIds, sortedCritical));
349
416
  checks.push({
350
417
  id: 'scenario_coverage',
351
418
  status: primaryCoverage && runtimeCoverage ? 'pass' : 'fail',
@@ -16,6 +16,6 @@ exports.GIT_FILE_SOURCES_V1 = Object.freeze([
16
16
  'commit',
17
17
  ]);
18
18
  function isGitFileSourceV1(value) {
19
- return (typeof value === 'string' && exports.GIT_FILE_SOURCES_V1.includes(value));
19
+ return typeof value === 'string' && exports.GIT_FILE_SOURCES_V1.includes(value);
20
20
  }
21
21
  //# sourceMappingURL=git-file-source.js.map
@@ -191,9 +191,7 @@ function createGitHistoryReader(options) {
191
191
  const chosen = parents[Math.min(parentIndex, parents.length - 1)];
192
192
  return {
193
193
  baseOid: chosen,
194
- baseLabel: parents.length > 1
195
- ? `父提交 ${parentIndex + 1}/${parents.length}`
196
- : '父提交',
194
+ baseLabel: parents.length > 1 ? `父提交 ${parentIndex + 1}/${parents.length}` : '父提交',
197
195
  };
198
196
  };
199
197
  return {
@@ -276,7 +274,12 @@ function createGitHistoryReader(options) {
276
274
  // The body is a separate call: a commit message may contain any byte, including the
277
275
  // separators the list format relies on.
278
276
  const message = await runGit(['log', '-1', '--format=%B', summary.id]);
279
- const committerRaw = (await runGit(['log', '-1', `--format=%cn${FIELD_SEPARATOR}%ce${FIELD_SEPARATOR}%cI`, summary.id])).trim();
277
+ const committerRaw = (await runGit([
278
+ 'log',
279
+ '-1',
280
+ `--format=%cn${FIELD_SEPARATOR}%ce${FIELD_SEPARATOR}%cI`,
281
+ summary.id,
282
+ ])).trim();
280
283
  const [committerName, authorEmail, committedAt] = committerRaw.split(FIELD_SEPARATOR);
281
284
  const base = await resolveBase(summary.id, parentIndex);
282
285
  return Object.freeze({
@@ -475,17 +478,16 @@ function createGitHistoryReader(options) {
475
478
  .toString()
476
479
  .trim();
477
480
  if (!tracked) {
478
- return Object.freeze({ path, rev: rev ?? null, lines: Object.freeze([]), truncated: false });
481
+ return Object.freeze({
482
+ path,
483
+ rev: rev ?? null,
484
+ lines: Object.freeze([]),
485
+ truncated: false,
486
+ });
479
487
  }
480
488
  // `--porcelain` is machine-readable and stable; the date stays ISO so the client never
481
489
  // re-implements time. Uncommitted lines show as boundary/not-committed metadata.
482
- const raw = await runGit([
483
- 'blame',
484
- '--porcelain',
485
- ...(target ? [target] : []),
486
- '--',
487
- path,
488
- ]);
490
+ const raw = await runGit(['blame', '--porcelain', ...(target ? [target] : []), '--', path]);
489
491
  const commits = new Map();
490
492
  const lines = [];
491
493
  let currentOid = null;
@@ -541,7 +543,11 @@ function createGitHistoryReader(options) {
541
543
  else if (key === 'boundary')
542
544
  boundary = true;
543
545
  if (pendingAuthor && pendingTime && pendingSummary && !commits.has(currentOid)) {
544
- commits.set(currentOid, Object.freeze({ author: pendingAuthor, authoredAt: pendingTime, summary: pendingSummary }));
546
+ commits.set(currentOid, Object.freeze({
547
+ author: pendingAuthor,
548
+ authoredAt: pendingTime,
549
+ summary: pendingSummary,
550
+ }));
545
551
  }
546
552
  }
547
553
  return Object.freeze({
@@ -28,8 +28,32 @@ export interface WebGitStatusCountsV1 {
28
28
  readonly untracked: number;
29
29
  readonly loaded: number;
30
30
  }
31
+ /**
32
+ * v0.3.19 (G317-22) — what this workspace actually is, stated instead of inferred.
33
+ *
34
+ * `git rev-parse --show-toplevel` fails for a bare repository, and that used to be reported
35
+ * as "not a repository at all". Those are two different facts and the reader is owed the
36
+ * right one:
37
+ *
38
+ * - `worktree` — an ordinary repository with a working tree; the panel reads changes.
39
+ * - `bare` — a repository with **no** working tree. It is a repository, its history
40
+ * exists, but this panel has no changes and no diff to read, and it must
41
+ * never invent any.
42
+ * - `absent` — no repository here at all.
43
+ *
44
+ * `isRepository` stays the coarse question ("is this a repository?"). Anything that needs
45
+ * a working tree must ask `hasWorktree`.
46
+ */
47
+ export type GitRepositoryKindV1 = 'worktree' | 'bare' | 'absent';
31
48
  export interface WebGitStatusV1 {
32
49
  readonly isRepository: boolean;
50
+ /** v0.3.19 (G317-22) — the precise answer to "what is this workspace". */
51
+ readonly repositoryKind: GitRepositoryKindV1;
52
+ /**
53
+ * v0.3.19 (G317-22) — true only for `repositoryKind: 'worktree'`. Every worktree-backed
54
+ * surface (change lists, diffs, staging, commit) is meaningless when this is false.
55
+ */
56
+ readonly hasWorktree: boolean;
33
57
  readonly repositoryRevision: string;
34
58
  readonly rootLabel?: string;
35
59
  readonly branch: string | null;
@@ -84,10 +84,15 @@ class GitReadModelServiceV1 {
84
84
  const nextOffset = offset + page.length;
85
85
  return Object.freeze({
86
86
  isRepository: snapshot.isRepository,
87
+ repositoryKind: snapshot.repositoryKind,
88
+ hasWorktree: snapshot.repositoryKind === 'worktree',
87
89
  repositoryRevision: snapshot.revision,
88
90
  ...(snapshot.root ? { rootLabel: (0, path_1.basename)(snapshot.root) } : {}),
89
91
  branch: snapshot.branch,
90
- detached: snapshot.isRepository && !snapshot.branch,
92
+ // v0.3.19 (G317-22) — only a worktree-backed checkout can be "detached". A bare
93
+ // repository has no branch either, and reporting that as a detached HEAD invented a
94
+ // state the repository is not in.
95
+ detached: snapshot.repositoryKind === 'worktree' && !snapshot.branch,
91
96
  head: snapshot.head ? snapshot.head.slice(0, 12) : null,
92
97
  upstream: snapshot.upstream,
93
98
  ahead: snapshot.ahead,
@@ -454,6 +459,7 @@ class GitReadModelServiceV1 {
454
459
  }
455
460
  async capture() {
456
461
  let root;
462
+ let repositoryKind = 'absent';
457
463
  try {
458
464
  root =
459
465
  this.repositoryRoot ??
@@ -461,11 +467,53 @@ class GitReadModelServiceV1 {
461
467
  if (!isWithinRoot(root, this.workspace))
462
468
  throw new Error('repository root escaped workspace');
463
469
  this.repositoryRoot = root;
470
+ repositoryKind = 'worktree';
464
471
  }
465
472
  catch {
473
+ // v0.3.19 (G317-22) — `--show-toplevel` fails for a bare repository too, so the failure
474
+ // alone cannot say which case this is. Ask directly. Conflating "no repository" with
475
+ // "a repository that has no working tree" told the reader the wrong thing about a
476
+ // directory that is in fact a valid repository.
477
+ const bare = (await this.tryGit(['rev-parse', '--is-bare-repository'], this.workspace))
478
+ ?.trim()
479
+ .toLowerCase();
480
+ repositoryKind = bare === 'true' ? 'bare' : 'absent';
481
+ }
482
+ if (repositoryKind === 'bare') {
483
+ // A bare repository has no index and no working tree, so there is nothing to report as
484
+ // changed — but its HEAD is real and worth stating. `clean` stays true in `status()`
485
+ // because zero files differ from an index that does not exist; `hasWorktree` is what
486
+ // tells the reader not to trust that as "you have nothing to commit".
487
+ const [branchResult, headResult] = await Promise.all([
488
+ this.tryGit(['symbolic-ref', '--quiet', '--short', 'HEAD'], this.workspace),
489
+ this.tryGit(['rev-parse', 'HEAD'], this.workspace),
490
+ ]);
491
+ const branch = branchResult?.trim() || null;
492
+ const head = headResult?.trim() || null;
493
+ const revision = (0, crypto_1.createHash)('sha256')
494
+ .update(JSON.stringify({ bare: this.workspace, branch, head }))
495
+ .digest('hex');
496
+ return Object.freeze({
497
+ isRepository: true,
498
+ repositoryKind: 'bare',
499
+ branch,
500
+ head,
501
+ upstream: null,
502
+ ahead: 0,
503
+ behind: 0,
504
+ records: Object.freeze([]),
505
+ rawStatus: '',
506
+ stagedRecords: 0,
507
+ conflictedRecords: 0,
508
+ indexDigest: (0, crypto_1.createHash)('sha256').update('').digest('hex'),
509
+ revision,
510
+ });
511
+ }
512
+ if (repositoryKind === 'absent') {
466
513
  const revision = (0, crypto_1.createHash)('sha256').update(`not-git:${this.workspace}`).digest('hex');
467
514
  return Object.freeze({
468
515
  isRepository: false,
516
+ repositoryKind: 'absent',
469
517
  branch: null,
470
518
  head: null,
471
519
  upstream: null,
@@ -479,6 +527,11 @@ class GitReadModelServiceV1 {
479
527
  revision,
480
528
  });
481
529
  }
530
+ if (!root) {
531
+ // Only the `worktree` branch assigns a root and both other kinds returned above, so this
532
+ // is unreachable today — it exists so the invariant is enforced, not merely inferred.
533
+ throw new Error('Resolved a non-worktree repository without a root.');
534
+ }
482
535
  const [rawStatus, branchResult, headResult, upstreamResult, indexListing] = await Promise.all([
483
536
  this.runGit(['status', '--porcelain=v1', '-z', '--untracked-files=all'], root),
484
537
  this.tryGit(['symbolic-ref', '--quiet', '--short', 'HEAD'], root),
@@ -532,6 +585,7 @@ class GitReadModelServiceV1 {
532
585
  .digest('hex');
533
586
  return Object.freeze({
534
587
  isRepository: true,
588
+ repositoryKind: 'worktree',
535
589
  root,
536
590
  branch,
537
591
  head,
@@ -941,6 +995,13 @@ class GitReadModelServiceV1 {
941
995
  }
942
996
  async requireRepositoryRoot() {
943
997
  const snapshot = await this.capture();
998
+ if (snapshot.repositoryKind === 'bare') {
999
+ // v0.3.19 (G317-22) — a bare repository *is* a repository, so "unavailable" was the
1000
+ // wrong word for it. What it lacks is a working tree, and this panel is worktree-based:
1001
+ // the limit is stated rather than worked around, and reading from a different working
1002
+ // tree would widen access in a way the workspace boundary does not allow.
1003
+ throw new Error('This workspace is a bare repository. The Git panel reads a working tree, so it has no changes or history to show here.');
1004
+ }
944
1005
  if (!snapshot.isRepository || !snapshot.root) {
945
1006
  throw new Error('Git repository is unavailable.');
946
1007
  }
@@ -1196,7 +1257,16 @@ function diffCommands(path, source, ignoreWhitespace = false, wordDiff = false)
1196
1257
  return Object.freeze([
1197
1258
  {
1198
1259
  title: 'Staged',
1199
- args: ['diff', '--cached', ...modes, '--no-ext-diff', '--no-textconv', '--unified=3', '--', path],
1260
+ args: [
1261
+ 'diff',
1262
+ '--cached',
1263
+ ...modes,
1264
+ '--no-ext-diff',
1265
+ '--no-textconv',
1266
+ '--unified=3',
1267
+ '--',
1268
+ path,
1269
+ ],
1200
1270
  },
1201
1271
  ]);
1202
1272
  }
@@ -8,7 +8,7 @@ import type { ToolDetailPage, ToolDetailSummary } from '../runtime/tool-detail-r
8
8
  import type { SessionMeta } from '../services/session-storage';
9
9
  import type { ToolConfirmationPolicy } from '../services/global-config';
10
10
  import type { WebFileContentPageV1, WebFileNodeV1, WebFileTreePageV1 } from './file-read-service';
11
- import type { GitCommitIdentityV1, GitCommitOutcomeV1, GitCommitPreviewV1, GitIndexEntryV1, GitMutationResultV1, GitWorktreeSourceV1, WebGitCommitV1, WebGitDiffPageV1, WebGitFileSourceV1, WebGitFileV1, WebGitLogPageV1, WebGitStatusCountsV1, WebGitStatusV1 } from './git-read-model-service';
11
+ import type { GitCommitIdentityV1, GitCommitOutcomeV1, GitCommitPreviewV1, GitIndexEntryV1, GitMutationResultV1, GitRepositoryKindV1, GitWorktreeSourceV1, WebGitCommitV1, WebGitDiffPageV1, WebGitFileSourceV1, WebGitFileV1, WebGitLogPageV1, WebGitStatusCountsV1, WebGitStatusV1 } from './git-read-model-service';
12
12
  import type { GitDiffCapabilitiesV2, GitDiffDocumentV2, GitDiffDocumentKindV2, GitDiffLineKindV2, GitDiffLineV2, GitHunkV2 } from './git-diff-document';
13
13
  import type { WebReviewSnapshotV1 } from './review-service';
14
14
  import type { WebSessionRuntimeSummaryV1 } from './session-runtime-registry';
@@ -423,9 +423,9 @@ export type WebWorkspaceInvalidationReasonV1 = 'context-change' | 'filesystem-ch
423
423
  export type WebToolDetailSummaryV1 = ToolDetailSummary;
424
424
  export type WebToolDetailPageV1 = ToolDetailPage;
425
425
  export type { GitBlameLineV1, GitGraphPageV1, GitGraphRowV1, GitBlameResultV1, GitRefKindV1, GitRefV1, GitRefsV1, GitCommitSummaryV1, GitCommitDetailV1, GitCommitFileV1, GitCommitFilesV1, GitHistoryQueryV1, GitHistoryPageV1, GitFileHistoryPageV1, } from './git-history-service';
426
- export type { GitCompareFileV1, GitCompareModeV1, GitCompareResultV1, } from './git-compare-service';
426
+ export type { GitCompareFileV1, GitCompareModeV1, GitCompareResultV1 } from './git-compare-service';
427
427
  export type { GitBlobResultV1, GitConflictShapeV1, GitConflictVersionsV1, GitFileVersionV1, GitSubmoduleEntryV1, GitVersionLabelV1, } from './git-version-service';
428
- export type { WebFileContentPageV1, WebFileNodeV1, WebFileTreePageV1, GitCommitIdentityV1, GitCommitOutcomeV1, GitCommitPreviewV1, GitIndexEntryV1, GitMutationResultV1, GitWorktreeSourceV1, GitDiffCapabilitiesV2, GitDiffDocumentV2, GitDiffDocumentKindV2, GitDiffLineKindV2, GitDiffLineV2, GitHunkV2, WebGitCommitV1, WebGitDiffPageV1, WebGitFileSourceV1, WebGitFileV1, WebGitLogPageV1, WebGitStatusCountsV1, WebGitStatusV1, WebReviewSnapshotV1, WebTerminalCreateResultV1, WebTerminalExitV1, WebTerminalGapV1, WebTerminalMetadataV1, WebTerminalOutputFrameV1, WebTerminalStateV1, };
428
+ export type { WebFileContentPageV1, WebFileNodeV1, WebFileTreePageV1, GitCommitIdentityV1, GitCommitOutcomeV1, GitCommitPreviewV1, GitIndexEntryV1, GitMutationResultV1, GitWorktreeSourceV1, GitDiffCapabilitiesV2, GitDiffDocumentV2, GitDiffDocumentKindV2, GitDiffLineKindV2, GitDiffLineV2, GitHunkV2, GitRepositoryKindV1, WebGitCommitV1, WebGitDiffPageV1, WebGitFileSourceV1, WebGitFileV1, WebGitLogPageV1, WebGitStatusCountsV1, WebGitStatusV1, WebReviewSnapshotV1, WebTerminalCreateResultV1, WebTerminalExitV1, WebTerminalGapV1, WebTerminalMetadataV1, WebTerminalOutputFrameV1, WebTerminalStateV1, };
429
429
  export interface WebBootstrapV1 {
430
430
  readonly apiVersion: 1;
431
431
  readonly productVersion: string;
@@ -607,7 +607,16 @@ async function handleRequest(context) {
607
607
  if (method === 'GET' && path === '/git/history') {
608
608
  const contextGuard = requireContextGuardQuery(url);
609
609
  sendJson(response, 200, await context.workbench.gitHistory(contextGuard, {
610
- ...optionalQueryParams(url, ['message', 'author', 'sha', 'path', 'since', 'until', 'rev', 'cursor']),
610
+ ...optionalQueryParams(url, [
611
+ 'message',
612
+ 'author',
613
+ 'sha',
614
+ 'path',
615
+ 'since',
616
+ 'until',
617
+ 'rev',
618
+ 'cursor',
619
+ ]),
611
620
  pageSize: boundedInteger(url.searchParams.get('pageSize'), 30, 1, 100),
612
621
  }));
613
622
  return;