harnery 0.45.1 → 0.46.0
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/dist/commander.d.ts +13 -0
- package/dist/commander.d.ts.map +1 -1
- package/dist/commands/admission.d.ts.map +1 -1
- package/dist/commands/admission.js +214 -27
- package/dist/commands/agents.d.ts +4 -0
- package/dist/commands/agents.d.ts.map +1 -1
- package/dist/commands/agents.js +102 -25
- package/dist/commands/browse.d.ts +3 -0
- package/dist/commands/browse.d.ts.map +1 -1
- package/dist/commands/browse.js +172 -8
- package/dist/commands/fetch.js +1 -0
- package/dist/commands/ledger-v3.d.ts.map +1 -1
- package/dist/commands/ledger-v3.js +8 -2
- package/dist/commands/presence.d.ts.map +1 -1
- package/dist/commands/presence.js +40 -0
- package/dist/commands/qa-run.d.ts.map +1 -1
- package/dist/commands/qa-run.js +4 -3
- package/dist/commands/review-pack.d.ts.map +1 -1
- package/dist/commands/review-pack.js +33 -6
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +6 -45
- package/dist/core/agents/coord-client.d.ts.map +1 -1
- package/dist/core/agents/coord-client.js +0 -18
- package/dist/core/agents/index.d.ts +1 -0
- package/dist/core/agents/index.d.ts.map +1 -1
- package/dist/core/agents/index.js +1 -0
- package/dist/core/agents/live-authority-v3.d.ts.map +1 -1
- package/dist/core/agents/live-authority-v3.js +11 -4
- package/dist/core/agents/render/session-context.d.ts +0 -6
- package/dist/core/agents/render/session-context.d.ts.map +1 -1
- package/dist/core/agents/render/session-context.js +1 -6
- package/dist/core/agents/session-events.d.ts.map +1 -1
- package/dist/core/agents/session-events.js +12 -4
- package/dist/core/agents/state/heartbeat-reader.d.ts +3 -0
- package/dist/core/agents/state/heartbeat-reader.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.d.ts +3 -1
- package/dist/core/agents/state/live-coordination-view.d.ts.map +1 -1
- package/dist/core/agents/state/live-coordination-view.js +6 -2
- package/dist/core/agents/state/parent-reference-contract.d.ts +16 -0
- package/dist/core/agents/state/parent-reference-contract.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference-contract.js +1 -0
- package/dist/core/agents/state/parent-reference.d.ts +18 -0
- package/dist/core/agents/state/parent-reference.d.ts.map +1 -0
- package/dist/core/agents/state/parent-reference.js +119 -0
- package/dist/core/agents/state/pidmap.js +2 -2
- package/dist/core/agents/state/proc-start.d.ts +20 -31
- package/dist/core/agents/state/proc-start.d.ts.map +1 -1
- package/dist/core/agents/state/proc-start.js +37 -70
- package/dist/core/agents/state/service-process.d.ts +61 -0
- package/dist/core/agents/state/service-process.d.ts.map +1 -0
- package/dist/core/agents/state/service-process.js +143 -0
- package/dist/core/config.d.ts +39 -0
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/config.js +63 -0
- package/dist/core/context/index.d.ts +6 -4
- package/dist/core/context/index.d.ts.map +1 -1
- package/dist/core/context/index.js +194 -256
- package/dist/core/context/observation.d.ts +9 -9
- package/dist/core/context/observation.d.ts.map +1 -1
- package/dist/core/context/observation.js +37 -6
- package/dist/core/diagnostics/path-identity.d.ts +1 -1
- package/dist/core/diagnostics/path-identity.d.ts.map +1 -1
- package/dist/core/diagnostics/path-identity.js +51 -6
- package/dist/core/events/v3/canonical.js +43 -14
- package/dist/core/events/v3/index.d.ts +1 -1
- package/dist/core/events/v3/index.d.ts.map +1 -1
- package/dist/core/events/v3/index.js +1 -1
- package/dist/core/events/v3/producers/recorder.d.ts +34 -0
- package/dist/core/events/v3/producers/recorder.d.ts.map +1 -1
- package/dist/core/events/v3/producers/recorder.js +117 -13
- package/dist/core/events/v3/reader.d.ts.map +1 -1
- package/dist/core/events/v3/reader.js +53 -13
- package/dist/core/events/v3/runtime-build.d.ts +14 -0
- package/dist/core/events/v3/runtime-build.d.ts.map +1 -1
- package/dist/core/events/v3/runtime-build.js +44 -0
- package/dist/core/events/v3/validate.d.ts.map +1 -1
- package/dist/core/events/v3/validate.js +36 -1
- package/dist/core/governor/index.d.ts +2 -1
- package/dist/core/governor/index.d.ts.map +1 -1
- package/dist/core/governor/index.js +2 -1
- package/dist/core/governor/service-read.d.ts +3 -10
- package/dist/core/governor/service-read.d.ts.map +1 -1
- package/dist/core/governor/service-read.js +10 -94
- package/dist/core/governor/service.d.ts +3 -7
- package/dist/core/governor/service.d.ts.map +1 -1
- package/dist/core/governor/service.js +17 -182
- package/dist/core/hooks/adapter/native-parent.d.ts +23 -0
- package/dist/core/hooks/adapter/native-parent.d.ts.map +1 -0
- package/dist/core/hooks/adapter/native-parent.js +91 -0
- package/dist/core/hooks/cli.js +79 -4
- package/dist/core/hooks/resolve/owner.d.ts +0 -11
- package/dist/core/hooks/resolve/owner.d.ts.map +1 -1
- package/dist/core/hooks/resolve/owner.js +2 -25
- package/dist/core/hooks/shell-admission.d.ts +84 -0
- package/dist/core/hooks/shell-admission.d.ts.map +1 -0
- package/dist/core/hooks/shell-admission.js +622 -0
- package/dist/core/hooks/shell-commit-tree.d.ts +2 -0
- package/dist/core/hooks/shell-commit-tree.d.ts.map +1 -0
- package/dist/core/hooks/shell-commit-tree.js +104 -0
- package/dist/core/inbox/contract.d.ts +12 -0
- package/dist/core/inbox/contract.d.ts.map +1 -1
- package/dist/core/inbox/contract.js +16 -0
- package/dist/core/inbox/peer-messages.d.ts +41 -16
- package/dist/core/inbox/peer-messages.d.ts.map +1 -1
- package/dist/core/inbox/peer-messages.js +372 -15
- package/dist/core/inbox/service.d.ts +8 -1
- package/dist/core/inbox/service.d.ts.map +1 -1
- package/dist/core/inbox/service.js +83 -3
- package/dist/core/inbox/targets.d.ts +17 -3
- package/dist/core/inbox/targets.d.ts.map +1 -1
- package/dist/core/inbox/targets.js +77 -2
- package/dist/core/presence/git.d.ts +18 -12
- package/dist/core/presence/git.d.ts.map +1 -1
- package/dist/core/presence/git.js +32 -38
- package/dist/core/presence/index.d.ts +2 -2
- package/dist/core/presence/index.d.ts.map +1 -1
- package/dist/core/presence/index.js +8 -6
- package/dist/core/presence/mail.d.ts +133 -0
- package/dist/core/presence/mail.d.ts.map +1 -0
- package/dist/core/presence/mail.js +464 -0
- package/dist/core/resources/sampler.js +2 -2
- package/dist/core/resources/service-status.d.ts +2 -14
- package/dist/core/resources/service-status.d.ts.map +1 -1
- package/dist/core/resources/service-status.js +3 -23
- package/dist/core/resources/service.d.ts +3 -7
- package/dist/core/resources/service.d.ts.map +1 -1
- package/dist/core/resources/service.js +12 -101
- package/dist/core/resources/status.d.ts.map +1 -1
- package/dist/core/resources/status.js +3 -2
- package/dist/core/semantic/contract.d.ts +5 -5
- package/dist/core/semantic/service.d.ts.map +1 -1
- package/dist/core/semantic/service.js +9 -2
- package/dist/core/servers/index.d.ts +0 -2
- package/dist/core/servers/index.d.ts.map +1 -1
- package/dist/core/servers/index.js +3 -18
- package/dist/core/storage/modes.d.ts +4 -0
- package/dist/core/storage/modes.d.ts.map +1 -1
- package/dist/core/storage/modes.js +4 -0
- package/dist/core/storage/segments.d.ts.map +1 -1
- package/dist/core/storage/segments.js +5 -4
- package/dist/core/supervisor/service.d.ts +4 -7
- package/dist/core/supervisor/service.d.ts.map +1 -1
- package/dist/core/supervisor/service.js +23 -122
- package/dist/core/supervisor/services.js +2 -2
- package/dist/core/supervisor/status.d.ts +3 -17
- package/dist/core/supervisor/status.d.ts.map +1 -1
- package/dist/core/supervisor/status.js +7 -70
- package/dist/core/workflow/engine.js +2 -1
- package/dist/core/workflow/index.d.ts +3 -4
- package/dist/core/workflow/index.d.ts.map +1 -1
- package/dist/core/workflow/index.js +2 -2
- package/dist/core/workflow/proof.d.ts.map +1 -1
- package/dist/core/workflow/proof.js +30 -74
- package/dist/core/workflow/resume-lock.d.ts +17 -0
- package/dist/core/workflow/resume-lock.d.ts.map +1 -0
- package/dist/core/workflow/resume-lock.js +158 -0
- package/dist/core/workflow/run-state.d.ts +0 -21
- package/dist/core/workflow/run-state.d.ts.map +1 -1
- package/dist/core/workflow/run-state.js +3 -174
- package/dist/core/workflow/workspaces/leases.d.ts +4 -0
- package/dist/core/workflow/workspaces/leases.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/leases.js +1 -1
- package/dist/core/workflow/workspaces/local-git.js +91 -210
- package/dist/core/workflow/workspaces/ownership.d.ts +0 -7
- package/dist/core/workflow/workspaces/ownership.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/ownership.js +3 -130
- package/dist/core/workflow/workspaces/reclaim.d.ts +4 -7
- package/dist/core/workflow/workspaces/reclaim.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/reclaim.js +0 -4
- package/dist/core/workflow/workspaces/recovery.d.ts +1 -1
- package/dist/core/workflow/workspaces/recovery.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/recovery.js +1 -1
- package/dist/core/workflow/workspaces/state.d.ts.map +1 -1
- package/dist/core/workflow/workspaces/state.js +1 -4
- package/dist/lib/admission-history.d.ts +96 -0
- package/dist/lib/admission-history.d.ts.map +1 -0
- package/dist/lib/admission-history.js +196 -0
- package/dist/lib/admission.d.ts +56 -7
- package/dist/lib/admission.d.ts.map +1 -1
- package/dist/lib/admission.js +161 -19
- package/dist/lib/browser/client.d.ts +18 -0
- package/dist/lib/browser/client.d.ts.map +1 -1
- package/dist/lib/browser/client.js +100 -12
- package/dist/lib/browser/content-checks.d.ts +3 -1
- package/dist/lib/browser/content-checks.d.ts.map +1 -1
- package/dist/lib/browser/content-checks.js +10 -2
- package/dist/lib/browser/critique.d.ts +32 -0
- package/dist/lib/browser/critique.d.ts.map +1 -1
- package/dist/lib/browser/critique.js +72 -3
- package/dist/lib/browser/geometry.d.ts +2 -0
- package/dist/lib/browser/geometry.d.ts.map +1 -1
- package/dist/lib/browser/geometry.js +61 -15
- package/dist/lib/browser/index.d.ts +1 -1
- package/dist/lib/browser/index.d.ts.map +1 -1
- package/dist/lib/browser/index.js +1 -1
- package/dist/lib/browser/page-review-contracts.d.ts +6 -1
- package/dist/lib/browser/page-review-contracts.d.ts.map +1 -1
- package/dist/lib/browser/page-review-contracts.js +6 -1
- package/dist/lib/browser/page-review-judge.d.ts +3 -1
- package/dist/lib/browser/page-review-judge.d.ts.map +1 -1
- package/dist/lib/browser/page-review-judge.js +41 -11
- package/dist/lib/browser/page-review-pack.d.ts +3 -0
- package/dist/lib/browser/page-review-pack.d.ts.map +1 -1
- package/dist/lib/browser/page-review-pack.js +5 -2
- package/dist/lib/browser/qa-run-contracts.d.ts +11 -0
- package/dist/lib/browser/qa-run-contracts.d.ts.map +1 -1
- package/dist/lib/browser/qa-run-contracts.js +11 -4
- package/dist/lib/browser/qa-run.d.ts.map +1 -1
- package/dist/lib/browser/qa-run.js +73 -16
- package/dist/lib/browser/runts.d.ts +4 -2
- package/dist/lib/browser/runts.d.ts.map +1 -1
- package/dist/lib/browser/runts.js +84 -33
- package/dist/lib/browser/windows-chrome.d.ts +14 -0
- package/dist/lib/browser/windows-chrome.d.ts.map +1 -1
- package/dist/lib/browser/windows-chrome.js +42 -0
- package/dist/lib/browser/windows-login-sync.d.ts +110 -0
- package/dist/lib/browser/windows-login-sync.d.ts.map +1 -0
- package/dist/lib/browser/windows-login-sync.js +171 -0
- package/dist/lib/cookies/client.d.ts +30 -5
- package/dist/lib/cookies/client.d.ts.map +1 -1
- package/dist/lib/cookies/client.js +55 -10
- package/dist/lib/cookies/index.d.ts +1 -1
- package/dist/lib/cookies/index.d.ts.map +1 -1
- package/dist/lib/cookies/index.js +1 -1
- package/dist/lib/docs-metadata-sync.d.ts.map +1 -1
- package/dist/lib/docs-metadata-sync.js +41 -2
- package/dist/lib/durable-job.d.ts +8 -0
- package/dist/lib/durable-job.d.ts.map +1 -1
- package/dist/lib/durable-job.js +10 -0
- package/dist/lib/http/client.d.ts +9 -8
- package/dist/lib/http/client.d.ts.map +1 -1
- package/dist/lib/http/client.js +44 -2
- package/dist/lib/instructions/templates.js +4 -4
- package/package.json +2 -2
- package/src/commander.ts +13 -0
- package/src/commands/admission.ts +272 -29
- package/src/commands/agents.ts +132 -27
- package/src/commands/browse.ts +208 -6
- package/src/commands/fetch.ts +1 -0
- package/src/commands/ledger-v3.ts +8 -2
- package/src/commands/presence.ts +49 -0
- package/src/commands/qa-run.ts +4 -3
- package/src/commands/review-pack.ts +41 -5
- package/src/commands/workflow.ts +8 -58
- package/src/core/agents/coord-client.ts +0 -20
- package/src/core/agents/index.ts +6 -0
- package/src/core/agents/live-authority-v3.ts +11 -4
- package/src/core/agents/render/session-context.ts +1 -7
- package/src/core/agents/session-events.ts +18 -3
- package/src/core/agents/state/heartbeat-reader.ts +3 -0
- package/src/core/agents/state/live-coordination-view.ts +13 -6
- package/src/core/agents/state/parent-reference-contract.ts +14 -0
- package/src/core/agents/state/parent-reference.ts +179 -0
- package/src/core/agents/state/pidmap.ts +2 -2
- package/src/core/agents/state/proc-start.ts +41 -69
- package/src/core/agents/state/service-process.ts +217 -0
- package/src/core/config.ts +85 -0
- package/src/core/context/index.ts +207 -272
- package/src/core/context/observation.ts +40 -9
- package/src/core/diagnostics/path-identity.ts +52 -6
- package/src/core/events/v3/canonical.ts +46 -13
- package/src/core/events/v3/index.ts +3 -0
- package/src/core/events/v3/producers/recorder.ts +151 -13
- package/src/core/events/v3/reader.ts +55 -11
- package/src/core/events/v3/runtime-build.ts +62 -0
- package/src/core/events/v3/validate.ts +42 -1
- package/src/core/governor/index.ts +1 -2
- package/src/core/governor/service-read.ts +15 -114
- package/src/core/governor/service.ts +36 -220
- package/src/core/hooks/adapter/native-parent.ts +109 -0
- package/src/core/hooks/cli.ts +83 -4
- package/src/core/hooks/resolve/owner.ts +2 -25
- package/src/core/hooks/shell-admission.ts +634 -0
- package/src/core/hooks/shell-commit-tree.ts +102 -0
- package/src/core/inbox/contract.ts +18 -0
- package/src/core/inbox/peer-messages.ts +501 -15
- package/src/core/inbox/service.ts +108 -3
- package/src/core/inbox/targets.ts +94 -3
- package/src/core/presence/git.ts +45 -37
- package/src/core/presence/index.ts +16 -8
- package/src/core/presence/mail.ts +620 -0
- package/src/core/resources/sampler.ts +3 -2
- package/src/core/resources/service-status.ts +6 -35
- package/src/core/resources/service.ts +23 -133
- package/src/core/resources/status.ts +3 -2
- package/src/core/semantic/service.ts +8 -2
- package/src/core/servers/index.ts +3 -23
- package/src/core/storage/modes.ts +6 -0
- package/src/core/storage/segments.ts +5 -4
- package/src/core/supervisor/service.ts +33 -155
- package/src/core/supervisor/services.ts +2 -2
- package/src/core/supervisor/status.ts +10 -85
- package/src/core/workflow/engine.ts +1 -1
- package/src/core/workflow/index.ts +2 -13
- package/src/core/workflow/proof.ts +35 -83
- package/src/core/workflow/resume-lock.ts +179 -0
- package/src/core/workflow/run-state.ts +3 -207
- package/src/core/workflow/workspaces/leases.ts +1 -1
- package/src/core/workflow/workspaces/local-git.ts +120 -239
- package/src/core/workflow/workspaces/ownership.ts +3 -158
- package/src/core/workflow/workspaces/reclaim.ts +3 -9
- package/src/core/workflow/workspaces/recovery.ts +1 -1
- package/src/core/workflow/workspaces/state.ts +1 -5
- package/src/lib/admission-history.ts +267 -0
- package/src/lib/admission.ts +217 -19
- package/src/lib/browser/client.ts +147 -35
- package/src/lib/browser/content-checks.ts +14 -3
- package/src/lib/browser/critique.ts +113 -3
- package/src/lib/browser/geometry.ts +63 -14
- package/src/lib/browser/index.ts +5 -0
- package/src/lib/browser/page-review-contracts.ts +6 -1
- package/src/lib/browser/page-review-judge.ts +58 -13
- package/src/lib/browser/page-review-pack.ts +13 -2
- package/src/lib/browser/qa-run-contracts.ts +30 -4
- package/src/lib/browser/qa-run.ts +78 -17
- package/src/lib/browser/runts.ts +85 -37
- package/src/lib/browser/windows-chrome.ts +50 -0
- package/src/lib/browser/windows-login-sync.ts +275 -0
- package/src/lib/cookies/client.ts +62 -10
- package/src/lib/cookies/index.ts +3 -0
- package/src/lib/docs-metadata-sync.ts +44 -2
- package/src/lib/durable-job.ts +15 -0
- package/src/lib/http/client.ts +63 -1
- package/src/lib/instructions/templates.ts +4 -4
- package/dist/core/agents/state/process-handle.d.ts +0 -26
- package/dist/core/agents/state/process-handle.d.ts.map +0 -1
- package/dist/core/agents/state/process-handle.js +0 -121
- package/dist/core/workflow/recovery-audit.d.ts +0 -30
- package/dist/core/workflow/recovery-audit.d.ts.map +0 -1
- package/dist/core/workflow/recovery-audit.js +0 -116
- package/src/core/agents/state/process-handle.ts +0 -165
- package/src/core/workflow/recovery-audit.ts +0 -181
|
@@ -18,6 +18,8 @@
|
|
|
18
18
|
// Toolkit tier: this module must not import src/core (layering check).
|
|
19
19
|
|
|
20
20
|
import { createHash } from "node:crypto";
|
|
21
|
+
import type { CritiqueUsage, CritiqueUsageTotals } from "./critique.js";
|
|
22
|
+
import { PAGE_REVIEW_MAX_TILE_BUDGET } from "./page-review-contracts.js";
|
|
21
23
|
import type { QaContext, QaManifest } from "./qa-plan.js";
|
|
22
24
|
|
|
23
25
|
export const QA_RUN_JOB_SCHEMA_VERSION = 1 as const;
|
|
@@ -152,6 +154,9 @@ export interface QaRunCritiqueTileTiming {
|
|
|
152
154
|
tile_id: string;
|
|
153
155
|
duration_ms: number;
|
|
154
156
|
outcome: "passed" | "failed";
|
|
157
|
+
/** Tokens the provider reported for this tile, retries included. Absent
|
|
158
|
+
* when the backend reports none; that is unknown, not zero. */
|
|
159
|
+
usage?: CritiqueUsage;
|
|
155
160
|
}
|
|
156
161
|
|
|
157
162
|
/** What share of the page the critique tiles covered, lifted from the browse
|
|
@@ -184,6 +189,11 @@ export interface QaRunCritiqueOutcome {
|
|
|
184
189
|
dismissed?: Array<{ finding_key: string; target: string; disposition: string }>;
|
|
185
190
|
/** Tile coverage of the page. Absent when the capture died or reported none. */
|
|
186
191
|
coverage?: QaRunCritiqueCoverage;
|
|
192
|
+
/** `full`: every band reviewed. `capped`: bands left out; a signoff blocker.
|
|
193
|
+
* `sampled`: signoff on a page whose complete review would exceed the
|
|
194
|
+
* per-run tile ceiling; every edge band, gate hit, and required scope was
|
|
195
|
+
* reviewed and the uncovered intervals are listed in `coverage` (ADR 0224). */
|
|
196
|
+
coverage_mode?: "full" | "capped" | "sampled";
|
|
187
197
|
}
|
|
188
198
|
|
|
189
199
|
/** The judge stage as one unit: every context's tiles through one bounded
|
|
@@ -199,6 +209,8 @@ export interface QaRunCritiquePool {
|
|
|
199
209
|
latency_ms?: Record<string, QaRunCritiqueLatency>;
|
|
200
210
|
/** One entry per attempted tile, in context and tile order. */
|
|
201
211
|
tile_timings?: QaRunCritiqueTileTiming[];
|
|
212
|
+
/** Tokens summed over the attempted tiles. */
|
|
213
|
+
usage?: CritiqueUsageTotals;
|
|
202
214
|
}
|
|
203
215
|
|
|
204
216
|
/** Where the run's page review pack lives: the on-disk evidence an agent can
|
|
@@ -518,8 +530,15 @@ export function validateQaRunJob(value: unknown): QaRunJobValidation {
|
|
|
518
530
|
}
|
|
519
531
|
if (p?.critique_tile_budget !== undefined) {
|
|
520
532
|
const n = p.critique_tile_budget;
|
|
521
|
-
if (
|
|
522
|
-
|
|
533
|
+
if (
|
|
534
|
+
typeof n !== "number" ||
|
|
535
|
+
!Number.isInteger(n) ||
|
|
536
|
+
n < 8 ||
|
|
537
|
+
n > PAGE_REVIEW_MAX_TILE_BUDGET
|
|
538
|
+
) {
|
|
539
|
+
errors.push(
|
|
540
|
+
`policy.critique_tile_budget must be an integer between 8 and ${PAGE_REVIEW_MAX_TILE_BUDGET}`,
|
|
541
|
+
);
|
|
523
542
|
}
|
|
524
543
|
}
|
|
525
544
|
if (p?.command_concurrency !== undefined) {
|
|
@@ -542,8 +561,15 @@ export function validateQaRunJob(value: unknown): QaRunJobValidation {
|
|
|
542
561
|
}
|
|
543
562
|
if (p?.critique_max_tiles !== undefined) {
|
|
544
563
|
const n = p.critique_max_tiles;
|
|
545
|
-
if (
|
|
546
|
-
|
|
564
|
+
if (
|
|
565
|
+
typeof n !== "number" ||
|
|
566
|
+
!Number.isInteger(n) ||
|
|
567
|
+
n < 1 ||
|
|
568
|
+
n > PAGE_REVIEW_MAX_TILE_BUDGET
|
|
569
|
+
) {
|
|
570
|
+
errors.push(
|
|
571
|
+
`policy.critique_max_tiles must be an integer between 1 and ${PAGE_REVIEW_MAX_TILE_BUDGET}`,
|
|
572
|
+
);
|
|
547
573
|
}
|
|
548
574
|
}
|
|
549
575
|
if (p?.critique_pool !== undefined) {
|
|
@@ -24,6 +24,7 @@ import {
|
|
|
24
24
|
} from "./page-review-budget.js";
|
|
25
25
|
import {
|
|
26
26
|
PAGE_REVIEW_DEFAULT_TILE_BUDGET,
|
|
27
|
+
PAGE_REVIEW_MAX_TILE_BUDGET,
|
|
27
28
|
type PageReviewCapturePlan,
|
|
28
29
|
type PageReviewContextAllocation,
|
|
29
30
|
} from "./page-review-contracts.js";
|
|
@@ -911,10 +912,29 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
911
912
|
log(`critique provider failed to load: ${err instanceof Error ? err.message : String(err)}`);
|
|
912
913
|
}
|
|
913
914
|
}
|
|
914
|
-
const bandArgs =
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
915
|
+
const bandArgs = (ctx: QaRunContext): string[] => {
|
|
916
|
+
const args = [
|
|
917
|
+
...contextRenderArgs(ctx),
|
|
918
|
+
...(job.checks ?? [])
|
|
919
|
+
.filter((check) => check.contexts === undefined || check.contexts.includes(ctx.id))
|
|
920
|
+
.flatMap((check) => check.args),
|
|
921
|
+
];
|
|
922
|
+
let requested = "1400";
|
|
923
|
+
for (let i = 0; i < args.length; i++) {
|
|
924
|
+
if (args[i] === "--check-critique-band") requested = args[i + 1] ?? "";
|
|
925
|
+
else if (args[i]?.startsWith("--check-critique-band=")) {
|
|
926
|
+
requested = args[i]!.slice("--check-critique-band=".length);
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// A larger explicit request needs a declared route budget. Missing or
|
|
930
|
+
// invalid metadata must not turn capture planning into an unbounded path.
|
|
931
|
+
const budget = provider?.tileBudgetPx;
|
|
932
|
+
const cap = budget !== undefined && Number.isFinite(budget) && budget >= 200 ? budget : 1400;
|
|
933
|
+
return [
|
|
934
|
+
"--check-critique-band",
|
|
935
|
+
String(Math.max(200, Math.min(Number.parseInt(requested, 10), cap))),
|
|
936
|
+
];
|
|
937
|
+
};
|
|
918
938
|
const scopeArgs =
|
|
919
939
|
visual === "full-page"
|
|
920
940
|
? []
|
|
@@ -961,7 +981,7 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
961
981
|
"--qa-state",
|
|
962
982
|
ctx.state,
|
|
963
983
|
...scopeArgs,
|
|
964
|
-
...bandArgs,
|
|
984
|
+
...bandArgs(ctx),
|
|
965
985
|
...critiqueMaxTilesArgs,
|
|
966
986
|
]
|
|
967
987
|
: []),
|
|
@@ -1185,6 +1205,10 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1185
1205
|
|
|
1186
1206
|
enterStage("capture");
|
|
1187
1207
|
const captureStart = Date.now();
|
|
1208
|
+
// Candidate tiles a complete review of every context would need. Above the
|
|
1209
|
+
// per-run ceiling, signoff accepts a sampled critique (ADR 0224).
|
|
1210
|
+
let fullCoverageTiles = 0;
|
|
1211
|
+
const sampledContexts = new Set<string>();
|
|
1188
1212
|
if (runsVisual) {
|
|
1189
1213
|
writeFileSync(
|
|
1190
1214
|
join(outDir, "preliminary-gates.json"),
|
|
@@ -1205,6 +1229,7 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1205
1229
|
);
|
|
1206
1230
|
return candidate;
|
|
1207
1231
|
});
|
|
1232
|
+
fullCoverageTiles = plans.reduce((sum, plan) => sum + plan.candidates.length, 0);
|
|
1208
1233
|
const ceilings =
|
|
1209
1234
|
job.policy?.critique_max_tiles === undefined
|
|
1210
1235
|
? undefined
|
|
@@ -1263,7 +1288,7 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1263
1288
|
.flatMap((check) => check.args),
|
|
1264
1289
|
...scopeArgs,
|
|
1265
1290
|
...critiqueMaxTilesArgs,
|
|
1266
|
-
...bandArgs,
|
|
1291
|
+
...bandArgs(ctx),
|
|
1267
1292
|
];
|
|
1268
1293
|
log(
|
|
1269
1294
|
`capture ${ctx.id}${scopeArgs.length > 0 ? ` [${manifest.scopes.map((s) => s.selector).join(",")}]` : ""}`,
|
|
@@ -1398,6 +1423,7 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1398
1423
|
provider: judged.pool.provider,
|
|
1399
1424
|
...(latency ? { latency_ms: latency } : {}),
|
|
1400
1425
|
tile_timings: judged.tile_timings,
|
|
1426
|
+
...(judged.usage ? { usage: judged.usage } : {}),
|
|
1401
1427
|
};
|
|
1402
1428
|
for (const row of judged.contexts) {
|
|
1403
1429
|
judgedById.set(row.context_id, row);
|
|
@@ -1477,17 +1503,43 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1477
1503
|
});
|
|
1478
1504
|
}
|
|
1479
1505
|
// Selected bands may leave gaps anywhere. Signoff needs complete
|
|
1480
|
-
// coverage
|
|
1506
|
+
// coverage when the whole page fits under the per-run ceiling. Past
|
|
1507
|
+
// it, complete coverage is unavailable by policy, so signoff accepts a
|
|
1508
|
+
// sampled critique that still reviewed every edge band, gate hit, and
|
|
1509
|
+
// required scope; the deterministic gates covered the whole page.
|
|
1510
|
+
let coverageMode: QaRunCritiqueOutcome["coverage_mode"] = row.coverage
|
|
1511
|
+
? row.coverage.capped
|
|
1512
|
+
? "capped"
|
|
1513
|
+
: "full"
|
|
1514
|
+
: undefined;
|
|
1481
1515
|
if (job.mode === "signoff" && row.coverage.capped && contextOutcome !== "failed") {
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1516
|
+
// Without the allocation record nothing proves the required regions
|
|
1517
|
+
// were selected, so a sampled signoff is refused.
|
|
1518
|
+
const allocated = row.record.allocation_coverage;
|
|
1519
|
+
const requiredOmitted = allocated
|
|
1520
|
+
? [
|
|
1521
|
+
...allocated.omitted_gate_hits.map((id) => `gate hit ${id}`),
|
|
1522
|
+
...allocated.omitted_scopes.map((scope) => `scope ${scope}`),
|
|
1523
|
+
]
|
|
1524
|
+
: ["its allocation record (recapture this context)"];
|
|
1525
|
+
if (fullCoverageTiles > PAGE_REVIEW_MAX_TILE_BUDGET && requiredOmitted.length === 0) {
|
|
1526
|
+
coverageMode = "sampled";
|
|
1527
|
+
sampledContexts.add(row.context_id);
|
|
1528
|
+
} else {
|
|
1529
|
+
contextOutcome = "unknown";
|
|
1530
|
+
blockers.push({
|
|
1531
|
+
stage: "critique",
|
|
1532
|
+
context_id: row.context_id,
|
|
1533
|
+
reason:
|
|
1534
|
+
`critique coverage capped: ${row.coverage.bands_reviewed} of ${row.coverage.bands_total} bands ` +
|
|
1535
|
+
`reviewed (${row.coverage.reviewed_height_px} of ${row.coverage.page_height_px} px)` +
|
|
1536
|
+
(requiredOmitted.length > 0
|
|
1537
|
+
? `; the budget left out ${requiredOmitted.join(", ")}. Raise ` +
|
|
1538
|
+
`policy.critique_tile_budget (at most ${PAGE_REVIEW_MAX_TILE_BUDGET}) so every gate hit and scope is reviewed`
|
|
1539
|
+
: `; the full page needs ${fullCoverageTiles} tiles, so raise ` +
|
|
1540
|
+
`policy.critique_tile_budget (and any explicit per-context ceiling) to ${fullCoverageTiles}`),
|
|
1541
|
+
});
|
|
1542
|
+
}
|
|
1491
1543
|
}
|
|
1492
1544
|
const scopeById = new Map(row.record.tiles.map((tile) => [tile.id, tile.scope]));
|
|
1493
1545
|
critique.push({
|
|
@@ -1497,6 +1549,7 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1497
1549
|
tiles_reviewed: row.tiles_reviewed,
|
|
1498
1550
|
tiles_reused: row.tiles_reused,
|
|
1499
1551
|
outcome: contextOutcome,
|
|
1552
|
+
...(coverageMode ? { coverage_mode: coverageMode } : {}),
|
|
1500
1553
|
...(dismissed.length > 0 ? { dismissed } : {}),
|
|
1501
1554
|
findings: row.findings.map((finding) => {
|
|
1502
1555
|
const selector = scopeById.get(finding.tile_id);
|
|
@@ -1510,7 +1563,15 @@ export async function runQaMatrix(options: QaRunMatrixOptions): Promise<QaRunRes
|
|
|
1510
1563
|
coverage: row.coverage,
|
|
1511
1564
|
});
|
|
1512
1565
|
}
|
|
1513
|
-
finalizePack(
|
|
1566
|
+
finalizePack(
|
|
1567
|
+
"critique",
|
|
1568
|
+
toCritiqueRecords(judged).map((record) =>
|
|
1569
|
+
sampledContexts.has(record.context_id)
|
|
1570
|
+
? { ...record, coverage_mode: "sampled" as const }
|
|
1571
|
+
: record,
|
|
1572
|
+
),
|
|
1573
|
+
judged.pool,
|
|
1574
|
+
);
|
|
1514
1575
|
}
|
|
1515
1576
|
if (runsVisual) restoreHeadlessOnly();
|
|
1516
1577
|
wall.critique = Date.now() - critiqueStart;
|
package/src/lib/browser/runts.ts
CHANGED
|
@@ -39,7 +39,7 @@ export interface RuntsResult {
|
|
|
39
39
|
outcome: "pass" | "fail" | "unknown";
|
|
40
40
|
/** Text blocks scanned (post length/inline-children filters). */
|
|
41
41
|
scannedBlocks: number;
|
|
42
|
-
/** True when the
|
|
42
|
+
/** True when the sweep reached its findings limit or the internal block cap. */
|
|
43
43
|
truncated: boolean;
|
|
44
44
|
runts: RuntHit[];
|
|
45
45
|
}
|
|
@@ -47,13 +47,20 @@ export interface RuntsResult {
|
|
|
47
47
|
/**
|
|
48
48
|
* Build the JS function passed to `page.evaluate`. `scope` narrows the sweep
|
|
49
49
|
* to one container (null = whole body); `minChars` filters out tiny labels
|
|
50
|
-
* that can't meaningfully wrap (default 40).
|
|
50
|
+
* that can't meaningfully wrap (default 40). `maxFindings` bounds the retained
|
|
51
|
+
* failure list (default 50; integer 1..5000), not the separate 5000-block cap.
|
|
51
52
|
*/
|
|
52
53
|
export function buildRuntsCheck(): (args: {
|
|
53
54
|
scope: string | null;
|
|
54
55
|
minChars: number;
|
|
56
|
+
maxFindings?: number;
|
|
55
57
|
}) => RuntsResult {
|
|
56
|
-
return ({ scope, minChars }) => {
|
|
58
|
+
return ({ scope, minChars, maxFindings = 50 }) => {
|
|
59
|
+
// page.evaluate has no module closure: validate this boundary too, even
|
|
60
|
+
// when a caller evaluates the built function without the Browser client.
|
|
61
|
+
if (!Number.isInteger(maxFindings) || maxFindings < 1 || maxFindings > 5000) {
|
|
62
|
+
throw new Error("maxFindings must be an integer from 1 to 5000.");
|
|
63
|
+
}
|
|
57
64
|
const INLINE = new Set([
|
|
58
65
|
"B",
|
|
59
66
|
"I",
|
|
@@ -74,7 +81,7 @@ export function buildRuntsCheck(): (args: {
|
|
|
74
81
|
]);
|
|
75
82
|
const ATOMIC = /@|https?:|\.(com|org|net|io|co)\b|\d-\d{3}/;
|
|
76
83
|
const BLOCK_CAP = 5000;
|
|
77
|
-
const RUNT_CAP =
|
|
84
|
+
const RUNT_CAP = maxFindings;
|
|
78
85
|
|
|
79
86
|
const root = scope ? document.querySelector(scope) : document.body;
|
|
80
87
|
if (!root) {
|
|
@@ -128,34 +135,78 @@ export function buildRuntsCheck(): (args: {
|
|
|
128
135
|
}
|
|
129
136
|
scanned++;
|
|
130
137
|
|
|
131
|
-
//
|
|
132
|
-
|
|
133
|
-
|
|
138
|
+
// Inline markup does not create word boundaries. Keep offsets into the
|
|
139
|
+
// original text nodes so one Range can measure a word split by emphasis
|
|
140
|
+
// or followed by punctuation in a separate node. BR still separates words.
|
|
141
|
+
const segments: Array<{ node: Text; start: number; end: number }> = [];
|
|
142
|
+
let content = "";
|
|
143
|
+
const tw = document.createTreeWalker(el, NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT);
|
|
134
144
|
for (let n = tw.nextNode(); n; n = tw.nextNode()) {
|
|
145
|
+
if (n instanceof Element && n.tagName === "BR") content += "\n";
|
|
146
|
+
if (n.nodeType !== Node.TEXT_NODE) continue;
|
|
135
147
|
const value = n.nodeValue ?? "";
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
148
|
+
segments.push({
|
|
149
|
+
node: n as Text,
|
|
150
|
+
start: content.length,
|
|
151
|
+
end: content.length + value.length,
|
|
152
|
+
});
|
|
153
|
+
content += value;
|
|
154
|
+
}
|
|
155
|
+
type Word = { value: string; rect: DOMRect; node: Node };
|
|
156
|
+
const lines: Array<{ top: number; bottom: number; words: Map<number, Word> }> = [];
|
|
157
|
+
let segmentIndex = 0;
|
|
158
|
+
for (const m of content.matchAll(/\S+/g)) {
|
|
159
|
+
const lexical = /[\p{L}\p{N}]/u.exec(m[0]);
|
|
160
|
+
if (!lexical) continue; // punctuation alone is not an orphan word
|
|
161
|
+
const end = m.index + m[0].length;
|
|
162
|
+
while (segments[segmentIndex] && segments[segmentIndex]!.end <= m.index) segmentIndex++;
|
|
163
|
+
const first = segments[segmentIndex];
|
|
164
|
+
if (!first) continue;
|
|
165
|
+
let endIndex = segmentIndex;
|
|
166
|
+
while (segments[endIndex] && segments[endIndex]!.end < end) endIndex++;
|
|
167
|
+
const final = segments[endIndex];
|
|
168
|
+
if (!final) continue;
|
|
169
|
+
let wordIndex = segmentIndex;
|
|
170
|
+
while (segments[wordIndex]!.end <= m.index + lexical.index) wordIndex++;
|
|
171
|
+
const wordNode = segments[wordIndex]!.node;
|
|
172
|
+
const range = document.createRange();
|
|
173
|
+
range.setStart(first.node, m.index - first.start);
|
|
174
|
+
range.setEnd(final.node, end - final.start);
|
|
175
|
+
for (const rect of range.getClientRects()) {
|
|
176
|
+
if (!rect.width || !rect.height) continue;
|
|
177
|
+
// Different inline fonts have different glyph tops on the same line.
|
|
178
|
+
// Require substantial vertical overlap rather than exact rounded tops.
|
|
179
|
+
let line = lines.find(
|
|
180
|
+
(candidate) =>
|
|
181
|
+
Math.min(candidate.bottom, rect.bottom) - Math.max(candidate.top, rect.top) >
|
|
182
|
+
Math.min(candidate.bottom - candidate.top, rect.height) / 2,
|
|
183
|
+
);
|
|
184
|
+
if (!line) {
|
|
185
|
+
line = { top: rect.top, bottom: rect.bottom, words: new Map() };
|
|
186
|
+
lines.push(line);
|
|
150
187
|
}
|
|
188
|
+
line.top = Math.min(line.top, rect.top);
|
|
189
|
+
line.bottom = Math.max(line.bottom, rect.bottom);
|
|
190
|
+
const previous = line.words.get(m.index)?.rect;
|
|
191
|
+
const box = previous
|
|
192
|
+
? new DOMRect(
|
|
193
|
+
Math.min(previous.left, rect.left),
|
|
194
|
+
Math.min(previous.top, rect.top),
|
|
195
|
+
Math.max(previous.right, rect.right) - Math.min(previous.left, rect.left),
|
|
196
|
+
Math.max(previous.bottom, rect.bottom) - Math.min(previous.top, rect.top),
|
|
197
|
+
)
|
|
198
|
+
: rect;
|
|
199
|
+
line.words.set(m.index, { value: m[0], rect: box, node: wordNode });
|
|
151
200
|
}
|
|
152
201
|
}
|
|
153
|
-
|
|
154
|
-
if (
|
|
155
|
-
const
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
202
|
+
lines.sort((a, b) => a.top - b.top);
|
|
203
|
+
if (lines.length < 2) continue; // single line, no runt risk
|
|
204
|
+
const lastLine = lines[lines.length - 1];
|
|
205
|
+
if (lastLine?.words.size !== 1) continue;
|
|
206
|
+
const [wordOffset, last] = [...lastLine.words.entries()][0]!;
|
|
207
|
+
// A word split across visual lines is not a separate final orphan word.
|
|
208
|
+
if (lines.slice(0, -1).some((line) => line.words.has(wordOffset))) continue;
|
|
209
|
+
const word = last.value;
|
|
159
210
|
if (ATOMIC.test(word)) continue;
|
|
160
211
|
// A chip is not prose. A lone token whose glyphs live inside a padded,
|
|
161
212
|
// boxed inline element (a <code> chip, pill, or badge) is a UI control:
|
|
@@ -165,11 +216,7 @@ export function buildRuntsCheck(): (args: {
|
|
|
165
216
|
// inline ancestry (tag-based, since flex/grid parents blockify computed
|
|
166
217
|
// display) and skip the finding when a chip hosts it.
|
|
167
218
|
const chipHosted = (() => {
|
|
168
|
-
for (
|
|
169
|
-
let e = last.node.parentElement;
|
|
170
|
-
e && e !== el && INLINE.has(e.tagName);
|
|
171
|
-
e = e.parentElement
|
|
172
|
-
) {
|
|
219
|
+
for (let e = last.node.parentElement; e && INLINE.has(e.tagName); e = e.parentElement) {
|
|
173
220
|
const cs = getComputedStyle(e);
|
|
174
221
|
const padX =
|
|
175
222
|
(Number.parseFloat(cs.paddingLeft) || 0) + (Number.parseFloat(cs.paddingRight) || 0);
|
|
@@ -177,6 +224,7 @@ export function buildRuntsCheck(): (args: {
|
|
|
177
224
|
(cs.backgroundColor !== "rgba(0, 0, 0, 0)" && cs.backgroundColor !== "transparent") ||
|
|
178
225
|
(Number.parseFloat(cs.borderTopWidth) || 0) > 0;
|
|
179
226
|
if (padX > 0 && boxed) return true;
|
|
227
|
+
if (e === el) break;
|
|
180
228
|
}
|
|
181
229
|
return false;
|
|
182
230
|
})();
|
|
@@ -187,12 +235,12 @@ export function buildRuntsCheck(): (args: {
|
|
|
187
235
|
word,
|
|
188
236
|
snippet: text.replace(/\s+/g, " ").trim().slice(0, 80),
|
|
189
237
|
rect: {
|
|
190
|
-
x: Math.round(last.
|
|
191
|
-
y: Math.round(last.
|
|
192
|
-
width: Math.round(last.
|
|
193
|
-
height: Math.round(last.
|
|
238
|
+
x: Math.round(last.rect.x + sx),
|
|
239
|
+
y: Math.round(last.rect.y + sy),
|
|
240
|
+
width: Math.round(last.rect.width),
|
|
241
|
+
height: Math.round(last.rect.height),
|
|
194
242
|
},
|
|
195
|
-
lines:
|
|
243
|
+
lines: lines.length,
|
|
196
244
|
});
|
|
197
245
|
if (runts.length >= RUNT_CAP) {
|
|
198
246
|
truncated = true;
|
|
@@ -479,6 +479,56 @@ export function stopWindowsChromeProfile(
|
|
|
479
479
|
return parseStopOutput(result.stdout, profileWindows);
|
|
480
480
|
}
|
|
481
481
|
|
|
482
|
+
/**
|
|
483
|
+
* PowerShell that lists the Chrome processes running on the profile matched
|
|
484
|
+
* by `$Pattern` and prints their ids as a JSON array. It never closes them.
|
|
485
|
+
*/
|
|
486
|
+
export function runningScript(): string {
|
|
487
|
+
return [
|
|
488
|
+
"param([string]$Pattern)",
|
|
489
|
+
"$ErrorActionPreference = 'Stop'",
|
|
490
|
+
PROFILE_PROCESS_FUNCTION,
|
|
491
|
+
"$ids = @(Get-ProfileChrome | ForEach-Object { [int]$_.ProcessId })",
|
|
492
|
+
"[Console]::Out.Write((ConvertTo-Json -Compress -InputObject $ids))",
|
|
493
|
+
].join("\r\n");
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
/** Parse the running script's stdout into process ids. */
|
|
497
|
+
export function parseRunningOutput(stdout: string): number[] {
|
|
498
|
+
const trimmed = stdout.trim();
|
|
499
|
+
if (!trimmed) return [];
|
|
500
|
+
const parsed = JSON.parse(trimmed) as number | number[] | null;
|
|
501
|
+
if (parsed === null) return [];
|
|
502
|
+
return (Array.isArray(parsed) ? parsed : [parsed]).map(Number);
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
/**
|
|
506
|
+
* Ids of the Chrome processes running on `profileWindows`, for callers that
|
|
507
|
+
* must leave an open profile alone. Throws when the check cannot run.
|
|
508
|
+
*/
|
|
509
|
+
export function windowsChromeProfileProcesses(profileWindows: string): number[] {
|
|
510
|
+
const result = runPowerShellScript(
|
|
511
|
+
"profile-running",
|
|
512
|
+
runningScript(),
|
|
513
|
+
["-Pattern", profileCommandLinePattern(profileWindows)],
|
|
514
|
+
30_000,
|
|
515
|
+
);
|
|
516
|
+
if (result.status !== 0) {
|
|
517
|
+
throw new Error(
|
|
518
|
+
`Checking for Chrome on ${profileWindows} failed: ${(result.stderr || result.stdout || result.error?.message || "no output").trim().slice(0, 400)}`,
|
|
519
|
+
);
|
|
520
|
+
}
|
|
521
|
+
return parseRunningOutput(result.stdout);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
/** Linux form of a drive-letter Windows path (`C:\x` becomes `/mnt/c/x`). */
|
|
525
|
+
export function windowsPathToWsl(windowsPath: string): string {
|
|
526
|
+
const match = /^([A-Za-z]):\\(.*)$/.exec(windowsPath);
|
|
527
|
+
if (!match) throw new Error(`Not a drive-letter path: ${windowsPath}`);
|
|
528
|
+
const rest = (match[2] ?? "").replace(/\\+/g, "/").replace(/\/+$/, "");
|
|
529
|
+
return `/mnt/${(match[1] as string).toLowerCase()}${rest ? `/${rest}` : ""}`;
|
|
530
|
+
}
|
|
531
|
+
|
|
482
532
|
/** Windows form of a `/mnt/<drive>/...` path. */
|
|
483
533
|
export function wslPathToWindows(linuxPath: string): string {
|
|
484
534
|
const match = /^\/mnt\/([a-z])\/(.*)$/.exec(linuxPath);
|