@loucompanion/forge-bridge 0.1.3-dev.f66395c3ac21 → 0.1.4-dev.04a1e5b9e484
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/README.md +165 -1
- package/dist/bridge/bridge-chat.types.d.ts +111 -0
- package/dist/bridge/bridge-chat.types.js +401 -0
- package/dist/bridge/bridge.base.d.ts +9 -1
- package/dist/bridge/bridge.service.d.ts +129 -3
- package/dist/bridge/bridge.service.js +2427 -70
- package/dist/bridge/bridge.types.d.ts +218 -2
- package/dist/bridge/dispatch-result-outbox.d.ts +25 -0
- package/dist/bridge/dispatch-result-outbox.js +62 -0
- package/dist/bridge/dispatch-result-worker.d.ts +2 -0
- package/dist/bridge/dispatch-result-worker.js +122 -0
- package/dist/bridge/internals.d.ts +2 -0
- package/dist/bridge/internals.js +1 -0
- package/dist/bridge/readiness-result-outbox.d.ts +21 -0
- package/dist/bridge/readiness-result-outbox.js +54 -0
- package/dist/bridge/readiness-result-worker.d.ts +2 -0
- package/dist/bridge/readiness-result-worker.js +121 -0
- package/dist/bridge/runner-host-capabilities.d.ts +15 -0
- package/dist/bridge/runner-host-capabilities.js +93 -0
- package/dist/cleanup/cleanup.service.d.ts +14 -2
- package/dist/cleanup/cleanup.service.js +496 -3
- package/dist/cleanup/cleanup.types.d.ts +10 -0
- package/dist/cleanup/index.d.ts +1 -1
- package/dist/cleanup/index.js +1 -1
- package/dist/cleanup/internals.d.ts +2 -2
- package/dist/cleanup/internals.js +1 -1
- package/dist/cli/cli.help.d.ts +2 -0
- package/dist/cli/cli.help.js +217 -0
- package/dist/cli/cli.service.d.ts +55 -7
- package/dist/cli/cli.service.js +429 -219
- package/dist/cli/local-config.d.ts +7 -0
- package/dist/cli/local-config.js +51 -3
- package/dist/runtime/aspnet-runtime-prebuild.d.ts +36 -0
- package/dist/runtime/aspnet-runtime-prebuild.js +341 -0
- package/dist/runtime/attempt-database-broker.d.ts +89 -0
- package/dist/runtime/attempt-database-broker.js +390 -0
- package/dist/runtime/attempt-node-runtime-launcher.d.ts +18 -0
- package/dist/runtime/attempt-node-runtime-launcher.js +140 -0
- package/dist/runtime/attempt-path-operation.d.ts +21 -0
- package/dist/runtime/attempt-path-operation.js +94 -0
- package/dist/runtime/attempt-private-directory.d.ts +6 -0
- package/dist/runtime/attempt-private-directory.js +54 -0
- package/dist/runtime/attempt-process-supervisor-registry.d.ts +20 -0
- package/dist/runtime/attempt-process-supervisor-registry.js +64 -0
- package/dist/runtime/attempt-process-supervisor.d.ts +102 -0
- package/dist/runtime/attempt-process-supervisor.js +421 -0
- package/dist/runtime/canonical-source-file.d.ts +3 -0
- package/dist/runtime/canonical-source-file.js +54 -0
- package/dist/runtime/docker-engine-probe.d.ts +90 -0
- package/dist/runtime/docker-engine-probe.js +333 -0
- package/dist/runtime/generated-output-reset.d.ts +20 -0
- package/dist/runtime/generated-output-reset.js +157 -0
- package/dist/runtime/host-database-capability-boundary.d.ts +16 -0
- package/dist/runtime/host-database-capability-boundary.js +62 -0
- package/dist/runtime/host-diagnostics.d.ts +11 -0
- package/dist/runtime/host-diagnostics.js +192 -0
- package/dist/runtime/host-execution-root.d.ts +4 -0
- package/dist/runtime/host-execution-root.js +39 -0
- package/dist/runtime/host-listener-inspector.d.ts +51 -0
- package/dist/runtime/host-listener-inspector.js +385 -0
- package/dist/runtime/host-port-broker.d.ts +76 -0
- package/dist/runtime/host-port-broker.js +333 -0
- package/dist/runtime/host-runtime-contract.d.ts +58 -0
- package/dist/runtime/host-runtime-contract.js +813 -0
- package/dist/runtime/host-runtime-lease.d.ts +217 -0
- package/dist/runtime/host-runtime-lease.js +2487 -0
- package/dist/runtime/host-runtime-promotion.d.ts +32 -0
- package/dist/runtime/host-runtime-promotion.js +206 -0
- package/dist/runtime/memory-capacity.d.ts +70 -0
- package/dist/runtime/memory-capacity.js +123 -0
- package/dist/runtime/native-test-postgres-fallback.d.ts +25 -0
- package/dist/runtime/native-test-postgres-fallback.js +91 -0
- package/dist/runtime/node-host-runtime-adapter.d.ts +54 -0
- package/dist/runtime/node-host-runtime-adapter.js +286 -0
- package/dist/runtime/pinned-directory-process.d.ts +9 -0
- package/dist/runtime/pinned-directory-process.js +61 -0
- package/dist/runtime/pinned-source-directory.d.ts +17 -0
- package/dist/runtime/pinned-source-directory.js +94 -0
- package/dist/runtime/playwright-host-runtime-adapter.d.ts +21 -0
- package/dist/runtime/playwright-host-runtime-adapter.js +268 -0
- package/dist/runtime/prepared-runtime-identity.d.ts +6 -0
- package/dist/runtime/prepared-runtime-identity.js +218 -0
- package/dist/runtime/prepared-runtime-readiness.d.ts +30 -0
- package/dist/runtime/prepared-runtime-readiness.js +285 -0
- package/dist/runtime/runtime-isolation-lease.d.ts +67 -0
- package/dist/runtime/runtime-isolation-lease.js +161 -0
- package/dist/runtime/workspace-resource-estimator.d.ts +17 -0
- package/dist/runtime/workspace-resource-estimator.js +174 -0
- package/dist/sandbox/command-runner.d.ts +4 -0
- package/dist/sandbox/command-runner.js +47 -0
- package/dist/sandbox/contract.d.ts +5 -0
- package/dist/sandbox/contract.js +276 -0
- package/dist/sandbox/event-budget.d.ts +11 -0
- package/dist/sandbox/event-budget.js +27 -0
- package/dist/sandbox/image-policy.d.ts +35 -0
- package/dist/sandbox/image-policy.js +247 -0
- package/dist/sandbox/index.d.ts +5 -0
- package/dist/sandbox/index.js +5 -0
- package/dist/sandbox/lease-watchdog.d.ts +18 -0
- package/dist/sandbox/lease-watchdog.js +67 -0
- package/dist/sandbox/preflight.d.ts +84 -0
- package/dist/sandbox/preflight.js +521 -0
- package/dist/sandbox/process.d.ts +20 -0
- package/dist/sandbox/process.js +209 -0
- package/dist/sandbox/provider-auth/codex.d.ts +26 -0
- package/dist/sandbox/provider-auth/codex.js +212 -0
- package/dist/sandbox/repository-lock.d.ts +6 -0
- package/dist/sandbox/repository-lock.js +129 -0
- package/dist/sandbox/runtime.d.ts +302 -0
- package/dist/sandbox/runtime.js +2368 -0
- package/dist/sandbox/sandbox.types.d.ts +93 -0
- package/dist/sandbox/sandbox.types.js +1 -0
- package/dist/sandbox/smoke.d.ts +76 -0
- package/dist/sandbox/smoke.js +380 -0
- package/dist/sandbox/worker-supervisor.d.ts +13 -0
- package/dist/sandbox/worker-supervisor.js +36 -0
- package/dist/sandbox/worker.d.ts +1 -0
- package/dist/sandbox/worker.js +225 -0
- package/dist/sandbox/workspace.service.d.ts +13 -0
- package/dist/sandbox/workspace.service.js +74 -0
- package/dist/session/managed-attempt-tools.d.ts +28 -0
- package/dist/session/managed-attempt-tools.js +1016 -0
- package/dist/session/managed-build-coordinator.d.ts +5 -0
- package/dist/session/managed-build-coordinator.js +106 -0
- package/dist/session/managed-build-receipt.d.ts +21 -0
- package/dist/session/managed-build-receipt.js +337 -0
- package/dist/session/managed-verification-timeouts.d.ts +8 -0
- package/dist/session/managed-verification-timeouts.js +17 -0
- package/dist/session/phase-completion.artifacts.d.ts +27 -0
- package/dist/session/phase-completion.artifacts.js +829 -0
- package/dist/session/phase-completion.d.ts +53 -42
- package/dist/session/phase-completion.helpers.d.ts +117 -0
- package/dist/session/phase-completion.helpers.js +707 -0
- package/dist/session/phase-completion.js +125 -3007
- package/dist/session/phase-completion.prompts.d.ts +14 -0
- package/dist/session/phase-completion.prompts.js +233 -0
- package/dist/session/phase-completion.runtime.d.ts +49 -0
- package/dist/session/phase-completion.runtime.js +840 -0
- package/dist/session/phase-completion.schemas.d.ts +3 -0
- package/dist/session/phase-completion.schemas.js +859 -0
- package/dist/session/phase-completion.validation.d.ts +38 -0
- package/dist/session/phase-completion.validation.js +918 -0
- package/dist/session/pre-publication-validation.d.ts +75 -0
- package/dist/session/pre-publication-validation.js +661 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.d.ts +72 -0
- package/dist/session/provider-cli/bridge-cli.conformance-fixtures.js +462 -0
- package/dist/session/provider-cli/ci-repair-readiness.d.ts +16 -0
- package/dist/session/provider-cli/ci-repair-readiness.js +110 -0
- package/dist/session/provider-cli/provider-cli.artifacts.d.ts +95 -0
- package/dist/session/provider-cli/provider-cli.artifacts.js +812 -0
- package/dist/session/provider-cli/provider-cli.base.d.ts +1 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.d.ts +12 -0
- package/dist/session/provider-cli/provider-cli.deterministic-payloads.js +279 -0
- package/dist/session/provider-cli/provider-cli.deterministic.d.ts +14 -0
- package/dist/session/provider-cli/provider-cli.deterministic.js +407 -0
- package/dist/session/provider-cli/provider-cli.process.d.ts +99 -0
- package/dist/session/provider-cli/provider-cli.process.js +874 -0
- package/dist/session/provider-cli/provider-cli.prompts.d.ts +52 -0
- package/dist/session/provider-cli/provider-cli.prompts.js +715 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.d.ts +11 -0
- package/dist/session/provider-cli/provider-cli.qa-sidecars.js +66 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.d.ts +6 -0
- package/dist/session/provider-cli/provider-cli.repair-epoch.js +11 -0
- package/dist/session/provider-cli/provider-cli.runtime.d.ts +22 -0
- package/dist/session/provider-cli/provider-cli.runtime.js +92 -0
- package/dist/session/provider-cli/provider-cli.service.d.ts +47 -53
- package/dist/session/provider-cli/provider-cli.service.js +1258 -2497
- package/dist/session/provider-cli/provider-cli.types.d.ts +8 -0
- package/dist/session/provider-cli/provider-output-state.d.ts +32 -0
- package/dist/session/provider-cli/provider-output-state.js +817 -0
- package/dist/session/provider-cli/provider-usage.d.ts +7 -0
- package/dist/session/provider-cli/provider-usage.js +28 -5
- package/dist/session/session-redaction.d.ts +3 -0
- package/dist/session/session-redaction.js +448 -0
- package/dist/session/session.base.d.ts +6 -1
- package/dist/session/session.service.d.ts +8 -2
- package/dist/session/session.service.js +586 -59
- package/dist/session/session.types.d.ts +324 -3
- package/dist/session/session.types.js +939 -10
- package/dist/session/unreferenced-exports.d.ts +12 -0
- package/dist/session/unreferenced-exports.js +59 -0
- package/dist/shared/format/format-bytes.d.ts +1 -0
- package/dist/shared/format/format-bytes.js +9 -0
- package/dist/shared/git/git.js +2 -1
- package/dist/shared/path/path.d.ts +1 -0
- package/dist/shared/path/path.js +21 -1
- package/dist/shared/process/dotnet-toolchain.d.ts +17 -0
- package/dist/shared/process/dotnet-toolchain.js +307 -0
- package/dist/shared/process/host-process-recovery.d.ts +12 -0
- package/dist/shared/process/host-process-recovery.js +79 -0
- package/dist/shared/process/host-process-supervisor.d.ts +59 -0
- package/dist/shared/process/host-process-supervisor.js +331 -0
- package/dist/shared/process/process.d.ts +3 -0
- package/dist/shared/process/process.js +13 -7
- package/dist/shared/process/windows-launch.d.ts +8 -0
- package/dist/shared/process/windows-launch.js +41 -0
- package/dist/shared/provider-capabilities.d.ts +4 -0
- package/dist/shared/provider-capabilities.js +88 -0
- package/dist/shared/redaction/redaction.d.ts +1 -0
- package/dist/shared/redaction/redaction.js +47 -4
- package/dist/shared/time/monotonic.d.ts +1 -0
- package/dist/shared/time/monotonic.js +8 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/worktree/artifact-injector.d.ts +3 -2
- package/dist/worktree/artifact-injector.js +82 -10
- package/dist/worktree/dependency-warmer.d.ts +53 -1
- package/dist/worktree/dependency-warmer.js +619 -11
- package/dist/worktree/index.d.ts +2 -1
- package/dist/worktree/index.js +1 -0
- package/dist/worktree/prior-phase-artifact-hydrator.d.ts +2 -0
- package/dist/worktree/prior-phase-artifact-hydrator.js +136 -0
- package/dist/worktree/story-qa-reference-hydrator.d.ts +4 -0
- package/dist/worktree/story-qa-reference-hydrator.js +253 -0
- package/dist/worktree/worktree-paths.d.ts +4 -0
- package/dist/worktree/worktree-paths.js +30 -0
- package/dist/worktree/worktree.base.d.ts +3 -0
- package/dist/worktree/worktree.service.d.ts +16 -3
- package/dist/worktree/worktree.service.js +334 -54
- package/dist/worktree/worktree.types.d.ts +9 -1
- package/package.json +12 -7
- package/runtime/windows-job-supervisor.ps1 +722 -0
- package/runtime/windows-listener-owner.ps1 +33 -0
|
@@ -1,61 +1,110 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
1
2
|
import fs from "node:fs/promises";
|
|
2
3
|
import path from "node:path";
|
|
4
|
+
import { RepositoryCoordinatorLock } from "../sandbox/repository-lock.js";
|
|
3
5
|
import { GitClient } from "../shared/git/git.js";
|
|
4
6
|
import { ensureDirectory, isSubPath, resolveExistingDirectoryPath, safeSegment } from "../shared/path/path.js";
|
|
5
7
|
import { filterArtifactsForLane, injectWorktreeArtifacts } from "./artifact-injector.js";
|
|
8
|
+
import { hydratePriorPhaseArtifacts } from "./prior-phase-artifact-hydrator.js";
|
|
9
|
+
import { materializeStoryQaReferences } from "./story-qa-reference-hydrator.js";
|
|
6
10
|
import { warmWorktreeDependencies } from "./dependency-warmer.js";
|
|
11
|
+
import { defaultWorktreeCoordinatorStateRoot, managedRepoRoot, resolveManagedWorktreeRoot, } from "./worktree-paths.js";
|
|
7
12
|
export class GitWorktreeService {
|
|
8
13
|
git;
|
|
9
|
-
|
|
14
|
+
coordinator;
|
|
15
|
+
constructor(git = new GitClient(), coordinator = new RepositoryCoordinatorLock(defaultWorktreeCoordinatorStateRoot())) {
|
|
10
16
|
this.git = git;
|
|
17
|
+
this.coordinator = coordinator;
|
|
11
18
|
}
|
|
12
19
|
async prepare(request) {
|
|
13
20
|
const allowedRoot = await ensureDirectory(request.allowedRepoRoot);
|
|
14
21
|
const baseBranch = normalizeBranchName(request.baseBranch, "develop");
|
|
15
|
-
const repoPath = await this.resolveRepoPath(request.repo, allowedRoot, baseBranch);
|
|
16
|
-
await this.ensureGitIdentity(repoPath);
|
|
17
|
-
await this.ensureBaseBranch(repoPath, baseBranch);
|
|
18
22
|
const shortId = request.workItem.id.replace(/-/g, "").slice(0, 12);
|
|
19
23
|
const kind = safeSegment(request.workItem.kind, "work-item");
|
|
20
24
|
const fallbackWorktreeKey = `${kind}/${shortId}`;
|
|
21
25
|
const worktreeKey = request.worktreeKey?.trim() || fallbackWorktreeKey;
|
|
22
26
|
const worktreeSegments = worktreeKeySegments(worktreeKey, kind, shortId);
|
|
23
27
|
const branchName = normalizeBranchName(worktreeSegments.join("/"), "work-item/unknown");
|
|
24
|
-
const
|
|
25
|
-
const
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
await
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
if (!
|
|
28
|
+
const creationCoordinationPath = this.repoCreationCoordinationPath(request.repo, allowedRoot);
|
|
29
|
+
const repoPath = await this.coordinator.withLock(creationCoordinationPath, async () => {
|
|
30
|
+
return await this.resolveRepoPath(request.repo, allowedRoot, baseBranch);
|
|
31
|
+
});
|
|
32
|
+
const resolvedWorktreePath = await this.coordinator.withLock(repoPath, async () => {
|
|
33
|
+
await this.ensureGitIdentity(repoPath);
|
|
34
|
+
const baseStartPoint = await this.resolveBaseStartPoint(repoPath, baseBranch);
|
|
35
|
+
const worktreeRoot = resolveManagedWorktreeRoot(allowedRoot, repoPath);
|
|
36
|
+
const worktreePath = path.join(worktreeRoot, ...worktreeSegments);
|
|
37
|
+
const resolvedWorktreeParent = await ensureDirectory(path.dirname(worktreePath));
|
|
38
|
+
if (!isSubPath(allowedRoot, resolvedWorktreeParent)) {
|
|
39
|
+
throw new Error(`Worktree path must stay under ${allowedRoot}.`);
|
|
40
|
+
}
|
|
41
|
+
let selectedWorktreePath = worktreePath;
|
|
42
|
+
if (!(await directoryExists(worktreePath))) {
|
|
43
|
+
// A prior runner can disappear after Git registers its worktree but before
|
|
44
|
+
// normal cleanup completes. Release that missing-directory registration
|
|
45
|
+
// immediately so retrying the same lease remains idempotent.
|
|
46
|
+
await this.git.run(repoPath, ["worktree", "prune", "--expire", "now"]);
|
|
47
|
+
const registeredWorktreePath = await this.registeredManagedWorktreePath(repoPath, branchName, allowedRoot, worktreeSegments);
|
|
48
|
+
if (!registeredWorktreePath) {
|
|
49
|
+
const branchRef = `refs/heads/${branchName}`;
|
|
50
|
+
if (request.preserveExistingState && await this.gitRefExists(repoPath, branchRef)) {
|
|
51
|
+
// The checkout directory can disappear while its branch still
|
|
52
|
+
// contains completed agent work. Reattach that branch verbatim;
|
|
53
|
+
// `-B ... baseStartPoint` would silently reset it and lose progress.
|
|
54
|
+
await this.git.run(repoPath, ["worktree", "add", worktreePath, branchName]);
|
|
55
|
+
}
|
|
56
|
+
else {
|
|
57
|
+
await this.git.run(repoPath, ["worktree", "add", "-B", branchName, worktreePath, baseStartPoint]);
|
|
58
|
+
}
|
|
59
|
+
return await resolveExistingDirectoryPath(worktreePath);
|
|
60
|
+
}
|
|
61
|
+
// The selected checkout can change when a Bridge reconnects or a local
|
|
62
|
+
// runner takes over. Its managed root therefore changes even though the
|
|
63
|
+
// deterministic lease branch does not. Reattach to the registered,
|
|
64
|
+
// Forge-owned worktree instead of trying to check out that branch twice.
|
|
65
|
+
selectedWorktreePath = registeredWorktreePath;
|
|
66
|
+
}
|
|
67
|
+
const existingWorktreePath = await resolveExistingDirectoryPath(selectedWorktreePath);
|
|
68
|
+
if (!isSubPath(allowedRoot, existingWorktreePath)) {
|
|
39
69
|
throw new Error(`Worktree path must stay under ${allowedRoot}.`);
|
|
40
70
|
}
|
|
41
|
-
|
|
71
|
+
if (!(await gitMetadataExists(existingWorktreePath, allowedRoot))) {
|
|
72
|
+
await this.restoreOrphanedWorktree({
|
|
73
|
+
repoPath,
|
|
74
|
+
worktreePath: existingWorktreePath,
|
|
75
|
+
branchName,
|
|
76
|
+
baseStartPoint,
|
|
77
|
+
allowedRoot,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
const currentBranch = await this.currentBranch(existingWorktreePath);
|
|
42
81
|
if (currentBranch !== branchName) {
|
|
43
|
-
throw new Error(`Existing worktree ${
|
|
82
|
+
throw new Error(`Existing worktree ${existingWorktreePath} is on branch '${currentBranch}', expected '${branchName}'.`);
|
|
83
|
+
}
|
|
84
|
+
if (!request.preserveExistingState && shouldResetExistingWorktree(request)) {
|
|
85
|
+
await this.resetWorktreeToBase(existingWorktreePath, baseStartPoint);
|
|
44
86
|
}
|
|
87
|
+
return existingWorktreePath;
|
|
88
|
+
});
|
|
89
|
+
// Reference inputs and an exact-tree local repair checkpoint must survive output cleanup.
|
|
90
|
+
await materializeStoryQaReferences(resolvedWorktreePath, request, this.git);
|
|
91
|
+
if (!request.preserveExistingState) {
|
|
92
|
+
await clearRunArtifactRoots(resolvedWorktreePath, request);
|
|
45
93
|
}
|
|
46
|
-
await
|
|
47
|
-
await clearRunArtifactRoots(resolvedWorktreePath, request);
|
|
94
|
+
const hydratedPriorPhaseArtifactCount = await hydratePriorPhaseArtifacts(resolvedWorktreePath, request.priorPhaseArtifacts);
|
|
48
95
|
const qaReworkPacketPath = await materializeQaReworkPacket(resolvedWorktreePath, request);
|
|
49
96
|
const artifacts = filterArtifactsForLane(request.artifacts, request.lane, request.phase);
|
|
50
97
|
if (artifacts.length > 0) {
|
|
51
|
-
await injectWorktreeArtifacts(resolvedWorktreePath, artifacts);
|
|
98
|
+
await injectWorktreeArtifacts(resolvedWorktreePath, artifacts, await this.trackedForgePaths(resolvedWorktreePath));
|
|
52
99
|
}
|
|
53
|
-
const dependencyWarm =
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
100
|
+
const dependencyWarm = requiresMutableEnvironmentPreparation(request)
|
|
101
|
+
? await warmWorktreeDependencies({
|
|
102
|
+
baseCheckoutPath: repoPath,
|
|
103
|
+
worktreePath: resolvedWorktreePath,
|
|
104
|
+
allowedRoot,
|
|
105
|
+
warmPathOverrides: request.warmPathOverrides
|
|
106
|
+
})
|
|
107
|
+
: undefined;
|
|
59
108
|
return {
|
|
60
109
|
repoPath,
|
|
61
110
|
worktreePath: resolvedWorktreePath,
|
|
@@ -63,19 +112,88 @@ export class GitWorktreeService {
|
|
|
63
112
|
baseBranch,
|
|
64
113
|
cleanupWorktrees: request.cleanupWorktrees,
|
|
65
114
|
dependencyWarm,
|
|
66
|
-
qaReworkPacketPath
|
|
115
|
+
qaReworkPacketPath,
|
|
116
|
+
hydratedPriorPhaseArtifactCount
|
|
67
117
|
};
|
|
68
118
|
}
|
|
119
|
+
async trackedForgePaths(worktreePath) {
|
|
120
|
+
const output = await this.git.run(worktreePath, ["ls-files", "-z", "--", ".forge"]);
|
|
121
|
+
return new Set(output.split("\0").filter(Boolean).map(value => value.replaceAll("\\", "/")));
|
|
122
|
+
}
|
|
123
|
+
async registeredManagedWorktreePath(repoPath, branchName, allowedRoot, worktreeSegments) {
|
|
124
|
+
const listing = await this.git.run(repoPath, ["worktree", "list", "--porcelain", "-z"]);
|
|
125
|
+
const registered = parseRegisteredWorktrees(repoPath, listing)
|
|
126
|
+
.find(entry => entry.branchName === `refs/heads/${branchName}`);
|
|
127
|
+
if (!registered) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
const resolved = await resolveExistingDirectoryPath(registered.worktreePath);
|
|
131
|
+
if (!isManagedLeaseWorktreePath(allowedRoot, resolved, worktreeSegments)) {
|
|
132
|
+
throw new Error(`Branch '${branchName}' is already checked out outside its Forge-managed lease path at ${resolved}.`);
|
|
133
|
+
}
|
|
134
|
+
return resolved;
|
|
135
|
+
}
|
|
136
|
+
repoCreationCoordinationPath(repo, allowedRoot) {
|
|
137
|
+
if (repo.serverLocalPath && repo.serverLocalPath.trim().length > 0) {
|
|
138
|
+
return path.resolve(repo.serverLocalPath);
|
|
139
|
+
}
|
|
140
|
+
return this.managedRepoPath(repo, allowedRoot);
|
|
141
|
+
}
|
|
69
142
|
async release(handle) {
|
|
70
|
-
if (!handle.cleanupWorktrees
|
|
143
|
+
if (!handle.cleanupWorktrees) {
|
|
71
144
|
return;
|
|
72
145
|
}
|
|
146
|
+
await this.coordinator.withLock(handle.repoPath, async () => {
|
|
147
|
+
if (await directoryExists(handle.worktreePath)) {
|
|
148
|
+
// On Windows, `git worktree remove` can delete the `.git` link and then
|
|
149
|
+
// fail on a locked build output. That leaves a full source tree which is
|
|
150
|
+
// no longer a Git worktree and makes every retry fail during prepare.
|
|
151
|
+
// Move the tree out of its stable lease path first. If the rename fails,
|
|
152
|
+
// leave the valid worktree untouched rather than partially unregistering it.
|
|
153
|
+
const releasePath = `${handle.worktreePath}.forge-release-${randomUUID()}`;
|
|
154
|
+
await renameWorktreeForRelease(handle.worktreePath, releasePath);
|
|
155
|
+
await this.git.run(handle.repoPath, ["worktree", "prune", "--expire", "now"]);
|
|
156
|
+
await fs.rm(releasePath, {
|
|
157
|
+
recursive: true,
|
|
158
|
+
force: true,
|
|
159
|
+
maxRetries: 10,
|
|
160
|
+
retryDelay: 100,
|
|
161
|
+
}).catch(() => undefined);
|
|
162
|
+
}
|
|
163
|
+
// The process can disappear after Git registers a worktree but before the
|
|
164
|
+
// directory is fully materialized (or after an external cleanup removes it).
|
|
165
|
+
// Always prune so those missing-directory registrations do not accumulate.
|
|
166
|
+
await this.git.run(handle.repoPath, ["worktree", "prune", "--expire", "now"]).catch(() => undefined);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
async restoreOrphanedWorktree(input) {
|
|
170
|
+
const orphanStat = await fs.lstat(input.worktreePath);
|
|
171
|
+
if (orphanStat.isSymbolicLink() || !orphanStat.isDirectory()) {
|
|
172
|
+
throw new Error(`Refusing to recover redirected or non-directory worktree ${input.worktreePath}.`);
|
|
173
|
+
}
|
|
174
|
+
const quarantinePath = `${input.worktreePath}.forge-orphan-${randomUUID()}`;
|
|
175
|
+
if (!isSubPath(input.allowedRoot, quarantinePath)) {
|
|
176
|
+
throw new Error(`Worktree recovery path must stay under ${input.allowedRoot}.`);
|
|
177
|
+
}
|
|
178
|
+
await fs.rename(input.worktreePath, quarantinePath);
|
|
73
179
|
try {
|
|
74
|
-
await this.git.run(
|
|
75
|
-
await this.git.run(
|
|
180
|
+
await this.git.run(input.repoPath, ["worktree", "prune", "--expire", "now"]);
|
|
181
|
+
await this.git.run(input.repoPath, [
|
|
182
|
+
"worktree",
|
|
183
|
+
"add",
|
|
184
|
+
"-B",
|
|
185
|
+
input.branchName,
|
|
186
|
+
input.worktreePath,
|
|
187
|
+
input.baseStartPoint,
|
|
188
|
+
]);
|
|
189
|
+
await overlayOrphanedWorktree(input.worktreePath, quarantinePath);
|
|
190
|
+
await fs.rm(quarantinePath, { recursive: true, force: true });
|
|
76
191
|
}
|
|
77
|
-
catch {
|
|
78
|
-
await
|
|
192
|
+
catch (error) {
|
|
193
|
+
await this.git.run(input.repoPath, ["worktree", "remove", "--force", input.worktreePath]).catch(() => undefined);
|
|
194
|
+
await fs.rm(input.worktreePath, { recursive: true, force: true }).catch(() => undefined);
|
|
195
|
+
await fs.rename(quarantinePath, input.worktreePath).catch(() => undefined);
|
|
196
|
+
throw new Error(`Failed to recover orphaned worktree ${input.worktreePath}.`, { cause: error });
|
|
79
197
|
}
|
|
80
198
|
}
|
|
81
199
|
async resolveRepoPath(repo, allowedRoot, baseBranch) {
|
|
@@ -93,7 +211,7 @@ export class GitWorktreeService {
|
|
|
93
211
|
if (discovered) {
|
|
94
212
|
return discovered;
|
|
95
213
|
}
|
|
96
|
-
const repoPath =
|
|
214
|
+
const repoPath = this.managedRepoPath(repo, allowedRoot);
|
|
97
215
|
if (!(await gitMetadataExists(repoPath, allowedRoot))) {
|
|
98
216
|
const repoPathExists = await pathExists(repoPath);
|
|
99
217
|
if (repoPathExists) {
|
|
@@ -124,6 +242,9 @@ export class GitWorktreeService {
|
|
|
124
242
|
}
|
|
125
243
|
return resolved;
|
|
126
244
|
}
|
|
245
|
+
managedRepoPath(repo, allowedRoot) {
|
|
246
|
+
return path.join(managedRepoRoot(allowedRoot), `${safeSegment(repo.name, "repo")}-${repo.id.replace(/-/g, "")}`);
|
|
247
|
+
}
|
|
127
248
|
async findExistingRepoPath(repo, allowedRoot) {
|
|
128
249
|
const expectedRemote = normalizeRemote(repo.remoteUrl);
|
|
129
250
|
const rawName = repo.name.trim();
|
|
@@ -164,21 +285,34 @@ export class GitWorktreeService {
|
|
|
164
285
|
const resolved = await resolveExistingDirectoryPath(candidate);
|
|
165
286
|
return isSubPath(allowedRoot, resolved) ? resolved : null;
|
|
166
287
|
}
|
|
167
|
-
async
|
|
168
|
-
if (
|
|
169
|
-
|
|
288
|
+
async resolveBaseStartPoint(repoPath, baseBranch) {
|
|
289
|
+
if (baseBranch === "HEAD") {
|
|
290
|
+
const head = (await this.git.run(repoPath, ["rev-parse", "--verify", "HEAD^{commit}"])).trim();
|
|
291
|
+
if (!/^[0-9a-f]{40,64}$/iu.test(head)) {
|
|
292
|
+
throw new Error("The repository checkout does not have a valid committed HEAD revision.");
|
|
293
|
+
}
|
|
294
|
+
return head;
|
|
170
295
|
}
|
|
296
|
+
const localRef = `refs/heads/${baseBranch}`;
|
|
297
|
+
const remoteRef = `refs/remotes/origin/${baseBranch}`;
|
|
171
298
|
const origin = await this.git.run(repoPath, ["remote", "get-url", "origin"]).catch(() => "");
|
|
172
|
-
if (origin.trim().length
|
|
173
|
-
|
|
299
|
+
if (origin.trim().length > 0) {
|
|
300
|
+
const fetched = await this.git.run(repoPath, ["fetch", "--prune", "origin"])
|
|
301
|
+
.then(() => true)
|
|
302
|
+
.catch(() => false);
|
|
303
|
+
if (fetched && await this.gitRefExists(repoPath, remoteRef)) {
|
|
304
|
+
return remoteRef;
|
|
305
|
+
}
|
|
174
306
|
}
|
|
175
|
-
await this.
|
|
176
|
-
|
|
177
|
-
await this.git.run(repoPath, ["branch", baseBranch, `refs/remotes/origin/${baseBranch}`]);
|
|
178
|
-
return;
|
|
307
|
+
if (await this.gitRefExists(repoPath, localRef)) {
|
|
308
|
+
return localRef;
|
|
179
309
|
}
|
|
180
310
|
throw new Error(`Base branch '${baseBranch}' was not found locally or on origin.`);
|
|
181
311
|
}
|
|
312
|
+
async resetWorktreeToBase(worktreePath, baseStartPoint) {
|
|
313
|
+
await this.git.run(worktreePath, ["reset", "--hard", baseStartPoint]);
|
|
314
|
+
await this.git.run(worktreePath, ["clean", "-fd"]);
|
|
315
|
+
}
|
|
182
316
|
async gitRefExists(repoPath, gitRef) {
|
|
183
317
|
return this.git.run(repoPath, ["show-ref", "--verify", "--quiet", gitRef]).then(() => true).catch(() => false);
|
|
184
318
|
}
|
|
@@ -190,14 +324,31 @@ export class GitWorktreeService {
|
|
|
190
324
|
await this.git.run(repoPath, ["config", "user.name", "Forge Bridge"]);
|
|
191
325
|
}
|
|
192
326
|
}
|
|
193
|
-
function
|
|
194
|
-
|
|
327
|
+
export async function renameWorktreeForRelease(source, destination, rename = fs.rename, wait = milliseconds => new Promise(resolve => setTimeout(resolve, milliseconds)), maxAttempts = 8) {
|
|
328
|
+
const attempts = Number.isFinite(maxAttempts) && maxAttempts > 0
|
|
329
|
+
? Math.max(1, Math.floor(maxAttempts))
|
|
330
|
+
: 1;
|
|
331
|
+
for (let attempt = 1; attempt <= attempts; attempt += 1) {
|
|
332
|
+
try {
|
|
333
|
+
await rename(source, destination);
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
if (!isTransientWorktreeRenameError(error) || attempt === attempts)
|
|
338
|
+
throw error;
|
|
339
|
+
await wait(Math.min(1_000, 100 * (2 ** (attempt - 1))));
|
|
340
|
+
}
|
|
341
|
+
}
|
|
195
342
|
}
|
|
196
|
-
function
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
343
|
+
function isTransientWorktreeRenameError(error) {
|
|
344
|
+
if (!error || typeof error !== "object" || !("code" in error))
|
|
345
|
+
return false;
|
|
346
|
+
const code = String(error.code ?? "").toUpperCase();
|
|
347
|
+
return code === "EPERM" || code === "EACCES" || code === "EBUSY" || code === "ENOTEMPTY";
|
|
348
|
+
}
|
|
349
|
+
export function requiresMutableEnvironmentPreparation(request) {
|
|
350
|
+
// Missing runtime intent is treated conservatively for older dispatch packets.
|
|
351
|
+
return request.applicationRuntimeRequired !== false;
|
|
201
352
|
}
|
|
202
353
|
function isCloneableRemote(remoteUrl) {
|
|
203
354
|
const trimmed = remoteUrl?.trim() ?? "";
|
|
@@ -206,9 +357,12 @@ function isCloneableRemote(remoteUrl) {
|
|
|
206
357
|
}
|
|
207
358
|
return trimmed.startsWith("git@") || trimmed.includes("://") || path.isAbsolute(trimmed);
|
|
208
359
|
}
|
|
209
|
-
async function clearRunArtifactRoots(worktreePath, request) {
|
|
360
|
+
export async function clearRunArtifactRoots(worktreePath, request) {
|
|
210
361
|
const roots = new Set();
|
|
211
362
|
roots.add(workItemArtifactRoot(worktreePath, request.workItem.id));
|
|
363
|
+
for (const artifact of request.priorPhaseArtifacts ?? []) {
|
|
364
|
+
roots.add(workItemArtifactRoot(worktreePath, artifact.workItemId));
|
|
365
|
+
}
|
|
212
366
|
const ownerType = normalizeOwnerType(request.ownerType);
|
|
213
367
|
const ownerId = request.ownerId?.trim();
|
|
214
368
|
if (ownerType && ownerId) {
|
|
@@ -269,6 +423,132 @@ function normalizeOwnerType(value) {
|
|
|
269
423
|
}
|
|
270
424
|
return null;
|
|
271
425
|
}
|
|
426
|
+
function shouldResetExistingWorktree(request) {
|
|
427
|
+
const lane = request.lane.trim().toLowerCase();
|
|
428
|
+
const phase = request.phase.trim().toLowerCase().replace(/-/g, "_");
|
|
429
|
+
if (lane === "develop" && (phase === "implementation" || phase === "self_review" || phase === "validation")) {
|
|
430
|
+
return false;
|
|
431
|
+
}
|
|
432
|
+
if (lane === "qa" || phase === "qa") {
|
|
433
|
+
return false;
|
|
434
|
+
}
|
|
435
|
+
return true;
|
|
436
|
+
}
|
|
437
|
+
function parseRegisteredWorktrees(repoPath, output) {
|
|
438
|
+
const worktrees = [];
|
|
439
|
+
let current = null;
|
|
440
|
+
for (const field of output.split("\0")) {
|
|
441
|
+
if (field.startsWith("worktree ")) {
|
|
442
|
+
if (current)
|
|
443
|
+
worktrees.push(current);
|
|
444
|
+
const rawPath = field.slice("worktree ".length);
|
|
445
|
+
current = {
|
|
446
|
+
worktreePath: path.resolve(path.isAbsolute(rawPath) ? rawPath : path.join(repoPath, rawPath)),
|
|
447
|
+
branchName: null,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
else if (field.startsWith("branch ") && current) {
|
|
451
|
+
current.branchName = field.slice("branch ".length).trim();
|
|
452
|
+
}
|
|
453
|
+
}
|
|
454
|
+
if (current)
|
|
455
|
+
worktrees.push(current);
|
|
456
|
+
return worktrees;
|
|
457
|
+
}
|
|
458
|
+
function isManagedLeaseWorktreePath(allowedRoot, candidate, worktreeSegments) {
|
|
459
|
+
if (!isSubPath(allowedRoot, candidate)) {
|
|
460
|
+
return false;
|
|
461
|
+
}
|
|
462
|
+
const relativeSegments = path.relative(path.resolve(allowedRoot), path.resolve(candidate))
|
|
463
|
+
.split(path.sep)
|
|
464
|
+
.filter(Boolean);
|
|
465
|
+
const normalize = (value) => process.platform === "win32" ? value.toLowerCase() : value;
|
|
466
|
+
if (normalize(relativeSegments[0] ?? "") !== ".forge-worktrees"
|
|
467
|
+
|| relativeSegments.length !== 2 + worktreeSegments.length) {
|
|
468
|
+
return false;
|
|
469
|
+
}
|
|
470
|
+
const suffix = relativeSegments.slice(-worktreeSegments.length).map(normalize);
|
|
471
|
+
return suffix.every((segment, index) => segment === normalize(worktreeSegments[index] ?? ""));
|
|
472
|
+
}
|
|
473
|
+
async function overlayOrphanedWorktree(worktreePath, orphanPath) {
|
|
474
|
+
// A failed Windows cleanup can remove only part of a worktree before its
|
|
475
|
+
// `.git` link disappears. Start from Git's clean checkout so missing tracked
|
|
476
|
+
// files are restored, then overlay every surviving agent file. Moving entries
|
|
477
|
+
// on the same volume preserves pnpm junctions without requiring symlink
|
|
478
|
+
// privileges, unlike fs.cp.
|
|
479
|
+
const backupRoot = `${worktreePath}.forge-base-${randomUUID()}`;
|
|
480
|
+
const moves = [];
|
|
481
|
+
try {
|
|
482
|
+
await fs.mkdir(backupRoot, { recursive: true });
|
|
483
|
+
const orphanEntries = await fs.readdir(orphanPath, { withFileTypes: true });
|
|
484
|
+
for (const entry of orphanEntries) {
|
|
485
|
+
if (entry.name === ".git") {
|
|
486
|
+
continue;
|
|
487
|
+
}
|
|
488
|
+
await overlayEntry({
|
|
489
|
+
sourcePath: path.join(orphanPath, entry.name),
|
|
490
|
+
destinationPath: path.join(worktreePath, entry.name),
|
|
491
|
+
backupPath: path.join(backupRoot, entry.name),
|
|
492
|
+
moves,
|
|
493
|
+
});
|
|
494
|
+
}
|
|
495
|
+
await fs.rm(backupRoot, { recursive: true, force: true });
|
|
496
|
+
}
|
|
497
|
+
catch (error) {
|
|
498
|
+
for (const move of moves.reverse()) {
|
|
499
|
+
await fs.mkdir(path.dirname(move.sourcePath), { recursive: true }).catch(() => undefined);
|
|
500
|
+
await fs.rename(move.destinationPath, move.sourcePath).catch(() => undefined);
|
|
501
|
+
if (move.backupPath) {
|
|
502
|
+
await fs.mkdir(path.dirname(move.destinationPath), { recursive: true }).catch(() => undefined);
|
|
503
|
+
await fs.rename(move.backupPath, move.destinationPath).catch(() => undefined);
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
await fs.rm(backupRoot, { recursive: true, force: true }).catch(() => undefined);
|
|
507
|
+
throw error;
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
async function overlayEntry(input) {
|
|
511
|
+
const sourceStat = await fs.lstat(input.sourcePath);
|
|
512
|
+
const destinationStat = await fs.lstat(input.destinationPath).catch(() => null);
|
|
513
|
+
if (sourceStat.isDirectory() &&
|
|
514
|
+
!sourceStat.isSymbolicLink() &&
|
|
515
|
+
destinationStat?.isDirectory() &&
|
|
516
|
+
!destinationStat.isSymbolicLink()) {
|
|
517
|
+
const entries = await fs.readdir(input.sourcePath, { withFileTypes: true });
|
|
518
|
+
for (const entry of entries) {
|
|
519
|
+
await overlayEntry({
|
|
520
|
+
sourcePath: path.join(input.sourcePath, entry.name),
|
|
521
|
+
destinationPath: path.join(input.destinationPath, entry.name),
|
|
522
|
+
backupPath: path.join(input.backupPath, entry.name),
|
|
523
|
+
moves: input.moves,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
let preservedBasePath;
|
|
529
|
+
if (destinationStat) {
|
|
530
|
+
await fs.mkdir(path.dirname(input.backupPath), { recursive: true });
|
|
531
|
+
await fs.rename(input.destinationPath, input.backupPath);
|
|
532
|
+
preservedBasePath = input.backupPath;
|
|
533
|
+
}
|
|
534
|
+
else {
|
|
535
|
+
await fs.mkdir(path.dirname(input.destinationPath), { recursive: true });
|
|
536
|
+
}
|
|
537
|
+
try {
|
|
538
|
+
await fs.rename(input.sourcePath, input.destinationPath);
|
|
539
|
+
}
|
|
540
|
+
catch (error) {
|
|
541
|
+
if (preservedBasePath) {
|
|
542
|
+
await fs.rename(preservedBasePath, input.destinationPath).catch(() => undefined);
|
|
543
|
+
}
|
|
544
|
+
throw error;
|
|
545
|
+
}
|
|
546
|
+
input.moves.push({
|
|
547
|
+
sourcePath: input.sourcePath,
|
|
548
|
+
destinationPath: input.destinationPath,
|
|
549
|
+
backupPath: preservedBasePath,
|
|
550
|
+
});
|
|
551
|
+
}
|
|
272
552
|
function qaReworkPacketFileName(packetPath) {
|
|
273
553
|
const normalized = packetPath.trim().replace(/\\/g, "/");
|
|
274
554
|
const fileName = normalized.split("/").filter(Boolean).at(-1) ?? "";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DispatchQaReworkContext, DispatchRepo, DispatchWorkItem, DispatchResolvedArtifact } from "../session/session.types.js";
|
|
1
|
+
import type { DispatchQaReworkContext, DispatchPriorPhaseArtifact, DispatchRepo, DispatchWorkItem, DispatchResolvedArtifact, DispatchStoryQaContext } from "../session/session.types.js";
|
|
2
2
|
import type { WarmDependenciesResult } from "./dependency-warmer.js";
|
|
3
3
|
export interface WorktreePrepareRequest {
|
|
4
4
|
readonly repo: DispatchRepo;
|
|
@@ -12,8 +12,15 @@ export interface WorktreePrepareRequest {
|
|
|
12
12
|
readonly allowedRepoRoot: string;
|
|
13
13
|
readonly cleanupWorktrees: boolean;
|
|
14
14
|
readonly artifacts: readonly DispatchResolvedArtifact[];
|
|
15
|
+
readonly priorPhaseArtifacts?: readonly DispatchPriorPhaseArtifact[];
|
|
16
|
+
readonly storyQa?: DispatchStoryQaContext | null;
|
|
17
|
+
readonly referenceRunId?: string;
|
|
18
|
+
readonly referenceCycleRunId?: string;
|
|
19
|
+
readonly effectivePlanAmendmentHash?: string | null;
|
|
15
20
|
readonly qaRework?: DispatchQaReworkContext | null;
|
|
16
21
|
readonly warmPathOverrides?: readonly string[];
|
|
22
|
+
readonly applicationRuntimeRequired?: boolean;
|
|
23
|
+
readonly preserveExistingState?: boolean;
|
|
17
24
|
}
|
|
18
25
|
export interface WorktreeHandle {
|
|
19
26
|
readonly repoPath: string;
|
|
@@ -23,4 +30,5 @@ export interface WorktreeHandle {
|
|
|
23
30
|
readonly cleanupWorktrees: boolean;
|
|
24
31
|
readonly dependencyWarm?: WarmDependenciesResult;
|
|
25
32
|
readonly qaReworkPacketPath?: string;
|
|
33
|
+
readonly hydratedPriorPhaseArtifactCount?: number;
|
|
26
34
|
}
|
package/package.json
CHANGED
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@loucompanion/forge-bridge",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4-dev.04a1e5b9e484",
|
|
4
4
|
"description": "Forge runner bridge CLI for connecting local developer machines to Forge dispatch.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
+
"forge": "dist/cli/bin.js",
|
|
8
9
|
"forge-bridge": "dist/cli/bin.js"
|
|
9
10
|
},
|
|
10
11
|
"main": "./dist/index.js",
|
|
@@ -18,6 +19,7 @@
|
|
|
18
19
|
},
|
|
19
20
|
"files": [
|
|
20
21
|
"dist",
|
|
22
|
+
"runtime",
|
|
21
23
|
"README.md"
|
|
22
24
|
],
|
|
23
25
|
"repository": {
|
|
@@ -30,20 +32,22 @@
|
|
|
30
32
|
"url": "https://github.com/coleman399/Forge/issues"
|
|
31
33
|
},
|
|
32
34
|
"engines": {
|
|
33
|
-
"node": "
|
|
35
|
+
"node": "^22.13.0 || >=24.0.0"
|
|
34
36
|
},
|
|
35
37
|
"publishConfig": {
|
|
36
38
|
"access": "public"
|
|
37
39
|
},
|
|
38
40
|
"scripts": {
|
|
39
41
|
"build": "tsc -p tsconfig.json",
|
|
40
|
-
"test": "
|
|
42
|
+
"test": "node scripts/run-owned-tests.mjs",
|
|
43
|
+
"test:residue": "node scripts/run-owned-residue-gate.mjs",
|
|
41
44
|
"test:watch": "vitest",
|
|
42
|
-
"smoke:package": "
|
|
43
|
-
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\""
|
|
45
|
+
"smoke:package": "tsc -p tsconfig.json && node scripts/smoke-package.mjs",
|
|
46
|
+
"lint": "eslint \"src/**/*.ts\" \"scripts/**/*.mjs\" \"vitest.config.ts\" \"vitest.setup.ts\""
|
|
44
47
|
},
|
|
45
48
|
"dependencies": {
|
|
46
|
-
"@microsoft/signalr": "^8.0.17"
|
|
49
|
+
"@microsoft/signalr": "^8.0.17",
|
|
50
|
+
"yaml": "^2.9.0"
|
|
47
51
|
},
|
|
48
52
|
"devDependencies": {
|
|
49
53
|
"@eslint/js": "^10.0.1",
|
|
@@ -52,6 +56,7 @@
|
|
|
52
56
|
"globals": "^17.6.0",
|
|
53
57
|
"typescript": "^5.8.3",
|
|
54
58
|
"typescript-eslint": "^8.61.1",
|
|
55
|
-
"
|
|
59
|
+
"vite": "^7.3.7",
|
|
60
|
+
"vitest": "^4.1.12"
|
|
56
61
|
}
|
|
57
62
|
}
|