@the-open-engine/zeroshot 6.38.0 → 6.39.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/cli/event-copy.js +8 -7
- package/cli/event-copy.ts +12 -0
- package/cli/index.js +7 -31
- package/cli/json-export.js +61 -64
- package/cli/json-export.ts +112 -0
- package/cli/message-formatter-utils.js +68 -56
- package/cli/message-formatter-utils.ts +121 -0
- package/cli/message-formatters-normal.js +124 -259
- package/cli/message-formatters-normal.ts +250 -0
- package/cli/message-formatters-watch.js +81 -84
- package/cli/message-formatters-watch.ts +189 -0
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +3 -1
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/cluster/generated/protocol-schema.cjs +1 -1
- package/lib/cluster/generated/protocol-schema.mjs +1 -1
- package/lib/cluster/generated/protocol.d.cts +8 -0
- package/lib/cluster/generated/protocol.d.mts +8 -0
- package/lib/cluster/generated/protocol.d.ts +8 -0
- package/lib/clusters-registry.d.ts +23 -0
- package/lib/clusters-registry.js +17 -29
- package/lib/completion.d.ts +12 -0
- package/lib/completion.js +84 -94
- package/lib/compose-utils.d.ts +32 -0
- package/lib/compose-utils.js +57 -72
- package/lib/detached-startup.d.ts +83 -0
- package/lib/detached-startup.js +231 -302
- package/lib/docker-config.d.ts +43 -0
- package/lib/docker-config.js +150 -325
- package/lib/docker-env-auth.d.ts +17 -0
- package/lib/docker-env-auth.js +75 -0
- package/lib/git-remote-utils.d.ts +48 -0
- package/lib/git-remote-utils.js +147 -213
- package/lib/id-detector.d.ts +15 -0
- package/lib/id-detector.js +37 -41
- package/lib/path-check.d.ts +30 -0
- package/lib/path-check.js +42 -45
- package/lib/process-liveness.d.ts +16 -0
- package/lib/process-liveness.js +19 -12
- package/lib/provider-credential-path.d.ts +16 -0
- package/lib/provider-credential-path.js +31 -29
- package/lib/provider-defaults.d.ts +30 -0
- package/lib/provider-defaults.js +47 -42
- package/lib/provider-detection.d.ts +11 -0
- package/lib/provider-detection.js +91 -54
- package/lib/provider-names.d.ts +38 -0
- package/lib/provider-names.js +56 -64
- package/lib/repo-settings.d.ts +28 -0
- package/lib/repo-settings.js +83 -59
- package/lib/run-mode.d.ts +21 -0
- package/lib/run-mode.js +21 -20
- package/lib/run-plan.d.ts +28 -0
- package/lib/run-plan.js +17 -16
- package/lib/settings/claude-auth.d.ts +35 -0
- package/lib/settings/claude-auth.js +30 -47
- package/lib/settings-defaults.d.ts +20 -0
- package/lib/settings-defaults.js +149 -0
- package/lib/settings-error.d.ts +7 -0
- package/lib/settings-error.js +8 -0
- package/lib/settings-issue-providers.d.ts +10 -0
- package/lib/settings-issue-providers.js +12 -0
- package/lib/settings-models.d.ts +22 -0
- package/lib/settings-models.js +93 -0
- package/lib/settings-storage.d.ts +23 -0
- package/lib/settings-storage.js +205 -0
- package/lib/settings-validation.d.ts +7 -0
- package/lib/settings-validation.js +182 -0
- package/lib/settings.d.ts +29 -0
- package/lib/settings.js +41 -736
- package/lib/setup-apply-types.d.ts +66 -0
- package/lib/setup-apply-types.js +2 -0
- package/lib/setup-apply.d.ts +39 -0
- package/lib/setup-apply.js +267 -264
- package/lib/setup-journal.d.ts +41 -0
- package/lib/setup-journal.js +119 -76
- package/lib/setup-plan-types.d.ts +102 -0
- package/lib/setup-plan-types.js +2 -0
- package/lib/setup-plan.d.ts +30 -0
- package/lib/setup-plan.js +300 -324
- package/lib/setup-undo.d.ts +44 -0
- package/lib/setup-undo.js +61 -74
- package/lib/start-cluster-config.d.ts +26 -0
- package/lib/start-cluster-config.js +56 -0
- package/lib/start-cluster-environment.d.ts +40 -0
- package/lib/start-cluster-environment.js +134 -0
- package/lib/start-cluster-run-options.d.ts +53 -0
- package/lib/start-cluster-run-options.js +147 -0
- package/lib/start-cluster.d.ts +64 -0
- package/lib/start-cluster.js +98 -454
- package/lib/stream-json-parser.d.ts +17 -0
- package/lib/stream-json-parser.js +51 -52
- package/npm-shrinkwrap.json +2 -2
- package/package.json +12 -6
- package/scripts/postinstall.js +27 -0
- package/scripts/rust-distribution.js +23 -14
- package/src/agent/agent-command-proofs-context.js +35 -45
- package/src/agent/agent-command-proofs-context.ts +87 -0
- package/src/agent/agent-config-settings.js +19 -0
- package/src/agent/agent-config-settings.ts +41 -0
- package/src/agent/agent-config.js +122 -231
- package/src/agent/agent-config.ts +285 -0
- package/src/agent/agent-context-builder.js +38 -273
- package/src/agent/agent-context-builder.ts +51 -0
- package/src/agent/agent-context-environment-sections.js +105 -0
- package/src/agent/agent-context-environment-sections.ts +134 -0
- package/src/agent/agent-context-packs.js +165 -0
- package/src/agent/agent-context-packs.ts +296 -0
- package/src/agent/agent-context-prompt-sections.js +100 -0
- package/src/agent/agent-context-prompt-sections.ts +148 -0
- package/src/agent/agent-context-sections.js +25 -334
- package/src/agent/agent-context-sections.ts +36 -0
- package/src/agent/agent-context-sources.js +96 -149
- package/src/agent/agent-context-sources.ts +253 -0
- package/src/agent/agent-context-types.js +2 -0
- package/src/agent/agent-context-types.ts +132 -0
- package/src/agent/agent-context-validation-sections.js +73 -0
- package/src/agent/agent-context-validation-sections.ts +129 -0
- package/src/agent/agent-input-injector.js +99 -118
- package/src/agent/agent-input-injector.ts +195 -0
- package/src/agent/agent-liveness-poll.js +74 -78
- package/src/agent/agent-liveness-poll.ts +163 -0
- package/src/agent/agent-quality-gate-schema.js +77 -86
- package/src/agent/agent-quality-gate-schema.ts +135 -0
- package/src/agent/agent-quality-gates-context.js +44 -50
- package/src/agent/agent-quality-gates-context.ts +91 -0
- package/src/agent/agent-stuck-detector.js +171 -239
- package/src/agent/agent-stuck-detector.ts +288 -0
- package/src/agent/agent-trigger-evaluator.js +29 -46
- package/src/agent/agent-trigger-evaluator.ts +98 -0
- package/src/agent/context-metrics.js +110 -141
- package/src/agent/context-metrics.ts +261 -0
- package/src/agent/context-pack-builder.js +72 -358
- package/src/agent/context-pack-builder.ts +105 -0
- package/src/agent/context-pack-core.js +70 -0
- package/src/agent/context-pack-core.ts +96 -0
- package/src/agent/context-pack-selection.js +119 -0
- package/src/agent/context-pack-selection.ts +187 -0
- package/src/agent/context-pack-truncation.js +139 -0
- package/src/agent/context-pack-truncation.ts +208 -0
- package/src/agent/context-pack-types.js +2 -0
- package/src/agent/context-pack-types.ts +98 -0
- package/src/agent/context-replay-policy.js +30 -39
- package/src/agent/context-replay-policy.ts +71 -0
- package/src/agent/critical-agent-policy.js +6 -10
- package/src/agent/critical-agent-policy.ts +20 -0
- package/src/agent/guidance-queue.js +74 -87
- package/src/agent/guidance-queue.ts +166 -0
- package/src/agent/output-extraction-error-detail.js +45 -0
- package/src/agent/output-extraction-error-detail.ts +49 -0
- package/src/agent/output-extraction-failures.js +125 -0
- package/src/agent/output-extraction-failures.ts +149 -0
- package/src/agent/output-extraction-json.js +234 -0
- package/src/agent/output-extraction-json.ts +235 -0
- package/src/agent/output-extraction-pi.js +124 -0
- package/src/agent/output-extraction-pi.ts +137 -0
- package/src/agent/output-extraction-types.js +2 -0
- package/src/agent/output-extraction-types.ts +49 -0
- package/src/agent/output-extraction.js +17 -617
- package/src/agent/output-extraction.ts +30 -0
- package/src/agent/output-reformatter-errors.js +83 -0
- package/src/agent/output-reformatter-errors.ts +106 -0
- package/src/agent/output-reformatter-types.js +2 -0
- package/src/agent/output-reformatter-types.ts +86 -0
- package/src/agent/output-reformatter.js +140 -185
- package/src/agent/output-reformatter.ts +238 -0
- package/src/agent/pi-terminal-lifecycle-types.js +2 -0
- package/src/agent/pi-terminal-lifecycle-types.ts +41 -0
- package/src/agent/pi-terminal-lifecycle.js +186 -206
- package/src/agent/pi-terminal-lifecycle.ts +297 -0
- package/src/agent/provider-control-plane.js +22 -21
- package/src/agent/provider-control-plane.ts +52 -0
- package/src/agent/provider-terminal-failure-types.js +2 -0
- package/src/agent/provider-terminal-failure-types.ts +60 -0
- package/src/agent/provider-terminal-failure.js +194 -191
- package/src/agent/provider-terminal-failure.ts +262 -0
- package/src/agent/rate-limit-backoff.js +31 -74
- package/src/agent/rate-limit-backoff.ts +71 -0
- package/src/agent/schema-utils.js +85 -125
- package/src/agent/schema-utils.ts +148 -0
- package/src/agent/structured-output-error.js +34 -33
- package/src/agent/structured-output-error.ts +105 -0
- package/src/agent/validation-platform.js +27 -27
- package/src/agent/validation-platform.ts +49 -0
- package/src/agent-cli-provider/adapters/codex.ts +3 -1
- package/src/attach/attach-client.js +232 -405
- package/src/attach/attach-client.ts +300 -0
- package/src/attach/attach-server-cleanup.js +43 -0
- package/src/attach/attach-server-cleanup.ts +39 -0
- package/src/attach/attach-server-clients.js +117 -0
- package/src/attach/attach-server-clients.ts +122 -0
- package/src/attach/attach-server-events.js +38 -0
- package/src/attach/attach-server-events.ts +36 -0
- package/src/attach/attach-server-pty.js +45 -0
- package/src/attach/attach-server-pty.ts +64 -0
- package/src/attach/attach-server-runtime.js +73 -0
- package/src/attach/attach-server-runtime.ts +62 -0
- package/src/attach/attach-server-socket.js +28 -0
- package/src/attach/attach-server-socket.ts +23 -0
- package/src/attach/attach-server-types.js +2 -0
- package/src/attach/attach-server-types.ts +82 -0
- package/src/attach/attach-server.js +106 -523
- package/src/attach/attach-server.ts +158 -0
- package/src/attach/index.js +18 -35
- package/src/attach/index.ts +18 -0
- package/src/attach/protocol.js +95 -184
- package/src/attach/protocol.ts +181 -0
- package/src/attach/ring-buffer.js +76 -102
- package/src/attach/ring-buffer.ts +109 -0
- package/src/attach/send-input.js +64 -75
- package/src/attach/send-input.ts +94 -0
- package/src/attach/socket-discovery.js +164 -218
- package/src/attach/socket-discovery.ts +230 -0
- package/src/attach/socket-paths.js +42 -61
- package/src/attach/socket-paths.ts +81 -0
- package/src/claude-credentials.js +51 -54
- package/src/claude-credentials.ts +89 -0
- package/src/cluster/generated/protocol-schema.ts +1 -1
- package/src/cluster/generated/protocol.ts +2 -1
- package/src/command-proofs.js +110 -141
- package/src/command-proofs.ts +216 -0
- package/src/config-router.js +81 -73
- package/src/config-router.ts +131 -0
- package/src/copy-worker.js +53 -29
- package/src/copy-worker.ts +75 -0
- package/src/darwin-keychain-boundary.js +80 -111
- package/src/darwin-keychain-boundary.ts +148 -0
- package/src/guidance-topics.js +4 -5
- package/src/guidance-topics.ts +10 -0
- package/src/input-helpers.js +33 -54
- package/src/input-helpers.ts +58 -0
- package/src/ledger-sequence.js +45 -48
- package/src/ledger-sequence.ts +63 -0
- package/src/legacy-lib/clusters-registry.ts +40 -0
- package/src/legacy-lib/completion.ts +148 -0
- package/src/legacy-lib/compose-utils.ts +102 -0
- package/src/legacy-lib/detached-startup.ts +449 -0
- package/src/legacy-lib/docker-config.ts +282 -0
- package/src/legacy-lib/docker-env-auth.ts +116 -0
- package/src/legacy-lib/git-remote-utils.ts +244 -0
- package/src/legacy-lib/id-detector.ts +57 -0
- package/src/legacy-lib/js-yaml.d.ts +3 -0
- package/src/legacy-lib/omelette.d.ts +15 -0
- package/src/legacy-lib/path-check.ts +89 -0
- package/src/legacy-lib/process-liveness.ts +36 -0
- package/src/legacy-lib/provider-credential-path.ts +57 -0
- package/src/legacy-lib/provider-defaults.ts +114 -0
- package/src/legacy-lib/provider-detection.ts +67 -0
- package/src/legacy-lib/provider-names.ts +126 -0
- package/src/legacy-lib/repo-settings.ts +88 -0
- package/src/legacy-lib/run-mode.ts +58 -0
- package/src/legacy-lib/run-plan.ts +49 -0
- package/src/legacy-lib/settings/claude-auth.ts +80 -0
- package/src/legacy-lib/settings-defaults.ts +212 -0
- package/src/legacy-lib/settings-error.ts +8 -0
- package/src/legacy-lib/settings-issue-providers.ts +18 -0
- package/src/legacy-lib/settings-models.ts +124 -0
- package/src/legacy-lib/settings-storage.ts +277 -0
- package/src/legacy-lib/settings-validation.ts +220 -0
- package/src/legacy-lib/settings.ts +87 -0
- package/src/legacy-lib/setup-apply-types.ts +70 -0
- package/src/legacy-lib/setup-apply.ts +456 -0
- package/src/legacy-lib/setup-journal.ts +185 -0
- package/src/legacy-lib/setup-plan-types.ts +96 -0
- package/src/legacy-lib/setup-plan.ts +500 -0
- package/src/legacy-lib/setup-undo.ts +142 -0
- package/src/legacy-lib/start-cluster-config.ts +124 -0
- package/src/legacy-lib/start-cluster-environment.ts +165 -0
- package/src/legacy-lib/start-cluster-run-options.ts +279 -0
- package/src/legacy-lib/start-cluster.ts +237 -0
- package/src/legacy-lib/stream-json-parser.ts +88 -0
- package/src/message-buffer.js +55 -71
- package/src/message-buffer.ts +107 -0
- package/src/message-bus-bridge.js +92 -133
- package/src/message-bus-bridge.ts +149 -0
- package/src/name-generator-shared.js +220 -0
- package/src/name-generator-shared.ts +231 -0
- package/src/name-generator.js +8 -217
- package/src/name-generator.ts +27 -0
- package/src/omp-blob-root.js +83 -64
- package/src/omp-blob-root.ts +116 -0
- package/src/omp-config-overlay.js +65 -38
- package/src/omp-config-overlay.ts +110 -0
- package/src/omp-execution-fingerprint.js +38 -43
- package/src/omp-execution-fingerprint.ts +92 -0
- package/src/omp-session-limits.js +10 -15
- package/src/omp-session-limits.ts +53 -0
- package/src/omp-session-partition.js +2 -2
- package/src/omp-session-verifier.js +1 -1
- package/src/orchestrator.js +19 -4
- package/src/providers/anthropic/index.js +3 -1
- package/src/providers/anthropic/index.ts +13 -0
- package/src/providers/capabilities.js +19 -27
- package/src/providers/capabilities.ts +44 -0
- package/src/providers/google/index.js +3 -1
- package/src/providers/google/index.ts +13 -0
- package/src/providers/openai/index.js +3 -1
- package/src/providers/openai/index.ts +13 -0
- package/src/providers/opencode/index.js +3 -1
- package/src/providers/opencode/index.ts +13 -0
- package/src/quality-gates.js +67 -127
- package/src/quality-gates.ts +149 -0
- package/src/repo-settings-access.js +40 -0
- package/src/repo-settings-access.ts +64 -0
- package/src/schemas/sub-cluster.js +127 -183
- package/src/schemas/sub-cluster.ts +238 -0
- package/src/state-snapshot-normalization.js +224 -0
- package/src/state-snapshot-normalization.ts +222 -0
- package/src/state-snapshot.js +192 -359
- package/src/state-snapshot.ts +291 -0
- package/src/state-snapshotter.js +118 -130
- package/src/state-snapshotter.ts +167 -0
- package/src/sub-cluster-wrapper.js +4 -13
- package/src/task-run-model-args.js +87 -124
- package/src/task-run-model-args.ts +204 -0
- package/src/task-runner.js +5 -25
- package/src/task-runner.ts +28 -0
- package/src/task-startup-error.js +73 -58
- package/src/task-startup-error.ts +126 -0
- package/src/template-validation/consensus-gate-contracts.js +2 -0
- package/src/template-validation/consensus-gate-contracts.ts +70 -0
- package/src/template-validation/consensus-gate-scenarios.js +76 -0
- package/src/template-validation/consensus-gate-scenarios.ts +130 -0
- package/src/template-validation/consensus-gate-stage.js +34 -0
- package/src/template-validation/consensus-gate-stage.ts +56 -0
- package/src/template-validation/random-topology-contracts.js +2 -0
- package/src/template-validation/random-topology-contracts.ts +54 -0
- package/src/template-validation/random-topology-dispatch.js +25 -0
- package/src/template-validation/random-topology-dispatch.ts +33 -0
- package/src/template-validation/random-topology-message-loop.js +53 -0
- package/src/template-validation/random-topology-message-loop.ts +73 -0
- package/src/template-validation/random-topology-operations.js +79 -0
- package/src/template-validation/random-topology-operations.ts +101 -0
- package/src/template-validation/random-topology-runtime.js +25 -0
- package/src/template-validation/random-topology-runtime.ts +43 -0
- package/src/template-validation/random-topology-scenario.js +52 -0
- package/src/template-validation/random-topology-scenario.ts +57 -0
- package/src/template-validation/random-topology-schema.js +159 -0
- package/src/template-validation/random-topology-schema.ts +190 -0
- package/src/template-validation/random-topology-state.js +28 -0
- package/src/template-validation/random-topology-state.ts +30 -0
- package/src/template-validation/random-topology-trigger-evaluation.js +45 -0
- package/src/template-validation/random-topology-trigger-evaluation.ts +65 -0
- package/src/template-validation/random-topology-trigger.js +75 -0
- package/src/template-validation/random-topology-trigger.ts +88 -0
- package/src/template-validation/report-formatter.js +40 -46
- package/src/template-validation/report-formatter.ts +101 -0
- package/src/template-validation/simulate-consensus-gates.js +111 -302
- package/src/template-validation/simulate-consensus-gates.ts +196 -0
- package/src/template-validation/simulate-random-topology.js +26 -519
- package/src/template-validation/simulate-random-topology.ts +57 -0
- package/src/template-validation/simulate-two-stage-validation.js +41 -265
- package/src/template-validation/simulate-two-stage-validation.ts +55 -0
- package/src/template-validation/simulation-agent-runtime.js +25 -0
- package/src/template-validation/simulation-agent-runtime.ts +40 -0
- package/src/template-validation/simulation-agent.js +29 -0
- package/src/template-validation/simulation-agent.ts +73 -0
- package/src/template-validation/simulation-runtime.js +34 -0
- package/src/template-validation/simulation-runtime.ts +72 -0
- package/src/template-validation/two-stage-contracts.js +2 -0
- package/src/template-validation/two-stage-contracts.ts +41 -0
- package/src/template-validation/two-stage-inputs.js +21 -0
- package/src/template-validation/two-stage-inputs.ts +29 -0
- package/src/template-validation/two-stage-results.js +30 -0
- package/src/template-validation/two-stage-results.ts +40 -0
- package/src/template-validation/two-stage-scenario.js +86 -0
- package/src/template-validation/two-stage-scenario.ts +120 -0
- package/src/worktree-claude-config.js +182 -220
- package/src/worktree-claude-config.ts +272 -0
- package/src/worktree-tooling-env.js +103 -125
- package/src/worktree-tooling-env.ts +142 -0
- package/task-lib/completion.js +2 -3
- package/task-lib/completion.ts +9 -0
- package/task-lib/config.js +1 -4
- package/task-lib/config.ts +11 -0
- package/task-lib/name-generator.js +4 -226
- package/task-lib/name-generator.ts +11 -0
- package/task-lib/omp-session-cleanup.js +1 -1
- package/task-lib/omp-storage-root.js +8 -12
- package/task-lib/omp-storage-root.ts +62 -0
- package/task-lib/process-termination.js +143 -166
- package/task-lib/process-termination.ts +268 -0
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import fs = require('fs');
|
|
2
|
+
import os = require('os');
|
|
3
|
+
import path = require('path');
|
|
4
|
+
import worktreeToolingEnv = require('./worktree-tooling-env');
|
|
5
|
+
|
|
6
|
+
const { resolveWorktreeRoot } = worktreeToolingEnv;
|
|
7
|
+
|
|
8
|
+
type MutableRecord = Record<string, unknown>;
|
|
9
|
+
|
|
10
|
+
interface HookCommand {
|
|
11
|
+
type?: string;
|
|
12
|
+
command?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
interface HookEntry {
|
|
16
|
+
matcher?: string;
|
|
17
|
+
hooks?: readonly HookCommand[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
interface OverlayOptions {
|
|
21
|
+
includeDangerousGit?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
interface WorktreeOptions {
|
|
25
|
+
worktreePath?: string;
|
|
26
|
+
cwd?: string;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const CLAUDE_DIRNAME = '.claude';
|
|
30
|
+
const MCP_BASENAME = '.mcp.json';
|
|
31
|
+
const SETTINGS_BASENAME = 'settings.json';
|
|
32
|
+
const OVERLAY_PREFIX = 'zeroshot-claude-settings-';
|
|
33
|
+
const CLAUDE_SETTINGS_ENV = 'ZEROSHOT_CLAUDE_SETTINGS_FILE';
|
|
34
|
+
const CLAUDE_MCP_CONFIG_ENV = 'ZEROSHOT_CLAUDE_MCP_CONFIG_FILE';
|
|
35
|
+
const ASK_USER_HOOK = 'block-ask-user-question.py';
|
|
36
|
+
const DANGEROUS_GIT_HOOK = 'block-dangerous-git.py';
|
|
37
|
+
|
|
38
|
+
function isMutableRecord(value: unknown): value is MutableRecord {
|
|
39
|
+
return typeof value === 'object' && value !== null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isHookEntries(value: unknown): value is HookEntry[] {
|
|
43
|
+
return Array.isArray(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readSettings(settingsPath: string): MutableRecord {
|
|
47
|
+
if (!fs.existsSync(settingsPath)) return {};
|
|
48
|
+
|
|
49
|
+
let parsed: unknown;
|
|
50
|
+
try {
|
|
51
|
+
parsed = JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
54
|
+
throw new Error(`Could not parse Claude settings overlay ${settingsPath}: ${message}`);
|
|
55
|
+
}
|
|
56
|
+
if (!isMutableRecord(parsed)) {
|
|
57
|
+
throw new TypeError(`Claude settings overlay ${settingsPath} must contain an object.`);
|
|
58
|
+
}
|
|
59
|
+
return parsed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function writeSettings(settingsPath: string, settings: MutableRecord): void {
|
|
63
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2), { mode: 0o600 });
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function requireTargetClaudeDir(targetClaudeDir: unknown): string {
|
|
67
|
+
if (typeof targetClaudeDir !== 'string' || !targetClaudeDir) {
|
|
68
|
+
throw new Error('Claude safety hooks require an explicit per-run settings directory.');
|
|
69
|
+
}
|
|
70
|
+
if (!isClaudeSettingsOverlayDirectory(targetClaudeDir)) {
|
|
71
|
+
throw new Error(
|
|
72
|
+
`Claude safety hooks require a Zeroshot-owned Claude settings overlay: ${targetClaudeDir}`
|
|
73
|
+
);
|
|
74
|
+
}
|
|
75
|
+
return targetClaudeDir;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function copyHookScript(targetClaudeDir: string, hookScriptName: string): string {
|
|
79
|
+
const hooksDir = path.join(targetClaudeDir, 'hooks');
|
|
80
|
+
fs.mkdirSync(hooksDir, { recursive: true });
|
|
81
|
+
|
|
82
|
+
const sourcePath = path.join(__dirname, '..', 'cluster-hooks', hookScriptName);
|
|
83
|
+
if (!fs.existsSync(sourcePath)) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`Claude safety hook ${hookScriptName} is missing from the Zeroshot installation.`
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const destinationPath = path.join(hooksDir, hookScriptName);
|
|
90
|
+
fs.copyFileSync(sourcePath, destinationPath);
|
|
91
|
+
fs.chmodSync(destinationPath, 0o755);
|
|
92
|
+
return destinationPath;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function ensurePreToolUseHooks(settings: MutableRecord): HookEntry[] {
|
|
96
|
+
let hooks = settings.hooks;
|
|
97
|
+
if (!hooks) {
|
|
98
|
+
hooks = {};
|
|
99
|
+
settings.hooks = hooks;
|
|
100
|
+
}
|
|
101
|
+
if (!isMutableRecord(hooks)) {
|
|
102
|
+
throw new TypeError('Claude settings hooks must be an object.');
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
let preToolUse = hooks.PreToolUse;
|
|
106
|
+
if (!preToolUse) {
|
|
107
|
+
preToolUse = [];
|
|
108
|
+
hooks.PreToolUse = preToolUse;
|
|
109
|
+
}
|
|
110
|
+
if (!isHookEntries(preToolUse)) {
|
|
111
|
+
throw new TypeError('Claude settings hooks.PreToolUse must be an array.');
|
|
112
|
+
}
|
|
113
|
+
return preToolUse;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function ensureAskUserQuestionHook(targetClaudeDir: unknown): void {
|
|
117
|
+
const overlayDir = requireTargetClaudeDir(targetClaudeDir);
|
|
118
|
+
const hookScriptPath = copyHookScript(overlayDir, ASK_USER_HOOK);
|
|
119
|
+
const settingsPath = path.join(overlayDir, SETTINGS_BASENAME);
|
|
120
|
+
const settings = readSettings(settingsPath);
|
|
121
|
+
const hooks = ensurePreToolUseHooks(settings);
|
|
122
|
+
const hasHook = hooks.some(
|
|
123
|
+
(entry) =>
|
|
124
|
+
entry.matcher === 'AskUserQuestion' ||
|
|
125
|
+
entry.hooks?.some((hook) => hook.command?.includes(ASK_USER_HOOK))
|
|
126
|
+
);
|
|
127
|
+
|
|
128
|
+
if (!hasHook) {
|
|
129
|
+
hooks.push({
|
|
130
|
+
matcher: 'AskUserQuestion',
|
|
131
|
+
hooks: [{ type: 'command', command: hookScriptPath }],
|
|
132
|
+
});
|
|
133
|
+
writeSettings(settingsPath, settings);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
function ensureDangerousGitHook(targetClaudeDir: unknown): void {
|
|
138
|
+
const overlayDir = requireTargetClaudeDir(targetClaudeDir);
|
|
139
|
+
const hookScriptPath = copyHookScript(overlayDir, DANGEROUS_GIT_HOOK);
|
|
140
|
+
const settingsPath = path.join(overlayDir, SETTINGS_BASENAME);
|
|
141
|
+
const settings = readSettings(settingsPath);
|
|
142
|
+
const hooks = ensurePreToolUseHooks(settings);
|
|
143
|
+
const hasHook = hooks.some(
|
|
144
|
+
(entry) =>
|
|
145
|
+
entry.matcher === 'Bash' &&
|
|
146
|
+
entry.hooks?.some((hook) => hook.command?.includes(DANGEROUS_GIT_HOOK))
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
if (!hasHook) {
|
|
150
|
+
hooks.push({
|
|
151
|
+
matcher: 'Bash',
|
|
152
|
+
hooks: [{ type: 'command', command: hookScriptPath }],
|
|
153
|
+
});
|
|
154
|
+
writeSettings(settingsPath, settings);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function prepareClaudeSettingsOverlay(options: OverlayOptions = {}): string {
|
|
159
|
+
const overlayDir = fs.mkdtempSync(path.join(os.tmpdir(), OVERLAY_PREFIX));
|
|
160
|
+
fs.chmodSync(overlayDir, 0o700);
|
|
161
|
+
try {
|
|
162
|
+
ensureAskUserQuestionHook(overlayDir);
|
|
163
|
+
if (options.includeDangerousGit) ensureDangerousGitHook(overlayDir);
|
|
164
|
+
return path.join(overlayDir, SETTINGS_BASENAME);
|
|
165
|
+
} catch (error) {
|
|
166
|
+
fs.rmSync(overlayDir, { recursive: true, force: true });
|
|
167
|
+
throw error;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function cleanupClaudeSettingsOverlay(settingsPath: unknown): boolean {
|
|
172
|
+
if (!isCanonicalClaudeSettingsOverlayPath(settingsPath)) return false;
|
|
173
|
+
|
|
174
|
+
const overlayDir = path.dirname(settingsPath);
|
|
175
|
+
if (!fs.existsSync(overlayDir)) return true;
|
|
176
|
+
if (!isClaudeSettingsOverlayPath(settingsPath)) return false;
|
|
177
|
+
|
|
178
|
+
fs.rmSync(overlayDir, { recursive: true, force: true });
|
|
179
|
+
return true;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
function isCanonicalClaudeSettingsOverlayPath(settingsPath: unknown): settingsPath is string {
|
|
183
|
+
if (typeof settingsPath !== 'string' || !settingsPath) return false;
|
|
184
|
+
const resolvedSettingsPath = path.resolve(settingsPath);
|
|
185
|
+
const overlayDir = path.dirname(resolvedSettingsPath);
|
|
186
|
+
return (
|
|
187
|
+
resolvedSettingsPath === settingsPath &&
|
|
188
|
+
path.basename(resolvedSettingsPath) === SETTINGS_BASENAME &&
|
|
189
|
+
path.dirname(overlayDir) === path.resolve(os.tmpdir()) &&
|
|
190
|
+
path.basename(overlayDir).startsWith(OVERLAY_PREFIX)
|
|
191
|
+
);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function isClaudeSettingsOverlayPath(
|
|
195
|
+
settingsPath: unknown,
|
|
196
|
+
platform: NodeJS.Platform = process.platform
|
|
197
|
+
): settingsPath is string {
|
|
198
|
+
if (!isCanonicalClaudeSettingsOverlayPath(settingsPath)) return false;
|
|
199
|
+
|
|
200
|
+
const overlayDir = path.dirname(settingsPath);
|
|
201
|
+
try {
|
|
202
|
+
const stat = fs.lstatSync(overlayDir);
|
|
203
|
+
const ownedByProcess = typeof process.getuid !== 'function' || stat.uid === process.getuid();
|
|
204
|
+
const privateMode = platform === 'win32' || (stat.mode & 0o777) === 0o700;
|
|
205
|
+
return stat.isDirectory() && !stat.isSymbolicLink() && ownedByProcess && privateMode;
|
|
206
|
+
} catch {
|
|
207
|
+
return false;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function isCanonicalClaudeSettingsOverlayDirectory(overlayDir: unknown): overlayDir is string {
|
|
212
|
+
if (
|
|
213
|
+
typeof overlayDir !== 'string' ||
|
|
214
|
+
!overlayDir ||
|
|
215
|
+
path.resolve(overlayDir) !== overlayDir
|
|
216
|
+
) {
|
|
217
|
+
return false;
|
|
218
|
+
}
|
|
219
|
+
return isCanonicalClaudeSettingsOverlayPath(path.join(overlayDir, SETTINGS_BASENAME));
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isClaudeSettingsOverlayDirectory(overlayDir: unknown): overlayDir is string {
|
|
223
|
+
return (
|
|
224
|
+
typeof overlayDir === 'string' &&
|
|
225
|
+
Boolean(overlayDir) &&
|
|
226
|
+
isClaudeSettingsOverlayPath(path.join(overlayDir, SETTINGS_BASENAME))
|
|
227
|
+
);
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function resolveRepoMcpConfigPath(options: WorktreeOptions = {}): string | null {
|
|
231
|
+
const worktreeRoot = resolveWorktreeRoot(options.worktreePath || options.cwd);
|
|
232
|
+
if (!worktreeRoot) return null;
|
|
233
|
+
|
|
234
|
+
const candidates = [
|
|
235
|
+
path.join(worktreeRoot, MCP_BASENAME),
|
|
236
|
+
path.join(worktreeRoot, CLAUDE_DIRNAME, MCP_BASENAME),
|
|
237
|
+
];
|
|
238
|
+
return candidates.find((candidate) => fs.existsSync(candidate)) || null;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
function resolveContainerMcpConfigPath(options: WorktreeOptions = {}): string | null {
|
|
242
|
+
const worktreeRoot = resolveWorktreeRoot(options.worktreePath || options.cwd);
|
|
243
|
+
const hostConfigPath = resolveRepoMcpConfigPath(options);
|
|
244
|
+
if (!worktreeRoot || !hostConfigPath) return null;
|
|
245
|
+
|
|
246
|
+
const relativePath = path.relative(worktreeRoot, hostConfigPath);
|
|
247
|
+
if (
|
|
248
|
+
!relativePath ||
|
|
249
|
+
path.isAbsolute(relativePath) ||
|
|
250
|
+
relativePath === '..' ||
|
|
251
|
+
relativePath.startsWith(`..${path.sep}`)
|
|
252
|
+
) {
|
|
253
|
+
throw new Error(`Repository MCP config is outside the mounted workspace: ${hostConfigPath}`);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
return path.posix.join('/workspace', relativePath.split(path.sep).join('/'));
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export = {
|
|
260
|
+
CLAUDE_MCP_CONFIG_ENV,
|
|
261
|
+
CLAUDE_SETTINGS_ENV,
|
|
262
|
+
cleanupClaudeSettingsOverlay,
|
|
263
|
+
ensureAskUserQuestionHook,
|
|
264
|
+
ensureDangerousGitHook,
|
|
265
|
+
isCanonicalClaudeSettingsOverlayDirectory,
|
|
266
|
+
isCanonicalClaudeSettingsOverlayPath,
|
|
267
|
+
isClaudeSettingsOverlayDirectory,
|
|
268
|
+
isClaudeSettingsOverlayPath,
|
|
269
|
+
prepareClaudeSettingsOverlay,
|
|
270
|
+
resolveContainerMcpConfigPath,
|
|
271
|
+
resolveRepoMcpConfigPath,
|
|
272
|
+
};
|
|
@@ -1,153 +1,131 @@
|
|
|
1
|
-
|
|
2
|
-
const
|
|
3
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
4
|
const TOOLING_METADATA_RELATIVE_PATH = path.join('.zeroshot', 'tooling-env.json');
|
|
5
5
|
const DEFAULT_TOOL_BIN_RELATIVE_PATHS = ['.zeroshot/bin', '.worktree-tool-bin'];
|
|
6
6
|
const FALLBACK_BIN_PREFIX = '.worktree-tool-bin.';
|
|
7
|
-
|
|
7
|
+
function isToolingMetadata(value) {
|
|
8
|
+
return typeof value === 'object' && value !== null;
|
|
9
|
+
}
|
|
8
10
|
function pathKeyForEnv(env) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
if (Object.prototype.hasOwnProperty.call(env, 'PATH')) {
|
|
12
|
+
return 'PATH';
|
|
13
|
+
}
|
|
14
|
+
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') ?? 'PATH';
|
|
13
15
|
}
|
|
14
|
-
|
|
15
16
|
function resolveExistingRealPath(candidatePath) {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
try {
|
|
18
|
+
return fs.realpathSync(candidatePath);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
return null;
|
|
22
|
+
}
|
|
21
23
|
}
|
|
22
|
-
|
|
23
24
|
function isWithinRoot(candidatePath, rootPath) {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
return (
|
|
31
|
-
candidateRealPath === rootRealPath || candidateRealPath.startsWith(`${rootRealPath}${path.sep}`)
|
|
32
|
-
);
|
|
25
|
+
const candidateRealPath = resolveExistingRealPath(candidatePath);
|
|
26
|
+
const rootRealPath = resolveExistingRealPath(rootPath);
|
|
27
|
+
if (!candidateRealPath || !rootRealPath) {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
return (candidateRealPath === rootRealPath || candidateRealPath.startsWith(`${rootRealPath}${path.sep}`));
|
|
33
31
|
}
|
|
34
|
-
|
|
35
32
|
function dedupePaths(entries) {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return orderedEntries;
|
|
33
|
+
const seen = new Set();
|
|
34
|
+
const orderedEntries = [];
|
|
35
|
+
for (const entry of entries) {
|
|
36
|
+
if (!entry || seen.has(entry)) {
|
|
37
|
+
continue;
|
|
38
|
+
}
|
|
39
|
+
seen.add(entry);
|
|
40
|
+
orderedEntries.push(entry);
|
|
41
|
+
}
|
|
42
|
+
return orderedEntries;
|
|
48
43
|
}
|
|
49
|
-
|
|
50
44
|
function hasToolingMetadata(dirPath) {
|
|
51
|
-
|
|
45
|
+
return fs.existsSync(path.join(dirPath, TOOLING_METADATA_RELATIVE_PATH));
|
|
52
46
|
}
|
|
53
|
-
|
|
54
47
|
function hasGitEntry(dirPath) {
|
|
55
|
-
|
|
48
|
+
return fs.existsSync(path.join(dirPath, '.git'));
|
|
56
49
|
}
|
|
57
|
-
|
|
58
50
|
function resolveWorktreeRoot(startDir) {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
}
|
|
77
|
-
currentDir = parentDir;
|
|
78
|
-
}
|
|
51
|
+
if (!startDir) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
let currentDir = path.resolve(startDir);
|
|
55
|
+
let nearestGitRoot = null;
|
|
56
|
+
while (true) {
|
|
57
|
+
if (hasToolingMetadata(currentDir)) {
|
|
58
|
+
return currentDir;
|
|
59
|
+
}
|
|
60
|
+
if (!nearestGitRoot && hasGitEntry(currentDir)) {
|
|
61
|
+
nearestGitRoot = currentDir;
|
|
62
|
+
}
|
|
63
|
+
const parentDir = path.dirname(currentDir);
|
|
64
|
+
if (parentDir === currentDir) {
|
|
65
|
+
return nearestGitRoot;
|
|
66
|
+
}
|
|
67
|
+
currentDir = parentDir;
|
|
68
|
+
}
|
|
79
69
|
}
|
|
80
|
-
|
|
81
70
|
function readToolingMetadata(worktreeRoot) {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
71
|
+
const metadataPath = path.join(worktreeRoot, TOOLING_METADATA_RELATIVE_PATH);
|
|
72
|
+
if (!fs.existsSync(metadataPath)) {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
try {
|
|
76
|
+
const parsed = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
77
|
+
if (!isToolingMetadata(parsed)) {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
return parsed;
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return null;
|
|
84
|
+
}
|
|
96
85
|
}
|
|
97
|
-
|
|
98
86
|
function listFallbackBinDirectories(worktreeRoot) {
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
87
|
+
try {
|
|
88
|
+
return fs
|
|
89
|
+
.readdirSync(worktreeRoot, { withFileTypes: true })
|
|
90
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith(FALLBACK_BIN_PREFIX))
|
|
91
|
+
.map((entry) => path.join(worktreeRoot, entry.name));
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return [];
|
|
95
|
+
}
|
|
107
96
|
}
|
|
108
|
-
|
|
109
97
|
function resolveWorktreeToolBinEntries(options = {}) {
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
candidates.push(path.join(worktreeRoot, relativePath));
|
|
129
|
-
}
|
|
130
|
-
candidates.push(...listFallbackBinDirectories(worktreeRoot));
|
|
131
|
-
|
|
132
|
-
return dedupePaths(candidates).filter((candidatePath) =>
|
|
133
|
-
isWithinRoot(candidatePath, worktreeRoot)
|
|
134
|
-
);
|
|
98
|
+
const worktreeRoot = resolveWorktreeRoot(options.worktreePath || options.cwd);
|
|
99
|
+
if (!worktreeRoot) {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
const metadata = readToolingMetadata(worktreeRoot);
|
|
103
|
+
const hasExplicitWorktreePath = typeof options.worktreePath === 'string' && options.worktreePath.trim().length > 0;
|
|
104
|
+
if (!metadata && !hasExplicitWorktreePath) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const candidates = [];
|
|
108
|
+
if (typeof metadata?.toolBinDir === 'string' && metadata.toolBinDir.trim()) {
|
|
109
|
+
candidates.push(metadata.toolBinDir.trim());
|
|
110
|
+
}
|
|
111
|
+
for (const relativePath of DEFAULT_TOOL_BIN_RELATIVE_PATHS) {
|
|
112
|
+
candidates.push(path.join(worktreeRoot, relativePath));
|
|
113
|
+
}
|
|
114
|
+
candidates.push(...listFallbackBinDirectories(worktreeRoot));
|
|
115
|
+
return dedupePaths(candidates).filter((candidatePath) => isWithinRoot(candidatePath, worktreeRoot));
|
|
135
116
|
}
|
|
136
|
-
|
|
137
117
|
function prependWorktreeToolBinToEnv(env, options = {}) {
|
|
138
|
-
|
|
139
|
-
|
|
118
|
+
const toolBinEntries = resolveWorktreeToolBinEntries(options);
|
|
119
|
+
if (toolBinEntries.length === 0) {
|
|
120
|
+
return env;
|
|
121
|
+
}
|
|
122
|
+
const pathKey = pathKeyForEnv(env);
|
|
123
|
+
const existingEntries = (env[pathKey] || '').split(path.delimiter).filter(Boolean);
|
|
124
|
+
env[pathKey] = dedupePaths([...toolBinEntries, ...existingEntries]).join(path.delimiter);
|
|
140
125
|
return env;
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
const pathKey = pathKeyForEnv(env);
|
|
144
|
-
const existingEntries = (env[pathKey] || '').split(path.delimiter).filter(Boolean);
|
|
145
|
-
env[pathKey] = dedupePaths([...toolBinEntries, ...existingEntries]).join(path.delimiter);
|
|
146
|
-
return env;
|
|
147
126
|
}
|
|
148
|
-
|
|
149
127
|
module.exports = {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
128
|
+
prependWorktreeToolBinToEnv,
|
|
129
|
+
resolveWorktreeRoot,
|
|
130
|
+
resolveWorktreeToolBinEntries,
|
|
153
131
|
};
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import fs = require('fs');
|
|
2
|
+
import path = require('path');
|
|
3
|
+
interface WorktreeToolingOptions {
|
|
4
|
+
worktreePath?: string;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
}
|
|
7
|
+
interface ToolingMetadata {
|
|
8
|
+
toolBinDir?: unknown;
|
|
9
|
+
}
|
|
10
|
+
const TOOLING_METADATA_RELATIVE_PATH = path.join('.zeroshot', 'tooling-env.json');
|
|
11
|
+
const DEFAULT_TOOL_BIN_RELATIVE_PATHS = ['.zeroshot/bin', '.worktree-tool-bin'];
|
|
12
|
+
const FALLBACK_BIN_PREFIX = '.worktree-tool-bin.';
|
|
13
|
+
function isToolingMetadata(value: unknown): value is ToolingMetadata {
|
|
14
|
+
return typeof value === 'object' && value !== null;
|
|
15
|
+
}
|
|
16
|
+
function pathKeyForEnv(env: NodeJS.ProcessEnv): string {
|
|
17
|
+
if (Object.prototype.hasOwnProperty.call(env, 'PATH')) {
|
|
18
|
+
return 'PATH';
|
|
19
|
+
}
|
|
20
|
+
return Object.keys(env).find((key) => key.toUpperCase() === 'PATH') ?? 'PATH';
|
|
21
|
+
}
|
|
22
|
+
function resolveExistingRealPath(candidatePath: string): string | null {
|
|
23
|
+
try {
|
|
24
|
+
return fs.realpathSync(candidatePath);
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function isWithinRoot(candidatePath: string, rootPath: string): boolean {
|
|
30
|
+
const candidateRealPath = resolveExistingRealPath(candidatePath);
|
|
31
|
+
const rootRealPath = resolveExistingRealPath(rootPath);
|
|
32
|
+
if (!candidateRealPath || !rootRealPath) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return (
|
|
36
|
+
candidateRealPath === rootRealPath || candidateRealPath.startsWith(`${rootRealPath}${path.sep}`)
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
function dedupePaths(entries: Iterable<string | null | undefined>): string[] {
|
|
40
|
+
const seen = new Set<string>();
|
|
41
|
+
const orderedEntries: string[] = [];
|
|
42
|
+
for (const entry of entries) {
|
|
43
|
+
if (!entry || seen.has(entry)) {
|
|
44
|
+
continue;
|
|
45
|
+
}
|
|
46
|
+
seen.add(entry);
|
|
47
|
+
orderedEntries.push(entry);
|
|
48
|
+
}
|
|
49
|
+
return orderedEntries;
|
|
50
|
+
}
|
|
51
|
+
function hasToolingMetadata(dirPath: string): boolean {
|
|
52
|
+
return fs.existsSync(path.join(dirPath, TOOLING_METADATA_RELATIVE_PATH));
|
|
53
|
+
}
|
|
54
|
+
function hasGitEntry(dirPath: string): boolean {
|
|
55
|
+
return fs.existsSync(path.join(dirPath, '.git'));
|
|
56
|
+
}
|
|
57
|
+
function resolveWorktreeRoot(startDir: string | null | undefined): string | null {
|
|
58
|
+
if (!startDir) {
|
|
59
|
+
return null;
|
|
60
|
+
}
|
|
61
|
+
let currentDir = path.resolve(startDir);
|
|
62
|
+
let nearestGitRoot: string | null = null;
|
|
63
|
+
while (true) {
|
|
64
|
+
if (hasToolingMetadata(currentDir)) {
|
|
65
|
+
return currentDir;
|
|
66
|
+
}
|
|
67
|
+
if (!nearestGitRoot && hasGitEntry(currentDir)) {
|
|
68
|
+
nearestGitRoot = currentDir;
|
|
69
|
+
}
|
|
70
|
+
const parentDir = path.dirname(currentDir);
|
|
71
|
+
if (parentDir === currentDir) {
|
|
72
|
+
return nearestGitRoot;
|
|
73
|
+
}
|
|
74
|
+
currentDir = parentDir;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
function readToolingMetadata(worktreeRoot: string): ToolingMetadata | null {
|
|
78
|
+
const metadataPath = path.join(worktreeRoot, TOOLING_METADATA_RELATIVE_PATH);
|
|
79
|
+
if (!fs.existsSync(metadataPath)) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
try {
|
|
83
|
+
const parsed: unknown = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
84
|
+
if (!isToolingMetadata(parsed)) {
|
|
85
|
+
return null;
|
|
86
|
+
}
|
|
87
|
+
return parsed;
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function listFallbackBinDirectories(worktreeRoot: string): string[] {
|
|
93
|
+
try {
|
|
94
|
+
return fs
|
|
95
|
+
.readdirSync(worktreeRoot, { withFileTypes: true })
|
|
96
|
+
.filter((entry) => entry.isDirectory() && entry.name.startsWith(FALLBACK_BIN_PREFIX))
|
|
97
|
+
.map((entry) => path.join(worktreeRoot, entry.name));
|
|
98
|
+
} catch {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function resolveWorktreeToolBinEntries(options: WorktreeToolingOptions = {}): string[] {
|
|
103
|
+
const worktreeRoot = resolveWorktreeRoot(options.worktreePath || options.cwd);
|
|
104
|
+
if (!worktreeRoot) {
|
|
105
|
+
return [];
|
|
106
|
+
}
|
|
107
|
+
const metadata = readToolingMetadata(worktreeRoot);
|
|
108
|
+
const hasExplicitWorktreePath =
|
|
109
|
+
typeof options.worktreePath === 'string' && options.worktreePath.trim().length > 0;
|
|
110
|
+
if (!metadata && !hasExplicitWorktreePath) {
|
|
111
|
+
return [];
|
|
112
|
+
}
|
|
113
|
+
const candidates: string[] = [];
|
|
114
|
+
if (typeof metadata?.toolBinDir === 'string' && metadata.toolBinDir.trim()) {
|
|
115
|
+
candidates.push(metadata.toolBinDir.trim());
|
|
116
|
+
}
|
|
117
|
+
for (const relativePath of DEFAULT_TOOL_BIN_RELATIVE_PATHS) {
|
|
118
|
+
candidates.push(path.join(worktreeRoot, relativePath));
|
|
119
|
+
}
|
|
120
|
+
candidates.push(...listFallbackBinDirectories(worktreeRoot));
|
|
121
|
+
return dedupePaths(candidates).filter((candidatePath) =>
|
|
122
|
+
isWithinRoot(candidatePath, worktreeRoot)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
function prependWorktreeToolBinToEnv(
|
|
126
|
+
env: NodeJS.ProcessEnv,
|
|
127
|
+
options: WorktreeToolingOptions = {}
|
|
128
|
+
): NodeJS.ProcessEnv {
|
|
129
|
+
const toolBinEntries = resolveWorktreeToolBinEntries(options);
|
|
130
|
+
if (toolBinEntries.length === 0) {
|
|
131
|
+
return env;
|
|
132
|
+
}
|
|
133
|
+
const pathKey = pathKeyForEnv(env);
|
|
134
|
+
const existingEntries = (env[pathKey] || '').split(path.delimiter).filter(Boolean);
|
|
135
|
+
env[pathKey] = dedupePaths([...toolBinEntries, ...existingEntries]).join(path.delimiter);
|
|
136
|
+
return env;
|
|
137
|
+
}
|
|
138
|
+
export = {
|
|
139
|
+
prependWorktreeToolBinToEnv,
|
|
140
|
+
resolveWorktreeRoot,
|
|
141
|
+
resolveWorktreeToolBinEntries,
|
|
142
|
+
};
|
package/task-lib/completion.js
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
* Shell completion setup for ct
|
|
3
3
|
* Placeholder - completion not yet implemented
|
|
4
4
|
*/
|
|
5
|
-
|
|
6
5
|
export function setupCompletion() {
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
// TODO: Implement shell completion
|
|
7
|
+
// For now, this is a no-op
|
|
9
8
|
}
|