opencode-dux 1.0.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/LICENSE +21 -0
- package/README.md +452 -0
- package/dist/agents/descriptions.d.ts +6 -0
- package/dist/agents/designer.d.ts +2 -0
- package/dist/agents/explorer.d.ts +2 -0
- package/dist/agents/fixer.d.ts +2 -0
- package/dist/agents/index.d.ts +22 -0
- package/dist/agents/interpreter.d.ts +2 -0
- package/dist/agents/librarian.d.ts +2 -0
- package/dist/agents/oracle.d.ts +2 -0
- package/dist/agents/orchestrator.d.ts +27 -0
- package/dist/agents/overrides.d.ts +18 -0
- package/dist/agents/prompt-blocks.d.ts +97 -0
- package/dist/agents/steward.d.ts +3 -0
- package/dist/cli/config-io.d.ts +24 -0
- package/dist/cli/config-manager.d.ts +4 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +1006 -0
- package/dist/cli/install.d.ts +2 -0
- package/dist/cli/mcps.d.ts +13 -0
- package/dist/cli/model-key-normalization.d.ts +1 -0
- package/dist/cli/paths.d.ts +35 -0
- package/dist/cli/providers.d.ts +137 -0
- package/dist/cli/skills.d.ts +22 -0
- package/dist/cli/system.d.ts +5 -0
- package/dist/cli/types.d.ts +38 -0
- package/dist/config/constants.d.ts +12 -0
- package/dist/config/index.d.ts +4 -0
- package/dist/config/loader.d.ts +40 -0
- package/dist/config/runtime-preset.d.ts +12 -0
- package/dist/config/schema.d.ts +281 -0
- package/dist/config/utils.d.ts +10 -0
- package/dist/discovery/local/types.d.ts +79 -0
- package/dist/discovery/local.d.ts +73 -0
- package/dist/discovery/mcp-servers.d.ts +88 -0
- package/dist/discovery/skills.d.ts +94 -0
- package/dist/hooks/apply-patch/codec.d.ts +7 -0
- package/dist/hooks/apply-patch/errors.d.ts +25 -0
- package/dist/hooks/apply-patch/execution-context.d.ts +27 -0
- package/dist/hooks/apply-patch/index.d.ts +15 -0
- package/dist/hooks/apply-patch/matching.d.ts +26 -0
- package/dist/hooks/apply-patch/operations.d.ts +3 -0
- package/dist/hooks/apply-patch/patch.d.ts +2 -0
- package/dist/hooks/apply-patch/prepared-changes.d.ts +17 -0
- package/dist/hooks/apply-patch/resolution.d.ts +19 -0
- package/dist/hooks/apply-patch/rewrite.d.ts +7 -0
- package/dist/hooks/apply-patch/test-helpers.d.ts +6 -0
- package/dist/hooks/apply-patch/types.d.ts +80 -0
- package/dist/hooks/auto-update-checker/cache.d.ts +11 -0
- package/dist/hooks/auto-update-checker/checker.d.ts +32 -0
- package/dist/hooks/auto-update-checker/constants.d.ts +11 -0
- package/dist/hooks/auto-update-checker/index.d.ts +18 -0
- package/dist/hooks/auto-update-checker/types.d.ts +22 -0
- package/dist/hooks/chat-headers.d.ts +16 -0
- package/dist/hooks/context-pressure-reminder/index.d.ts +33 -0
- package/dist/hooks/delegate-task-retry/guidance.d.ts +2 -0
- package/dist/hooks/delegate-task-retry/hook.d.ts +8 -0
- package/dist/hooks/delegate-task-retry/index.d.ts +4 -0
- package/dist/hooks/delegate-task-retry/patterns.d.ts +11 -0
- package/dist/hooks/filter-available-skills/index.d.ts +32 -0
- package/dist/hooks/foreground-fallback/index.d.ts +72 -0
- package/dist/hooks/image-hook.d.ts +5 -0
- package/dist/hooks/index.d.ts +14 -0
- package/dist/hooks/json-error-recovery/hook.d.ts +18 -0
- package/dist/hooks/json-error-recovery/index.d.ts +1 -0
- package/dist/hooks/phase-reminder/index.d.ts +26 -0
- package/dist/hooks/post-file-tool-nudge/index.d.ts +19 -0
- package/dist/hooks/task-session-manager/index.d.ts +52 -0
- package/dist/hooks/todo-continuation/index.d.ts +53 -0
- package/dist/hooks/todo-continuation/todo-hygiene.d.ts +35 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +31782 -0
- package/dist/mcp/context7.d.ts +6 -0
- package/dist/mcp/grep-app.d.ts +6 -0
- package/dist/mcp/index.d.ts +13 -0
- package/dist/mcp/types.d.ts +12 -0
- package/dist/mcp/websearch.d.ts +9 -0
- package/dist/skills/registry.d.ts +29 -0
- package/dist/subscriptions/accounts-store.d.ts +57 -0
- package/dist/subscriptions/index.d.ts +13 -0
- package/dist/subscriptions/neuralwatt-scraper.d.ts +14 -0
- package/dist/subscriptions/opencode-go-scraper.d.ts +27 -0
- package/dist/subscriptions/types.d.ts +115 -0
- package/dist/subscriptions/usage-service.d.ts +74 -0
- package/dist/tools/ast-grep/cli.d.ts +15 -0
- package/dist/tools/ast-grep/constants.d.ts +25 -0
- package/dist/tools/ast-grep/downloader.d.ts +5 -0
- package/dist/tools/ast-grep/index.d.ts +10 -0
- package/dist/tools/ast-grep/tools.d.ts +3 -0
- package/dist/tools/ast-grep/types.d.ts +30 -0
- package/dist/tools/ast-grep/utils.d.ts +4 -0
- package/dist/tools/delegate.d.ts +14 -0
- package/dist/tools/index.d.ts +5 -0
- package/dist/tools/preset-manager.d.ts +27 -0
- package/dist/tools/smartfetch/binary.d.ts +3 -0
- package/dist/tools/smartfetch/cache.d.ts +6 -0
- package/dist/tools/smartfetch/constants.d.ts +12 -0
- package/dist/tools/smartfetch/index.d.ts +3 -0
- package/dist/tools/smartfetch/network.d.ts +38 -0
- package/dist/tools/smartfetch/secondary-model.d.ts +28 -0
- package/dist/tools/smartfetch/tool.d.ts +3 -0
- package/dist/tools/smartfetch/types.d.ts +122 -0
- package/dist/tools/smartfetch/utils.d.ts +18 -0
- package/dist/tui-state.d.ts +168 -0
- package/dist/tui.d.ts +37 -0
- package/dist/tui.js +1896 -0
- package/dist/utils/agent-variant.d.ts +63 -0
- package/dist/utils/compat.d.ts +30 -0
- package/dist/utils/env.d.ts +1 -0
- package/dist/utils/index.d.ts +9 -0
- package/dist/utils/internal-initiator.d.ts +6 -0
- package/dist/utils/logger.d.ts +8 -0
- package/dist/utils/polling.d.ts +21 -0
- package/dist/utils/session-manager.d.ts +55 -0
- package/dist/utils/session.d.ts +90 -0
- package/dist/utils/subagent-depth.d.ts +35 -0
- package/dist/utils/system-collapse.d.ts +6 -0
- package/dist/utils/task.d.ts +4 -0
- package/dist/utils/zip-extractor.d.ts +1 -0
- package/index.ts +1 -0
- package/opencode-dux.schema.json +634 -0
- package/package.json +103 -0
- package/src/agents/descriptions.ts +55 -0
- package/src/agents/designer.test.ts +86 -0
- package/src/agents/designer.ts +154 -0
- package/src/agents/display-name.test.ts +186 -0
- package/src/agents/explorer.test.ts +79 -0
- package/src/agents/explorer.ts +144 -0
- package/src/agents/fixer.test.ts +79 -0
- package/src/agents/fixer.ts +145 -0
- package/src/agents/index.test.ts +472 -0
- package/src/agents/index.ts +248 -0
- package/src/agents/interpreter.ts +136 -0
- package/src/agents/librarian.test.ts +80 -0
- package/src/agents/librarian.ts +145 -0
- package/src/agents/oracle.test.ts +89 -0
- package/src/agents/oracle.ts +184 -0
- package/src/agents/orchestrator.test.ts +116 -0
- package/src/agents/orchestrator.ts +574 -0
- package/src/agents/overrides.ts +95 -0
- package/src/agents/prompt-blocks.test.ts +114 -0
- package/src/agents/prompt-blocks.ts +640 -0
- package/src/agents/steward.ts +146 -0
- package/src/cli/config-io.test.ts +536 -0
- package/src/cli/config-io.ts +473 -0
- package/src/cli/config-manager.test.ts +141 -0
- package/src/cli/config-manager.ts +4 -0
- package/src/cli/index.ts +88 -0
- package/src/cli/install.ts +282 -0
- package/src/cli/mcps.test.ts +62 -0
- package/src/cli/mcps.ts +39 -0
- package/src/cli/model-key-normalization.test.ts +21 -0
- package/src/cli/model-key-normalization.ts +60 -0
- package/src/cli/paths.test.ts +167 -0
- package/src/cli/paths.ts +144 -0
- package/src/cli/providers.test.ts +118 -0
- package/src/cli/providers.ts +141 -0
- package/src/cli/skills.test.ts +111 -0
- package/src/cli/skills.ts +103 -0
- package/src/cli/system.test.ts +91 -0
- package/src/cli/system.ts +180 -0
- package/src/cli/types.ts +43 -0
- package/src/config/constants.ts +58 -0
- package/src/config/index.ts +4 -0
- package/src/config/loader.test.ts +1194 -0
- package/src/config/loader.ts +269 -0
- package/src/config/model-resolution.test.ts +176 -0
- package/src/config/runtime-preset.test.ts +61 -0
- package/src/config/runtime-preset.ts +37 -0
- package/src/config/schema.ts +248 -0
- package/src/config/utils.test.ts +41 -0
- package/src/config/utils.ts +23 -0
- package/src/discovery/local/types.ts +85 -0
- package/src/discovery/local.ts +322 -0
- package/src/discovery/mcp-servers.ts +804 -0
- package/src/discovery/skills.ts +959 -0
- package/src/hooks/apply-patch/codec.test.ts +184 -0
- package/src/hooks/apply-patch/codec.ts +352 -0
- package/src/hooks/apply-patch/errors.ts +117 -0
- package/src/hooks/apply-patch/execution-context.ts +432 -0
- package/src/hooks/apply-patch/hook.test.ts +768 -0
- package/src/hooks/apply-patch/index.ts +126 -0
- package/src/hooks/apply-patch/matching.test.ts +215 -0
- package/src/hooks/apply-patch/matching.ts +586 -0
- package/src/hooks/apply-patch/operations.test.ts +1535 -0
- package/src/hooks/apply-patch/operations.ts +3 -0
- package/src/hooks/apply-patch/patch.ts +9 -0
- package/src/hooks/apply-patch/prepared-changes.ts +400 -0
- package/src/hooks/apply-patch/resolution.test.ts +420 -0
- package/src/hooks/apply-patch/resolution.ts +437 -0
- package/src/hooks/apply-patch/rewrite.ts +496 -0
- package/src/hooks/apply-patch/test-helpers.ts +52 -0
- package/src/hooks/apply-patch/types.ts +111 -0
- package/src/hooks/auto-update-checker/cache.test.ts +179 -0
- package/src/hooks/auto-update-checker/cache.ts +188 -0
- package/src/hooks/auto-update-checker/checker.test.ts +159 -0
- package/src/hooks/auto-update-checker/checker.ts +308 -0
- package/src/hooks/auto-update-checker/constants.ts +33 -0
- package/src/hooks/auto-update-checker/index.test.ts +282 -0
- package/src/hooks/auto-update-checker/index.ts +225 -0
- package/src/hooks/auto-update-checker/types.ts +26 -0
- package/src/hooks/chat-headers.test.ts +236 -0
- package/src/hooks/chat-headers.ts +97 -0
- package/src/hooks/context-pressure-reminder/index.test.ts +179 -0
- package/src/hooks/context-pressure-reminder/index.ts +137 -0
- package/src/hooks/delegate-task-retry/guidance.ts +41 -0
- package/src/hooks/delegate-task-retry/hook.ts +23 -0
- package/src/hooks/delegate-task-retry/index.test.ts +38 -0
- package/src/hooks/delegate-task-retry/index.ts +7 -0
- package/src/hooks/delegate-task-retry/patterns.ts +79 -0
- package/src/hooks/filter-available-skills/index.test.ts +297 -0
- package/src/hooks/filter-available-skills/index.ts +160 -0
- package/src/hooks/foreground-fallback/index.test.ts +624 -0
- package/src/hooks/foreground-fallback/index.ts +374 -0
- package/src/hooks/image-hook.ts +6 -0
- package/src/hooks/index.ts +17 -0
- package/src/hooks/json-error-recovery/hook.ts +73 -0
- package/src/hooks/json-error-recovery/index.test.ts +111 -0
- package/src/hooks/json-error-recovery/index.ts +6 -0
- package/src/hooks/phase-reminder/index.test.ts +74 -0
- package/src/hooks/phase-reminder/index.ts +85 -0
- package/src/hooks/post-file-tool-nudge/index.test.ts +94 -0
- package/src/hooks/post-file-tool-nudge/index.ts +63 -0
- package/src/hooks/task-session-manager/index.test.ts +833 -0
- package/src/hooks/task-session-manager/index.ts +434 -0
- package/src/hooks/todo-continuation/index.test.ts +3026 -0
- package/src/hooks/todo-continuation/index.ts +878 -0
- package/src/hooks/todo-continuation/todo-hygiene.test.ts +204 -0
- package/src/hooks/todo-continuation/todo-hygiene.ts +207 -0
- package/src/index.ts +1672 -0
- package/src/mcp/context7.ts +14 -0
- package/src/mcp/grep-app.ts +11 -0
- package/src/mcp/index.test.ts +96 -0
- package/src/mcp/index.ts +66 -0
- package/src/mcp/types.ts +16 -0
- package/src/mcp/websearch.ts +47 -0
- package/src/skills/codemap/README.md +60 -0
- package/src/skills/codemap/SKILL.md +174 -0
- package/src/skills/codemap/scripts/codemap.mjs +483 -0
- package/src/skills/codemap/scripts/codemap.test.ts +129 -0
- package/src/skills/registry.ts +218 -0
- package/src/skills/simplify/README.md +19 -0
- package/src/skills/simplify/SKILL.md +138 -0
- package/src/subscriptions/accounts-store.test.ts +236 -0
- package/src/subscriptions/accounts-store.ts +184 -0
- package/src/subscriptions/index.ts +30 -0
- package/src/subscriptions/neuralwatt-scraper.ts +108 -0
- package/src/subscriptions/opencode-go-scraper.ts +301 -0
- package/src/subscriptions/types.ts +145 -0
- package/src/subscriptions/usage-service.test.ts +202 -0
- package/src/subscriptions/usage-service.ts +651 -0
- package/src/tools/ast-grep/cli.ts +257 -0
- package/src/tools/ast-grep/constants.ts +214 -0
- package/src/tools/ast-grep/downloader.ts +131 -0
- package/src/tools/ast-grep/index.ts +24 -0
- package/src/tools/ast-grep/tools.ts +117 -0
- package/src/tools/ast-grep/types.ts +51 -0
- package/src/tools/ast-grep/utils.ts +126 -0
- package/src/tools/delegate-handoff.test.ts +18 -0
- package/src/tools/delegate.ts +508 -0
- package/src/tools/index.ts +8 -0
- package/src/tools/preset-manager.test.ts +795 -0
- package/src/tools/preset-manager.ts +332 -0
- package/src/tools/smartfetch/binary.ts +58 -0
- package/src/tools/smartfetch/cache.test.ts +34 -0
- package/src/tools/smartfetch/cache.ts +112 -0
- package/src/tools/smartfetch/constants.ts +29 -0
- package/src/tools/smartfetch/index.ts +8 -0
- package/src/tools/smartfetch/network.test.ts +178 -0
- package/src/tools/smartfetch/network.ts +614 -0
- package/src/tools/smartfetch/secondary-model.test.ts +85 -0
- package/src/tools/smartfetch/secondary-model.ts +276 -0
- package/src/tools/smartfetch/tool.test.ts +60 -0
- package/src/tools/smartfetch/tool.ts +832 -0
- package/src/tools/smartfetch/types.ts +135 -0
- package/src/tools/smartfetch/utils.test.ts +24 -0
- package/src/tools/smartfetch/utils.ts +456 -0
- package/src/tui-state.test.ts +867 -0
- package/src/tui-state.ts +1255 -0
- package/src/tui.test.ts +336 -0
- package/src/tui.ts +1539 -0
- package/src/utils/agent-variant.test.ts +244 -0
- package/src/utils/agent-variant.ts +187 -0
- package/src/utils/compat.ts +91 -0
- package/src/utils/env.ts +12 -0
- package/src/utils/index.ts +9 -0
- package/src/utils/internal-initiator.ts +28 -0
- package/src/utils/logger.test.ts +220 -0
- package/src/utils/logger.ts +136 -0
- package/src/utils/polling.test.ts +191 -0
- package/src/utils/polling.ts +67 -0
- package/src/utils/session-manager.test.ts +173 -0
- package/src/utils/session-manager.ts +356 -0
- package/src/utils/session.test.ts +110 -0
- package/src/utils/session.ts +389 -0
- package/src/utils/subagent-depth.test.ts +170 -0
- package/src/utils/subagent-depth.ts +75 -0
- package/src/utils/system-collapse.test.ts +86 -0
- package/src/utils/system-collapse.ts +24 -0
- package/src/utils/task.test.ts +24 -0
- package/src/utils/task.ts +20 -0
- package/src/utils/zip-extractor.ts +102 -0
|
@@ -0,0 +1,432 @@
|
|
|
1
|
+
import type { Stats } from 'node:fs';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
|
|
5
|
+
import { parsePatchStrict } from './codec';
|
|
6
|
+
import {
|
|
7
|
+
createApplyPatchBlockedError,
|
|
8
|
+
createApplyPatchInternalError,
|
|
9
|
+
createApplyPatchValidationError,
|
|
10
|
+
createApplyPatchVerificationError,
|
|
11
|
+
getErrorMessage,
|
|
12
|
+
} from './errors';
|
|
13
|
+
import { applyHits, resolveUpdateChunksFromText } from './resolution';
|
|
14
|
+
import type {
|
|
15
|
+
ApplyPatchRuntimeOptions,
|
|
16
|
+
PatchHunk,
|
|
17
|
+
UpdatePatchHunk,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
type PathGuardContext = {
|
|
21
|
+
rootReal: Promise<string>;
|
|
22
|
+
worktreeReal?: Promise<string>;
|
|
23
|
+
realCache: Map<string, Promise<string>>;
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type FileCacheContext = {
|
|
27
|
+
stats: Map<string, Promise<Stats | null>>;
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
export type PreparedFileState =
|
|
31
|
+
| {
|
|
32
|
+
exists: false;
|
|
33
|
+
derived: boolean;
|
|
34
|
+
}
|
|
35
|
+
| {
|
|
36
|
+
exists: true;
|
|
37
|
+
text: string;
|
|
38
|
+
mode?: number;
|
|
39
|
+
derived: boolean;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
export type PatchExecutionContext = {
|
|
43
|
+
hunks: PatchHunk[];
|
|
44
|
+
pathsNormalized: boolean;
|
|
45
|
+
staged: Map<string, PreparedFileState>;
|
|
46
|
+
getPreparedFileState: (
|
|
47
|
+
filePath: string,
|
|
48
|
+
verb: 'update' | 'delete',
|
|
49
|
+
) => Promise<PreparedFileState>;
|
|
50
|
+
assertPreparedPathMissing: (
|
|
51
|
+
filePath: string,
|
|
52
|
+
verb: 'add' | 'move',
|
|
53
|
+
) => Promise<void>;
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export type ResolvedPreparedUpdate = {
|
|
57
|
+
resolved: Awaited<ReturnType<typeof resolveUpdateChunksFromText>>['resolved'];
|
|
58
|
+
nextText: string;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export function isMissingPathError(error: unknown): boolean {
|
|
62
|
+
return (
|
|
63
|
+
!!error &&
|
|
64
|
+
typeof error === 'object' &&
|
|
65
|
+
'code' in error &&
|
|
66
|
+
(error.code === 'ENOENT' || error.code === 'ENOTDIR')
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function real(target: string): Promise<string> {
|
|
71
|
+
const parts: string[] = [];
|
|
72
|
+
let current = path.resolve(target);
|
|
73
|
+
|
|
74
|
+
while (true) {
|
|
75
|
+
const exact = await fs.realpath(current).catch((error: unknown) => {
|
|
76
|
+
if (isMissingPathError(error)) {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
throw createApplyPatchInternalError(
|
|
81
|
+
`Failed to resolve real path: ${current}`,
|
|
82
|
+
error,
|
|
83
|
+
);
|
|
84
|
+
});
|
|
85
|
+
if (exact) {
|
|
86
|
+
return parts.length === 0 ? exact : path.join(exact, ...parts.reverse());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const parent = path.dirname(current);
|
|
90
|
+
if (parent === current) {
|
|
91
|
+
return parts.length === 0
|
|
92
|
+
? current
|
|
93
|
+
: path.join(current, ...parts.reverse());
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
parts.push(path.basename(current));
|
|
97
|
+
current = parent;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function inside(root: string, target: string): boolean {
|
|
102
|
+
const rel = path.relative(root, target);
|
|
103
|
+
return rel === '' || (!rel.startsWith('..') && !path.isAbsolute(rel));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function createPathGuardContext(
|
|
107
|
+
root: string,
|
|
108
|
+
worktree: string | undefined,
|
|
109
|
+
): PathGuardContext {
|
|
110
|
+
return {
|
|
111
|
+
rootReal: real(root),
|
|
112
|
+
worktreeReal: worktree && worktree !== '/' ? real(worktree) : undefined,
|
|
113
|
+
realCache: new Map(),
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
async function realCached(
|
|
118
|
+
ctx: PathGuardContext,
|
|
119
|
+
target: string,
|
|
120
|
+
): Promise<string> {
|
|
121
|
+
const resolvedTarget = path.resolve(target);
|
|
122
|
+
let pending = ctx.realCache.get(resolvedTarget);
|
|
123
|
+
if (!pending) {
|
|
124
|
+
pending = real(resolvedTarget);
|
|
125
|
+
ctx.realCache.set(resolvedTarget, pending);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return await pending;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function guard(ctx: PathGuardContext, target: string): Promise<void> {
|
|
132
|
+
const [targetReal, rootReal] = await Promise.all([
|
|
133
|
+
realCached(ctx, target),
|
|
134
|
+
ctx.rootReal,
|
|
135
|
+
]);
|
|
136
|
+
if (inside(rootReal, targetReal)) {
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (!ctx.worktreeReal) {
|
|
141
|
+
throw createApplyPatchBlockedError(
|
|
142
|
+
`patch contains path outside workspace root: ${target}`,
|
|
143
|
+
);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const treeReal = await ctx.worktreeReal;
|
|
147
|
+
if (inside(treeReal, targetReal)) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
throw createApplyPatchBlockedError(
|
|
152
|
+
`patch contains path outside workspace root: ${target}`,
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function createFileCacheContext(): FileCacheContext {
|
|
157
|
+
return { stats: new Map() };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async function statCached(
|
|
161
|
+
ctx: FileCacheContext,
|
|
162
|
+
filePath: string,
|
|
163
|
+
): Promise<Stats | null> {
|
|
164
|
+
let pending = ctx.stats.get(filePath);
|
|
165
|
+
if (!pending) {
|
|
166
|
+
const nextPending = fs.stat(filePath).catch((error: unknown) => {
|
|
167
|
+
if (isMissingPathError(error)) {
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
throw createApplyPatchInternalError(
|
|
172
|
+
`Failed to stat file for patch verification: ${filePath}`,
|
|
173
|
+
error,
|
|
174
|
+
);
|
|
175
|
+
});
|
|
176
|
+
ctx.stats.set(filePath, nextPending);
|
|
177
|
+
pending = nextPending;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
return await pending;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
async function assertRegularFile(
|
|
184
|
+
ctx: FileCacheContext,
|
|
185
|
+
filePath: string,
|
|
186
|
+
verb: 'update' | 'delete',
|
|
187
|
+
): Promise<void> {
|
|
188
|
+
const stat = await statCached(ctx, filePath);
|
|
189
|
+
if (!stat || stat.isDirectory()) {
|
|
190
|
+
throw createApplyPatchVerificationError(
|
|
191
|
+
`Failed to read file to ${verb}: ${filePath}`,
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function collectPatchTargets(root: string, hunks: PatchHunk[]): string[] {
|
|
197
|
+
const targets = new Set<string>();
|
|
198
|
+
|
|
199
|
+
for (const hunk of hunks) {
|
|
200
|
+
targets.add(path.resolve(root, hunk.path));
|
|
201
|
+
|
|
202
|
+
if (hunk.type === 'update' && hunk.move_path) {
|
|
203
|
+
targets.add(path.resolve(root, hunk.move_path));
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return [...targets];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function toRelativePatchPath(root: string, target: string): string {
|
|
211
|
+
const relative = path.relative(root, target);
|
|
212
|
+
return (relative.length === 0 ? '.' : relative).replaceAll('\\', '/');
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
function normalizePatchPath(root: string, value: string): string {
|
|
216
|
+
return path.isAbsolute(value)
|
|
217
|
+
? toRelativePatchPath(root, path.resolve(value))
|
|
218
|
+
: value;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function normalizePatchPaths(
|
|
222
|
+
root: string,
|
|
223
|
+
hunks: PatchHunk[],
|
|
224
|
+
): {
|
|
225
|
+
hunks: PatchHunk[];
|
|
226
|
+
changed: boolean;
|
|
227
|
+
} {
|
|
228
|
+
const resolvedRoot = path.resolve(root);
|
|
229
|
+
const normalized: PatchHunk[] = [];
|
|
230
|
+
let changed = false;
|
|
231
|
+
|
|
232
|
+
for (const hunk of hunks) {
|
|
233
|
+
const normalizedPath = normalizePatchPath(resolvedRoot, hunk.path);
|
|
234
|
+
|
|
235
|
+
if (hunk.type !== 'update') {
|
|
236
|
+
changed ||= normalizedPath !== hunk.path;
|
|
237
|
+
normalized.push(
|
|
238
|
+
normalizedPath === hunk.path
|
|
239
|
+
? hunk
|
|
240
|
+
: {
|
|
241
|
+
...hunk,
|
|
242
|
+
path: normalizedPath,
|
|
243
|
+
},
|
|
244
|
+
);
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const normalizedMovePath = hunk.move_path
|
|
249
|
+
? normalizePatchPath(resolvedRoot, hunk.move_path)
|
|
250
|
+
: undefined;
|
|
251
|
+
changed ||=
|
|
252
|
+
normalizedPath !== hunk.path || normalizedMovePath !== hunk.move_path;
|
|
253
|
+
|
|
254
|
+
normalized.push(
|
|
255
|
+
normalizedPath === hunk.path && normalizedMovePath === hunk.move_path
|
|
256
|
+
? hunk
|
|
257
|
+
: {
|
|
258
|
+
...hunk,
|
|
259
|
+
path: normalizedPath,
|
|
260
|
+
move_path: normalizedMovePath,
|
|
261
|
+
},
|
|
262
|
+
);
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
return { hunks: normalized, changed };
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function guardPatchTargets(
|
|
269
|
+
root: string,
|
|
270
|
+
worktree: string | undefined,
|
|
271
|
+
targets: string[],
|
|
272
|
+
): Promise<number> {
|
|
273
|
+
const guardContext = createPathGuardContext(root, worktree);
|
|
274
|
+
|
|
275
|
+
for (const target of targets) {
|
|
276
|
+
await guard(guardContext, target);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
return targets.length;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function parseValidatedPatch(patchText: string): PatchHunk[] {
|
|
283
|
+
let hunks: PatchHunk[];
|
|
284
|
+
|
|
285
|
+
try {
|
|
286
|
+
hunks = parsePatchStrict(patchText).hunks;
|
|
287
|
+
} catch (error) {
|
|
288
|
+
throw createApplyPatchValidationError(getErrorMessage(error));
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
if (hunks.length === 0) {
|
|
292
|
+
const clean = patchText.replace(/\r\n/g, '\n').replace(/\r/g, '\n').trim();
|
|
293
|
+
if (clean === '*** Begin Patch\n*** End Patch') {
|
|
294
|
+
throw createApplyPatchValidationError('empty patch');
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
throw createApplyPatchValidationError('no hunks found');
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
return hunks;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function readPreparedFileText(
|
|
304
|
+
filePath: string,
|
|
305
|
+
verb: 'update' | 'delete',
|
|
306
|
+
): Promise<string> {
|
|
307
|
+
try {
|
|
308
|
+
return await fs.readFile(filePath, 'utf-8');
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (isMissingPathError(error)) {
|
|
311
|
+
throw createApplyPatchVerificationError(
|
|
312
|
+
`Failed to read file to ${verb}: ${filePath}`,
|
|
313
|
+
);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
throw createApplyPatchInternalError(
|
|
317
|
+
`Failed to read file for patch verification: ${filePath}`,
|
|
318
|
+
error,
|
|
319
|
+
);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
export async function createPatchExecutionContext(
|
|
324
|
+
root: string,
|
|
325
|
+
patchText: string,
|
|
326
|
+
worktree?: string,
|
|
327
|
+
): Promise<PatchExecutionContext> {
|
|
328
|
+
const parsedHunks = parseValidatedPatch(patchText);
|
|
329
|
+
await guardPatchTargets(
|
|
330
|
+
root,
|
|
331
|
+
worktree,
|
|
332
|
+
collectPatchTargets(root, parsedHunks),
|
|
333
|
+
);
|
|
334
|
+
const normalized = normalizePatchPaths(root, parsedHunks);
|
|
335
|
+
const files = createFileCacheContext();
|
|
336
|
+
const staged = new Map<string, PreparedFileState>();
|
|
337
|
+
|
|
338
|
+
async function assertPreparedPathMissing(
|
|
339
|
+
filePath: string,
|
|
340
|
+
verb: 'add' | 'move',
|
|
341
|
+
): Promise<void> {
|
|
342
|
+
const existing = staged.get(filePath);
|
|
343
|
+
if (existing) {
|
|
344
|
+
if (!existing.exists) {
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
throw createApplyPatchVerificationError(
|
|
349
|
+
verb === 'add'
|
|
350
|
+
? `Add File target already exists: ${filePath}`
|
|
351
|
+
: `Move destination already exists: ${filePath}`,
|
|
352
|
+
);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const stat = await statCached(files, filePath);
|
|
356
|
+
if (!stat) {
|
|
357
|
+
return;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
throw createApplyPatchVerificationError(
|
|
361
|
+
verb === 'add'
|
|
362
|
+
? `Add File target already exists: ${filePath}`
|
|
363
|
+
: `Move destination already exists: ${filePath}`,
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
async function getPreparedFileState(
|
|
368
|
+
filePath: string,
|
|
369
|
+
verb: 'update' | 'delete',
|
|
370
|
+
): Promise<PreparedFileState> {
|
|
371
|
+
const existing = staged.get(filePath);
|
|
372
|
+
if (existing) {
|
|
373
|
+
if (!existing.exists) {
|
|
374
|
+
throw createApplyPatchVerificationError(
|
|
375
|
+
`Failed to read file to ${verb}: ${filePath}`,
|
|
376
|
+
);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
return existing;
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
await assertRegularFile(files, filePath, verb);
|
|
383
|
+
const stat = await statCached(files, filePath);
|
|
384
|
+
const text = await readPreparedFileText(filePath, verb);
|
|
385
|
+
const state: PreparedFileState = {
|
|
386
|
+
exists: true,
|
|
387
|
+
text,
|
|
388
|
+
mode: stat ? stat.mode & 0o7777 : undefined,
|
|
389
|
+
derived: false,
|
|
390
|
+
};
|
|
391
|
+
staged.set(filePath, state);
|
|
392
|
+
return state;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
hunks: normalized.hunks,
|
|
397
|
+
pathsNormalized: normalized.changed,
|
|
398
|
+
staged,
|
|
399
|
+
getPreparedFileState,
|
|
400
|
+
assertPreparedPathMissing,
|
|
401
|
+
};
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
export function resolvePreparedUpdate(
|
|
405
|
+
filePath: string,
|
|
406
|
+
currentText: string,
|
|
407
|
+
hunk: UpdatePatchHunk,
|
|
408
|
+
cfg: ApplyPatchRuntimeOptions,
|
|
409
|
+
): ResolvedPreparedUpdate {
|
|
410
|
+
try {
|
|
411
|
+
const { lines, resolved, eol, hasFinalNewline } =
|
|
412
|
+
resolveUpdateChunksFromText(filePath, currentText, hunk.chunks, cfg);
|
|
413
|
+
|
|
414
|
+
return {
|
|
415
|
+
resolved,
|
|
416
|
+
nextText: applyHits(
|
|
417
|
+
lines,
|
|
418
|
+
resolved.map((chunk) => chunk.hit),
|
|
419
|
+
eol,
|
|
420
|
+
hasFinalNewline,
|
|
421
|
+
),
|
|
422
|
+
};
|
|
423
|
+
} catch (error) {
|
|
424
|
+
throw createApplyPatchVerificationError(getErrorMessage(error), error);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
export function stageAddedText(contents: string): string {
|
|
429
|
+
return contents.length === 0 || contents.endsWith('\n')
|
|
430
|
+
? contents
|
|
431
|
+
: `${contents}\n`;
|
|
432
|
+
}
|