@orion-agents/orion-code 0.3.21 → 0.3.24

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 (43) hide show
  1. package/CHANGELOG.md +67 -6
  2. package/README.md +6 -6
  3. package/README.zh-CN.md +5 -5
  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/release-receipts.d.ts +3 -3
  11. package/dist/runtime/release-receipts.js +29 -0
  12. package/dist/runtime/tool-gateway.d.ts +6 -0
  13. package/dist/runtime/tool-gateway.js +37 -7
  14. package/dist/services/config-dir.d.ts +3 -2
  15. package/dist/services/config-dir.js +6 -29
  16. package/dist/services/repo-intelligence/index.d.ts +11 -0
  17. package/dist/services/repo-intelligence/index.js +20 -0
  18. package/dist/services/repo-intelligence/recall.d.ts +44 -0
  19. package/dist/services/repo-intelligence/recall.js +115 -0
  20. package/dist/services/repo-intelligence/symbol-index.d.ts +52 -0
  21. package/dist/services/repo-intelligence/symbol-index.js +258 -0
  22. package/dist/services/session-storage.d.ts +18 -2
  23. package/dist/services/session-storage.js +196 -60
  24. package/dist/web/protocol.d.ts +8 -0
  25. package/dist/web/workbench-controller.js +46 -2
  26. package/dist/web-client/assets/{FilesPanel-CKeXPofZ.js → FilesPanel-BjEDjE7l.js} +1 -1
  27. package/dist/web-client/assets/{GitPanel-DollPxv0.js → GitPanel-DvgAZDRA.js} +1 -1
  28. package/dist/web-client/assets/{ReviewPanel-CNADacIn.js → ReviewPanel-BMPaYKDY.js} +1 -1
  29. package/dist/web-client/assets/{TerminalPanel-Cyns4uJt.js → TerminalPanel-Da1ZEhGF.js} +1 -1
  30. package/dist/web-client/assets/index-DZOlUgt6.js +20 -0
  31. package/dist/web-client/assets/{useAutoNotice-BLADeFFj.js → useAutoNotice-Ylj5bfSH.js} +1 -1
  32. package/dist/web-client/index.html +1 -1
  33. package/npm-shrinkwrap.json +2 -2
  34. package/package.json +1 -1
  35. package/dist/services/auto-fix/autoFixConfig.d.ts +0 -30
  36. package/dist/services/auto-fix/autoFixConfig.js +0 -78
  37. package/dist/services/auto-fix/autoFixHook.d.ts +0 -27
  38. package/dist/services/auto-fix/autoFixHook.js +0 -99
  39. package/dist/services/auto-fix/autoFixRunner.d.ts +0 -60
  40. package/dist/services/auto-fix/autoFixRunner.js +0 -207
  41. package/dist/services/auto-fix/index.d.ts +0 -7
  42. package/dist/services/auto-fix/index.js +0 -17
  43. package/dist/web-client/assets/index-BhGhORAX.js +0 -20
package/CHANGELOG.md CHANGED
@@ -22,13 +22,74 @@ which is **not** a pass.
22
22
 
23
23
  ## [Unreleased]
24
24
 
25
- ## [0.3.21] — UNRELEASED
25
+ ## [0.3.24]
26
26
 
27
- > **Status: candidate.** Open. Adds user-authorized local project-root discovery to
28
- > `LOCAL WORKSPACE` (bounded scan, explicit inspect → confirm → activate reuse, no
29
- > Home/network-volume scanning, no silent registration) and corrects the v0.3.20
30
- > release-facts documentation (`docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`).
31
- > Not merged, tagged or published.
27
+ > **Status: candidate.** Implementation on `codex/v0.3.24`, based on `main@6ed47fd`.
28
+ > Plan: `docs/plan/v0.3.24-plan.md`. Registry and release status must be
29
+ > verified from their own immutable receipts, not inferred from this changelog.
30
+
31
+ ### Fixed
32
+
33
+ - Session command and Composer requests track only their originating Session;
34
+ sending no longer blocks another Session's Composer or session creation.
35
+ - Same-Context snapshots across a live event stream are reused when unchanged;
36
+ warm selection does not re-fetch or disable input. Background changes and
37
+ disconnections still require synchronization before writes.
38
+ - Foreground display caches retain streamed messages, tool cards and loaded
39
+ history instead of restoring an older HTTP tail when switching back.
40
+ - Creation is single-flight and respects a later foreground selection. Late
41
+ send responses do not clear, focus or replace another Session's draft.
42
+ - Web creation bounds asynchronous Git branch probes; project storage identity
43
+ resolution reuses an invalidatable bounded cache instead of repeatedly
44
+ spawning synchronous Git on every storage helper call.
45
+ - Cross-Workspace navigation remains usable while another Session is running;
46
+ the most recent navigation intent wins when Context activation is in flight.
47
+ - Per-Session scroll position and loaded history survive navigation, while
48
+ foreground Snapshot and SSE reconciliation no longer skips background events.
49
+ - Web Session creation waits for file locks without freezing the Host event
50
+ loop, and cold Catalog scans yield to unrelated HTTP and SSE traffic.
51
+
52
+ ## [0.3.23] — UNRELEASED
53
+
54
+ > **Status: candidate.** Open. Coding Intelligence & Verified Execution — wires the live
55
+ > coding-task evaluation to the production runtime (isolated workspaces, machine-verified
56
+ > success, five-outcome classification), adds repo-intelligence symbol indexing and
57
+ > recall, and consolidates the bounded verification/repair loop into the HarnessKernel
58
+ > completion audit. Plan: `docs/plan/v0.3.23-plan.md`. Not merged, tagged or published.
59
+
60
+ ### Added (planned)
61
+
62
+ - Live Coding Eval: `LiveTaskExecutor` + `compare-receipts` A/B reporting; five-outcome
63
+ classification with orthogonal falseComplete; `command_exit_zero` / `invariant` checks.
64
+ - Repo Intelligence: lightweight TS/JS symbol index with incremental maintenance and
65
+ sourced recall (Recall@K evaluated).
66
+ - Verified Repair: structured failure attribution, bounded repair loop (feature-flagged),
67
+ five-state completion audit; the orphaned auto-fix / verification-profile modules merge
68
+ into the wired stack and are removed.
69
+
70
+ ## [0.3.22] — 2026-10-09
71
+
72
+ > **Status: published.** Released as `@orion-agents/orion-code@0.3.22` from
73
+ > `refs/tags/v0.3.22`, and merged to `main` as `9f09e0a` (#265). Hardens ToolGateway
74
+ > in-flight request identity (T22-01), adds the durability fault-injection matrix (T22-02),
75
+ > the four-state release verification gate (T22-03), and a real coding-task evaluation
76
+ > harness (T22-04). Plan: `docs/plan/v0.3.22-plan.md`. GitHub Release:
77
+ > https://github.com/orion-agents/orion-code/releases/tag/v0.3.22。
78
+
79
+ ### Fixed (product)
80
+
81
+ - ToolGateway in-flight dedupe now compares the full request digest: the same invocationId
82
+ with different arguments, tool, snapshot, or parent conflicts
83
+ (`ORION_TOOL_INVOCATION_CONFLICT`) instead of reusing the running execution's promise.
84
+
85
+ ## [0.3.21] — 2026-10-09
86
+
87
+ > **Status: published.** Released as `@orion-agents/orion-code@0.3.21` from
88
+ > `refs/tags/v0.3.21`, and merged to `main` as `0d22c9a` (#264). Adds user-authorized
89
+ > local project-root discovery to `LOCAL WORKSPACE` (bounded scan, explicit
90
+ > inspect → confirm → activate reuse, no Home/network-volume scanning, no silent
91
+ > registration) and corrects the v0.3.20 release-facts documentation
92
+ > (`docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`).
32
93
 
33
94
  ### Added (planned)
34
95
 
package/README.md CHANGED
@@ -2,12 +2,12 @@
2
2
 
3
3
  Local-first, goal-driven coding agent for the terminal and browser.
4
4
 
5
- > v0.3.21 candidate — open. Adds user-authorized local project-root discovery to
6
- > `LOCAL WORKSPACE` (bounded scan, explicit inspect → confirm → activate reuse). The previous
7
- > release 0.3.20 is on npm (registry `latest`) and tagged `v0.3.20` (`e658932`, #261) —
8
- > release facts: `docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`.
5
+ > v0.3.24 development focus — Web session responsiveness: Session-scoped request admission,
6
+ > cached foreground switching, retained live views, and bounded Git probes during creation.
7
+ > Built on the merged v0.3.23 source (`6ed47fd`, #266).
9
8
 
10
9
  [中文说明](README.zh-CN.md) ·
10
+ [v0.3.24 plan](docs/plan/v0.3.24-plan.md) ·
11
11
  [v0.3.7 plan](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
12
12
  [v0.3.6 plan](docs/plan/v0.3.6-plan.md) ·
13
13
  [v0.3.5 plan](docs/plan/v0.3.5-plan.md) ·
@@ -167,10 +167,10 @@ user extension boundaries.
167
167
  Node.js 22.12+, 24, and 26 are supported. Use Node 24 LTS for production or Node 26 Current for
168
168
  current development environments. Node 20 is upstream EOL and is no longer a v0.3 runtime.
169
169
 
170
- After the immutable `0.3.5` npm receipt exists:
170
+ To install the pinned v0.3.24 package when it is available on npm:
171
171
 
172
172
  ```bash
173
- npm install -g @orion-agents/orion-code@0.3.21
173
+ npm install -g @orion-agents/orion-code@0.3.24
174
174
  orion --version
175
175
  orion doctor
176
176
  ```
package/README.zh-CN.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  面向终端与浏览器、本地优先的目标驱动 Coding Agent。
4
4
 
5
- > v0.3.21 候选版本:进行中。为 `LOCAL WORKSPACE` 增加用户显式授权的项目根目录发现(有界
6
- > 扫描、复用 inspect → confirm → activate)。上一版 0.3.20 已发布 npm(registry `latest`)
7
- > 并打 tag `v0.3.20`(`e658932`,#261)——发布事实:`docs/plan/evidence/v0.3.20-e2e/release-receipt-v0.3.20.md`。
5
+ > v0.3.24 开发重点:Web 会话响应性。发送按会话等待、有效缓存即时切换、保留实时显示内容,
6
+ > 新建使用有界 Git 探测。基于已合并的 v0.3.23 源码(`6ed47fd`,#266)。
8
7
 
9
8
  [English](README.md) ·
9
+ [v0.3.24 方案](docs/plan/v0.3.24-plan.md) ·
10
10
  [v0.3.7 方案](docs/plan/v0.3.7-left-rail-improvement-plan.md) ·
11
11
  [v0.3.6 方案](docs/plan/v0.3.6-plan.md) ·
12
12
  [v0.3.4 稳定化方案](docs/plan/v0.3.4-stabilization-plan.md) ·
@@ -98,10 +98,10 @@ MCP 仍是用户可见的扩展边界。
98
98
  支持 Node.js 22.12+、24 和 26。生产环境建议使用 Node 24 LTS,当前开发环境也支持 Node 26
99
99
  Current。Node 20 已结束上游维护,不再属于 v0.3 Runtime 合同。
100
100
 
101
- 当 npm 已存在不可变的 `0.3.4` 发布凭据后:
101
+ v0.3.24 在 npm 可用后,可安装固定版本:
102
102
 
103
103
  ```bash
104
- npm install -g @orion-agents/orion-code@0.3.21
104
+ npm install -g @orion-agents/orion-code@0.3.24
105
105
  orion --version
106
106
  orion doctor
107
107
  ```
@@ -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). */