@phyzess/pappra 0.1.0-beta.13 → 0.1.0-beta.18
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 +134 -15
- package/assets/integrations/skills/pappra/SKILL.md +28 -7
- package/assets/integrations/skills/pappra/references/collaboration.md +633 -0
- package/assets/integrations/skills/pappra/references/environment.md +360 -0
- package/assets/integrations/skills/pappra/references/hosts.md +20 -0
- package/assets/integrations/skills/pappra/references/operations.md +36 -5
- package/dist/adapters/pi/recovery-extension.d.ts +65 -0
- package/dist/adapters/pi/recovery-extension.js +238 -0
- package/dist/adapters/pi/session-transition-extension.d.ts +30 -0
- package/dist/adapters/pi/session-transition-extension.js +265 -0
- package/dist/app/automatic-handoff-events.d.ts +26 -0
- package/dist/app/automatic-handoff-events.js +134 -0
- package/dist/app/bounded-file.d.ts +2 -0
- package/dist/app/bounded-file.js +2 -0
- package/dist/app/capability-evidence/claude.d.ts +37 -0
- package/dist/app/capability-evidence/claude.js +206 -0
- package/dist/app/capability-evidence/codex.d.ts +35 -0
- package/dist/app/capability-evidence/codex.js +193 -0
- package/dist/app/capability-evidence/index.d.ts +33 -0
- package/dist/app/capability-evidence/index.js +117 -0
- package/dist/app/capability-evidence/pi.d.ts +73 -0
- package/dist/app/capability-evidence/pi.js +669 -0
- package/dist/app/capability-evidence/shared.d.ts +69 -0
- package/dist/app/capability-evidence/shared.js +195 -0
- package/dist/app/collab-auto-handoff.d.ts +55 -0
- package/dist/app/collab-auto-handoff.js +384 -0
- package/dist/app/collab-bindings.d.ts +57 -0
- package/dist/app/collab-bindings.js +1006 -0
- package/dist/app/collab-bootstrap-transport.d.ts +163 -0
- package/dist/app/collab-bootstrap-transport.js +769 -0
- package/dist/app/collab-bootstrap.d.ts +137 -0
- package/dist/app/collab-bootstrap.js +3455 -0
- package/dist/app/collab-handoff.d.ts +12 -0
- package/dist/app/collab-handoff.js +511 -0
- package/dist/app/collab-messages.d.ts +2 -0
- package/dist/app/collab-messages.js +2 -0
- package/dist/app/collab-notify.d.ts +156 -0
- package/dist/app/collab-notify.js +1208 -0
- package/dist/app/collab-preflight.d.ts +34 -0
- package/dist/app/collab-preflight.js +96 -0
- package/dist/app/collab-recovery-executor.d.ts +161 -0
- package/dist/app/collab-recovery-executor.js +1153 -0
- package/dist/app/collab-recovery-projection.d.ts +103 -0
- package/dist/app/collab-recovery-projection.js +659 -0
- package/dist/app/collab-recovery.d.ts +25 -0
- package/dist/app/collab-recovery.js +429 -0
- package/dist/app/collab-resources.d.ts +16 -0
- package/dist/app/collab-resources.js +278 -0
- package/dist/app/collab-role-events.d.ts +22 -0
- package/dist/app/collab-role-events.js +329 -0
- package/dist/app/collab-role-orchestration.d.ts +53 -0
- package/dist/app/collab-role-orchestration.js +601 -0
- package/dist/app/collab-role-plan.d.ts +81 -0
- package/dist/app/collab-role-plan.js +504 -0
- package/dist/app/collab-role-repair.d.ts +109 -0
- package/dist/app/collab-role-repair.js +821 -0
- package/dist/app/collab-rollover.d.ts +263 -0
- package/dist/app/collab-rollover.js +1112 -0
- package/dist/app/collab-route.d.ts +64 -0
- package/dist/app/collab-route.js +240 -0
- package/dist/app/collab-session-transition.d.ts +221 -0
- package/dist/app/collab-session-transition.js +3334 -0
- package/dist/app/collab-state.d.ts +7 -0
- package/dist/app/collab-state.js +162 -0
- package/dist/app/collab-task-store.d.ts +22 -0
- package/dist/app/collab-task-store.js +204 -0
- package/dist/app/collab-work-execution.d.ts +127 -0
- package/dist/app/collab-work-execution.js +1027 -0
- package/dist/app/collab-work-plan.d.ts +87 -0
- package/dist/app/collab-work-plan.js +437 -0
- package/dist/app/collaboration.d.ts +8 -2
- package/dist/app/collaboration.js +101 -12
- package/dist/app/doctor.js +108 -5
- package/dist/app/host-mcp.js +34 -8
- package/dist/app/host-probe.js +4 -4
- package/dist/app/init.d.ts +58 -4
- package/dist/app/init.js +768 -155
- package/dist/app/managed-pi-bootstrap-admission.d.ts +14 -0
- package/dist/app/managed-pi-bootstrap-admission.js +104 -0
- package/dist/app/managed-pi-bootstrap-refresh.d.ts +10 -0
- package/dist/app/managed-pi-bootstrap-refresh.js +78 -0
- package/dist/app/managed-pi-delivery.d.ts +8 -0
- package/dist/app/managed-pi-delivery.js +110 -0
- package/dist/app/managed-pi-handoff-receiver.d.ts +15 -0
- package/dist/app/managed-pi-handoff-receiver.js +263 -0
- package/dist/app/managed-pi-handoff-reference.d.ts +3 -0
- package/dist/app/managed-pi-handoff-reference.js +60 -0
- package/dist/app/managed-pi-herdr.d.ts +21 -0
- package/dist/app/managed-pi-herdr.js +185 -0
- package/dist/app/managed-pi-initial-admission.d.ts +28 -0
- package/dist/app/managed-pi-initial-admission.js +416 -0
- package/dist/app/managed-pi-initial-launch.d.ts +5 -0
- package/dist/app/managed-pi-initial-launch.js +61 -0
- package/dist/app/managed-pi-native-observation.d.ts +36 -0
- package/dist/app/managed-pi-native-observation.js +191 -0
- package/dist/app/managed-pi-readiness.d.ts +29 -0
- package/dist/app/managed-pi-readiness.js +288 -0
- package/dist/app/managed-pi-repair-stop.d.ts +21 -0
- package/dist/app/managed-pi-repair-stop.js +371 -0
- package/dist/app/managed-pi-resume-admission.d.ts +32 -0
- package/dist/app/managed-pi-resume-admission.js +618 -0
- package/dist/app/managed-pi-retirement.d.ts +28 -0
- package/dist/app/managed-pi-retirement.js +112 -0
- package/dist/app/managed-pi-sdk-control.d.ts +7 -0
- package/dist/app/managed-pi-sdk-control.js +169 -0
- package/dist/app/managed-pi-sdk-runtime.d.ts +3 -0
- package/dist/app/managed-pi-sdk-runtime.js +483 -0
- package/dist/app/managed-pi-source-admission.d.ts +14 -0
- package/dist/app/managed-pi-source-admission.js +323 -0
- package/dist/app/managed-pi-transition-host.d.ts +36 -0
- package/dist/app/managed-pi-transition-host.js +292 -0
- package/dist/app/managed-pi.d.ts +54 -0
- package/dist/app/managed-pi.js +248 -0
- package/dist/app/shell-readiness.d.ts +66 -0
- package/dist/app/shell-readiness.js +368 -0
- package/dist/app/task-pin.d.ts +2 -23
- package/dist/app/task-pin.js +2 -113
- package/dist/cli/commands/collaboration-binding.d.ts +13 -0
- package/dist/cli/commands/collaboration-binding.js +118 -0
- package/dist/cli/commands/collaboration-bootstrap.d.ts +10 -0
- package/dist/cli/commands/collaboration-bootstrap.js +84 -0
- package/dist/cli/commands/collaboration-capabilities.d.ts +13 -0
- package/dist/cli/commands/collaboration-capabilities.js +127 -0
- package/dist/cli/commands/collaboration-handoff.d.ts +13 -0
- package/dist/cli/commands/collaboration-handoff.js +102 -0
- package/dist/cli/commands/collaboration-message.d.ts +17 -0
- package/dist/cli/commands/collaboration-message.js +177 -0
- package/dist/cli/commands/collaboration-plan.d.ts +8 -0
- package/dist/cli/commands/collaboration-plan.js +108 -0
- package/dist/cli/commands/collaboration-preflight.d.ts +10 -0
- package/dist/cli/commands/collaboration-preflight.js +167 -0
- package/dist/cli/commands/collaboration-recovery-run.d.ts +46 -0
- package/dist/cli/commands/collaboration-recovery-run.js +224 -0
- package/dist/cli/commands/collaboration-recovery.d.ts +11 -0
- package/dist/cli/commands/collaboration-recovery.js +104 -0
- package/dist/cli/commands/collaboration-resources.d.ts +11 -0
- package/dist/cli/commands/collaboration-resources.js +96 -0
- package/dist/cli/commands/collaboration-role-plan.d.ts +9 -0
- package/dist/cli/commands/collaboration-role-plan.js +69 -0
- package/dist/cli/commands/collaboration-role-run.d.ts +11 -0
- package/dist/cli/commands/collaboration-role-run.js +128 -0
- package/dist/cli/commands/collaboration-rollover.d.ts +10 -0
- package/dist/cli/commands/collaboration-rollover.js +103 -0
- package/dist/cli/commands/collaboration-state.d.ts +10 -0
- package/dist/cli/commands/collaboration-state.js +96 -0
- package/dist/cli/commands/collaboration-transition.d.ts +10 -0
- package/dist/cli/commands/collaboration-transition.js +75 -0
- package/dist/cli/commands/collaboration-work-run.d.ts +34 -0
- package/dist/cli/commands/collaboration-work-run.js +178 -0
- package/dist/cli/commands/collaboration.d.ts +4 -0
- package/dist/cli/commands/collaboration.js +39 -4
- package/dist/cli/commands/continuity.js +2 -6
- package/dist/cli/commands/docs.d.ts +2 -0
- package/dist/cli/commands/docs.js +89 -15
- package/dist/cli/commands/host.d.ts +7 -1
- package/dist/cli/commands/host.js +122 -7
- package/dist/cli/commands/init.d.ts +20 -3
- package/dist/cli/commands/init.js +897 -232
- package/dist/cli/commands/mcp.d.ts +1 -0
- package/dist/cli/commands/mcp.js +11 -5
- package/dist/cli/commands/repair.d.ts +12 -0
- package/dist/cli/commands/repair.js +41 -0
- package/dist/cli/commands/runtime.d.ts +1 -1
- package/dist/cli/commands/runtime.js +139 -38
- package/dist/cli/commands/session.d.ts +7 -0
- package/dist/cli/commands/session.js +82 -0
- package/dist/cli/commands/task.js +19 -5
- package/dist/cli/run.js +866 -24
- package/dist/code/identity.d.ts +7 -0
- package/dist/code/identity.js +7 -3
- package/dist/code/registration.js +7 -0
- package/dist/code/service.d.ts +9 -0
- package/dist/code/service.js +16 -3
- package/dist/code/text.js +19 -7
- package/dist/collab/admission.d.ts +91 -66
- package/dist/collab/admission.js +21 -6
- package/dist/collab/canonical.d.ts +1 -1
- package/dist/collab/canonical.js +44 -3
- package/dist/collab/envelope.d.ts +2 -2
- package/dist/collab/envelope.js +12 -12
- package/dist/collab/first-work.d.ts +3 -0
- package/dist/collab/first-work.js +15 -0
- package/dist/collab/handoff.d.ts +16 -0
- package/dist/collab/handoff.js +39 -5
- package/dist/collab/index.d.ts +97 -1
- package/dist/collab/index.js +9 -1
- package/dist/collab/messages.js +126 -3
- package/dist/collab/persisted-messages.d.ts +123 -0
- package/dist/collab/persisted-messages.js +496 -0
- package/dist/collab/preflight.d.ts +1 -1
- package/dist/collab/resources.d.ts +13 -3
- package/dist/collab/resources.js +37 -6
- package/dist/collab/rollover.d.ts +311 -0
- package/dist/collab/rollover.js +1024 -0
- package/dist/collab/store.d.ts +1 -0
- package/dist/collab/store.js +1 -4
- package/dist/collab/types.d.ts +2 -354
- package/dist/collab/validation.d.ts +2 -10
- package/dist/collab/validation.js +2 -303
- package/dist/collab/work-plan.d.ts +105 -0
- package/dist/collab/work-plan.js +843 -0
- package/dist/continuity/context.d.ts +11 -2
- package/dist/continuity/context.js +100 -13
- package/dist/continuity/handoff-bundle.d.ts +296 -0
- package/dist/continuity/handoff-bundle.js +477 -0
- package/dist/continuity/index.d.ts +2 -0
- package/dist/continuity/index.js +1 -0
- package/dist/continuity/paths.js +10 -2
- package/dist/continuity/service.js +91 -15
- package/dist/continuity/task-pin.d.ts +24 -0
- package/dist/continuity/task-pin.js +590 -0
- package/dist/doctor.d.ts +1 -0
- package/dist/doctor.js +6 -0
- package/dist/experimental/index.d.ts +13 -0
- package/dist/experimental/index.js +25 -0
- package/dist/experimental/pi.d.ts +4 -0
- package/dist/experimental/pi.js +12 -0
- package/dist/hosts/claude/native.js +1 -1
- package/dist/hosts/codex/native.js +1 -1
- package/dist/hosts/codex/protocol.js +1 -1
- package/dist/hosts/integration/bootstrap-config.d.ts +58 -0
- package/dist/hosts/integration/bootstrap-config.js +254 -0
- package/dist/hosts/integration/bundle-export.d.ts +44 -0
- package/dist/hosts/integration/bundle-export.js +366 -3
- package/dist/hosts/integration/bundle.js +3 -0
- package/dist/hosts/integration/hook.js +36 -9
- package/dist/hosts/integration/pappra-bootstrap-extension.mjs +80 -0
- package/dist/hosts/integration/pappra-extension-runtime.mjs +59 -24631
- package/dist/hosts/integration/pappra-hook.mjs +61 -24749
- package/dist/hosts/integration/pappra-recovery-extension.mjs +259 -0
- package/dist/hosts/integration/pappra-session-transition-extension.mjs +276 -0
- package/dist/hosts/integration/pi-bootstrap-extension.d.ts +36 -0
- package/dist/hosts/integration/pi-bootstrap-extension.js +101 -0
- package/dist/hosts/integration/pi-bootstrap-guard.d.ts +125 -0
- package/dist/hosts/integration/pi-bootstrap-guard.js +438 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.d.ts +63 -0
- package/dist/hosts/integration/pi-bootstrap-runtime.js +223 -0
- package/dist/hosts/integration/pi-native-events.d.ts +112 -0
- package/dist/hosts/integration/pi-native-events.js +348 -0
- package/dist/hosts/integration/pi-rollover-extension.d.ts +166 -0
- package/dist/hosts/integration/pi-rollover-extension.js +431 -0
- package/dist/hosts/integration/recovery-config.d.ts +48 -0
- package/dist/hosts/integration/recovery-config.js +117 -0
- package/dist/hosts/integration/service.d.ts +6 -1
- package/dist/hosts/integration/service.js +485 -35
- package/dist/hosts/integration/support.d.ts +7 -0
- package/dist/hosts/integration/support.js +9 -1
- package/dist/hosts/integration/templates.d.ts +3 -0
- package/dist/hosts/integration/templates.js +26 -3
- package/dist/hosts/pi/managed-host-identity.d.ts +2 -0
- package/dist/hosts/pi/managed-host-identity.js +40 -0
- package/dist/hosts/pi/managed-provider-diagnostics.d.ts +6 -0
- package/dist/hosts/pi/managed-provider-diagnostics.js +184 -0
- package/dist/hosts/pi/managed-sdk-session.d.ts +335 -0
- package/dist/hosts/pi/managed-sdk-session.js +1639 -0
- package/dist/hosts/pi/managed-session-extension.d.ts +20 -0
- package/dist/hosts/pi/managed-session-extension.js +84 -0
- package/dist/hosts/pi/managed-session.d.ts +177 -0
- package/dist/hosts/pi/managed-session.js +1557 -0
- package/dist/hosts/pi/protocol.js +1 -1
- package/dist/hosts/pi/query-result.js +1 -1
- package/dist/hosts/pi/resume-capability.d.ts +41 -0
- package/dist/hosts/pi/resume-capability.js +33 -0
- package/dist/hosts/pi/rpc.d.ts +182 -134
- package/dist/hosts/pi/rpc.js +230 -37
- package/dist/hosts/pilot.d.ts +97 -74
- package/dist/hosts/pilot.js +5 -1
- package/dist/hosts/version.d.ts +3 -0
- package/dist/hosts/version.js +18 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +0 -1
- package/dist/infra/bounded-file.d.ts +19 -0
- package/dist/infra/bounded-file.js +66 -0
- package/dist/infra/fs.js +10 -2
- package/dist/infra/git.d.ts +29 -0
- package/dist/infra/git.js +45 -9
- package/dist/infra/json-property.d.ts +2 -0
- package/dist/infra/json-property.js +69 -0
- package/dist/infra/json.d.ts +4 -0
- package/dist/infra/json.js +24 -0
- package/dist/infra/owned-process-group.js +1 -1
- package/dist/infra/private-lock.d.ts +16 -0
- package/dist/infra/private-lock.js +148 -0
- package/dist/infra/private-state.d.ts +7 -0
- package/dist/infra/private-state.js +146 -0
- package/dist/infra/process-identity.d.ts +12 -0
- package/dist/infra/process-identity.js +142 -0
- package/dist/infra/validation.d.ts +5 -0
- package/dist/infra/validation.js +15 -0
- package/dist/init.d.ts +1 -0
- package/dist/init.js +6 -0
- package/dist/install/apply.d.ts +9 -1
- package/dist/install/apply.js +176 -12
- package/dist/install/dependencies.d.ts +38 -0
- package/dist/install/dependencies.js +282 -0
- package/dist/install/ownership.js +30 -1
- package/dist/install/references.d.ts +31 -0
- package/dist/install/references.js +208 -0
- package/dist/install/repair.d.ts +37 -0
- package/dist/install/repair.js +798 -0
- package/dist/install/reset-local.d.ts +16 -0
- package/dist/install/reset-local.js +304 -0
- package/dist/install/rollback.d.ts +3 -0
- package/dist/install/rollback.js +9 -3
- package/dist/install/runtime.d.ts +10 -6
- package/dist/install/runtime.js +299 -59
- package/dist/install/source-fingerprint.d.ts +2 -0
- package/dist/install/source-fingerprint.js +103 -0
- package/dist/knowledge/catalog.d.ts +1 -0
- package/dist/knowledge/catalog.js +143 -10
- package/dist/knowledge/journal.js +17 -7
- package/dist/knowledge/paths.js +2 -1
- package/dist/knowledge/refs.js +4 -0
- package/dist/knowledge/register-batch.d.ts +4 -0
- package/dist/knowledge/register-batch.js +225 -0
- package/dist/knowledge/relocate.d.ts +5 -0
- package/dist/knowledge/relocate.js +92 -0
- package/dist/knowledge/scan.js +103 -47
- package/dist/knowledge/service.d.ts +3 -0
- package/dist/knowledge/service.js +33 -1
- package/dist/knowledge/write.d.ts +4 -1
- package/dist/knowledge/write.js +1 -1
- package/dist/mcp/server.d.ts +4 -2
- package/dist/mcp/server.js +87 -136
- package/dist/schemas/collaboration-state-validation.d.ts +6 -0
- package/dist/schemas/collaboration-state-validation.js +342 -0
- package/dist/schemas/collaboration-state.d.ts +436 -0
- package/dist/schemas/collaboration-state.js +1 -0
- package/dist/schemas/collaboration.d.ts +335 -0
- package/dist/schemas/collaboration.js +272 -0
- package/dist/schemas/continuity.d.ts +8 -0
- package/dist/schemas/continuity.js +13 -3
- package/dist/schemas/doctor.d.ts +1 -0
- package/dist/schemas/doctor.js +1 -0
- package/dist/schemas/document.d.ts +61 -0
- package/dist/schemas/document.js +9 -0
- package/dist/schemas/errors.d.ts +1 -0
- package/dist/schemas/errors.js +1 -0
- package/dist/schemas/host.d.ts +24 -0
- package/dist/schemas/host.js +19 -0
- package/dist/schemas/install.d.ts +8 -2
- package/dist/schemas/install.js +11 -1
- package/dist/schemas/managed-pi-execution.d.ts +16 -0
- package/dist/schemas/managed-pi-execution.js +2 -0
- package/dist/schemas/outcome.d.ts +9 -5
- package/dist/workspace/config-file.js +1 -3
- package/dist/workspace/documents.js +12 -2
- package/dist/workspace/repositories.d.ts +7 -0
- package/dist/workspace/repositories.js +14 -5
- package/package.json +35 -35
- package/skills/pappra/SKILL.md +28 -7
- package/skills/pappra/references/collaboration.md +633 -0
- package/skills/pappra/references/environment.md +360 -0
- package/skills/pappra/references/hosts.md +20 -0
- package/skills/pappra/references/operations.md +36 -5
- package/dist/app/collab-from.d.ts +0 -35
- package/dist/app/collab-from.js +0 -84
package/dist/app/init.js
CHANGED
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
import { randomUUID } from 'node:crypto';
|
|
2
2
|
import { basename, dirname, join, resolve } from 'node:path';
|
|
3
|
-
import { mkdir, open, readFile, rename, rm, rmdir, writeFile, } from 'node:fs/promises';
|
|
3
|
+
import { mkdir, open, readFile, readdir, rename, rm, rmdir, writeFile, } from 'node:fs/promises';
|
|
4
4
|
import { createDiagnostic, hasErrors, pointer, } from '../schemas/diagnostics.js';
|
|
5
5
|
import { diagnosticCodes } from '../schemas/errors.js';
|
|
6
6
|
import { INIT_CHOICES_SCHEMA, initPlanSchema, parseInitChoices, } from '../schemas/init.js';
|
|
7
7
|
import { machineConfigSchema } from '../schemas/machine.js';
|
|
8
|
-
import { isExternalRepositoryPath, isPathInside, isPortableRelativePath, normalizePortableRelativePath, resolvePortableRelativePath, samePath, } from '../schemas/paths.js';
|
|
8
|
+
import { isExternalRepositoryPath, isPathInside, isPortableRelativePath, normalizePortableRelativePath, resolvePortableRelativePath, samePath, toWorkspaceRelativePath, } from '../schemas/paths.js';
|
|
9
9
|
import { WORKSPACE_SCHEMA, validateWorkspaceConfigSemantics, workspaceConfigSchema, } from '../schemas/workspace.js';
|
|
10
10
|
import { getPackageInfo, getPackageRoot } from '../package-info.js';
|
|
11
11
|
import { spawnSync } from 'node:child_process';
|
|
12
12
|
import { executableOnPath } from '../hosts/detect.js';
|
|
13
13
|
import { HOST_SUPPORT } from '../hosts/integration/support.js';
|
|
14
14
|
import { observePath, resolveRealpath, sha256Digest } from '../infra/fs.js';
|
|
15
|
+
import { inspectGitRepository, } from '../infra/git.js';
|
|
15
16
|
import { inspectMachineConfig, inspectWorkspaceConfig, } from '../workspace/config.js';
|
|
16
17
|
import { discoverWorkspace, } from '../workspace/discover.js';
|
|
18
|
+
import { isExcludedPath } from '../workspace/ignore.js';
|
|
17
19
|
import { resolveWorkspaceRoot } from '../workspace/resolve.js';
|
|
18
20
|
import { buildInitPlan } from '../install/plan.js';
|
|
19
|
-
import { backupPath, ensureStatePathBoundary,
|
|
20
|
-
import { beginManagedApply, completeManagedApply, failManagedApply, recordManagedFile, updateMachineRuntime, } from '../install/apply.js';
|
|
21
|
+
import { backupPath, ensureStatePathBoundary, writeBytesAtomic, writeWorkspaceManifest, } from '../install/ownership.js';
|
|
22
|
+
import { beginManagedApply, completeManagedApply, failManagedApply, inspectWorkspaceInstallReadiness, recordManagedFile, updateMachineRuntime, verifyWorkspaceInstallReady, } from '../install/apply.js';
|
|
21
23
|
import { installRuntime, } from '../install/runtime.js';
|
|
22
24
|
const workspaceConfigRelativePath = '.pappra/workspace.json';
|
|
23
25
|
const machineConfigRelativePath = '.pappra/local/machine.json';
|
|
@@ -96,6 +98,33 @@ function candidateKey(value) {
|
|
|
96
98
|
export async function resolveInitRoot(rootPath) {
|
|
97
99
|
const requestedRoot = resolve(rootPath);
|
|
98
100
|
const resolution = await resolveWorkspaceRoot({ startPath: requestedRoot });
|
|
101
|
+
if (resolution.state === 'incomplete' && resolution.root !== null) {
|
|
102
|
+
const canonicalRequested = await resolveRealpath(requestedRoot);
|
|
103
|
+
const marker = join(resolution.root, '.pappra');
|
|
104
|
+
const local = join(marker, 'local');
|
|
105
|
+
const markerObservation = await observePath(marker);
|
|
106
|
+
const localObservation = await observePath(local);
|
|
107
|
+
const configObservation = await observePath(join(marker, 'workspace.json'));
|
|
108
|
+
if (canonicalRequested !== null &&
|
|
109
|
+
samePath(canonicalRequested, resolution.root) &&
|
|
110
|
+
markerObservation.kind === 'directory' &&
|
|
111
|
+
configObservation.kind === 'missing' &&
|
|
112
|
+
['missing', 'directory'].includes(localObservation.kind) &&
|
|
113
|
+
(await readdir(marker)).every((name) => name === 'local')) {
|
|
114
|
+
const ready = await inspectWorkspaceInstallReadiness(resolution.root);
|
|
115
|
+
if (!ready.ok)
|
|
116
|
+
return failure('unavailable', ready.code, ready.message, [], ready.nextAction);
|
|
117
|
+
return {
|
|
118
|
+
ok: true,
|
|
119
|
+
value: {
|
|
120
|
+
requestedRoot,
|
|
121
|
+
root: resolution.root,
|
|
122
|
+
registered: false,
|
|
123
|
+
diagnostics: resolution.diagnostics.filter((entry) => entry.code !== diagnosticCodes.workspaceRegistrationIncomplete),
|
|
124
|
+
},
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
}
|
|
99
128
|
if (resolution.state === 'unavailable' || resolution.state === 'incomplete') {
|
|
100
129
|
return failureFromDiagnostics('unavailable', resolution.diagnostics, diagnosticCodes.workspaceStartInvalid, `Cannot initialize from ${requestedRoot}.`);
|
|
101
130
|
}
|
|
@@ -145,6 +174,9 @@ export async function inspectInitContext(rootPath) {
|
|
|
145
174
|
if (!rootResult.ok)
|
|
146
175
|
return rootResult;
|
|
147
176
|
const root = rootResult.value.root;
|
|
177
|
+
const installReady = await inspectWorkspaceInstallReadiness(root);
|
|
178
|
+
if (!installReady.ok)
|
|
179
|
+
return failure('unavailable', installReady.code, installReady.message, [], installReady.nextAction);
|
|
148
180
|
const rootIdentity = await observeRootIdentity(root);
|
|
149
181
|
if (rootIdentity === null) {
|
|
150
182
|
return failure('unavailable', diagnosticCodes.workspaceRootInvalid, `Workspace root is no longer a directory: ${root}`, rootResult.value.diagnostics, 'Retry init from an existing workspace root; no plan was produced.');
|
|
@@ -252,6 +284,327 @@ const derivedExcludes = [
|
|
|
252
284
|
'test-results',
|
|
253
285
|
];
|
|
254
286
|
const derivedValidationScripts = ['lint', 'typecheck', 'test'];
|
|
287
|
+
/**
|
|
288
|
+
* Pure discovery-based selections shared by derived and interactive setup.
|
|
289
|
+
* Mode-specific repositories, validation, graph and host choices stay with
|
|
290
|
+
* their callers.
|
|
291
|
+
*/
|
|
292
|
+
export function deriveCommonInitDefaults(discovery) {
|
|
293
|
+
const knowledgeCandidates = listKnowledgeCandidates(discovery);
|
|
294
|
+
const knowledgeSources = [];
|
|
295
|
+
for (const name of ['AGENTS.md', 'CLAUDE.md']) {
|
|
296
|
+
const candidate = knowledgeCandidates.find((entry) => entry.path.toLowerCase() === name.toLowerCase());
|
|
297
|
+
if (candidate !== undefined)
|
|
298
|
+
knowledgeSources.push(candidate.path);
|
|
299
|
+
}
|
|
300
|
+
const docs = knowledgeCandidates.find((entry) => entry.path === 'docs');
|
|
301
|
+
if (docs !== undefined)
|
|
302
|
+
knowledgeSources.push(docs.path);
|
|
303
|
+
return {
|
|
304
|
+
name: basename(discovery.root),
|
|
305
|
+
knowledge: {
|
|
306
|
+
sources: knowledgeSources,
|
|
307
|
+
newDocuments: '.pappra/knowledge',
|
|
308
|
+
taskVisibility: 'private',
|
|
309
|
+
},
|
|
310
|
+
code: { symbols: 'auto', prepare: 'on-demand' },
|
|
311
|
+
excludes: [...derivedExcludes],
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Converts an existing valid workspace/machine pair into a lossless editable
|
|
316
|
+
* selection draft. Extensions and continuity are intentionally not part of
|
|
317
|
+
* InitChoices; plan reconstruction preserves them from the existing config.
|
|
318
|
+
*/
|
|
319
|
+
export function existingInitChoices(config, machine) {
|
|
320
|
+
return {
|
|
321
|
+
schema: INIT_CHOICES_SCHEMA,
|
|
322
|
+
name: config.name,
|
|
323
|
+
workspaceId: config.workspaceId,
|
|
324
|
+
repositories: config.repositories.map((repository) => ({
|
|
325
|
+
path: repository.path,
|
|
326
|
+
id: repository.id,
|
|
327
|
+
access: repository.access,
|
|
328
|
+
})),
|
|
329
|
+
knowledge: {
|
|
330
|
+
sources: [...config.knowledge.sources],
|
|
331
|
+
newDocuments: config.knowledge.newDocuments,
|
|
332
|
+
taskVisibility: config.knowledge.taskVisibility,
|
|
333
|
+
},
|
|
334
|
+
code: { ...config.code },
|
|
335
|
+
validation: config.validation.map((validation) => `workspace-config:${validation.id}`),
|
|
336
|
+
excludes: [...config.excludes],
|
|
337
|
+
hosts: [...(machine?.selectedHosts ?? [])],
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
function portableFieldError(label, value) {
|
|
341
|
+
if (value.includes('\0') || value.includes('\\')) {
|
|
342
|
+
return `${label} must use forward slashes and must not contain NUL or backslashes.`;
|
|
343
|
+
}
|
|
344
|
+
if (value.startsWith('/') ||
|
|
345
|
+
/^[A-Za-z]:/.test(value) ||
|
|
346
|
+
value.startsWith('\\\\')) {
|
|
347
|
+
return `${label} must be relative to the workspace root, not absolute.`;
|
|
348
|
+
}
|
|
349
|
+
if (!isPortableRelativePath(value)) {
|
|
350
|
+
return `${label} must be a workspace-relative path that does not escape the root.`;
|
|
351
|
+
}
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
/**
|
|
355
|
+
* Immediate UI field feedback using the same name schema, portable-path rules,
|
|
356
|
+
* literal-prefix exclusion policy and workspace-relative semantics as planning.
|
|
357
|
+
*/
|
|
358
|
+
export function validateInitField(field, value) {
|
|
359
|
+
if (field === 'name') {
|
|
360
|
+
const parsed = workspaceConfigSchema.shape.name.safeParse(value.trim());
|
|
361
|
+
return parsed.success
|
|
362
|
+
? null
|
|
363
|
+
: (parsed.error.issues[0]?.message ?? 'Use a non-empty workspace name.');
|
|
364
|
+
}
|
|
365
|
+
if (field === 'newDocuments') {
|
|
366
|
+
const parsed = workspaceConfigSchema.shape.knowledge.shape.newDocuments.safeParse(value);
|
|
367
|
+
if (!parsed.success) {
|
|
368
|
+
return (parsed.error.issues[0]?.message ??
|
|
369
|
+
'Use a workspace-relative new document destination.');
|
|
370
|
+
}
|
|
371
|
+
return portableFieldError('New document destination', value);
|
|
372
|
+
}
|
|
373
|
+
if (field === 'repositoryPath') {
|
|
374
|
+
return portableFieldError('Repository path', value);
|
|
375
|
+
}
|
|
376
|
+
if (field === 'knowledgePath') {
|
|
377
|
+
return portableFieldError('Knowledge source path', value);
|
|
378
|
+
}
|
|
379
|
+
for (const entry of value
|
|
380
|
+
.split(/[,\n]/)
|
|
381
|
+
.map((item) => item.trim())
|
|
382
|
+
.filter((item) => item !== '')) {
|
|
383
|
+
const pathError = portableFieldError('Exclude entry', entry);
|
|
384
|
+
if (pathError !== null)
|
|
385
|
+
return pathError;
|
|
386
|
+
if (/[*?[\]{}]/.test(entry)) {
|
|
387
|
+
return `Exclude entry "${entry}" contains glob metacharacters; use literal workspace-relative prefixes.`;
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
return null;
|
|
391
|
+
}
|
|
392
|
+
async function nestedWorkspaceBoundaryForPath(root, path, kind) {
|
|
393
|
+
let current = kind === 'directory' ? path : dirname(path);
|
|
394
|
+
while (!samePath(current, root)) {
|
|
395
|
+
if (!isPathInside(root, current))
|
|
396
|
+
return null;
|
|
397
|
+
const marker = await observePath(join(current, '.pappra'));
|
|
398
|
+
if (marker.kind !== 'missing')
|
|
399
|
+
return current;
|
|
400
|
+
const parent = dirname(current);
|
|
401
|
+
if (samePath(parent, current) || !isPathInside(root, parent))
|
|
402
|
+
return null;
|
|
403
|
+
current = parent;
|
|
404
|
+
}
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
function explicitPathDiagnostic(code, message, location, hint) {
|
|
408
|
+
return createDiagnostic({
|
|
409
|
+
code,
|
|
410
|
+
severity: 'error',
|
|
411
|
+
message,
|
|
412
|
+
source: 'init explicit path inspection',
|
|
413
|
+
pointer: location,
|
|
414
|
+
hint,
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
/** Inspects one user-entered path only; it never broadens workspace discovery. */
|
|
418
|
+
export async function inspectInitPathAddition(root, kind, inputPath, excludes = [], location = null) {
|
|
419
|
+
const label = kind === 'repository' ? 'Repository path' : 'Knowledge source path';
|
|
420
|
+
const pathError = portableFieldError(label, inputPath);
|
|
421
|
+
if (pathError !== null) {
|
|
422
|
+
return {
|
|
423
|
+
ok: false,
|
|
424
|
+
diagnostics: [
|
|
425
|
+
explicitPathDiagnostic(diagnosticCodes.initInvalidInput, pathError, location, 'Enter an existing workspace-relative path using forward slashes; absolute and escaping paths are not accepted.'),
|
|
426
|
+
],
|
|
427
|
+
};
|
|
428
|
+
}
|
|
429
|
+
if (inputPath === '~' || inputPath.startsWith('~/')) {
|
|
430
|
+
return {
|
|
431
|
+
ok: false,
|
|
432
|
+
diagnostics: [
|
|
433
|
+
explicitPathDiagnostic(diagnosticCodes.initInvalidInput, `${label} must not use a home-relative path: ${inputPath}`, location, 'Enter a path relative to the resolved workspace root.'),
|
|
434
|
+
],
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
if (kind === 'repository' && isExternalRepositoryPath(inputPath)) {
|
|
438
|
+
return {
|
|
439
|
+
ok: false,
|
|
440
|
+
diagnostics: [
|
|
441
|
+
explicitPathDiagnostic(diagnosticCodes.initInvalidInput, 'Explicit repository addition does not create external repository mappings.', location, 'Add an existing checkout located inside the workspace root.'),
|
|
442
|
+
],
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
const requestedPath = normalizePortableRelativePath(inputPath);
|
|
446
|
+
const candidatePath = resolvePortableRelativePath(root, requestedPath);
|
|
447
|
+
if (!isPathInside(root, candidatePath)) {
|
|
448
|
+
return {
|
|
449
|
+
ok: false,
|
|
450
|
+
diagnostics: [
|
|
451
|
+
explicitPathDiagnostic(diagnosticCodes.initInvalidInput, `${label} escapes the workspace root: ${inputPath}`, location, 'Keep the path within the resolved workspace root.'),
|
|
452
|
+
],
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
try {
|
|
456
|
+
const initial = await observePath(candidatePath);
|
|
457
|
+
if (initial.kind === 'missing') {
|
|
458
|
+
return {
|
|
459
|
+
ok: false,
|
|
460
|
+
diagnostics: [
|
|
461
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
462
|
+
? diagnosticCodes.repositoryPathMissing
|
|
463
|
+
: diagnosticCodes.knowledgeSourceMissing, `${label} does not exist: ${candidatePath}`, location, 'Correct the path or cancel the addition; init does not create the location.'),
|
|
464
|
+
],
|
|
465
|
+
};
|
|
466
|
+
}
|
|
467
|
+
const realPath = await resolveRealpath(candidatePath);
|
|
468
|
+
if (realPath === null) {
|
|
469
|
+
return {
|
|
470
|
+
ok: false,
|
|
471
|
+
diagnostics: [
|
|
472
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
473
|
+
? diagnosticCodes.repositoryPathMissing
|
|
474
|
+
: diagnosticCodes.knowledgeSourceNotReadable, `Cannot resolve ${label.toLowerCase()}: ${candidatePath}`, location, 'Check the entered path and permissions.'),
|
|
475
|
+
],
|
|
476
|
+
};
|
|
477
|
+
}
|
|
478
|
+
if (!isPathInside(root, realPath)) {
|
|
479
|
+
return {
|
|
480
|
+
ok: false,
|
|
481
|
+
diagnostics: [
|
|
482
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
483
|
+
? diagnosticCodes.repositorySymlinkEscape
|
|
484
|
+
: diagnosticCodes.knowledgeSourceSymlinkEscape, `${label} resolves outside the workspace root: ${candidatePath} -> ${realPath}`, location, 'Keep the resolved target inside the workspace; explicit additions do not create external mappings.'),
|
|
485
|
+
],
|
|
486
|
+
};
|
|
487
|
+
}
|
|
488
|
+
const target = await observePath(realPath);
|
|
489
|
+
if (target.kind !== 'file' && target.kind !== 'directory') {
|
|
490
|
+
return {
|
|
491
|
+
ok: false,
|
|
492
|
+
diagnostics: [
|
|
493
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
494
|
+
? diagnosticCodes.repositoryNotGit
|
|
495
|
+
: diagnosticCodes.knowledgeSourceNotReadable, `${label} is not an existing regular file or directory: ${realPath}`, location, 'Special files, FIFOs and unreadable locations are not accepted.'),
|
|
496
|
+
],
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
if (kind === 'repository' && target.kind !== 'directory') {
|
|
500
|
+
return {
|
|
501
|
+
ok: false,
|
|
502
|
+
diagnostics: [
|
|
503
|
+
explicitPathDiagnostic(diagnosticCodes.repositoryNotGit, `Repository path is not a checkout directory: ${realPath}`, location, 'Enter the exact existing Git checkout root, not a file or a subdirectory.'),
|
|
504
|
+
],
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
const nested = await nestedWorkspaceBoundaryForPath(root, realPath, target.kind);
|
|
508
|
+
if (nested !== null) {
|
|
509
|
+
return {
|
|
510
|
+
ok: false,
|
|
511
|
+
diagnostics: [
|
|
512
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
513
|
+
? diagnosticCodes.repositoryNestedWorkspace
|
|
514
|
+
: diagnosticCodes.discoveryNestedWorkspace, `${label} is inside nested workspace ${nested}.`, location, 'Configure the nested workspace separately; outer init does not absorb its paths.'),
|
|
515
|
+
],
|
|
516
|
+
};
|
|
517
|
+
}
|
|
518
|
+
const canonicalPath = toWorkspaceRelativePath(root, realPath);
|
|
519
|
+
if (canonicalPath === null) {
|
|
520
|
+
return {
|
|
521
|
+
ok: false,
|
|
522
|
+
diagnostics: [
|
|
523
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
524
|
+
? diagnosticCodes.repositorySymlinkEscape
|
|
525
|
+
: diagnosticCodes.knowledgeSourceSymlinkEscape, `${label} has no safe workspace-relative identity: ${realPath}`, location, 'Keep the resolved target inside the workspace root.'),
|
|
526
|
+
],
|
|
527
|
+
};
|
|
528
|
+
}
|
|
529
|
+
if (isExcludedPath(canonicalPath, excludes)) {
|
|
530
|
+
const label = kind === 'repository' ? 'Repository' : 'Knowledge source';
|
|
531
|
+
return {
|
|
532
|
+
ok: false,
|
|
533
|
+
diagnostics: [
|
|
534
|
+
explicitPathDiagnostic(kind === 'repository'
|
|
535
|
+
? diagnosticCodes.initRepositoryUnknown
|
|
536
|
+
: diagnosticCodes.initKnowledgeUnknown, `${label} is excluded by workspace policy: ${canonicalPath}`, location, 'Remove or change the exclude before adding this path.'),
|
|
537
|
+
],
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
let git = null;
|
|
541
|
+
if (kind === 'repository') {
|
|
542
|
+
const inspected = await inspectGitRepository(realPath);
|
|
543
|
+
if (!inspected.ok) {
|
|
544
|
+
return {
|
|
545
|
+
ok: false,
|
|
546
|
+
diagnostics: [
|
|
547
|
+
createDiagnostic({
|
|
548
|
+
code: inspected.code,
|
|
549
|
+
severity: 'error',
|
|
550
|
+
message: inspected.message,
|
|
551
|
+
source: 'init explicit repository path',
|
|
552
|
+
pointer: location,
|
|
553
|
+
hint: inspected.nextAction ??
|
|
554
|
+
'Check the exact repository path and Git metadata.',
|
|
555
|
+
}),
|
|
556
|
+
],
|
|
557
|
+
};
|
|
558
|
+
}
|
|
559
|
+
if (!samePath(inspected.value.root, realPath)) {
|
|
560
|
+
return {
|
|
561
|
+
ok: false,
|
|
562
|
+
diagnostics: [
|
|
563
|
+
explicitPathDiagnostic(diagnosticCodes.repositoryNotGit, `Entered location is not the exact repository root: ${realPath}; Git resolved ${inspected.value.root}.`, location, 'Enter the checkout root itself; init does not substitute a parent repository.'),
|
|
564
|
+
],
|
|
565
|
+
};
|
|
566
|
+
}
|
|
567
|
+
if (!isPathInside(root, inspected.value.root)) {
|
|
568
|
+
return {
|
|
569
|
+
ok: false,
|
|
570
|
+
diagnostics: [
|
|
571
|
+
explicitPathDiagnostic(diagnosticCodes.repositorySymlinkEscape, `Repository resolves outside the workspace root: ${inspected.value.root}`, location, 'Use a workspace-local checkout; external mapping creation is not part of init path addition.'),
|
|
572
|
+
],
|
|
573
|
+
};
|
|
574
|
+
}
|
|
575
|
+
git = inspected.value;
|
|
576
|
+
}
|
|
577
|
+
if (target.stats === null) {
|
|
578
|
+
return {
|
|
579
|
+
ok: false,
|
|
580
|
+
diagnostics: [
|
|
581
|
+
explicitPathDiagnostic(diagnosticCodes.fileUnreadable, `Cannot snapshot ${label.toLowerCase()}: ${realPath}`, location, 'Check the path and permissions, then try the explicit addition again.'),
|
|
582
|
+
],
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
return {
|
|
586
|
+
ok: true,
|
|
587
|
+
observation: {
|
|
588
|
+
kind,
|
|
589
|
+
requestedPath,
|
|
590
|
+
path: canonicalPath,
|
|
591
|
+
realPath,
|
|
592
|
+
targetKind: target.kind,
|
|
593
|
+
device: target.stats.dev,
|
|
594
|
+
inode: target.stats.ino,
|
|
595
|
+
git,
|
|
596
|
+
},
|
|
597
|
+
};
|
|
598
|
+
}
|
|
599
|
+
catch (error) {
|
|
600
|
+
return {
|
|
601
|
+
ok: false,
|
|
602
|
+
diagnostics: [
|
|
603
|
+
explicitPathDiagnostic(diagnosticCodes.fileUnreadable, `Cannot inspect ${label.toLowerCase()} ${candidatePath}: ${error instanceof Error ? error.message : String(error)}`, location, 'Check permissions and the path; init does not open document bodies during path addition.'),
|
|
604
|
+
],
|
|
605
|
+
};
|
|
606
|
+
}
|
|
607
|
+
}
|
|
255
608
|
export function detectedHostVersion(command, pathEnv) {
|
|
256
609
|
const executable = executableOnPath(command, pathEnv);
|
|
257
610
|
if (executable === null)
|
|
@@ -290,27 +643,15 @@ export async function deriveInitChoices(discovery, options = {}) {
|
|
|
290
643
|
});
|
|
291
644
|
return { path, access };
|
|
292
645
|
});
|
|
646
|
+
const common = deriveCommonInitDefaults(discovery);
|
|
293
647
|
const knowledgeCandidates = listKnowledgeCandidates(discovery);
|
|
294
|
-
const
|
|
295
|
-
const knowledgeSources = [];
|
|
296
|
-
for (const name of rootRuleFiles) {
|
|
297
|
-
const candidate = knowledgeCandidates.find((entry) => entry.path.toLowerCase() === name.toLowerCase());
|
|
298
|
-
if (candidate === undefined)
|
|
299
|
-
continue;
|
|
300
|
-
knowledgeSources.push(candidate.path);
|
|
648
|
+
for (const source of common.knowledge.sources) {
|
|
301
649
|
rules.push({
|
|
302
650
|
field: 'knowledge.sources',
|
|
303
|
-
value:
|
|
304
|
-
reason:
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
const docs = knowledgeCandidates.find((entry) => entry.path === 'docs');
|
|
308
|
-
if (docs !== undefined) {
|
|
309
|
-
knowledgeSources.push(docs.path);
|
|
310
|
-
rules.push({
|
|
311
|
-
field: 'knowledge.sources',
|
|
312
|
-
value: docs.path,
|
|
313
|
-
reason: 'documentation directory reported by discovery',
|
|
651
|
+
value: source,
|
|
652
|
+
reason: source === 'docs'
|
|
653
|
+
? 'documentation directory reported by discovery'
|
|
654
|
+
: 'workspace-root rule file reported by discovery',
|
|
314
655
|
});
|
|
315
656
|
}
|
|
316
657
|
const codegraphDetected = executableOnPath('codegraph', pathEnv) !== null &&
|
|
@@ -325,12 +666,12 @@ export async function deriveInitChoices(discovery, options = {}) {
|
|
|
325
666
|
});
|
|
326
667
|
rules.push({
|
|
327
668
|
field: 'code.symbols',
|
|
328
|
-
value:
|
|
669
|
+
value: common.code.symbols,
|
|
329
670
|
reason: 'symbol providers are detected passively at use time',
|
|
330
671
|
});
|
|
331
672
|
rules.push({
|
|
332
673
|
field: 'code.prepare',
|
|
333
|
-
value:
|
|
674
|
+
value: common.code.prepare,
|
|
334
675
|
reason: 'index preparation stays an explicit, bounded action',
|
|
335
676
|
});
|
|
336
677
|
const validation = [];
|
|
@@ -347,7 +688,7 @@ export async function deriveInitChoices(discovery, options = {}) {
|
|
|
347
688
|
}
|
|
348
689
|
rules.push({
|
|
349
690
|
field: 'excludes',
|
|
350
|
-
value:
|
|
691
|
+
value: common.excludes.join(', '),
|
|
351
692
|
reason: 'built-in defaults keep generated and dependency trees out of scope',
|
|
352
693
|
});
|
|
353
694
|
const hosts = [];
|
|
@@ -371,7 +712,7 @@ export async function deriveInitChoices(discovery, options = {}) {
|
|
|
371
712
|
reason: `host executable reports the pinned version ${version}`,
|
|
372
713
|
});
|
|
373
714
|
}
|
|
374
|
-
const name =
|
|
715
|
+
const name = common.name;
|
|
375
716
|
rules.push({
|
|
376
717
|
field: 'name',
|
|
377
718
|
value: name,
|
|
@@ -381,14 +722,14 @@ export async function deriveInitChoices(discovery, options = {}) {
|
|
|
381
722
|
schema: INIT_CHOICES_SCHEMA,
|
|
382
723
|
name,
|
|
383
724
|
repositories,
|
|
384
|
-
knowledge:
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
725
|
+
knowledge: common.knowledge,
|
|
726
|
+
code: {
|
|
727
|
+
graph,
|
|
728
|
+
symbols: common.code.symbols,
|
|
729
|
+
prepare: common.code.prepare,
|
|
388
730
|
},
|
|
389
|
-
code: { graph, symbols: 'auto', prepare: 'on-demand' },
|
|
390
731
|
validation,
|
|
391
|
-
excludes: [...
|
|
732
|
+
excludes: [...common.excludes],
|
|
392
733
|
hosts,
|
|
393
734
|
};
|
|
394
735
|
return {
|
|
@@ -402,7 +743,7 @@ export async function deriveInitChoices(discovery, options = {}) {
|
|
|
402
743
|
},
|
|
403
744
|
};
|
|
404
745
|
}
|
|
405
|
-
function selectRepositories(choices, discovery, root) {
|
|
746
|
+
function selectRepositories(choices, discovery, root, additionalRepositories = new Map(), reservedRepositoryIds = []) {
|
|
406
747
|
const diagnostics = [];
|
|
407
748
|
const byKey = new Map();
|
|
408
749
|
for (const repository of discovery.repositories) {
|
|
@@ -411,6 +752,14 @@ function selectRepositories(choices, discovery, root) {
|
|
|
411
752
|
byKey.set(normalizePortableRelativePath(repository.relativePath), repository);
|
|
412
753
|
}
|
|
413
754
|
}
|
|
755
|
+
for (const [key, repository] of additionalRepositories) {
|
|
756
|
+
if (!byKey.has(key))
|
|
757
|
+
byKey.set(key, repository);
|
|
758
|
+
}
|
|
759
|
+
const reservedIds = new Set([
|
|
760
|
+
...reservedRepositoryIds,
|
|
761
|
+
...choices.repositories.flatMap((choice) => choice.id === undefined ? [] : [choice.id]),
|
|
762
|
+
]);
|
|
414
763
|
const usedIds = new Set();
|
|
415
764
|
const selected = [];
|
|
416
765
|
const seenRepositories = new Set();
|
|
@@ -421,10 +770,10 @@ function selectRepositories(choices, discovery, root) {
|
|
|
421
770
|
diagnostics.push(createDiagnostic({
|
|
422
771
|
code: diagnosticCodes.initRepositoryUnknown,
|
|
423
772
|
severity: 'error',
|
|
424
|
-
message: `Init selection references an undiscovered repository: ${choice.path}`,
|
|
773
|
+
message: `Init selection references an undiscovered or uninspected repository: ${choice.path}`,
|
|
425
774
|
source: 'init choices',
|
|
426
775
|
pointer: pointer('repositories', index, 'path'),
|
|
427
|
-
hint: 'Select a
|
|
776
|
+
hint: 'Select a discovered checkout or add an existing workspace-relative checkout through the bounded path action.',
|
|
428
777
|
}));
|
|
429
778
|
continue;
|
|
430
779
|
}
|
|
@@ -435,7 +784,7 @@ function selectRepositories(choices, discovery, root) {
|
|
|
435
784
|
message: `Repository selection repeats ${repository.root}.`,
|
|
436
785
|
source: 'init choices',
|
|
437
786
|
pointer: pointer('repositories', index, 'path'),
|
|
438
|
-
hint: 'Keep one selection per
|
|
787
|
+
hint: 'Keep one selection per canonical checkout.',
|
|
439
788
|
}));
|
|
440
789
|
continue;
|
|
441
790
|
}
|
|
@@ -445,9 +794,8 @@ function selectRepositories(choices, discovery, root) {
|
|
|
445
794
|
(repository.relativePath === null || repository.relativePath === '.'
|
|
446
795
|
? basename(root)
|
|
447
796
|
: basename(repository.relativePath));
|
|
448
|
-
const id = choice.id
|
|
449
|
-
|
|
450
|
-
: uniqueIdentifier(sanitizeIdentifier(derivedBase), usedIds);
|
|
797
|
+
const id = choice.id ??
|
|
798
|
+
uniqueIdentifier(sanitizeIdentifier(derivedBase), new Set([...reservedIds, ...usedIds]));
|
|
451
799
|
if (choice.id !== undefined) {
|
|
452
800
|
if (usedIds.has(choice.id)) {
|
|
453
801
|
diagnostics.push(createDiagnostic({
|
|
@@ -460,8 +808,8 @@ function selectRepositories(choices, discovery, root) {
|
|
|
460
808
|
}));
|
|
461
809
|
continue;
|
|
462
810
|
}
|
|
463
|
-
usedIds.add(choice.id);
|
|
464
811
|
}
|
|
812
|
+
usedIds.add(id);
|
|
465
813
|
const configPath = external
|
|
466
814
|
? repository.configuredPath
|
|
467
815
|
: (repository.relativePath ?? repository.configuredPath ?? '.');
|
|
@@ -486,11 +834,16 @@ function selectRepositories(choices, discovery, root) {
|
|
|
486
834
|
}
|
|
487
835
|
return { selected, diagnostics };
|
|
488
836
|
}
|
|
489
|
-
function selectKnowledgeSources(choices, discovery) {
|
|
837
|
+
function selectKnowledgeSources(choices, discovery, retainedSources = [], explicitSources = new Map()) {
|
|
490
838
|
const allowed = new Map(listKnowledgeCandidates(discovery).map((candidate) => [
|
|
491
839
|
candidateKey(candidate.path),
|
|
492
840
|
candidate.path,
|
|
493
841
|
]));
|
|
842
|
+
for (const [key, path] of explicitSources) {
|
|
843
|
+
if (!allowed.has(key))
|
|
844
|
+
allowed.set(key, path);
|
|
845
|
+
}
|
|
846
|
+
const retained = new Map(retainedSources.map((source) => [candidateKey(source), source]));
|
|
494
847
|
const diagnostics = [];
|
|
495
848
|
const sources = [];
|
|
496
849
|
const seen = new Set();
|
|
@@ -498,6 +851,22 @@ function selectKnowledgeSources(choices, discovery) {
|
|
|
498
851
|
const key = candidateKey(source);
|
|
499
852
|
const canonical = allowed.get(key);
|
|
500
853
|
if (canonical === undefined) {
|
|
854
|
+
const kept = retained.get(key);
|
|
855
|
+
if (kept !== undefined) {
|
|
856
|
+
diagnostics.push(createDiagnostic({
|
|
857
|
+
code: diagnosticCodes.initKnowledgeUnknown,
|
|
858
|
+
severity: 'warning',
|
|
859
|
+
message: `Configured knowledge source is retained but not currently discovered: ${kept}`,
|
|
860
|
+
source: 'init choices',
|
|
861
|
+
pointer: pointer('knowledge', 'sources', index),
|
|
862
|
+
hint: 'Keep the source if it is intentionally unavailable, or remove it explicitly.',
|
|
863
|
+
}));
|
|
864
|
+
if (!seen.has(kept)) {
|
|
865
|
+
seen.add(kept);
|
|
866
|
+
sources.push(kept);
|
|
867
|
+
}
|
|
868
|
+
continue;
|
|
869
|
+
}
|
|
501
870
|
diagnostics.push(createDiagnostic({
|
|
502
871
|
code: diagnosticCodes.initKnowledgeUnknown,
|
|
503
872
|
severity: 'error',
|
|
@@ -515,13 +884,167 @@ function selectKnowledgeSources(choices, discovery) {
|
|
|
515
884
|
}
|
|
516
885
|
return { sources, diagnostics };
|
|
517
886
|
}
|
|
887
|
+
function pathObservationKey(kind, path) {
|
|
888
|
+
return `${kind}:${candidateKey(path)}`;
|
|
889
|
+
}
|
|
890
|
+
function sameInitPathObservation(left, right) {
|
|
891
|
+
if (left.kind !== right.kind ||
|
|
892
|
+
candidateKey(left.path) !== candidateKey(right.path) ||
|
|
893
|
+
!samePath(left.realPath, right.realPath) ||
|
|
894
|
+
left.targetKind !== right.targetKind ||
|
|
895
|
+
left.device !== right.device ||
|
|
896
|
+
left.inode !== right.inode) {
|
|
897
|
+
return false;
|
|
898
|
+
}
|
|
899
|
+
if (left.git === null || right.git === null)
|
|
900
|
+
return left.git === right.git;
|
|
901
|
+
return (samePath(left.git.root, right.git.root) &&
|
|
902
|
+
samePath(left.git.gitDir, right.git.gitDir) &&
|
|
903
|
+
samePath(left.git.commonDir, right.git.commonDir) &&
|
|
904
|
+
left.git.kind === right.git.kind);
|
|
905
|
+
}
|
|
906
|
+
function repositorySelectionFromPathObservation(observation) {
|
|
907
|
+
if (observation.kind !== 'repository' || observation.git === null) {
|
|
908
|
+
throw new Error('Repository selection requires a Git path observation.');
|
|
909
|
+
}
|
|
910
|
+
return {
|
|
911
|
+
id: null,
|
|
912
|
+
external: false,
|
|
913
|
+
access: 'read',
|
|
914
|
+
root: observation.git.root,
|
|
915
|
+
relativePath: observation.path,
|
|
916
|
+
configuredPath: observation.path,
|
|
917
|
+
git: observation.git,
|
|
918
|
+
kind: observation.git.kind,
|
|
919
|
+
diagnostics: [],
|
|
920
|
+
};
|
|
921
|
+
}
|
|
922
|
+
async function inspectExplicitInitPathSelections(root, choices, discovery, preservedConfig, priorObservations) {
|
|
923
|
+
const repositories = new Map();
|
|
924
|
+
const knowledgeSources = new Map();
|
|
925
|
+
const observations = [];
|
|
926
|
+
const diagnostics = [];
|
|
927
|
+
const priorByPath = new Map();
|
|
928
|
+
for (const observation of priorObservations) {
|
|
929
|
+
const key = pathObservationKey(observation.kind, observation.path);
|
|
930
|
+
if (!priorByPath.has(key))
|
|
931
|
+
priorByPath.set(key, observation);
|
|
932
|
+
}
|
|
933
|
+
const discoveredRepositories = new Map();
|
|
934
|
+
for (const repository of discovery.repositories) {
|
|
935
|
+
discoveredRepositories.set(repositoryKey(repository), repository);
|
|
936
|
+
if (repository.relativePath !== null) {
|
|
937
|
+
discoveredRepositories.set(candidateKey(repository.relativePath), repository);
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
const discoveredKnowledge = new Map(listKnowledgeCandidates(discovery).map((candidate) => [
|
|
941
|
+
candidateKey(candidate.path),
|
|
942
|
+
candidate.path,
|
|
943
|
+
]));
|
|
944
|
+
const retainedRepositoryPaths = new Set((preservedConfig?.repositories ?? []).map((repository) => candidateKey(repository.path)));
|
|
945
|
+
const retainedKnowledgePaths = new Set((preservedConfig?.knowledge.sources ?? []).map(candidateKey));
|
|
946
|
+
const inspectedChoices = new Set();
|
|
947
|
+
let failureStatus = 'invalid';
|
|
948
|
+
const inspectChoice = async (kind, inputPath, location) => {
|
|
949
|
+
const inputKey = candidateKey(inputPath);
|
|
950
|
+
const inspectionKey = pathObservationKey(kind, inputKey);
|
|
951
|
+
if (inspectedChoices.has(inspectionKey))
|
|
952
|
+
return;
|
|
953
|
+
inspectedChoices.add(inspectionKey);
|
|
954
|
+
const inspected = await inspectInitPathAddition(root, kind, inputPath, choices.excludes, location);
|
|
955
|
+
if (!inspected.ok) {
|
|
956
|
+
diagnostics.push(...inspected.diagnostics);
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
const observation = inspected.observation;
|
|
960
|
+
const prior = priorByPath.get(pathObservationKey(kind, observation.path)) ??
|
|
961
|
+
priorByPath.get(inspectionKey);
|
|
962
|
+
if (prior !== undefined && !sameInitPathObservation(prior, observation)) {
|
|
963
|
+
failureStatus = 'conflict';
|
|
964
|
+
diagnostics.push(createDiagnostic({
|
|
965
|
+
code: diagnosticCodes.initPlanStale,
|
|
966
|
+
severity: 'error',
|
|
967
|
+
message: `Explicit ${kind} path changed after it was inspected: ${observation.path}`,
|
|
968
|
+
source: 'init explicit path inspection',
|
|
969
|
+
pointer: location,
|
|
970
|
+
hint: 'Review the changed location and re-enter it deliberately; init does not refresh path identity automatically.',
|
|
971
|
+
}));
|
|
972
|
+
return;
|
|
973
|
+
}
|
|
974
|
+
observations.push(observation);
|
|
975
|
+
if (kind === 'repository') {
|
|
976
|
+
const discovered = [...discovery.repositories].find((entry) => samePath(entry.root, observation.realPath));
|
|
977
|
+
const source = discovered ?? repositorySelectionFromPathObservation(observation);
|
|
978
|
+
repositories.set(inputKey, source);
|
|
979
|
+
repositories.set(candidateKey(observation.path), source);
|
|
980
|
+
}
|
|
981
|
+
else {
|
|
982
|
+
const canonical = discoveredKnowledge.get(candidateKey(observation.path)) ??
|
|
983
|
+
observation.path;
|
|
984
|
+
knowledgeSources.set(inputKey, canonical);
|
|
985
|
+
knowledgeSources.set(candidateKey(observation.path), canonical);
|
|
986
|
+
}
|
|
987
|
+
};
|
|
988
|
+
for (const [index, choice] of choices.repositories.entries()) {
|
|
989
|
+
const key = candidateKey(choice.path);
|
|
990
|
+
const hasPriorObservation = priorByPath.has(pathObservationKey('repository', key));
|
|
991
|
+
if (!hasPriorObservation &&
|
|
992
|
+
(retainedRepositoryPaths.has(key) ||
|
|
993
|
+
(discoveredRepositories.has(key) &&
|
|
994
|
+
!isExcludedPath(key, choices.excludes)))) {
|
|
995
|
+
continue;
|
|
996
|
+
}
|
|
997
|
+
await inspectChoice('repository', choice.path, pointer('repositories', index, 'path'));
|
|
998
|
+
}
|
|
999
|
+
for (const [index, source] of choices.knowledge.sources.entries()) {
|
|
1000
|
+
const key = candidateKey(source);
|
|
1001
|
+
const hasPriorObservation = priorByPath.has(pathObservationKey('knowledge', key));
|
|
1002
|
+
if (!hasPriorObservation &&
|
|
1003
|
+
(retainedKnowledgePaths.has(key) ||
|
|
1004
|
+
(discoveredKnowledge.has(key) &&
|
|
1005
|
+
!isExcludedPath(key, choices.excludes)))) {
|
|
1006
|
+
continue;
|
|
1007
|
+
}
|
|
1008
|
+
await inspectChoice('knowledge', source, pointer('knowledge', 'sources', index));
|
|
1009
|
+
}
|
|
1010
|
+
if (diagnostics.some((entry) => entry.severity === 'error')) {
|
|
1011
|
+
return { ok: false, status: failureStatus, diagnostics };
|
|
1012
|
+
}
|
|
1013
|
+
return {
|
|
1014
|
+
ok: true,
|
|
1015
|
+
value: { repositories, knowledgeSources, observations },
|
|
1016
|
+
};
|
|
1017
|
+
}
|
|
1018
|
+
async function revalidateExplicitInitPaths(root, observations, excludes) {
|
|
1019
|
+
const diagnostics = [];
|
|
1020
|
+
for (const previous of observations) {
|
|
1021
|
+
const current = await inspectInitPathAddition(root, previous.kind, previous.path, excludes);
|
|
1022
|
+
if (!current.ok ||
|
|
1023
|
+
!sameInitPathObservation(previous, current.observation)) {
|
|
1024
|
+
diagnostics.push(createDiagnostic({
|
|
1025
|
+
code: diagnosticCodes.initPlanStale,
|
|
1026
|
+
severity: 'error',
|
|
1027
|
+
message: `Explicit ${previous.kind} path changed or became unsafe before the final plan: ${previous.path}`,
|
|
1028
|
+
source: 'init explicit path inspection',
|
|
1029
|
+
pointer: null,
|
|
1030
|
+
hint: 'Review and re-enter the exact path; init does not refresh a stale path observation automatically.',
|
|
1031
|
+
}));
|
|
1032
|
+
}
|
|
1033
|
+
}
|
|
1034
|
+
return diagnostics;
|
|
1035
|
+
}
|
|
518
1036
|
function findSelectedRepositoryForCandidate(candidate, selected) {
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
(
|
|
522
|
-
|
|
523
|
-
return repository;
|
|
1037
|
+
if (candidate.repositoryId !== null) {
|
|
1038
|
+
for (const repository of selected) {
|
|
1039
|
+
if (repository.originalId === candidate.repositoryId)
|
|
1040
|
+
return repository;
|
|
524
1041
|
}
|
|
1042
|
+
// A configured/discovered repository identity is authoritative. A
|
|
1043
|
+
// different checkout that merely reuses its final selected ID is not a
|
|
1044
|
+
// substitute, and working-directory evidence must not override it.
|
|
1045
|
+
return null;
|
|
1046
|
+
}
|
|
1047
|
+
for (const repository of selected) {
|
|
525
1048
|
if (candidate.workingDirectory !== null &&
|
|
526
1049
|
samePath(candidate.workingDirectory, repository.repository.root)) {
|
|
527
1050
|
return repository;
|
|
@@ -529,7 +1052,7 @@ function findSelectedRepositoryForCandidate(candidate, selected) {
|
|
|
529
1052
|
}
|
|
530
1053
|
return null;
|
|
531
1054
|
}
|
|
532
|
-
function selectValidationEntries(choices, discovery, selected) {
|
|
1055
|
+
function selectValidationEntries(choices, discovery, selected, strictExistingBinding = false) {
|
|
533
1056
|
const byId = new Map(discovery.validationCandidates.map((candidate) => [
|
|
534
1057
|
candidate.id,
|
|
535
1058
|
candidate,
|
|
@@ -537,6 +1060,12 @@ function selectValidationEntries(choices, discovery, selected) {
|
|
|
537
1060
|
const diagnostics = [];
|
|
538
1061
|
const validation = [];
|
|
539
1062
|
const usedIds = new Set();
|
|
1063
|
+
const existingIds = new Set();
|
|
1064
|
+
for (const candidate of discovery.validationCandidates) {
|
|
1065
|
+
if (candidate.origin !== 'workspace-config')
|
|
1066
|
+
continue;
|
|
1067
|
+
existingIds.add(candidate.id.slice('workspace-config:'.length));
|
|
1068
|
+
}
|
|
540
1069
|
const seen = new Set();
|
|
541
1070
|
for (const [index, candidateId] of choices.validation.entries()) {
|
|
542
1071
|
if (seen.has(candidateId))
|
|
@@ -558,7 +1087,9 @@ function selectValidationEntries(choices, discovery, selected) {
|
|
|
558
1087
|
if (repository === null) {
|
|
559
1088
|
diagnostics.push(createDiagnostic({
|
|
560
1089
|
code: diagnosticCodes.initValidationUnbound,
|
|
561
|
-
severity: '
|
|
1090
|
+
severity: strictExistingBinding && candidate.origin === 'workspace-config'
|
|
1091
|
+
? 'error'
|
|
1092
|
+
: 'warning',
|
|
562
1093
|
message: `Validation candidate "${candidateId}" is not bound to a selected repository.`,
|
|
563
1094
|
source: 'init choices',
|
|
564
1095
|
pointer: pointer('validation', index),
|
|
@@ -566,9 +1097,32 @@ function selectValidationEntries(choices, discovery, selected) {
|
|
|
566
1097
|
}));
|
|
567
1098
|
continue;
|
|
568
1099
|
}
|
|
569
|
-
const
|
|
1100
|
+
const existing = candidate.origin === 'workspace-config';
|
|
1101
|
+
const base = existing
|
|
1102
|
+
? candidate.id.slice('workspace-config:'.length)
|
|
1103
|
+
: sanitizeIdentifier(candidate.id.replace(/^(?:workspace-config|package-script):/, ''));
|
|
1104
|
+
let id;
|
|
1105
|
+
if (existing) {
|
|
1106
|
+
if (usedIds.has(base)) {
|
|
1107
|
+
diagnostics.push(createDiagnostic({
|
|
1108
|
+
code: diagnosticCodes.initValidationUnknown,
|
|
1109
|
+
severity: 'error',
|
|
1110
|
+
message: `Configured validation id "${base}" is selected more than once.`,
|
|
1111
|
+
source: 'init choices',
|
|
1112
|
+
pointer: pointer('validation', index),
|
|
1113
|
+
hint: 'Keep one selection per configured validation id.',
|
|
1114
|
+
}));
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
id = base;
|
|
1118
|
+
usedIds.add(id);
|
|
1119
|
+
}
|
|
1120
|
+
else {
|
|
1121
|
+
id = uniqueIdentifier(base, new Set([...usedIds, ...existingIds]));
|
|
1122
|
+
usedIds.add(id);
|
|
1123
|
+
}
|
|
570
1124
|
validation.push({
|
|
571
|
-
id
|
|
1125
|
+
id,
|
|
572
1126
|
repositoryId: repository.id,
|
|
573
1127
|
executable: candidate.executable,
|
|
574
1128
|
args: [...candidate.args],
|
|
@@ -576,13 +1130,13 @@ function selectValidationEntries(choices, discovery, selected) {
|
|
|
576
1130
|
}
|
|
577
1131
|
return { validation, diagnostics };
|
|
578
1132
|
}
|
|
579
|
-
function buildWorkspaceConfig(root, choices, discovery, runtimeVersion) {
|
|
1133
|
+
function buildWorkspaceConfig(root, choices, discovery, runtimeVersion, options = {}) {
|
|
580
1134
|
const diagnostics = [];
|
|
581
|
-
const repositorySelection = selectRepositories(choices, discovery, root);
|
|
1135
|
+
const repositorySelection = selectRepositories(choices, discovery, root, options.explicitRepositories, options.preserveFrom?.repositories.map((repository) => repository.id) ?? []);
|
|
582
1136
|
diagnostics.push(...repositorySelection.diagnostics);
|
|
583
|
-
const knowledgeSelection = selectKnowledgeSources(choices, discovery);
|
|
1137
|
+
const knowledgeSelection = selectKnowledgeSources(choices, discovery, options.preserveFrom?.knowledge.sources ?? [], options.explicitKnowledgeSources);
|
|
584
1138
|
diagnostics.push(...knowledgeSelection.diagnostics);
|
|
585
|
-
const validationSelection = selectValidationEntries(choices, discovery, repositorySelection.selected);
|
|
1139
|
+
const validationSelection = selectValidationEntries(choices, discovery, repositorySelection.selected, options.strictExistingValidation === true);
|
|
586
1140
|
diagnostics.push(...validationSelection.diagnostics);
|
|
587
1141
|
if (hasErrors(diagnostics)) {
|
|
588
1142
|
return {
|
|
@@ -594,7 +1148,7 @@ function buildWorkspaceConfig(root, choices, discovery, runtimeVersion) {
|
|
|
594
1148
|
const name = choices.name?.trim() ?? '';
|
|
595
1149
|
const config = {
|
|
596
1150
|
schema: WORKSPACE_SCHEMA,
|
|
597
|
-
workspaceId: choices.workspaceId ?? randomUUID(),
|
|
1151
|
+
workspaceId: options.preserveFrom?.workspaceId ?? choices.workspaceId ?? randomUUID(),
|
|
598
1152
|
name: name === '' ? basename(root) || 'workspace' : name,
|
|
599
1153
|
runtimeVersion,
|
|
600
1154
|
repositories: repositorySelection.selected.map((repository) => ({
|
|
@@ -610,7 +1164,10 @@ function buildWorkspaceConfig(root, choices, discovery, runtimeVersion) {
|
|
|
610
1164
|
code: choices.code,
|
|
611
1165
|
validation: [...validationSelection.validation],
|
|
612
1166
|
excludes: [...new Set(choices.excludes)],
|
|
613
|
-
extensions: {},
|
|
1167
|
+
extensions: options.preserveFrom?.extensions ?? {},
|
|
1168
|
+
...(options.preserveFrom?.continuity === undefined
|
|
1169
|
+
? {}
|
|
1170
|
+
: { continuity: options.preserveFrom.continuity }),
|
|
614
1171
|
};
|
|
615
1172
|
const parsed = workspaceConfigSchema.safeParse(config);
|
|
616
1173
|
if (!parsed.success) {
|
|
@@ -649,13 +1206,15 @@ function buildWorkspaceConfig(root, choices, discovery, runtimeVersion) {
|
|
|
649
1206
|
diagnostics,
|
|
650
1207
|
};
|
|
651
1208
|
}
|
|
652
|
-
function scopeRepositories(config, discovery) {
|
|
1209
|
+
function scopeRepositories(config, discovery, selected = []) {
|
|
653
1210
|
const byConfiguredPath = new Map(discovery.repositories.map((repository) => [
|
|
654
1211
|
repository.configuredPath,
|
|
655
1212
|
repository,
|
|
656
1213
|
]));
|
|
1214
|
+
const bySelectedPath = new Map(selected.map((repository) => [candidateKey(repository.path), repository]));
|
|
657
1215
|
return config.repositories.map((repository) => {
|
|
658
|
-
const
|
|
1216
|
+
const planned = bySelectedPath.get(candidateKey(repository.path));
|
|
1217
|
+
const discovered = planned?.repository ?? byConfiguredPath.get(repository.path);
|
|
659
1218
|
return {
|
|
660
1219
|
id: repository.id,
|
|
661
1220
|
path: repository.path,
|
|
@@ -664,7 +1223,9 @@ function scopeRepositories(config, discovery) {
|
|
|
664
1223
|
root: discovered?.root ?? null,
|
|
665
1224
|
kind: discovered?.kind ?? null,
|
|
666
1225
|
dirty: discovered?.git.dirty ?? null,
|
|
667
|
-
declaredBy:
|
|
1226
|
+
declaredBy: planned?.originalId === null
|
|
1227
|
+
? 'planned'
|
|
1228
|
+
: 'existing',
|
|
668
1229
|
};
|
|
669
1230
|
});
|
|
670
1231
|
}
|
|
@@ -802,6 +1363,13 @@ export async function planWorkspaceInit(options) {
|
|
|
802
1363
|
choices = parsedChoices.value;
|
|
803
1364
|
}
|
|
804
1365
|
const existingConfig = context.workspaceInspection.value;
|
|
1366
|
+
if (existingConfig !== null &&
|
|
1367
|
+
options.reconfigure === true &&
|
|
1368
|
+
choices !== null &&
|
|
1369
|
+
choices.workspaceId !== undefined &&
|
|
1370
|
+
choices.workspaceId !== existingConfig.workspaceId) {
|
|
1371
|
+
return failure('invalid', diagnosticCodes.initInvalidInput, 'Reconfiguration cannot change workspaceId; the existing workspace identity is stable.', context.diagnostics, 'Remove the conflicting workspaceId from the reconfiguration selections, or create a new workspace separately.');
|
|
1372
|
+
}
|
|
805
1373
|
const existingMachine = context.machineInspection.value;
|
|
806
1374
|
const diagnostics = [...context.diagnostics];
|
|
807
1375
|
const runtimeVersion = options.runtimeVersion ??
|
|
@@ -814,6 +1382,11 @@ export async function planWorkspaceInit(options) {
|
|
|
814
1382
|
let needWorkspaceConfig = false;
|
|
815
1383
|
let needMachineConfig = false;
|
|
816
1384
|
let selected = [];
|
|
1385
|
+
let explicitPathSelections = {
|
|
1386
|
+
repositories: new Map(),
|
|
1387
|
+
knowledgeSources: new Map(),
|
|
1388
|
+
observations: [],
|
|
1389
|
+
};
|
|
817
1390
|
if (existingConfig !== null && options.reconfigure !== true) {
|
|
818
1391
|
config = existingConfig;
|
|
819
1392
|
if (choices !== null) {
|
|
@@ -839,7 +1412,19 @@ export async function planWorkspaceInit(options) {
|
|
|
839
1412
|
const effectiveChoices = existingConfig !== null && choices.workspaceId === undefined
|
|
840
1413
|
? { ...choices, workspaceId: existingConfig.workspaceId }
|
|
841
1414
|
: choices;
|
|
842
|
-
const
|
|
1415
|
+
const explicitPaths = await inspectExplicitInitPathSelections(context.root, effectiveChoices, context.discovery, existingConfig, options.pathObservations ?? []);
|
|
1416
|
+
if (!explicitPaths.ok) {
|
|
1417
|
+
return failureFromDiagnostics(explicitPaths.status, explicitPaths.diagnostics, diagnosticCodes.initInvalidInput, 'Explicit init paths could not be inspected safely.');
|
|
1418
|
+
}
|
|
1419
|
+
explicitPathSelections = explicitPaths.value;
|
|
1420
|
+
const built = buildWorkspaceConfig(context.root, effectiveChoices, context.discovery, runtimeVersion, {
|
|
1421
|
+
...(existingConfig === null ? {} : { preserveFrom: existingConfig }),
|
|
1422
|
+
...(options.reconfigure === true
|
|
1423
|
+
? { strictExistingValidation: true }
|
|
1424
|
+
: {}),
|
|
1425
|
+
explicitRepositories: explicitPaths.value.repositories,
|
|
1426
|
+
explicitKnowledgeSources: explicitPaths.value.knowledgeSources,
|
|
1427
|
+
});
|
|
843
1428
|
diagnostics.push(...built.diagnostics);
|
|
844
1429
|
if (built.config === null) {
|
|
845
1430
|
return failureFromDiagnostics('invalid', diagnostics, diagnosticCodes.initInvalidInput, 'Init selections do not form a valid workspace configuration.');
|
|
@@ -848,8 +1433,21 @@ export async function planWorkspaceInit(options) {
|
|
|
848
1433
|
selected = built.selected;
|
|
849
1434
|
needWorkspaceConfig = true;
|
|
850
1435
|
if (existingMachine !== null) {
|
|
851
|
-
|
|
852
|
-
|
|
1436
|
+
const desiredHosts = [
|
|
1437
|
+
...new Set(choices.hosts),
|
|
1438
|
+
];
|
|
1439
|
+
const hostsChanged = desiredHosts.length !== existingMachine.selectedHosts.length ||
|
|
1440
|
+
desiredHosts.some((host) => !existingMachine.selectedHosts.includes(host));
|
|
1441
|
+
if (options.reconfigure === true &&
|
|
1442
|
+
existingConfig !== null &&
|
|
1443
|
+
hostsChanged) {
|
|
1444
|
+
machine = { ...existingMachine, selectedHosts: desiredHosts };
|
|
1445
|
+
needMachineConfig = true;
|
|
1446
|
+
}
|
|
1447
|
+
else {
|
|
1448
|
+
machine = existingMachine;
|
|
1449
|
+
diagnostics.push(info(diagnosticCodes.initExistingConfigReused, 'Existing valid machine.json is reused while workspace.json is created.', context.machineInspection.path, 'Use a future configure flow to change local host or external repository mappings.'));
|
|
1450
|
+
}
|
|
853
1451
|
}
|
|
854
1452
|
else {
|
|
855
1453
|
needMachineConfig = true;
|
|
@@ -888,6 +1486,10 @@ export async function planWorkspaceInit(options) {
|
|
|
888
1486
|
? {}
|
|
889
1487
|
: { discoveredAt: options.discoveredAt }),
|
|
890
1488
|
});
|
|
1489
|
+
const pathRevalidation = await revalidateExplicitInitPaths(context.root, explicitPathSelections.observations, config.excludes);
|
|
1490
|
+
if (pathRevalidation.length > 0) {
|
|
1491
|
+
return failureFromDiagnostics('conflict', [...diagnostics, ...pathRevalidation], diagnosticCodes.initPlanStale, 'An explicitly added path changed while init was planning.');
|
|
1492
|
+
}
|
|
891
1493
|
const finalWorkspace = await inspectWorkspaceConfig(context.root, {
|
|
892
1494
|
missing: 'info',
|
|
893
1495
|
});
|
|
@@ -905,7 +1507,7 @@ export async function planWorkspaceInit(options) {
|
|
|
905
1507
|
}
|
|
906
1508
|
const rootIdentity = context.rootIdentity;
|
|
907
1509
|
const scopeRepos = existingConfig !== null
|
|
908
|
-
? scopeRepositories(config, context.discovery)
|
|
1510
|
+
? scopeRepositories(config, context.discovery, selected)
|
|
909
1511
|
: selected.map((repository) => ({
|
|
910
1512
|
id: repository.id,
|
|
911
1513
|
path: repository.path,
|
|
@@ -1112,7 +1714,7 @@ async function cleanupCreatedFiles(root, created) {
|
|
|
1112
1714
|
return residual;
|
|
1113
1715
|
}
|
|
1114
1716
|
/**
|
|
1115
|
-
* Applies
|
|
1717
|
+
* Applies create and managed replacement operations from a plan, after revalidating root,
|
|
1116
1718
|
* configuration digest, target absence and exact bytes. External actions are
|
|
1117
1719
|
* reported as pending and never executed here.
|
|
1118
1720
|
*/
|
|
@@ -1213,17 +1815,47 @@ export async function applyInitPlan(bundle, options = {}) {
|
|
|
1213
1815
|
}
|
|
1214
1816
|
};
|
|
1215
1817
|
const restoreReplacements = async () => {
|
|
1818
|
+
const residual = [];
|
|
1819
|
+
const entries = originalManifest === null ? [] : [...originalManifest.entries];
|
|
1216
1820
|
for (const replacement of [...replacements].reverse()) {
|
|
1217
1821
|
try {
|
|
1218
|
-
await
|
|
1822
|
+
const current = await observePath(replacement.absolutePath);
|
|
1823
|
+
if (current.kind !== 'file' ||
|
|
1824
|
+
current.stats === null ||
|
|
1825
|
+
current.stats.dev !== replacement.identity.dev ||
|
|
1826
|
+
current.stats.ino !== replacement.identity.ino ||
|
|
1827
|
+
sha256Digest(await readFile(replacement.absolutePath)) !==
|
|
1828
|
+
replacement.afterDigest) {
|
|
1829
|
+
residual.push(replacement.path);
|
|
1830
|
+
continue;
|
|
1831
|
+
}
|
|
1832
|
+
const restored = await writeBytesAtomic(replacement.absolutePath, replacement.beforeBytes, root);
|
|
1833
|
+
if (!restored.ok)
|
|
1834
|
+
throw new Error(restored.message);
|
|
1835
|
+
const after = await observePath(replacement.absolutePath);
|
|
1836
|
+
if (after.kind !== 'file' || after.stats === null)
|
|
1837
|
+
throw new Error('Restored file is unreadable.');
|
|
1838
|
+
const index = entries.findIndex((entry) => entry.path === replacement.path);
|
|
1839
|
+
if (index >= 0)
|
|
1840
|
+
entries[index] = {
|
|
1841
|
+
...entries[index],
|
|
1842
|
+
afterDigest: replacement.backupDigest,
|
|
1843
|
+
identity: { dev: after.stats.dev, ino: after.stats.ino },
|
|
1844
|
+
};
|
|
1219
1845
|
}
|
|
1220
1846
|
catch {
|
|
1221
|
-
|
|
1847
|
+
residual.push(replacement.path);
|
|
1222
1848
|
}
|
|
1223
1849
|
}
|
|
1224
|
-
if (originalManifest !== null) {
|
|
1225
|
-
await writeWorkspaceManifest(root,
|
|
1850
|
+
if (originalManifest !== null && replacements.length > 0) {
|
|
1851
|
+
const restored = await writeWorkspaceManifest(root, {
|
|
1852
|
+
...originalManifest,
|
|
1853
|
+
entries,
|
|
1854
|
+
}).catch(() => ({ ok: false }));
|
|
1855
|
+
if (!restored.ok)
|
|
1856
|
+
residual.push('.pappra/local/install-manifest.json');
|
|
1226
1857
|
}
|
|
1858
|
+
return residual;
|
|
1227
1859
|
};
|
|
1228
1860
|
const rootBeforeWrite = await observePath(root);
|
|
1229
1861
|
if (rootBeforeWrite.kind !== 'directory' ||
|
|
@@ -1233,13 +1865,38 @@ export async function applyInitPlan(bundle, options = {}) {
|
|
|
1233
1865
|
return applyFailure('stale', diagnosticCodes.initPlanStale, 'Workspace root identity changed before writing.', 'Retry init from the current workspace root; no files were written.');
|
|
1234
1866
|
}
|
|
1235
1867
|
try {
|
|
1868
|
+
if (bundle.plan.files.length > 0) {
|
|
1869
|
+
const managedStart = await beginManagedApply(root, bundle.plan, bundle.writes, bundle.rootIdentity);
|
|
1870
|
+
if (!managedStart.ok) {
|
|
1871
|
+
return {
|
|
1872
|
+
ok: false,
|
|
1873
|
+
status: managedStart.status === 'invalid'
|
|
1874
|
+
? 'stale'
|
|
1875
|
+
: managedStart.status === 'locked'
|
|
1876
|
+
? 'conflict'
|
|
1877
|
+
: 'failed',
|
|
1878
|
+
appliedFiles: [],
|
|
1879
|
+
pendingExternalActions: [],
|
|
1880
|
+
residualPaths: managedStart.residualPaths,
|
|
1881
|
+
diagnostics: managedStart.diagnostics,
|
|
1882
|
+
};
|
|
1883
|
+
}
|
|
1884
|
+
managed = managedStart.context;
|
|
1885
|
+
originalManifest = managed.manifest;
|
|
1886
|
+
}
|
|
1887
|
+
else {
|
|
1888
|
+
const ready = await verifyWorkspaceInstallReady(root);
|
|
1889
|
+
if (!ready.ok)
|
|
1890
|
+
return applyFailure('conflict', ready.code, ready.message, ready.nextAction ?? 'Run pappra repair before retrying init.');
|
|
1891
|
+
}
|
|
1236
1892
|
for (const index of replaceIndexes) {
|
|
1237
1893
|
const file = bundle.plan.files[index];
|
|
1238
1894
|
const write = bundle.writes[index];
|
|
1239
1895
|
await options.beforeWrite?.(file.path, index);
|
|
1240
1896
|
const absolutePath = resolvePortableRelativePath(root, file.path);
|
|
1241
|
-
const
|
|
1242
|
-
const
|
|
1897
|
+
const backupFile = backupPath(root, managed.installId, file.path);
|
|
1898
|
+
const backupRef = toWorkspaceRelativePath(root, backupFile);
|
|
1899
|
+
const stateBoundary = await ensureStatePathBoundary(root, backupFile);
|
|
1243
1900
|
if (!stateBoundary.ok)
|
|
1244
1901
|
throw new Error(stateBoundary.message);
|
|
1245
1902
|
const beforeBytes = await readFile(absolutePath);
|
|
@@ -1248,12 +1905,33 @@ export async function applyInitPlan(bundle, options = {}) {
|
|
|
1248
1905
|
throw new Error(`Replacement target changed again: ${file.path}`);
|
|
1249
1906
|
}
|
|
1250
1907
|
const observation = await observePath(absolutePath);
|
|
1908
|
+
if (observation.kind !== 'file')
|
|
1909
|
+
throw new Error(`Unsafe replacement target: ${file.path}`);
|
|
1251
1910
|
const mode = observation.stats === null ? 0o644 : observation.stats.mode & 0o777;
|
|
1252
|
-
await mkdir(dirname(
|
|
1253
|
-
await writeFile(
|
|
1911
|
+
await mkdir(dirname(backupFile), { recursive: true });
|
|
1912
|
+
await writeFile(backupFile, beforeBytes, { mode: 0o600, flag: 'wx' });
|
|
1254
1913
|
const temporary = `${absolutePath}.pappra-replace-${randomUUID().slice(0, 8)}`;
|
|
1255
|
-
|
|
1256
|
-
|
|
1914
|
+
try {
|
|
1915
|
+
await writeFile(temporary, write.bytes, { mode, flag: 'wx' });
|
|
1916
|
+
const staged = await observePath(temporary);
|
|
1917
|
+
if (staged.kind !== 'file' || staged.stats === null)
|
|
1918
|
+
throw new Error('Replacement staging failed.');
|
|
1919
|
+
const recorded = await recordManagedFile(managed, file.path, file.nextDigest, { dev: staged.stats.dev, ino: staged.stats.ino }, { backupRef, backupDigest: sha256Digest(beforeBytes) });
|
|
1920
|
+
if (!recorded.ok)
|
|
1921
|
+
throw new Error(recorded.diagnostics[0]?.message ?? 'Cannot journal replacement.');
|
|
1922
|
+
const beforeRename = await observePath(absolutePath);
|
|
1923
|
+
if (beforeRename.kind !== 'file' ||
|
|
1924
|
+
beforeRename.stats?.dev !== observation.stats?.dev ||
|
|
1925
|
+
beforeRename.stats?.ino !== observation.stats?.ino ||
|
|
1926
|
+
sha256Digest(await readFile(absolutePath)) !==
|
|
1927
|
+
file.expectedBeforeDigest) {
|
|
1928
|
+
throw new Error(`Replacement target changed before publish: ${file.path}`);
|
|
1929
|
+
}
|
|
1930
|
+
await rename(temporary, absolutePath);
|
|
1931
|
+
}
|
|
1932
|
+
finally {
|
|
1933
|
+
await rm(temporary, { force: true }).catch(() => undefined);
|
|
1934
|
+
}
|
|
1257
1935
|
const after = await observePath(absolutePath);
|
|
1258
1936
|
if (after.kind !== 'file' || after.stats === null) {
|
|
1259
1937
|
throw new Error(`Replacement target is unreadable after writing: ${file.path}`);
|
|
@@ -1268,68 +1946,7 @@ export async function applyInitPlan(bundle, options = {}) {
|
|
|
1268
1946
|
identity: { dev: after.stats.dev, ino: after.stats.ino },
|
|
1269
1947
|
});
|
|
1270
1948
|
}
|
|
1271
|
-
if (replacements.length > 0) {
|
|
1272
|
-
const manifestRead = await readWorkspaceManifest(root);
|
|
1273
|
-
if (manifestRead.diagnostics.length > 0) {
|
|
1274
|
-
throw new Error(manifestRead.diagnostics[0]?.message ??
|
|
1275
|
-
'Workspace install manifest is unreadable.');
|
|
1276
|
-
}
|
|
1277
|
-
if (manifestRead.value === null) {
|
|
1278
|
-
throw new Error('Workspace install manifest is missing; Pappra does not adopt an unowned replacement.');
|
|
1279
|
-
}
|
|
1280
|
-
originalManifest = manifestRead.value;
|
|
1281
|
-
const entries = [...manifestRead.value.entries];
|
|
1282
|
-
const verifiedAt = new Date().toISOString();
|
|
1283
|
-
for (const replacement of replacements) {
|
|
1284
|
-
const entry = {
|
|
1285
|
-
path: replacement.path,
|
|
1286
|
-
installId: bundle.plan.planId,
|
|
1287
|
-
operation: 'replace-managed',
|
|
1288
|
-
beforeDigest: replacement.backupDigest,
|
|
1289
|
-
afterDigest: replacement.afterDigest,
|
|
1290
|
-
backupRef: replacement.backupRef,
|
|
1291
|
-
backupDigest: replacement.backupDigest,
|
|
1292
|
-
identity: replacement.identity,
|
|
1293
|
-
owner: 'pappra',
|
|
1294
|
-
verifiedAt,
|
|
1295
|
-
};
|
|
1296
|
-
const existingIndex = entries.findIndex((item) => item.path === replacement.path);
|
|
1297
|
-
if (existingIndex === -1)
|
|
1298
|
-
entries.push(entry);
|
|
1299
|
-
else
|
|
1300
|
-
entries[existingIndex] = entry;
|
|
1301
|
-
}
|
|
1302
|
-
const written = await writeWorkspaceManifest(root, {
|
|
1303
|
-
...manifestRead.value,
|
|
1304
|
-
entries,
|
|
1305
|
-
});
|
|
1306
|
-
if (!written.ok)
|
|
1307
|
-
throw new Error(written.message);
|
|
1308
|
-
}
|
|
1309
1949
|
if (createIndexes.length > 0) {
|
|
1310
|
-
const createPlan = {
|
|
1311
|
-
...bundle.plan,
|
|
1312
|
-
files: createIndexes.map((index) => bundle.plan.files[index]),
|
|
1313
|
-
};
|
|
1314
|
-
const createWrites = createIndexes.map((index) => bundle.writes[index]);
|
|
1315
|
-
const managedStart = await beginManagedApply(root, createPlan, createWrites, bundle.rootIdentity);
|
|
1316
|
-
if (!managedStart.ok) {
|
|
1317
|
-
const status = managedStart.status === 'invalid'
|
|
1318
|
-
? 'stale'
|
|
1319
|
-
: managedStart.status === 'locked'
|
|
1320
|
-
? 'conflict'
|
|
1321
|
-
: 'failed';
|
|
1322
|
-
await restoreReplacements();
|
|
1323
|
-
return {
|
|
1324
|
-
ok: false,
|
|
1325
|
-
status,
|
|
1326
|
-
appliedFiles: [],
|
|
1327
|
-
pendingExternalActions: [],
|
|
1328
|
-
residualPaths: managedStart.residualPaths,
|
|
1329
|
-
diagnostics: managedStart.diagnostics,
|
|
1330
|
-
};
|
|
1331
|
-
}
|
|
1332
|
-
managed = managedStart.context;
|
|
1333
1950
|
for (const index of createIndexes) {
|
|
1334
1951
|
const file = bundle.plan.files[index];
|
|
1335
1952
|
const write = bundle.writes[index];
|
|
@@ -1356,17 +1973,22 @@ export async function applyInitPlan(bundle, options = {}) {
|
|
|
1356
1973
|
finally {
|
|
1357
1974
|
await handle.close();
|
|
1358
1975
|
}
|
|
1976
|
+
await options.afterWrite?.(file.path, index);
|
|
1359
1977
|
created.push(record);
|
|
1360
1978
|
const recorded = await recordManagedFile(managed, file.path, file.nextDigest, { dev: record.dev, ino: record.ino });
|
|
1361
1979
|
if (!recorded.ok) {
|
|
1362
1980
|
throw new Error(recorded.diagnostics[0]?.message ?? `Cannot record ${file.path}.`);
|
|
1363
1981
|
}
|
|
1364
1982
|
}
|
|
1983
|
+
}
|
|
1984
|
+
if (managed !== null) {
|
|
1365
1985
|
const completed = await completeManagedApply(managed);
|
|
1366
1986
|
if (!completed.ok) {
|
|
1367
|
-
const residual =
|
|
1987
|
+
const residual = [
|
|
1988
|
+
...(await cleanupCreatedFiles(root, created)),
|
|
1989
|
+
...(await restoreReplacements()),
|
|
1990
|
+
];
|
|
1368
1991
|
const failed = await failManagedApply(managed, 'Cannot finalize managed installation manifest.', residual);
|
|
1369
|
-
await restoreReplacements();
|
|
1370
1992
|
return {
|
|
1371
1993
|
ok: false,
|
|
1372
1994
|
status: 'failed',
|
|
@@ -1395,8 +2017,10 @@ export async function applyInitPlan(bundle, options = {}) {
|
|
|
1395
2017
|
};
|
|
1396
2018
|
}
|
|
1397
2019
|
catch (error) {
|
|
1398
|
-
|
|
1399
|
-
|
|
2020
|
+
const residual = [
|
|
2021
|
+
...(await restoreReplacements()),
|
|
2022
|
+
...(await cleanupCreatedFiles(root, created)),
|
|
2023
|
+
];
|
|
1400
2024
|
for (const directory of createdDirectories.reverse()) {
|
|
1401
2025
|
await rmdir(directory).catch(() => undefined);
|
|
1402
2026
|
}
|
|
@@ -1494,24 +2118,13 @@ export async function bindAndRecordRuntime(root, installed) {
|
|
|
1494
2118
|
};
|
|
1495
2119
|
}
|
|
1496
2120
|
/**
|
|
1497
|
-
*
|
|
1498
|
-
*
|
|
1499
|
-
*
|
|
2121
|
+
* Ensures and binds the runtime package that is currently executing.
|
|
2122
|
+
*
|
|
2123
|
+
* The installer reuses an existing runtime only after its manifest, package
|
|
2124
|
+
* contents, executable and smoke checks pass; a regular file is not proof of a
|
|
2125
|
+
* verified durable runtime.
|
|
1500
2126
|
*/
|
|
1501
2127
|
export async function ensureDefaultRuntime(root, targetVersion) {
|
|
1502
|
-
const machine = await inspectMachineConfig(root);
|
|
1503
|
-
const binding = machine.value?.runtime ?? null;
|
|
1504
|
-
if (binding !== null && binding.version === targetVersion) {
|
|
1505
|
-
const executable = await observePath(binding.executable);
|
|
1506
|
-
if (executable.kind === 'file') {
|
|
1507
|
-
return {
|
|
1508
|
-
ok: true,
|
|
1509
|
-
completion: true,
|
|
1510
|
-
runtime: null,
|
|
1511
|
-
diagnostics: [],
|
|
1512
|
-
};
|
|
1513
|
-
}
|
|
1514
|
-
}
|
|
1515
2128
|
const installed = await installRuntime({
|
|
1516
2129
|
version: targetVersion,
|
|
1517
2130
|
source: { kind: 'self', path: getPackageRoot() },
|