@orion-agents/orion-code 0.3.22 → 0.3.25
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 +81 -3
- package/README.md +9 -5
- package/README.zh-CN.md +7 -4
- package/dist/harness/failure-attribution.d.ts +23 -0
- package/dist/harness/failure-attribution.js +103 -0
- package/dist/harness/verified-repair.d.ts +71 -0
- package/dist/harness/verified-repair.js +212 -0
- package/dist/product/paths.d.ts +20 -1
- package/dist/product/paths.js +293 -10
- package/dist/runtime/model-coordinator.d.ts +1 -0
- package/dist/runtime/model-coordinator.js +9 -2
- package/dist/runtime/release-receipts.d.ts +3 -3
- package/dist/runtime/release-receipts.js +29 -0
- package/dist/runtime/session-composer-control.js +5 -1
- package/dist/services/config-dir.d.ts +3 -2
- package/dist/services/config-dir.js +6 -29
- package/dist/services/repo-intelligence/index.d.ts +11 -0
- package/dist/services/repo-intelligence/index.js +20 -0
- package/dist/services/repo-intelligence/recall.d.ts +44 -0
- package/dist/services/repo-intelligence/recall.js +115 -0
- package/dist/services/repo-intelligence/symbol-index.d.ts +52 -0
- package/dist/services/repo-intelligence/symbol-index.js +258 -0
- package/dist/services/session-storage.d.ts +18 -2
- package/dist/services/session-storage.js +196 -60
- package/dist/services/workspace-registry.d.ts +9 -0
- package/dist/services/workspace-registry.js +13 -2
- package/dist/web/git-history-service.js +3 -0
- package/dist/web/git-read-model-service.d.ts +9 -0
- package/dist/web/git-read-model-service.js +35 -0
- package/dist/web/git-version-service.d.ts +8 -0
- package/dist/web/git-version-service.js +50 -26
- package/dist/web/protocol.d.ts +13 -0
- package/dist/web/workbench-controller.js +47 -2
- package/dist/web-client/assets/{FilesPanel-CKeXPofZ.js → FilesPanel-CdoCa8u0.js} +1 -1
- package/dist/web-client/assets/{GitPanel-DollPxv0.js → GitPanel-CRr_iIDz.js} +1 -1
- package/dist/web-client/assets/{ReviewPanel-CNADacIn.js → ReviewPanel-C1Cu2IRV.js} +1 -1
- package/dist/web-client/assets/{TerminalPanel-Cyns4uJt.js → TerminalPanel-BniCjatZ.js} +1 -1
- package/dist/web-client/assets/index-kQqA3li6.js +20 -0
- package/dist/web-client/assets/{useAutoNotice-BLADeFFj.js → useAutoNotice-DPXFWIwK.js} +1 -1
- package/dist/web-client/index.html +1 -1
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
- package/dist/services/auto-fix/autoFixConfig.d.ts +0 -30
- package/dist/services/auto-fix/autoFixConfig.js +0 -78
- package/dist/services/auto-fix/autoFixHook.d.ts +0 -27
- package/dist/services/auto-fix/autoFixHook.js +0 -99
- package/dist/services/auto-fix/autoFixRunner.d.ts +0 -60
- package/dist/services/auto-fix/autoFixRunner.js +0 -207
- package/dist/services/auto-fix/index.d.ts +0 -7
- package/dist/services/auto-fix/index.js +0 -17
- package/dist/web-client/assets/index-BhGhORAX.js +0 -20
package/CHANGELOG.md
CHANGED
|
@@ -22,12 +22,90 @@ which is **not** a pass.
|
|
|
22
22
|
|
|
23
23
|
## [Unreleased]
|
|
24
24
|
|
|
25
|
-
## [0.3.
|
|
25
|
+
## [0.3.25] — UNRELEASED
|
|
26
26
|
|
|
27
|
-
> **Status: candidate.** Open.
|
|
27
|
+
> **Status: candidate.** Open. Four open-issue fixes: the Git conflict view reads the
|
|
28
|
+
> edited working file from the filesystem instead of an unstored object (#269), file
|
|
29
|
+
> history walks the requested revision instead of always HEAD (#270), model switching
|
|
30
|
+
> gates the compact preflight on effective input budgets instead of raw context windows
|
|
31
|
+
> (#271), and the left project navigation keeps a stable registration order when
|
|
32
|
+
> sessions are activated (#272). Plan: `docs/plan/v0.3.25-plan.md`. Not merged, tagged
|
|
33
|
+
> or published.
|
|
34
|
+
|
|
35
|
+
### Fixed
|
|
36
|
+
|
|
37
|
+
- #269: `GET /git/conflict` no longer fails with `git cat-file: could not get object
|
|
38
|
+
info` after editing a conflicted file before staging — the `worktree` version is read
|
|
39
|
+
from the filesystem (contained within the repository root) instead of an object
|
|
40
|
+
`hash-object` never wrote.
|
|
41
|
+
- #270: `GET /git/file-history` with a `rev` now walks that revision; previously the
|
|
42
|
+
validated revision never reached the `git log` command and every query walked HEAD.
|
|
43
|
+
- #271: switching to a model with the same or larger raw context window but a smaller
|
|
44
|
+
effective input budget (larger output reserve) now reports `compactRequired` and runs
|
|
45
|
+
the compact preflight instead of silently persisting a model the history cannot fit.
|
|
46
|
+
|
|
47
|
+
### Changed
|
|
48
|
+
|
|
49
|
+
- #272: the left project navigation keeps a stable registration order for unpinned
|
|
50
|
+
projects (`registeredAt`, persisted by the workspace registry and exposed additively
|
|
51
|
+
on `WebWorkspaceSummaryV1`); activating sessions no longer reshuffles the tree.
|
|
52
|
+
Pinned projects keep `pinnedOrder` first.
|
|
53
|
+
|
|
54
|
+
## [0.3.24] — 2026-10-13
|
|
55
|
+
|
|
56
|
+
> **Status: published.** Released as `@orion-agents/orion-code@0.3.24` from
|
|
57
|
+
> `refs/tags/v0.3.24`, and merged to `main` as `85fadb4` (#268). Session
|
|
58
|
+
> navigation & responsiveness for the Web Workbench. Plan:
|
|
59
|
+
> `docs/plan/v0.3.24-plan.md`. GitHub Release:
|
|
60
|
+
> https://github.com/orion-agents/orion-code/releases/tag/v0.3.24
|
|
61
|
+
|
|
62
|
+
### Fixed
|
|
63
|
+
|
|
64
|
+
- Session command and Composer requests track only their originating Session;
|
|
65
|
+
sending no longer blocks another Session's Composer or session creation.
|
|
66
|
+
- Same-Context snapshots across a live event stream are reused when unchanged;
|
|
67
|
+
warm selection does not re-fetch or disable input. Background changes and
|
|
68
|
+
disconnections still require synchronization before writes.
|
|
69
|
+
- Foreground display caches retain streamed messages, tool cards and loaded
|
|
70
|
+
history instead of restoring an older HTTP tail when switching back.
|
|
71
|
+
- Creation is single-flight and respects a later foreground selection. Late
|
|
72
|
+
send responses do not clear, focus or replace another Session's draft.
|
|
73
|
+
- Web creation bounds asynchronous Git branch probes; project storage identity
|
|
74
|
+
resolution reuses an invalidatable bounded cache instead of repeatedly
|
|
75
|
+
spawning synchronous Git on every storage helper call.
|
|
76
|
+
- Cross-Workspace navigation remains usable while another Session is running;
|
|
77
|
+
the most recent navigation intent wins when Context activation is in flight.
|
|
78
|
+
- Per-Session scroll position and loaded history survive navigation, while
|
|
79
|
+
foreground Snapshot and SSE reconciliation no longer skips background events.
|
|
80
|
+
- Web Session creation waits for file locks without freezing the Host event
|
|
81
|
+
loop, and cold Catalog scans yield to unrelated HTTP and SSE traffic.
|
|
82
|
+
|
|
83
|
+
## [0.3.23] — UNRELEASED
|
|
84
|
+
|
|
85
|
+
> **Status: candidate.** Open. Coding Intelligence & Verified Execution — wires the live
|
|
86
|
+
> coding-task evaluation to the production runtime (isolated workspaces, machine-verified
|
|
87
|
+
> success, five-outcome classification), adds repo-intelligence symbol indexing and
|
|
88
|
+
> recall, and consolidates the bounded verification/repair loop into the HarnessKernel
|
|
89
|
+
> completion audit. Plan: `docs/plan/v0.3.23-plan.md`. Not merged, tagged or published.
|
|
90
|
+
|
|
91
|
+
### Added (planned)
|
|
92
|
+
|
|
93
|
+
- Live Coding Eval: `LiveTaskExecutor` + `compare-receipts` A/B reporting; five-outcome
|
|
94
|
+
classification with orthogonal falseComplete; `command_exit_zero` / `invariant` checks.
|
|
95
|
+
- Repo Intelligence: lightweight TS/JS symbol index with incremental maintenance and
|
|
96
|
+
sourced recall (Recall@K evaluated).
|
|
97
|
+
- Verified Repair: structured failure attribution, bounded repair loop (feature-flagged),
|
|
98
|
+
five-state completion audit; the orphaned auto-fix / verification-profile modules merge
|
|
99
|
+
into the wired stack and are removed.
|
|
100
|
+
|
|
101
|
+
## [0.3.22] — 2026-10-09
|
|
102
|
+
|
|
103
|
+
> **Status: published.** Released as `@orion-agents/orion-code@0.3.22` from
|
|
104
|
+
> `refs/tags/v0.3.22`, and merged to `main` as `9f09e0a` (#265). Hardens ToolGateway
|
|
28
105
|
> in-flight request identity (T22-01), adds the durability fault-injection matrix (T22-02),
|
|
29
106
|
> the four-state release verification gate (T22-03), and a real coding-task evaluation
|
|
30
|
-
> harness (T22-04). Plan: `docs/plan/v0.3.22-plan.md`.
|
|
107
|
+
> harness (T22-04). Plan: `docs/plan/v0.3.22-plan.md`. GitHub Release:
|
|
108
|
+
> https://github.com/orion-agents/orion-code/releases/tag/v0.3.22。
|
|
31
109
|
|
|
32
110
|
### Fixed (product)
|
|
33
111
|
|
package/README.md
CHANGED
|
@@ -2,11 +2,15 @@
|
|
|
2
2
|
|
|
3
3
|
Local-first, goal-driven coding agent for the terminal and browser.
|
|
4
4
|
|
|
5
|
-
> v0.3.
|
|
6
|
-
>
|
|
7
|
-
>
|
|
5
|
+
> v0.3.25 candidate — open. Four open-issue fixes: the Git conflict view reads the edited
|
|
6
|
+
> working file from the filesystem (#269), file history walks the requested revision
|
|
7
|
+
> (#270), model switching gates the compact preflight on effective input budgets (#271),
|
|
8
|
+
> and the left project navigation keeps a stable registration order (#272). The previous
|
|
9
|
+
> release 0.3.24 is on npm (registry `latest`), tagged `v0.3.24` (`85fadb4`, #268) and
|
|
10
|
+
> released on GitHub.
|
|
8
11
|
|
|
9
12
|
[中文说明](README.zh-CN.md) ·
|
|
13
|
+
[v0.3.24 plan](docs/plan/v0.3.24-plan.md) ·
|
|
10
14
|
[v0.3.7 plan](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
|
|
11
15
|
[v0.3.6 plan](docs/plan/v0.3.6-plan.md) ·
|
|
12
16
|
[v0.3.5 plan](docs/plan/v0.3.5-plan.md) ·
|
|
@@ -166,10 +170,10 @@ user extension boundaries.
|
|
|
166
170
|
Node.js 22.12+, 24, and 26 are supported. Use Node 24 LTS for production or Node 26 Current for
|
|
167
171
|
current development environments. Node 20 is upstream EOL and is no longer a v0.3 runtime.
|
|
168
172
|
|
|
169
|
-
|
|
173
|
+
To install the pinned v0.3.25 package when it is available on npm:
|
|
170
174
|
|
|
171
175
|
```bash
|
|
172
|
-
npm install -g @orion-agents/orion-code@0.3.
|
|
176
|
+
npm install -g @orion-agents/orion-code@0.3.25
|
|
173
177
|
orion --version
|
|
174
178
|
orion doctor
|
|
175
179
|
```
|
package/README.zh-CN.md
CHANGED
|
@@ -2,10 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
面向终端与浏览器、本地优先的目标驱动 Coding Agent。
|
|
4
4
|
|
|
5
|
-
> v0.3.
|
|
6
|
-
>
|
|
5
|
+
> v0.3.25 候选版本:进行中。四个开放 issue 修复:Git 冲突视图从文件系统读取已编辑的
|
|
6
|
+
> 工作区文件(#269)、文件历史按请求的 revision 遍历(#270)、模型切换按有效输入预算
|
|
7
|
+
> 触发压缩预检(#271)、左侧项目导航保持稳定注册序(#272)。上一版 0.3.24 已发布 npm
|
|
8
|
+
> (registry `latest`)、打 tag `v0.3.24`(`85fadb4`,#268)并发布 GitHub Release。
|
|
7
9
|
|
|
8
10
|
[English](README.md) ·
|
|
11
|
+
[v0.3.24 方案](docs/plan/v0.3.24-plan.md) ·
|
|
9
12
|
[v0.3.7 方案](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
|
|
10
13
|
[v0.3.6 方案](docs/plan/v0.3.6-plan.md) ·
|
|
11
14
|
[v0.3.4 稳定化方案](docs/plan/v0.3.4-stabilization-plan.md) ·
|
|
@@ -97,10 +100,10 @@ MCP 仍是用户可见的扩展边界。
|
|
|
97
100
|
支持 Node.js 22.12+、24 和 26。生产环境建议使用 Node 24 LTS,当前开发环境也支持 Node 26
|
|
98
101
|
Current。Node 20 已结束上游维护,不再属于 v0.3 Runtime 合同。
|
|
99
102
|
|
|
100
|
-
|
|
103
|
+
v0.3.25 在 npm 可用后,可安装固定版本:
|
|
101
104
|
|
|
102
105
|
```bash
|
|
103
|
-
npm install -g @orion-agents/orion-code@0.3.
|
|
106
|
+
npm install -g @orion-agents/orion-code@0.3.25
|
|
104
107
|
orion --version
|
|
105
108
|
orion doctor
|
|
106
109
|
```
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
export type FailureKind = 'typescript' | 'lint' | 'build' | 'unit-test' | 'integration-test' | 'environment' | 'provider' | 'unknown';
|
|
2
|
+
export interface FailureEntryV1 {
|
|
3
|
+
readonly file?: string;
|
|
4
|
+
readonly line?: number;
|
|
5
|
+
readonly code?: string;
|
|
6
|
+
readonly message: string;
|
|
7
|
+
}
|
|
8
|
+
export interface StructuredFailureEvidenceV1 {
|
|
9
|
+
readonly kind: FailureKind;
|
|
10
|
+
readonly summary: string;
|
|
11
|
+
readonly entries: readonly FailureEntryV1[];
|
|
12
|
+
/** Stable over identical failures; changes when the failure changes. */
|
|
13
|
+
readonly digest: string;
|
|
14
|
+
}
|
|
15
|
+
export declare function parseTypeScriptDiagnostics(output: string): readonly FailureEntryV1[];
|
|
16
|
+
export declare function parseEsLintOutput(output: string): readonly FailureEntryV1[];
|
|
17
|
+
export declare function parseJestFailures(output: string): readonly FailureEntryV1[];
|
|
18
|
+
/**
|
|
19
|
+
* Attribute a verification failure to a structured kind. Detection order:
|
|
20
|
+
* TypeScript → test failure → lint → build → provider → environment → unknown.
|
|
21
|
+
*/
|
|
22
|
+
export declare function attributeFailure(output: string, command?: string): StructuredFailureEvidenceV1;
|
|
23
|
+
//# sourceMappingURL=failure-attribution.d.ts.map
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseTypeScriptDiagnostics = parseTypeScriptDiagnostics;
|
|
4
|
+
exports.parseEsLintOutput = parseEsLintOutput;
|
|
5
|
+
exports.parseJestFailures = parseJestFailures;
|
|
6
|
+
exports.attributeFailure = attributeFailure;
|
|
7
|
+
/**
|
|
8
|
+
* v0.3.23 T23-C2 — structured failure attribution.
|
|
9
|
+
*
|
|
10
|
+
* Parses verification output (tsc, jest, eslint, build tooling, environment
|
|
11
|
+
* errors) into bounded structured evidence instead of feeding raw logs back to
|
|
12
|
+
* the model. Each attribution carries a stable digest so the repair loop can
|
|
13
|
+
* detect no-progress rounds objectively.
|
|
14
|
+
*/
|
|
15
|
+
const crypto_1 = require("crypto");
|
|
16
|
+
const TSC_PATTERN = /^(.+?)\((\d+),(\d+)\):\s+(?:error|warning)\s+(TS\d+):\s+(.+)$/gm;
|
|
17
|
+
const ESLINT_PATTERN = /^(.+?):(\d+):(\d+)\s+(error|warning)\s+(.+)$/gm;
|
|
18
|
+
const JEST_FAIL_PATTERN = /✕\s+(.+?)(?:\s*\(\d+\s*m?s\))?$/gm;
|
|
19
|
+
function digestOf(kind, entries) {
|
|
20
|
+
const hash = (0, crypto_1.createHash)('sha256');
|
|
21
|
+
hash.update(kind);
|
|
22
|
+
for (const entry of entries) {
|
|
23
|
+
hash.update(`${entry.file ?? ''}|${entry.line ?? ''}|${entry.code ?? ''}|${entry.message}`);
|
|
24
|
+
}
|
|
25
|
+
return hash.digest('hex').slice(0, 32);
|
|
26
|
+
}
|
|
27
|
+
function evidence(kind, entries, summary) {
|
|
28
|
+
return {
|
|
29
|
+
kind,
|
|
30
|
+
summary,
|
|
31
|
+
entries: Object.freeze(entries),
|
|
32
|
+
digest: digestOf(kind, entries),
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
function parseTypeScriptDiagnostics(output) {
|
|
36
|
+
const entries = [];
|
|
37
|
+
for (const match of output.matchAll(TSC_PATTERN)) {
|
|
38
|
+
entries.push({
|
|
39
|
+
file: match[1],
|
|
40
|
+
line: Number(match[2]),
|
|
41
|
+
code: match[4],
|
|
42
|
+
message: match[5].trim(),
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
return entries;
|
|
46
|
+
}
|
|
47
|
+
function parseEsLintOutput(output) {
|
|
48
|
+
const entries = [];
|
|
49
|
+
for (const match of output.matchAll(ESLINT_PATTERN)) {
|
|
50
|
+
entries.push({
|
|
51
|
+
file: match[1],
|
|
52
|
+
line: Number(match[2]),
|
|
53
|
+
code: match[4],
|
|
54
|
+
message: match[5].trim(),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
return entries;
|
|
58
|
+
}
|
|
59
|
+
function parseJestFailures(output) {
|
|
60
|
+
const entries = [];
|
|
61
|
+
for (const match of output.matchAll(JEST_FAIL_PATTERN)) {
|
|
62
|
+
entries.push({ message: match[1].trim() });
|
|
63
|
+
}
|
|
64
|
+
const summary = /Tests:\s+(\d+)\s+failed/.exec(output);
|
|
65
|
+
if (summary && entries.length === 0) {
|
|
66
|
+
entries.push({ message: `${summary[1]} test(s) failed` });
|
|
67
|
+
}
|
|
68
|
+
return entries;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Attribute a verification failure to a structured kind. Detection order:
|
|
72
|
+
* TypeScript → test failure → lint → build → provider → environment → unknown.
|
|
73
|
+
*/
|
|
74
|
+
function attributeFailure(output, command = '') {
|
|
75
|
+
const commandLower = command.toLowerCase();
|
|
76
|
+
const tsEntries = parseTypeScriptDiagnostics(output);
|
|
77
|
+
if (tsEntries.length > 0) {
|
|
78
|
+
return evidence('typescript', tsEntries.slice(0, 50), `${tsEntries.length} TypeScript diagnostic(s); first: ${tsEntries[0].file}:${tsEntries[0].line} ${tsEntries[0].code}`);
|
|
79
|
+
}
|
|
80
|
+
if (/✕|●.*›|Tests:\s+\d+\s+failed|FAIL\s+\S+test/i.test(output)) {
|
|
81
|
+
const jestEntries = parseJestFailures(output);
|
|
82
|
+
const kind = /integrat|e2e|playwright/i.test(output) || /e2e/i.test(commandLower)
|
|
83
|
+
? 'integration-test'
|
|
84
|
+
: 'unit-test';
|
|
85
|
+
return evidence(kind, jestEntries.slice(0, 50), `${jestEntries.length} failed test(s)`);
|
|
86
|
+
}
|
|
87
|
+
const lintEntries = parseEsLintOutput(output);
|
|
88
|
+
if (lintEntries.length > 0 || /eslint/i.test(commandLower)) {
|
|
89
|
+
return evidence('lint', lintEntries.slice(0, 50), `${lintEntries.length} lint problem(s)`);
|
|
90
|
+
}
|
|
91
|
+
if (/build failed|rollup|webpack|vite build|tsc && |error during build/i.test(output)) {
|
|
92
|
+
return evidence('build', [], 'build tooling reported a failure');
|
|
93
|
+
}
|
|
94
|
+
if (/provider|api key|rate limit|429|status 5|econnrefused|enotfound|fetch failed|timed out/i.test(output) ||
|
|
95
|
+
/provider/i.test(commandLower)) {
|
|
96
|
+
return evidence('provider', [], 'provider or network failure during verification');
|
|
97
|
+
}
|
|
98
|
+
if (/eacces|eperm|enoent|enoentries|no such file|permission denied|disk space|enospc/i.test(output)) {
|
|
99
|
+
return evidence('environment', [], 'environment or filesystem failure during verification');
|
|
100
|
+
}
|
|
101
|
+
return evidence('unknown', [], 'verification failed without a recognizable pattern');
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=failure-attribution.js.map
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { StructuredFailureEvidenceV1 } from './failure-attribution';
|
|
2
|
+
export interface VerificationRunV1 {
|
|
3
|
+
readonly ok: boolean;
|
|
4
|
+
readonly output: string;
|
|
5
|
+
readonly command: string;
|
|
6
|
+
readonly durationMs: number;
|
|
7
|
+
}
|
|
8
|
+
export interface RepairGuardSnapshotV1 {
|
|
9
|
+
/** Workspace-relative paths of test files present BEFORE the repair. */
|
|
10
|
+
readonly testFiles: readonly string[];
|
|
11
|
+
/** Path → count of assertion tokens (`expect(`) per test file. */
|
|
12
|
+
readonly assertionTokens: Readonly<Record<string, number>>;
|
|
13
|
+
/** Path → digest of acceptance/verification criteria files. */
|
|
14
|
+
readonly acceptanceDigests: Readonly<Record<string, string>>;
|
|
15
|
+
}
|
|
16
|
+
export declare class ForbiddenRepairError extends Error {
|
|
17
|
+
readonly guard: 'test_deleted' | 'assertion_weakened' | 'acceptance_modified';
|
|
18
|
+
constructor(message: string, guard: 'test_deleted' | 'assertion_weakened' | 'acceptance_modified');
|
|
19
|
+
}
|
|
20
|
+
export declare function snapshotTestFiles(workspace: string, testFilePaths: readonly string[]): RepairGuardSnapshotV1;
|
|
21
|
+
/**
|
|
22
|
+
* C4 — forbidden repair detection. Throws ForbiddenRepairError when the
|
|
23
|
+
* after-snapshot shows a test deleted, assertions materially weakened
|
|
24
|
+
* (>20% fewer `expect(` calls in a file), or an acceptance file modified.
|
|
25
|
+
*/
|
|
26
|
+
export declare function assertNoForbiddenRepair(before: RepairGuardSnapshotV1, after: RepairGuardSnapshotV1): void;
|
|
27
|
+
export type VerifiedRepairTerminal = 'completed' | 'failed' | 'blocked' | 'cancelled' | 'unverified';
|
|
28
|
+
export interface RepairRoundV1 {
|
|
29
|
+
readonly attempt: number;
|
|
30
|
+
readonly verification: VerificationRunV1;
|
|
31
|
+
readonly attribution: StructuredFailureEvidenceV1;
|
|
32
|
+
readonly repairRan: boolean;
|
|
33
|
+
readonly repairError?: string;
|
|
34
|
+
}
|
|
35
|
+
export interface VerifiedRepairOutcomeV1 {
|
|
36
|
+
readonly terminal: VerifiedRepairTerminal;
|
|
37
|
+
readonly attempts: number;
|
|
38
|
+
readonly rounds: readonly RepairRoundV1[];
|
|
39
|
+
readonly noProgress: boolean;
|
|
40
|
+
readonly failureReason?: string;
|
|
41
|
+
readonly lastAttribution?: StructuredFailureEvidenceV1;
|
|
42
|
+
}
|
|
43
|
+
export interface VerifiedRepairOptionsV1 {
|
|
44
|
+
/** Runs the (change-aware) verification command. */
|
|
45
|
+
readonly verify: () => Promise<VerificationRunV1>;
|
|
46
|
+
/** Performs the bounded repair for the given attribution. */
|
|
47
|
+
readonly repair: (attribution: StructuredFailureEvidenceV1, attempt: number) => Promise<void>;
|
|
48
|
+
/** Compares guard snapshots after each repair; throws on forbidden repairs. */
|
|
49
|
+
readonly guard?: (previous: RepairGuardSnapshotV1 | undefined) => Promise<RepairGuardSnapshotV1> | RepairGuardSnapshotV1;
|
|
50
|
+
readonly maxAttempts?: number;
|
|
51
|
+
readonly noProgressThreshold?: number;
|
|
52
|
+
readonly signal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The bounded verified-repair loop (C3). The first verification is the
|
|
56
|
+
* change-aware check; each failed round attributes the failure structurally
|
|
57
|
+
* (C2), runs one bounded repair, re-verifies, and evaluates progress.
|
|
58
|
+
*/
|
|
59
|
+
export declare function runVerifiedRepairLoop(options: VerifiedRepairOptionsV1): Promise<VerifiedRepairOutcomeV1>;
|
|
60
|
+
/**
|
|
61
|
+
* C5 — five-state completion mapping. A turn may only be `completed` with
|
|
62
|
+
* trusted verification evidence; no evidence maps to `unverified`.
|
|
63
|
+
*/
|
|
64
|
+
export declare function completionStateFromVerification(state: {
|
|
65
|
+
readonly cancelled: boolean;
|
|
66
|
+
readonly blockedReason?: string;
|
|
67
|
+
readonly verificationRan: boolean;
|
|
68
|
+
readonly verificationOk: boolean;
|
|
69
|
+
readonly agentClaimedSuccess: boolean;
|
|
70
|
+
}): VerifiedRepairTerminal;
|
|
71
|
+
//# sourceMappingURL=verified-repair.d.ts.map
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ForbiddenRepairError = void 0;
|
|
4
|
+
exports.snapshotTestFiles = snapshotTestFiles;
|
|
5
|
+
exports.assertNoForbiddenRepair = assertNoForbiddenRepair;
|
|
6
|
+
exports.runVerifiedRepairLoop = runVerifiedRepairLoop;
|
|
7
|
+
exports.completionStateFromVerification = completionStateFromVerification;
|
|
8
|
+
/**
|
|
9
|
+
* v0.3.23 T23-C3/C4/C5 — bounded verified-repair loop and the five-state
|
|
10
|
+
* completion audit.
|
|
11
|
+
*
|
|
12
|
+
* The loop alternates `verify` → structured attribution → caller-supplied
|
|
13
|
+
* `repair` with hard bounds: `maxAttempts` (default 3, from the archived
|
|
14
|
+
* AutoFixConfig semantics), an objective no-progress detector (identical
|
|
15
|
+
* attribution digests on consecutive rounds), a wall-clock/cancel signal, and
|
|
16
|
+
* an anti-cheat guard that maps forbidden repair behaviors (deleting tests,
|
|
17
|
+
* weakening assertions, touching acceptance criteria) to a hard `failed`.
|
|
18
|
+
*
|
|
19
|
+
* Completion states: completed / failed / blocked / cancelled / unverified.
|
|
20
|
+
* `unverified` is used when no trusted verification ran and nothing else
|
|
21
|
+
* explains the stop — a task can never be marked completed without
|
|
22
|
+
* verification evidence.
|
|
23
|
+
*/
|
|
24
|
+
const crypto_1 = require("crypto");
|
|
25
|
+
const failure_attribution_1 = require("./failure-attribution");
|
|
26
|
+
class ForbiddenRepairError extends Error {
|
|
27
|
+
constructor(message, guard) {
|
|
28
|
+
super(message);
|
|
29
|
+
this.guard = guard;
|
|
30
|
+
this.name = 'ForbiddenRepairError';
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
exports.ForbiddenRepairError = ForbiddenRepairError;
|
|
34
|
+
const hash = (value) => (0, crypto_1.createHash)('sha256').update(value).digest('hex').slice(0, 32);
|
|
35
|
+
function snapshotTestFiles(workspace, testFilePaths) {
|
|
36
|
+
const { existsSync, readFileSync } = require('fs');
|
|
37
|
+
const assertionTokens = {};
|
|
38
|
+
const acceptanceDigests = {};
|
|
39
|
+
for (const relative of testFilePaths) {
|
|
40
|
+
const target = joinWorkspace(workspace, relative);
|
|
41
|
+
if (!existsSync(target))
|
|
42
|
+
continue;
|
|
43
|
+
const content = readFileSync(target, 'utf8');
|
|
44
|
+
assertionTokens[relative] = (content.match(/expect\s*\(/g) ?? []).length;
|
|
45
|
+
acceptanceDigests[relative] = hash(content);
|
|
46
|
+
}
|
|
47
|
+
return { testFiles: testFilePaths, assertionTokens, acceptanceDigests };
|
|
48
|
+
}
|
|
49
|
+
function joinWorkspace(workspace, relative) {
|
|
50
|
+
return `${workspace.replace(/[\\/]+$/, '')}/${relative}`;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* C4 — forbidden repair detection. Throws ForbiddenRepairError when the
|
|
54
|
+
* after-snapshot shows a test deleted, assertions materially weakened
|
|
55
|
+
* (>20% fewer `expect(` calls in a file), or an acceptance file modified.
|
|
56
|
+
*/
|
|
57
|
+
function assertNoForbiddenRepair(before, after) {
|
|
58
|
+
for (const testFile of before.testFiles) {
|
|
59
|
+
const beforeTokens = before.assertionTokens[testFile] ?? 0;
|
|
60
|
+
const afterExists = after.testFiles.includes(testFile) && after.assertionTokens[testFile] !== undefined;
|
|
61
|
+
if (!afterExists) {
|
|
62
|
+
throw new ForbiddenRepairError(`a test file was deleted during repair: ${testFile}`, 'test_deleted');
|
|
63
|
+
}
|
|
64
|
+
const afterTokens = after.assertionTokens[testFile] ?? 0;
|
|
65
|
+
if (beforeTokens > 0 && afterTokens < Math.ceil(beforeTokens * 0.8)) {
|
|
66
|
+
throw new ForbiddenRepairError(`assertions were weakened during repair: ${testFile} (${beforeTokens} → ${afterTokens} expect calls)`, 'assertion_weakened');
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [file, beforeDigest] of Object.entries(before.acceptanceDigests)) {
|
|
70
|
+
const afterDigest = after.acceptanceDigests[file];
|
|
71
|
+
if (afterDigest !== undefined && afterDigest !== beforeDigest) {
|
|
72
|
+
throw new ForbiddenRepairError(`acceptance criteria were modified during repair: ${file}`, 'acceptance_modified');
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* The bounded verified-repair loop (C3). The first verification is the
|
|
78
|
+
* change-aware check; each failed round attributes the failure structurally
|
|
79
|
+
* (C2), runs one bounded repair, re-verifies, and evaluates progress.
|
|
80
|
+
*/
|
|
81
|
+
async function runVerifiedRepairLoop(options) {
|
|
82
|
+
const maxAttempts = options.maxAttempts ?? 3;
|
|
83
|
+
const noProgressThreshold = options.noProgressThreshold ?? 2;
|
|
84
|
+
const rounds = [];
|
|
85
|
+
const seenDigests = [];
|
|
86
|
+
let previousGuard;
|
|
87
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
88
|
+
if (options.signal?.aborted) {
|
|
89
|
+
return {
|
|
90
|
+
terminal: 'cancelled',
|
|
91
|
+
attempts: attempt - 1,
|
|
92
|
+
rounds,
|
|
93
|
+
noProgress: false,
|
|
94
|
+
failureReason: 'cancelled before verification',
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
let verification;
|
|
98
|
+
try {
|
|
99
|
+
verification = await options.verify();
|
|
100
|
+
}
|
|
101
|
+
catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
terminal: 'blocked',
|
|
104
|
+
attempts: attempt - 1,
|
|
105
|
+
rounds,
|
|
106
|
+
noProgress: false,
|
|
107
|
+
failureReason: `verification infrastructure failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const attribution = (0, failure_attribution_1.attributeFailure)(verification.output, verification.command);
|
|
111
|
+
rounds.push({
|
|
112
|
+
attempt,
|
|
113
|
+
verification,
|
|
114
|
+
attribution,
|
|
115
|
+
repairRan: false,
|
|
116
|
+
});
|
|
117
|
+
if (verification.ok) {
|
|
118
|
+
return {
|
|
119
|
+
terminal: 'completed',
|
|
120
|
+
attempts: attempt,
|
|
121
|
+
rounds,
|
|
122
|
+
noProgress: false,
|
|
123
|
+
lastAttribution: attribution,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
// Guard FIRST (before no-progress): a forbidden repair from the previous
|
|
127
|
+
// round must surface as failed, never as a soft no-progress block.
|
|
128
|
+
try {
|
|
129
|
+
previousGuard = options.guard ? await options.guard(previousGuard) : undefined;
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error instanceof ForbiddenRepairError) {
|
|
133
|
+
return {
|
|
134
|
+
terminal: 'failed',
|
|
135
|
+
attempts: attempt,
|
|
136
|
+
rounds,
|
|
137
|
+
noProgress: false,
|
|
138
|
+
failureReason: `forbidden repair: ${error.message}`,
|
|
139
|
+
lastAttribution: attribution,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
terminal: 'blocked',
|
|
144
|
+
attempts: attempt,
|
|
145
|
+
rounds,
|
|
146
|
+
noProgress: false,
|
|
147
|
+
failureReason: `repair guard failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
148
|
+
lastAttribution: attribution,
|
|
149
|
+
};
|
|
150
|
+
}
|
|
151
|
+
// No-progress detection: identical failure digests on consecutive rounds.
|
|
152
|
+
if (seenDigests.length >= noProgressThreshold) {
|
|
153
|
+
seenDigests.shift();
|
|
154
|
+
}
|
|
155
|
+
seenDigests.push(attribution.digest);
|
|
156
|
+
const stuck = seenDigests.length >= noProgressThreshold &&
|
|
157
|
+
seenDigests.every(digest => digest === seenDigests[0]);
|
|
158
|
+
if (stuck || attempt === maxAttempts) {
|
|
159
|
+
return {
|
|
160
|
+
terminal: stuck ? 'blocked' : 'failed',
|
|
161
|
+
attempts: attempt,
|
|
162
|
+
rounds,
|
|
163
|
+
noProgress: stuck,
|
|
164
|
+
failureReason: stuck
|
|
165
|
+
? `no progress: the same failure persisted across ${seenDigests.length} rounds`
|
|
166
|
+
: `verification still failing after ${maxAttempts} attempt(s)`,
|
|
167
|
+
lastAttribution: attribution,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
try {
|
|
171
|
+
await options.repair(attribution, attempt);
|
|
172
|
+
rounds[rounds.length - 1] = { ...rounds[rounds.length - 1], repairRan: true };
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
rounds[rounds.length - 1] = {
|
|
176
|
+
...rounds[rounds.length - 1],
|
|
177
|
+
repairRan: true,
|
|
178
|
+
repairError: error instanceof Error ? error.message : String(error),
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
if (options.signal?.aborted) {
|
|
182
|
+
return {
|
|
183
|
+
terminal: 'cancelled',
|
|
184
|
+
attempts: attempt,
|
|
185
|
+
rounds,
|
|
186
|
+
noProgress: false,
|
|
187
|
+
failureReason: 'cancelled during repair',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return {
|
|
192
|
+
terminal: 'failed',
|
|
193
|
+
attempts: maxAttempts,
|
|
194
|
+
rounds,
|
|
195
|
+
noProgress: false,
|
|
196
|
+
failureReason: `verification still failing after ${maxAttempts} attempt(s)`,
|
|
197
|
+
};
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* C5 — five-state completion mapping. A turn may only be `completed` with
|
|
201
|
+
* trusted verification evidence; no evidence maps to `unverified`.
|
|
202
|
+
*/
|
|
203
|
+
function completionStateFromVerification(state) {
|
|
204
|
+
if (state.cancelled)
|
|
205
|
+
return 'cancelled';
|
|
206
|
+
if (state.blockedReason)
|
|
207
|
+
return 'blocked';
|
|
208
|
+
if (!state.verificationRan)
|
|
209
|
+
return 'unverified';
|
|
210
|
+
return state.verificationOk ? 'completed' : 'failed';
|
|
211
|
+
}
|
|
212
|
+
//# sourceMappingURL=verified-repair.js.map
|
package/dist/product/paths.d.ts
CHANGED
|
@@ -29,8 +29,25 @@ export declare function getProjectsDir(): string;
|
|
|
29
29
|
export declare function getGlobalEnvPath(): string;
|
|
30
30
|
/** Encode an absolute project path into a stable directory key. */
|
|
31
31
|
export declare function encodeProjectPath(projectPath: string): string;
|
|
32
|
-
/**
|
|
32
|
+
/** Interactive Hosts must not persist an unverified Git project identity. */
|
|
33
|
+
export declare class ProjectStorageIdentityUnavailableError extends Error {
|
|
34
|
+
readonly code = "project_storage_identity_unavailable";
|
|
35
|
+
constructor(message?: string);
|
|
36
|
+
}
|
|
37
|
+
/** An operation-local verified identity; never a permanent external-env cache. */
|
|
38
|
+
export interface ProjectStorageIdentity {
|
|
39
|
+
readonly requestedPath: string;
|
|
40
|
+
readonly canonicalPath: string;
|
|
41
|
+
readonly projectKey: string;
|
|
42
|
+
readonly fingerprint: string;
|
|
43
|
+
}
|
|
44
|
+
export declare function resolveProjectStorageIdentityAsync(projectPath: string): Promise<ProjectStorageIdentity>;
|
|
45
|
+
/** Cheap lock-admission validation: filesystem/environment only, no Git spawn. */
|
|
46
|
+
export declare function assertProjectStorageIdentity(identity: ProjectStorageIdentity): void;
|
|
47
|
+
/** Resolve a project path for storage identity with bounded, invalidatable reuse. */
|
|
33
48
|
export declare function resolveProjectStoragePath(projectPath: string): string;
|
|
49
|
+
/** Pre-resolve interactive Host identities without blocking its event loop. */
|
|
50
|
+
export declare function resolveProjectStoragePathAsync(projectPath: string): Promise<string>;
|
|
34
51
|
export declare function getCanonicalProjectKey(projectPath: string): string;
|
|
35
52
|
export declare function getProjectDir(projectPath: string): string;
|
|
36
53
|
export declare function getProjectMetadataPath(projectPath: string): string;
|
|
@@ -59,6 +76,8 @@ export declare function getMemoryPath(type: MemoryType, cwd?: string): string;
|
|
|
59
76
|
export declare function getExistingMemoryPaths(cwd?: string): string[];
|
|
60
77
|
export declare function ensureConfigDir(): void;
|
|
61
78
|
export declare function ensureProjectDir(projectPath: string): void;
|
|
79
|
+
/** Use the already-verified identity; do not re-probe Git under explicit env. */
|
|
80
|
+
export declare function ensureResolvedProjectDir(identity: ProjectStorageIdentity): string;
|
|
62
81
|
/** Ordered project config candidate files (relative to project root). */
|
|
63
82
|
export declare const PROJECT_CONFIG_CANDIDATES: readonly [".orion-code.yaml", ".orion-code.yml", ".orion-code.json", "orion-code.yaml", "orion-code.yml", "orion.json"];
|
|
64
83
|
/** Project instructions file (relative to project root). */
|