@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.
Files changed (51) hide show
  1. package/CHANGELOG.md +81 -3
  2. package/README.md +9 -5
  3. package/README.zh-CN.md +7 -4
  4. package/dist/harness/failure-attribution.d.ts +23 -0
  5. package/dist/harness/failure-attribution.js +103 -0
  6. package/dist/harness/verified-repair.d.ts +71 -0
  7. package/dist/harness/verified-repair.js +212 -0
  8. package/dist/product/paths.d.ts +20 -1
  9. package/dist/product/paths.js +293 -10
  10. package/dist/runtime/model-coordinator.d.ts +1 -0
  11. package/dist/runtime/model-coordinator.js +9 -2
  12. package/dist/runtime/release-receipts.d.ts +3 -3
  13. package/dist/runtime/release-receipts.js +29 -0
  14. package/dist/runtime/session-composer-control.js +5 -1
  15. package/dist/services/config-dir.d.ts +3 -2
  16. package/dist/services/config-dir.js +6 -29
  17. package/dist/services/repo-intelligence/index.d.ts +11 -0
  18. package/dist/services/repo-intelligence/index.js +20 -0
  19. package/dist/services/repo-intelligence/recall.d.ts +44 -0
  20. package/dist/services/repo-intelligence/recall.js +115 -0
  21. package/dist/services/repo-intelligence/symbol-index.d.ts +52 -0
  22. package/dist/services/repo-intelligence/symbol-index.js +258 -0
  23. package/dist/services/session-storage.d.ts +18 -2
  24. package/dist/services/session-storage.js +196 -60
  25. package/dist/services/workspace-registry.d.ts +9 -0
  26. package/dist/services/workspace-registry.js +13 -2
  27. package/dist/web/git-history-service.js +3 -0
  28. package/dist/web/git-read-model-service.d.ts +9 -0
  29. package/dist/web/git-read-model-service.js +35 -0
  30. package/dist/web/git-version-service.d.ts +8 -0
  31. package/dist/web/git-version-service.js +50 -26
  32. package/dist/web/protocol.d.ts +13 -0
  33. package/dist/web/workbench-controller.js +47 -2
  34. package/dist/web-client/assets/{FilesPanel-CKeXPofZ.js → FilesPanel-CdoCa8u0.js} +1 -1
  35. package/dist/web-client/assets/{GitPanel-DollPxv0.js → GitPanel-CRr_iIDz.js} +1 -1
  36. package/dist/web-client/assets/{ReviewPanel-CNADacIn.js → ReviewPanel-C1Cu2IRV.js} +1 -1
  37. package/dist/web-client/assets/{TerminalPanel-Cyns4uJt.js → TerminalPanel-BniCjatZ.js} +1 -1
  38. package/dist/web-client/assets/index-kQqA3li6.js +20 -0
  39. package/dist/web-client/assets/{useAutoNotice-BLADeFFj.js → useAutoNotice-DPXFWIwK.js} +1 -1
  40. package/dist/web-client/index.html +1 -1
  41. package/npm-shrinkwrap.json +2 -2
  42. package/package.json +1 -1
  43. package/dist/services/auto-fix/autoFixConfig.d.ts +0 -30
  44. package/dist/services/auto-fix/autoFixConfig.js +0 -78
  45. package/dist/services/auto-fix/autoFixHook.d.ts +0 -27
  46. package/dist/services/auto-fix/autoFixHook.js +0 -99
  47. package/dist/services/auto-fix/autoFixRunner.d.ts +0 -60
  48. package/dist/services/auto-fix/autoFixRunner.js +0 -207
  49. package/dist/services/auto-fix/index.d.ts +0 -7
  50. package/dist/services/auto-fix/index.js +0 -17
  51. 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.22] — UNRELEASED
25
+ ## [0.3.25] — UNRELEASED
26
26
 
27
- > **Status: candidate.** Open. Agent Reliability & Efficiency — hardens ToolGateway
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`. Not merged, tagged or published.
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.22 candidate — open. Hardens agent reliability (ToolGateway in-flight request
6
- > identity) and builds a real coding-task evaluation harness. The previous release 0.3.21
7
- > is on npm (registry `latest`) and tagged `v0.3.21` (`0d22c9a`, #264).
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
- After the immutable `0.3.5` npm receipt exists:
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.22
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.22 候选版本:进行中。强化 Agent 可靠性(ToolGateway 执行中请求身份)并建设真实编码
6
- > 任务评测体系。上一版 0.3.21 已发布 npm(registry `latest`)并打 tag `v0.3.21`(`0d22c9a`,#264)。
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
- 当 npm 已存在不可变的 `0.3.4` 发布凭据后:
103
+ v0.3.25 在 npm 可用后,可安装固定版本:
101
104
 
102
105
  ```bash
103
- npm install -g @orion-agents/orion-code@0.3.22
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
@@ -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
- /** Resolve a project path for storage identity. */
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). */