patchwarden 0.4.0 → 0.6.1
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/PatchWarden.cmd +51 -0
- package/README.en.md +1022 -0
- package/README.md +897 -358
- package/dist/assessments/agentAssessor.d.ts +15 -0
- package/dist/assessments/agentAssessor.js +293 -0
- package/dist/assessments/assessmentStore.d.ts +133 -0
- package/dist/assessments/assessmentStore.js +238 -0
- package/dist/assessments/confirmCli.d.ts +9 -0
- package/dist/assessments/confirmCli.js +26 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.js +156 -3
- package/dist/direct/directAudit.d.ts +23 -0
- package/dist/direct/directAudit.js +309 -0
- package/dist/direct/directGuards.d.ts +20 -0
- package/dist/direct/directGuards.js +137 -0
- package/dist/direct/directPatch.d.ts +17 -0
- package/dist/direct/directPatch.js +113 -0
- package/dist/direct/directSessionStore.d.ts +63 -0
- package/dist/direct/directSessionStore.js +192 -0
- package/dist/direct/directVerification.d.ts +12 -0
- package/dist/direct/directVerification.js +96 -0
- package/dist/doctor.js +73 -5
- package/dist/logging.d.ts +52 -0
- package/dist/logging.js +123 -0
- package/dist/runner/agentInvocation.d.ts +23 -0
- package/dist/runner/agentInvocation.js +106 -0
- package/dist/runner/changeCapture.d.ts +70 -0
- package/dist/runner/changeCapture.js +278 -6
- package/dist/runner/runTask.js +271 -65
- package/dist/runner/simpleProcess.d.ts +21 -0
- package/dist/runner/simpleProcess.js +206 -0
- package/dist/runner/watch.js +17 -2
- package/dist/security/commandGuard.d.ts +2 -1
- package/dist/security/commandGuard.js +25 -3
- package/dist/security/riskEngine.d.ts +27 -0
- package/dist/security/riskEngine.js +118 -0
- package/dist/security/runtimeGuard.d.ts +6 -0
- package/dist/security/runtimeGuard.js +28 -0
- package/dist/smoke-test.js +1353 -7
- package/dist/test/unit/android-doctor.test.d.ts +1 -0
- package/dist/test/unit/android-doctor.test.js +118 -0
- package/dist/test/unit/chinese-path.test.d.ts +1 -0
- package/dist/test/unit/chinese-path.test.js +91 -0
- package/dist/test/unit/command-guard.test.d.ts +1 -0
- package/dist/test/unit/command-guard.test.js +160 -0
- package/dist/test/unit/direct-guards.test.d.ts +1 -0
- package/dist/test/unit/direct-guards.test.js +213 -0
- package/dist/test/unit/logging.test.d.ts +1 -0
- package/dist/test/unit/logging.test.js +275 -0
- package/dist/test/unit/path-guard.test.d.ts +1 -0
- package/dist/test/unit/path-guard.test.js +109 -0
- package/dist/test/unit/safe-status.test.d.ts +1 -0
- package/dist/test/unit/safe-status.test.js +165 -0
- package/dist/test/unit/sensitive-guard.test.d.ts +1 -0
- package/dist/test/unit/sensitive-guard.test.js +104 -0
- package/dist/test/unit/sync-file.test.d.ts +1 -0
- package/dist/test/unit/sync-file.test.js +154 -0
- package/dist/test/unit/watcher-status.test.d.ts +1 -0
- package/dist/test/unit/watcher-status.test.js +169 -0
- package/dist/tools/androidDoctor.d.ts +38 -0
- package/dist/tools/androidDoctor.js +391 -0
- package/dist/tools/applyPatch.d.ts +16 -0
- package/dist/tools/applyPatch.js +41 -0
- package/dist/tools/auditSession.d.ts +5 -0
- package/dist/tools/auditSession.js +12 -0
- package/dist/tools/auditTask.d.ts +7 -0
- package/dist/tools/auditTask.js +105 -7
- package/dist/tools/createDirectSession.d.ts +14 -0
- package/dist/tools/createDirectSession.js +54 -0
- package/dist/tools/createTask.d.ts +48 -1
- package/dist/tools/createTask.js +298 -47
- package/dist/tools/finalizeDirectSession.d.ts +19 -0
- package/dist/tools/finalizeDirectSession.js +84 -0
- package/dist/tools/getTaskSummary.d.ts +50 -0
- package/dist/tools/getTaskSummary.js +79 -2
- package/dist/tools/healthCheck.d.ts +27 -12
- package/dist/tools/healthCheck.js +44 -6
- package/dist/tools/readWorkspaceFile.d.ts +7 -1
- package/dist/tools/readWorkspaceFile.js +48 -2
- package/dist/tools/registry.js +270 -19
- package/dist/tools/runVerification.d.ts +16 -0
- package/dist/tools/runVerification.js +32 -0
- package/dist/tools/safeStatus.d.ts +19 -0
- package/dist/tools/safeStatus.js +72 -0
- package/dist/tools/savePlan.d.ts +1 -0
- package/dist/tools/savePlan.js +38 -7
- package/dist/tools/searchWorkspace.d.ts +19 -0
- package/dist/tools/searchWorkspace.js +205 -0
- package/dist/tools/syncFile.d.ts +18 -0
- package/dist/tools/syncFile.js +65 -0
- package/dist/tools/taskOutputs.d.ts +2 -2
- package/dist/tools/taskTemplates.js +2 -1
- package/dist/tools/toolCatalog.d.ts +4 -3
- package/dist/tools/toolCatalog.js +34 -5
- package/dist/tools/waitForTask.d.ts +2 -2
- package/dist/tools/waitForTask.js +1 -1
- package/dist/version.d.ts +2 -2
- package/dist/version.js +2 -2
- package/dist/watcherStatus.d.ts +1 -0
- package/dist/watcherStatus.js +96 -4
- package/docs/chatgpt-usage.md +101 -0
- package/docs/performance-notes.md +55 -0
- package/docs/release-checklist.md +14 -0
- package/docs/release-v0.6.0.md +71 -0
- package/docs/release-v0.6.1.md +75 -0
- package/examples/config.example.json +5 -0
- package/examples/openai-tunnel/README.md +11 -4
- package/examples/openai-tunnel/chatgpt-test-prompt.md +18 -14
- package/examples/openai-tunnel/tunnel-client.example.yaml +8 -0
- package/package.json +13 -8
- package/scripts/brand-check.js +58 -12
- package/scripts/control-smoke.js +206 -0
- package/scripts/http-mcp-smoke.js +10 -2
- package/scripts/launchers/Check-PatchWarden-Health.cmd +6 -0
- package/scripts/launchers/Reset-PatchWarden-Tunnel-Key.cmd +6 -0
- package/scripts/launchers/Restart-PatchWarden.cmd +6 -0
- package/scripts/launchers/Start-PatchWarden-Direct-Tunnel.cmd +7 -0
- package/scripts/launchers/Start-PatchWarden-Tunnel.cmd +7 -0
- package/scripts/lifecycle-smoke.js +402 -8
- package/scripts/manage-patchwarden.ps1 +639 -0
- package/scripts/mcp-manifest-check.js +123 -58
- package/scripts/mcp-smoke.js +160 -2
- package/scripts/pack-clean.js +160 -5
- package/scripts/package-manifest-check.js +78 -0
- package/scripts/patchwarden-mcp-direct.cmd +7 -0
- package/scripts/patchwarden-mcp-stdio.cmd +1 -1
- package/scripts/restart-patchwarden.ps1 +5 -4
- package/scripts/start-patchwarden-tunnel.ps1 +261 -30
- package/scripts/tunnel-supervisor-smoke.js +36 -4
- package/scripts/unit-tests.js +36 -0
- package/scripts/watcher-supervisor-smoke.js +10 -6
- package/src/assessments/agentAssessor.ts +324 -0
- package/src/assessments/assessmentStore.ts +426 -0
- package/src/assessments/confirmCli.ts +29 -0
- package/src/config.ts +178 -4
- package/src/direct/directAudit.ts +400 -0
- package/src/direct/directGuards.ts +279 -0
- package/src/direct/directPatch.ts +258 -0
- package/src/direct/directSessionStore.ts +345 -0
- package/src/direct/directVerification.ts +138 -0
- package/src/doctor.ts +103 -7
- package/src/logging.ts +152 -0
- package/src/runner/agentInvocation.ts +125 -0
- package/src/runner/changeCapture.ts +352 -6
- package/src/runner/runTask.ts +279 -63
- package/src/runner/simpleProcess.ts +223 -0
- package/src/runner/watch.ts +18 -2
- package/src/security/commandGuard.ts +46 -4
- package/src/security/riskEngine.ts +160 -0
- package/src/security/runtimeGuard.ts +41 -0
- package/src/smoke-test.ts +1291 -4
- package/src/test/unit/android-doctor.test.ts +158 -0
- package/src/test/unit/chinese-path.test.ts +106 -0
- package/src/test/unit/command-guard.test.ts +221 -0
- package/src/test/unit/direct-guards.test.ts +297 -0
- package/src/test/unit/logging.test.ts +325 -0
- package/src/test/unit/path-guard.test.ts +150 -0
- package/src/test/unit/safe-status.test.ts +187 -0
- package/src/test/unit/sensitive-guard.test.ts +124 -0
- package/src/test/unit/sync-file.test.ts +231 -0
- package/src/test/unit/watcher-status.test.ts +190 -0
- package/src/tools/androidDoctor.ts +424 -0
- package/src/tools/applyPatch.ts +86 -0
- package/src/tools/auditSession.ts +28 -0
- package/src/tools/auditTask.ts +111 -7
- package/src/tools/createDirectSession.ts +113 -0
- package/src/tools/createTask.ts +405 -55
- package/src/tools/finalizeDirectSession.ts +144 -0
- package/src/tools/getTaskSummary.ts +133 -2
- package/src/tools/healthCheck.ts +45 -6
- package/src/tools/readWorkspaceFile.ts +85 -2
- package/src/tools/registry.ts +305 -19
- package/src/tools/runVerification.ts +58 -0
- package/src/tools/safeStatus.ts +96 -0
- package/src/tools/savePlan.ts +57 -7
- package/src/tools/searchWorkspace.ts +275 -0
- package/src/tools/syncFile.ts +122 -0
- package/src/tools/taskTemplates.ts +2 -1
- package/src/tools/toolCatalog.ts +37 -6
- package/src/tools/waitForTask.ts +3 -3
- package/src/version.ts +2 -2
- package/src/watcherStatus.ts +101 -4
- package/tsconfig.json +18 -17
- package/Check-PatchWarden-Health.cmd +0 -6
- package/Reset-PatchWarden-Tunnel-Key.cmd +0 -6
- package/Restart-PatchWarden.cmd +0 -19
- package/Start-PatchWarden-Tunnel.cmd +0 -7
- package/docs/release-v0.3.0.md +0 -43
- package/docs/release-v0.4.0.md +0 -74
|
@@ -1,35 +1,64 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, readFileSync, rmSync } from "node:fs";
|
|
2
3
|
import { resolve } from "node:path";
|
|
3
4
|
import { fileURLToPath } from "node:url";
|
|
4
5
|
import { Client } from "@modelcontextprotocol/sdk/client/index.js";
|
|
5
6
|
import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
|
|
6
|
-
import { CHATGPT_CORE_TOOL_NAMES } from "../dist/tools/toolCatalog.js";
|
|
7
|
+
import { CHATGPT_CORE_TOOL_NAMES, CHATGPT_DIRECT_TOOL_NAMES } from "../dist/tools/toolCatalog.js";
|
|
7
8
|
|
|
8
9
|
const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
9
10
|
const jsonOnly = process.argv.includes("--json");
|
|
10
|
-
const
|
|
11
|
+
const profileIndex = process.argv.indexOf("--profile");
|
|
12
|
+
const profile = profileIndex >= 0 ? process.argv[profileIndex + 1] : "chatgpt_core";
|
|
13
|
+
if (profile !== "chatgpt_core" && profile !== "chatgpt_direct") {
|
|
14
|
+
throw new Error(`Unsupported manifest profile "${profile}".`);
|
|
15
|
+
}
|
|
16
|
+
const expectedTools = profile === "chatgpt_direct"
|
|
17
|
+
? [...CHATGPT_DIRECT_TOOL_NAMES]
|
|
18
|
+
: [...CHATGPT_CORE_TOOL_NAMES];
|
|
19
|
+
const defaultConfigPath = resolve(root, "patchwarden.config.json");
|
|
20
|
+
const transportEnv = {
|
|
21
|
+
...process.env,
|
|
22
|
+
PATCHWARDEN_TOOL_PROFILE: profile,
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
// When checking chatgpt_direct profile, ensure enableDirectProfile is true
|
|
26
|
+
// by creating a temporary config if needed.
|
|
27
|
+
let tempConfigPath = null;
|
|
28
|
+
if (profile === "chatgpt_direct") {
|
|
29
|
+
const baseConfig = existsSync(defaultConfigPath)
|
|
30
|
+
? JSON.parse(readFileSync(defaultConfigPath, "utf-8"))
|
|
31
|
+
: {};
|
|
32
|
+
if (!baseConfig.enableDirectProfile) {
|
|
33
|
+
const { mkdtempSync, writeFileSync, rmSync } = await import("node:fs");
|
|
34
|
+
const { tmpdir } = await import("node:os");
|
|
35
|
+
const { join } = await import("node:path");
|
|
36
|
+
tempConfigPath = join(mkdtempSync(join(tmpdir(), "pw-direct-")), "config.json");
|
|
37
|
+
writeFileSync(tempConfigPath, JSON.stringify({ ...baseConfig, enableDirectProfile: true }, null, 2), "utf-8");
|
|
38
|
+
transportEnv.PATCHWARDEN_CONFIG = tempConfigPath;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
11
41
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
});
|
|
42
|
+
if (!transportEnv.PATCHWARDEN_CONFIG) {
|
|
43
|
+
if (process.env.PATCHWARDEN_CONFIG) {
|
|
44
|
+
transportEnv.PATCHWARDEN_CONFIG = process.env.PATCHWARDEN_CONFIG;
|
|
45
|
+
} else if (existsSync(defaultConfigPath)) {
|
|
46
|
+
transportEnv.PATCHWARDEN_CONFIG = defaultConfigPath;
|
|
47
|
+
} else {
|
|
48
|
+
delete transportEnv.PATCHWARDEN_CONFIG;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const transport = new StdioClientTransport({
|
|
53
|
+
command: process.execPath,
|
|
54
|
+
args: [resolve(root, "dist", "index.js")],
|
|
55
|
+
cwd: root,
|
|
56
|
+
env: transportEnv,
|
|
57
|
+
stderr: "pipe",
|
|
58
|
+
});
|
|
30
59
|
|
|
31
60
|
const client = new Client(
|
|
32
|
-
{ name: "patchwarden-manifest-check", version: "0.
|
|
61
|
+
{ name: "patchwarden-manifest-check", version: "0.6.0" },
|
|
33
62
|
{ capabilities: {} }
|
|
34
63
|
);
|
|
35
64
|
|
|
@@ -38,10 +67,10 @@ try {
|
|
|
38
67
|
const listed = await client.listTools();
|
|
39
68
|
const names = listed.tools.map((tool) => tool.name);
|
|
40
69
|
if (JSON.stringify(names) !== JSON.stringify(expectedTools)) {
|
|
41
|
-
throw new Error(
|
|
70
|
+
throw new Error(`${profile} tool list mismatch. Expected ${expectedTools.join(", ")}; got ${names.join(", ")}`);
|
|
42
71
|
}
|
|
43
72
|
// Layer 2: tools/list must carry _meta with manifest hash for catalog sync
|
|
44
|
-
if (!listed._meta || listed._meta.tool_profile !==
|
|
73
|
+
if (!listed._meta || listed._meta.tool_profile !== profile || listed._meta.tool_count !== expectedTools.length) {
|
|
45
74
|
throw new Error(`tools/list _meta missing or wrong profile/count: ${JSON.stringify(listed._meta || null)}`);
|
|
46
75
|
}
|
|
47
76
|
if (typeof listed._meta.tool_manifest_sha256 !== "string" || listed._meta.tool_manifest_sha256.length !== 64) {
|
|
@@ -50,51 +79,77 @@ try {
|
|
|
50
79
|
if (typeof listed._meta.schema_epoch !== "string" || typeof listed._meta.server_version !== "string") {
|
|
51
80
|
throw new Error(`tools/list _meta missing schema_epoch/server_version: ${JSON.stringify(listed._meta)}`);
|
|
52
81
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
82
|
+
if (profile === "chatgpt_core") {
|
|
83
|
+
const createTask = listed.tools.find((tool) => tool.name === "create_task");
|
|
84
|
+
const createProperties = Object.keys(createTask?.inputSchema?.properties || {});
|
|
85
|
+
for (const requiredProperty of ["inline_plan", "verify_commands"]) {
|
|
86
|
+
if (!createProperties.includes(requiredProperty)) {
|
|
87
|
+
throw new Error(`create_task schema is missing ${requiredProperty}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const waitTool = listed.tools.find((tool) => tool.name === "wait_for_task");
|
|
91
|
+
const waitProperties = Object.keys(waitTool?.inputSchema?.properties || {});
|
|
92
|
+
if (!waitProperties.includes("timeout_seconds") || !waitProperties.includes("wait_seconds")) {
|
|
93
|
+
throw new Error("wait_for_task schema must expose timeout_seconds and wait_seconds");
|
|
94
|
+
}
|
|
95
|
+
const healthProperties = Object.keys(listed.tools.find((tool) => tool.name === "health_check")?.inputSchema?.properties || {});
|
|
96
|
+
if (!healthProperties.includes("detail")) throw new Error("health_check schema must expose detail");
|
|
97
|
+
const listTaskProperties = Object.keys(listed.tools.find((tool) => tool.name === "list_tasks")?.inputSchema?.properties || {});
|
|
98
|
+
if (!listTaskProperties.includes("repo_path") || !listTaskProperties.includes("active_only")) {
|
|
99
|
+
throw new Error("list_tasks schema must expose repo_path and active_only");
|
|
100
|
+
}
|
|
101
|
+
const summaryProperties = Object.keys(listed.tools.find((tool) => tool.name === "get_task_summary")?.inputSchema?.properties || {});
|
|
102
|
+
if (!summaryProperties.includes("view") || !summaryProperties.includes("max_items")) {
|
|
103
|
+
throw new Error("get_task_summary schema must expose view and max_items");
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
const directRequirements = {
|
|
107
|
+
create_direct_session: ["repo_path"],
|
|
108
|
+
apply_patch: ["session_id", "path", "expected_sha256", "operations"],
|
|
109
|
+
run_verification: ["session_id", "command"],
|
|
110
|
+
finalize_direct_session: ["session_id"],
|
|
111
|
+
audit_session: ["session_id"],
|
|
112
|
+
};
|
|
113
|
+
for (const [toolName, requiredProperties] of Object.entries(directRequirements)) {
|
|
114
|
+
const tool = listed.tools.find((entry) => entry.name === toolName);
|
|
115
|
+
const properties = Object.keys(tool?.inputSchema?.properties || {});
|
|
116
|
+
for (const property of requiredProperties) {
|
|
117
|
+
if (!properties.includes(property)) {
|
|
118
|
+
throw new Error(`${toolName} schema is missing ${property}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
58
121
|
}
|
|
59
|
-
}
|
|
60
|
-
const waitTool = listed.tools.find((tool) => tool.name === "wait_for_task");
|
|
61
|
-
const waitProperties = Object.keys(waitTool?.inputSchema?.properties || {});
|
|
62
|
-
if (!waitProperties.includes("timeout_seconds") || !waitProperties.includes("wait_seconds")) {
|
|
63
|
-
throw new Error("wait_for_task schema must expose timeout_seconds and wait_seconds");
|
|
64
|
-
}
|
|
65
|
-
const healthProperties = Object.keys(listed.tools.find((tool) => tool.name === "health_check")?.inputSchema?.properties || {});
|
|
66
|
-
if (!healthProperties.includes("detail")) throw new Error("health_check schema must expose detail");
|
|
67
|
-
const listTaskProperties = Object.keys(listed.tools.find((tool) => tool.name === "list_tasks")?.inputSchema?.properties || {});
|
|
68
|
-
if (!listTaskProperties.includes("repo_path") || !listTaskProperties.includes("active_only")) {
|
|
69
|
-
throw new Error("list_tasks schema must expose repo_path and active_only");
|
|
70
122
|
}
|
|
71
123
|
const healthResult = await client.callTool({ name: "health_check", arguments: {} });
|
|
72
124
|
if (healthResult.isError) throw new Error(String(healthResult.content?.[0]?.text || "health_check failed"));
|
|
73
125
|
const health = JSON.parse(String(healthResult.content?.[0]?.text || "{}"));
|
|
74
126
|
if (
|
|
75
|
-
health.tool_profile !==
|
|
127
|
+
health.tool_profile !== profile ||
|
|
76
128
|
health.tool_count !== expectedTools.length ||
|
|
77
|
-
!health.tool_manifest_sha256
|
|
129
|
+
!health.tool_manifest_sha256 ||
|
|
130
|
+
(profile === "chatgpt_direct" && health.direct_profile_enabled !== true)
|
|
78
131
|
) {
|
|
79
132
|
throw new Error(`health_check catalog mismatch: ${JSON.stringify(health)}`);
|
|
80
133
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
134
|
+
if (profile === "chatgpt_core") {
|
|
135
|
+
const hiddenResult = await client.callTool({ name: "get_plan", arguments: { plan_id: "stale-client-probe" } });
|
|
136
|
+
const hiddenPayload = JSON.parse(String(hiddenResult.content?.[0]?.text || "{}"));
|
|
137
|
+
if (
|
|
138
|
+
!hiddenResult.isError ||
|
|
139
|
+
hiddenPayload.reason !== "tool_catalog_mismatch" ||
|
|
140
|
+
hiddenPayload.refresh_required !== true ||
|
|
141
|
+
hiddenPayload.tool_manifest_sha256 !== health.tool_manifest_sha256
|
|
142
|
+
) {
|
|
143
|
+
throw new Error(`hidden tool mismatch guidance is incomplete: ${JSON.stringify(hiddenPayload)}`);
|
|
144
|
+
}
|
|
145
|
+
if (hiddenPayload.next_tool_call?.name !== "health_check" || hiddenPayload.next_tool_call?.arguments?.detail !== "self_diagnostic") {
|
|
146
|
+
throw new Error(`mismatch next_tool_call missing or wrong: ${JSON.stringify(hiddenPayload.next_tool_call || null)}`);
|
|
147
|
+
}
|
|
148
|
+
if (!Array.isArray(hiddenPayload.connector_refresh_steps) || hiddenPayload.connector_refresh_steps.length < 3) {
|
|
149
|
+
throw new Error(`mismatch connector_refresh_steps missing or too short: ${JSON.stringify(hiddenPayload.connector_refresh_steps || null)}`);
|
|
150
|
+
}
|
|
97
151
|
}
|
|
152
|
+
|
|
98
153
|
const output = {
|
|
99
154
|
ok: true,
|
|
100
155
|
server_version: health.server_version,
|
|
@@ -103,11 +158,18 @@ try {
|
|
|
103
158
|
tool_count: health.tool_count,
|
|
104
159
|
tool_names: health.tool_names,
|
|
105
160
|
tool_manifest_sha256: health.tool_manifest_sha256,
|
|
106
|
-
required_schema: {
|
|
161
|
+
required_schema: profile === "chatgpt_core" ? {
|
|
107
162
|
create_task: ["inline_plan", "verify_commands"],
|
|
108
163
|
wait_for_task: ["timeout_seconds", "wait_seconds"],
|
|
109
164
|
health_check: ["detail"],
|
|
110
165
|
list_tasks: ["repo_path", "active_only"],
|
|
166
|
+
get_task_summary: ["view", "max_items"],
|
|
167
|
+
} : {
|
|
168
|
+
create_direct_session: ["repo_path"],
|
|
169
|
+
apply_patch: ["session_id", "path", "expected_sha256", "operations"],
|
|
170
|
+
run_verification: ["session_id", "command"],
|
|
171
|
+
finalize_direct_session: ["session_id"],
|
|
172
|
+
audit_session: ["session_id"],
|
|
111
173
|
},
|
|
112
174
|
};
|
|
113
175
|
console.log(jsonOnly ? JSON.stringify(output) : JSON.stringify(output, null, 2));
|
|
@@ -117,4 +179,7 @@ try {
|
|
|
117
179
|
process.exitCode = 1;
|
|
118
180
|
} finally {
|
|
119
181
|
await client.close().catch(() => {});
|
|
182
|
+
if (tempConfigPath) {
|
|
183
|
+
try { rmSync(tempConfigPath, { force: true }); } catch {}
|
|
184
|
+
}
|
|
120
185
|
}
|
package/scripts/mcp-smoke.js
CHANGED
|
@@ -101,9 +101,13 @@ try {
|
|
|
101
101
|
const tools = await client.listTools();
|
|
102
102
|
const names = tools.tools.map((tool) => tool.name).sort();
|
|
103
103
|
const expected = [
|
|
104
|
+
"apply_patch",
|
|
105
|
+
"audit_session",
|
|
104
106
|
"audit_task",
|
|
105
107
|
"cancel_task",
|
|
108
|
+
"create_direct_session",
|
|
106
109
|
"create_task",
|
|
110
|
+
"finalize_direct_session",
|
|
107
111
|
"get_diff",
|
|
108
112
|
"get_plan",
|
|
109
113
|
"get_result",
|
|
@@ -121,7 +125,11 @@ try {
|
|
|
121
125
|
"list_workspace",
|
|
122
126
|
"read_workspace_file",
|
|
123
127
|
"retry_task",
|
|
128
|
+
"run_verification",
|
|
129
|
+
"safe_status",
|
|
124
130
|
"save_plan",
|
|
131
|
+
"search_workspace",
|
|
132
|
+
"sync_file",
|
|
125
133
|
"wait_for_task",
|
|
126
134
|
];
|
|
127
135
|
if (JSON.stringify(names) !== JSON.stringify(expected)) {
|
|
@@ -133,7 +141,7 @@ try {
|
|
|
133
141
|
if (!tools._meta || typeof tools._meta.tool_manifest_sha256 !== "string" || tools._meta.tool_manifest_sha256.length !== 64) {
|
|
134
142
|
throw new Error(`tools/list _meta missing manifest hash: ${JSON.stringify(tools._meta || null)}`);
|
|
135
143
|
}
|
|
136
|
-
if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !==
|
|
144
|
+
if (tools._meta.tool_profile !== "full" || tools._meta.tool_count !== 30) {
|
|
137
145
|
throw new Error(`tools/list _meta profile/count mismatch: ${JSON.stringify(tools._meta)}`);
|
|
138
146
|
}
|
|
139
147
|
if (typeof tools._meta.schema_epoch !== "string" || typeof tools._meta.server_version !== "string") {
|
|
@@ -204,7 +212,7 @@ try {
|
|
|
204
212
|
test_command: "npm test",
|
|
205
213
|
});
|
|
206
214
|
if (
|
|
207
|
-
task.server_version !== "0.
|
|
215
|
+
task.server_version !== "0.6.1" ||
|
|
208
216
|
!/^[a-f0-9]{64}$/.test(task.tool_manifest_sha256 || "") ||
|
|
209
217
|
task.next_tool_call?.name !== "wait_for_task" ||
|
|
210
218
|
task.next_tool_call?.arguments?.timeout_seconds !== 25
|
|
@@ -298,10 +306,17 @@ try {
|
|
|
298
306
|
if (!summary.terminal || summary.acceptance_status !== "ready_for_review") {
|
|
299
307
|
throw new Error(`unexpected terminal summary: ${JSON.stringify(summary)}`);
|
|
300
308
|
}
|
|
309
|
+
const compactSummary = await parseToolJson("get_task_summary", { task_id: task.task_id, view: "compact", max_items: 2 });
|
|
310
|
+
if (compactSummary.view !== "compact" || "log_tails" in compactSummary || compactSummary.artifact_hygiene?.max_items !== 2) {
|
|
311
|
+
throw new Error(`unexpected compact summary: ${JSON.stringify(compactSummary)}`);
|
|
312
|
+
}
|
|
301
313
|
const waited = await parseToolJson("wait_for_task", { task_id: task.task_id, timeout_seconds: 1 });
|
|
302
314
|
if (!waited.terminal || waited.continuation_required || waited.next_tool_call?.name !== "audit_task") {
|
|
303
315
|
throw new Error(`wait_for_task did not return terminal acceptance: ${JSON.stringify(waited)}`);
|
|
304
316
|
}
|
|
317
|
+
if (waited.summary?.view !== "compact" || "log_tails" in (waited.summary || {})) {
|
|
318
|
+
throw new Error(`wait_for_task did not embed compact evidence: ${JSON.stringify(waited.summary)}`);
|
|
319
|
+
}
|
|
305
320
|
const legacyWaited = await parseToolJson("wait_for_task", { task_id: task.task_id, wait_seconds: 1 });
|
|
306
321
|
if (!legacyWaited.terminal) throw new Error("legacy wait_seconds alias stopped working");
|
|
307
322
|
const conflictingWait = await client.callTool({
|
|
@@ -323,6 +338,149 @@ try {
|
|
|
323
338
|
}
|
|
324
339
|
await client.close();
|
|
325
340
|
ok("runner executes a task and writes result files");
|
|
341
|
+
|
|
342
|
+
// ── Direct profile checks (lightweight) ──────────────────────────
|
|
343
|
+
|
|
344
|
+
// 1. chatgpt_direct disabled: only health_check exposed
|
|
345
|
+
const disabledConfigPath = join(tempRoot, "direct-disabled.json");
|
|
346
|
+
writeFileSync(
|
|
347
|
+
disabledConfigPath,
|
|
348
|
+
JSON.stringify({
|
|
349
|
+
workspaceRoot,
|
|
350
|
+
plansDir: ".patchwarden/plans",
|
|
351
|
+
tasksDir: ".patchwarden/tasks",
|
|
352
|
+
agents: { codex: { command: "node", args: ["-e", "console.log('agent')"] } },
|
|
353
|
+
allowedTestCommands: ["npm test"],
|
|
354
|
+
maxReadFileBytes: 200000,
|
|
355
|
+
enableDirectProfile: false,
|
|
356
|
+
}, null, 2),
|
|
357
|
+
"utf-8"
|
|
358
|
+
);
|
|
359
|
+
|
|
360
|
+
const disabledTransport = new StdioClientTransport({
|
|
361
|
+
command: "node",
|
|
362
|
+
args: ["dist/index.js"],
|
|
363
|
+
cwd: root,
|
|
364
|
+
env: {
|
|
365
|
+
PATCHWARDEN_CONFIG: disabledConfigPath,
|
|
366
|
+
PATCHWARDEN_TOOL_PROFILE: "chatgpt_direct",
|
|
367
|
+
},
|
|
368
|
+
stderr: "pipe",
|
|
369
|
+
});
|
|
370
|
+
const disabledClient = new Client(
|
|
371
|
+
{ name: "patchwarden-direct-disabled", version: "0.1.0" },
|
|
372
|
+
{ capabilities: {} }
|
|
373
|
+
);
|
|
374
|
+
await disabledClient.connect(disabledTransport);
|
|
375
|
+
|
|
376
|
+
const disabledTools = await disabledClient.listTools();
|
|
377
|
+
const disabledNames = disabledTools.tools.map((t) => t.name).sort();
|
|
378
|
+
if (disabledNames.length !== 1 || disabledNames[0] !== "health_check") {
|
|
379
|
+
throw new Error(`chatgpt_direct disabled should expose only health_check, got: ${disabledNames.join(", ")}`);
|
|
380
|
+
}
|
|
381
|
+
if (disabledTools._meta.tool_count !== 1) {
|
|
382
|
+
throw new Error(`chatgpt_direct disabled tool_count should be 1, got ${disabledTools._meta.tool_count}`);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const disabledHealth = JSON.parse(
|
|
386
|
+
(await disabledClient.callTool({ name: "health_check", arguments: {} })).content?.[0]?.text || "{}"
|
|
387
|
+
);
|
|
388
|
+
if (disabledHealth.direct_profile_enabled !== false) {
|
|
389
|
+
throw new Error(`direct_profile_enabled should be false, got ${disabledHealth.direct_profile_enabled}`);
|
|
390
|
+
}
|
|
391
|
+
await disabledClient.close();
|
|
392
|
+
ok("chatgpt_direct disabled exposes only health_check with diagnostic");
|
|
393
|
+
|
|
394
|
+
// 2. chatgpt_direct enabled: 10 tools + minimal create_direct_session
|
|
395
|
+
const enabledConfigPath = join(tempRoot, "direct-enabled.json");
|
|
396
|
+
const directRepo = join(workspaceRoot, "direct-fixture");
|
|
397
|
+
mkdirSync(join(directRepo, "src"), { recursive: true });
|
|
398
|
+
writeFileSync(join(directRepo, "src", "index.ts"), "export const x = 1;\n", "utf-8");
|
|
399
|
+
writeFileSync(join(directRepo, "package.json"), JSON.stringify({
|
|
400
|
+
name: "direct-fixture",
|
|
401
|
+
private: true,
|
|
402
|
+
scripts: { test: 'node -e "console.log(\'ok\')"' },
|
|
403
|
+
}, null, 2), "utf-8");
|
|
404
|
+
|
|
405
|
+
writeFileSync(
|
|
406
|
+
enabledConfigPath,
|
|
407
|
+
JSON.stringify({
|
|
408
|
+
workspaceRoot,
|
|
409
|
+
plansDir: ".patchwarden/plans",
|
|
410
|
+
tasksDir: ".patchwarden/tasks",
|
|
411
|
+
agents: { codex: { command: "node", args: ["-e", "console.log('agent')"] } },
|
|
412
|
+
allowedTestCommands: ["npm test"],
|
|
413
|
+
maxReadFileBytes: 200000,
|
|
414
|
+
enableDirectProfile: true,
|
|
415
|
+
directAllowedCommands: ["npm test", "npm run build", "npm run lint"],
|
|
416
|
+
directSessionsDir: ".patchwarden/direct-sessions",
|
|
417
|
+
directSessionTtlSeconds: 3600,
|
|
418
|
+
directMaxPatchBytes: 200000,
|
|
419
|
+
directMaxFileBytes: 500000,
|
|
420
|
+
}, null, 2),
|
|
421
|
+
"utf-8"
|
|
422
|
+
);
|
|
423
|
+
|
|
424
|
+
const enabledTransport = new StdioClientTransport({
|
|
425
|
+
command: "node",
|
|
426
|
+
args: ["dist/index.js"],
|
|
427
|
+
cwd: root,
|
|
428
|
+
env: {
|
|
429
|
+
PATCHWARDEN_CONFIG: enabledConfigPath,
|
|
430
|
+
PATCHWARDEN_TOOL_PROFILE: "chatgpt_direct",
|
|
431
|
+
},
|
|
432
|
+
stderr: "pipe",
|
|
433
|
+
});
|
|
434
|
+
const enabledClient = new Client(
|
|
435
|
+
{ name: "patchwarden-direct-enabled", version: "0.1.0" },
|
|
436
|
+
{ capabilities: {} }
|
|
437
|
+
);
|
|
438
|
+
await enabledClient.connect(enabledTransport);
|
|
439
|
+
|
|
440
|
+
const enabledTools = await enabledClient.listTools();
|
|
441
|
+
const enabledNames = enabledTools.tools.map((t) => t.name).sort();
|
|
442
|
+
const expectedDirect = [
|
|
443
|
+
"apply_patch",
|
|
444
|
+
"audit_session",
|
|
445
|
+
"create_direct_session",
|
|
446
|
+
"finalize_direct_session",
|
|
447
|
+
"health_check",
|
|
448
|
+
"list_workspace",
|
|
449
|
+
"read_workspace_file",
|
|
450
|
+
"run_verification",
|
|
451
|
+
"search_workspace",
|
|
452
|
+
"sync_file",
|
|
453
|
+
];
|
|
454
|
+
if (JSON.stringify(enabledNames) !== JSON.stringify(expectedDirect)) {
|
|
455
|
+
throw new Error(`chatgpt_direct enabled tools mismatch: ${enabledNames.join(", ")}`);
|
|
456
|
+
}
|
|
457
|
+
if (enabledTools._meta.tool_count !== 10) {
|
|
458
|
+
throw new Error(`chatgpt_direct enabled tool_count should be 10, got ${enabledTools._meta.tool_count}`);
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// Minimal create_direct_session
|
|
462
|
+
const sessionResult = JSON.parse(
|
|
463
|
+
(await enabledClient.callTool({
|
|
464
|
+
name: "create_direct_session",
|
|
465
|
+
arguments: { repo_path: "direct-fixture", title: "smoke test" },
|
|
466
|
+
})).content?.[0]?.text || "{}"
|
|
467
|
+
);
|
|
468
|
+
if (!sessionResult.session_id || !sessionResult.session_id.startsWith("direct_")) {
|
|
469
|
+
throw new Error(`create_direct_session failed: ${JSON.stringify(sessionResult)}`);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const enabledHealth = JSON.parse(
|
|
473
|
+
(await enabledClient.callTool({ name: "health_check", arguments: {} })).content?.[0]?.text || "{}"
|
|
474
|
+
);
|
|
475
|
+
if (enabledHealth.direct_profile_enabled !== true) {
|
|
476
|
+
throw new Error(`direct_profile_enabled should be true, got ${enabledHealth.direct_profile_enabled}`);
|
|
477
|
+
}
|
|
478
|
+
if (enabledHealth.direct_tool_count !== 10) {
|
|
479
|
+
throw new Error(`direct_tool_count should be 10, got ${enabledHealth.direct_tool_count}`);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
await enabledClient.close();
|
|
483
|
+
ok("chatgpt_direct enabled exposes 10 tools and create_direct_session works");
|
|
326
484
|
} catch (error) {
|
|
327
485
|
fail("MCP smoke test", error);
|
|
328
486
|
} finally {
|
package/scripts/pack-clean.js
CHANGED
|
@@ -3,11 +3,14 @@ import {
|
|
|
3
3
|
cpSync,
|
|
4
4
|
existsSync,
|
|
5
5
|
mkdirSync,
|
|
6
|
+
readFileSync,
|
|
6
7
|
readdirSync,
|
|
7
8
|
rmSync,
|
|
8
9
|
statSync,
|
|
10
|
+
writeFileSync,
|
|
9
11
|
} from "node:fs";
|
|
10
12
|
import { execFileSync } from "node:child_process";
|
|
13
|
+
import { deflateRawSync } from "node:zlib";
|
|
11
14
|
import { join, relative, resolve } from "node:path";
|
|
12
15
|
import { fileURLToPath } from "node:url";
|
|
13
16
|
|
|
@@ -16,16 +19,17 @@ const root = resolve(scriptDir, "..");
|
|
|
16
19
|
const releaseDir = resolve(root, "release");
|
|
17
20
|
const archivePath = resolve(root, "patchwarden-release.tar.gz");
|
|
18
21
|
|
|
22
|
+
// Read version from package.json for zip naming
|
|
23
|
+
const pkg = JSON.parse(readFileSync(resolve(root, "package.json"), "utf-8"));
|
|
24
|
+
const zipArchivePath = resolve(root, `PatchWarden-v${pkg.version}.zip`);
|
|
25
|
+
|
|
19
26
|
const include = [
|
|
20
27
|
"dist",
|
|
21
28
|
"docs",
|
|
22
29
|
"examples",
|
|
23
30
|
"scripts",
|
|
24
31
|
"src",
|
|
25
|
-
"
|
|
26
|
-
"Restart-PatchWarden.cmd",
|
|
27
|
-
"Check-PatchWarden-Health.cmd",
|
|
28
|
-
"Reset-PatchWarden-Tunnel-Key.cmd",
|
|
32
|
+
"PatchWarden.cmd",
|
|
29
33
|
".gitignore",
|
|
30
34
|
"LICENSE",
|
|
31
35
|
"README.md",
|
|
@@ -44,8 +48,123 @@ const forbidden = [
|
|
|
44
48
|
/\.dpapi$/i,
|
|
45
49
|
/(^|[\\/])\.env$/,
|
|
46
50
|
/\.log$/,
|
|
51
|
+
/^docs[\\/]optimization-proposal\.md$/i,
|
|
52
|
+
/(^|[\\/])kill-patchwarden\.(cmd|ps1)$/i,
|
|
47
53
|
];
|
|
48
54
|
|
|
55
|
+
// ── Minimal zip writer helpers (POSIX paths, DEFLATE via zlib) ────
|
|
56
|
+
// Defined before main execution so const CRC_TABLE is initialized.
|
|
57
|
+
|
|
58
|
+
const CRC_TABLE = new Uint32Array(256);
|
|
59
|
+
for (let i = 0; i < 256; i++) {
|
|
60
|
+
let c = i;
|
|
61
|
+
for (let j = 0; j < 8; j++) {
|
|
62
|
+
c = (c & 1) ? (0xEDB88320 ^ (c >>> 1)) : (c >>> 1);
|
|
63
|
+
}
|
|
64
|
+
CRC_TABLE[i] = c;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function crc32(buf) {
|
|
68
|
+
let c = 0xFFFFFFFF;
|
|
69
|
+
for (let i = 0; i < buf.length; i++) {
|
|
70
|
+
c = CRC_TABLE[(c ^ buf[i]) & 0xFF] ^ (c >>> 8);
|
|
71
|
+
}
|
|
72
|
+
return (c ^ 0xFFFFFFFF) >>> 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function dosDateTime(date) {
|
|
76
|
+
const dosTime = (date.getHours() << 11) | (date.getMinutes() << 5) | (date.getSeconds() >> 1);
|
|
77
|
+
const dosDate = (((date.getFullYear() - 1980) & 0x7F) << 9) | ((date.getMonth() + 1) << 5) | date.getDate();
|
|
78
|
+
return { dosTime, dosDate };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function createZip(files, outputPath) {
|
|
82
|
+
const localParts = [];
|
|
83
|
+
const centralParts = [];
|
|
84
|
+
let offset = 0;
|
|
85
|
+
const now = new Date();
|
|
86
|
+
const { dosTime, dosDate } = dosDateTime(now);
|
|
87
|
+
|
|
88
|
+
for (const file of files) {
|
|
89
|
+
const posixPath = file.path.replace(/\\/g, "/");
|
|
90
|
+
const nameBuf = Buffer.from(posixPath, "utf-8");
|
|
91
|
+
const content = file.content;
|
|
92
|
+
const compressed = deflateRawSync(content);
|
|
93
|
+
const useDeflate = compressed.length < content.length;
|
|
94
|
+
const method = useDeflate ? 8 : 0;
|
|
95
|
+
const data = useDeflate ? compressed : content;
|
|
96
|
+
const crc = crc32(content);
|
|
97
|
+
|
|
98
|
+
const lh = Buffer.alloc(30);
|
|
99
|
+
lh.writeUInt32LE(0x04034b50, 0);
|
|
100
|
+
lh.writeUInt16LE(20, 4);
|
|
101
|
+
lh.writeUInt16LE(0, 6);
|
|
102
|
+
lh.writeUInt16LE(method, 8);
|
|
103
|
+
lh.writeUInt16LE(dosTime, 10);
|
|
104
|
+
lh.writeUInt16LE(dosDate, 12);
|
|
105
|
+
lh.writeUInt32LE(crc, 14);
|
|
106
|
+
lh.writeUInt32LE(data.length, 18);
|
|
107
|
+
lh.writeUInt32LE(content.length, 22);
|
|
108
|
+
lh.writeUInt16LE(nameBuf.length, 26);
|
|
109
|
+
lh.writeUInt16LE(0, 28);
|
|
110
|
+
|
|
111
|
+
const localHeaderOffset = offset;
|
|
112
|
+
localParts.push(Buffer.concat([lh, nameBuf, data]));
|
|
113
|
+
offset += lh.length + nameBuf.length + data.length;
|
|
114
|
+
|
|
115
|
+
const cd = Buffer.alloc(46);
|
|
116
|
+
cd.writeUInt32LE(0x02014b50, 0);
|
|
117
|
+
cd.writeUInt16LE(20, 4);
|
|
118
|
+
cd.writeUInt16LE(20, 6);
|
|
119
|
+
cd.writeUInt16LE(0, 8);
|
|
120
|
+
cd.writeUInt16LE(method, 10);
|
|
121
|
+
cd.writeUInt16LE(dosTime, 12);
|
|
122
|
+
cd.writeUInt16LE(dosDate, 14);
|
|
123
|
+
cd.writeUInt32LE(crc, 16);
|
|
124
|
+
cd.writeUInt32LE(data.length, 20);
|
|
125
|
+
cd.writeUInt32LE(content.length, 24);
|
|
126
|
+
cd.writeUInt16LE(nameBuf.length, 28);
|
|
127
|
+
cd.writeUInt16LE(0, 30);
|
|
128
|
+
cd.writeUInt16LE(0, 32);
|
|
129
|
+
cd.writeUInt16LE(0, 34);
|
|
130
|
+
cd.writeUInt16LE(0, 36);
|
|
131
|
+
cd.writeUInt32LE(0, 38);
|
|
132
|
+
cd.writeUInt32LE(localHeaderOffset, 42);
|
|
133
|
+
centralParts.push(Buffer.concat([cd, nameBuf]));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const centralDir = Buffer.concat(centralParts);
|
|
137
|
+
const centralDirOffset = offset;
|
|
138
|
+
|
|
139
|
+
const eocd = Buffer.alloc(22);
|
|
140
|
+
eocd.writeUInt32LE(0x06054b50, 0);
|
|
141
|
+
eocd.writeUInt16LE(0, 4);
|
|
142
|
+
eocd.writeUInt16LE(0, 6);
|
|
143
|
+
eocd.writeUInt16LE(files.length, 8);
|
|
144
|
+
eocd.writeUInt16LE(files.length, 10);
|
|
145
|
+
eocd.writeUInt32LE(centralDir.length, 12);
|
|
146
|
+
eocd.writeUInt32LE(centralDirOffset, 16);
|
|
147
|
+
eocd.writeUInt16LE(0, 20);
|
|
148
|
+
|
|
149
|
+
writeFileSync(outputPath, Buffer.concat([...localParts, centralDir, eocd]));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function readZipEntryNames(zipPath) {
|
|
153
|
+
const buf = readFileSync(zipPath);
|
|
154
|
+
const names = [];
|
|
155
|
+
for (let i = 0; i < buf.length - 4; i++) {
|
|
156
|
+
if (buf.readUInt32LE(i) === 0x02014b50) {
|
|
157
|
+
const nameLen = buf.readUInt16LE(i + 28);
|
|
158
|
+
const extraLen = buf.readUInt16LE(i + 30);
|
|
159
|
+
const commentLen = buf.readUInt16LE(i + 32);
|
|
160
|
+
const name = buf.toString("utf-8", i + 46, i + 46 + nameLen);
|
|
161
|
+
names.push(name);
|
|
162
|
+
i += 45 + nameLen + extraLen + commentLen;
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
return names;
|
|
166
|
+
}
|
|
167
|
+
|
|
49
168
|
console.log("[pack-clean] Preparing clean release directory...");
|
|
50
169
|
rmSync(releaseDir, { recursive: true, force: true });
|
|
51
170
|
rmSync(archivePath, { force: true });
|
|
@@ -116,7 +235,43 @@ if (badArchiveEntries.length > 0) {
|
|
|
116
235
|
|
|
117
236
|
const sizeKb = (statSync(archivePath).size / 1024).toFixed(1);
|
|
118
237
|
console.log(`[pack-clean] Release directory: ${releaseFiles.length} files`);
|
|
119
|
-
console.log(`[pack-clean] Archive: ${archivePath} (${sizeKb} KB)`);
|
|
238
|
+
console.log(`[pack-clean] tar.gz Archive: ${archivePath} (${sizeKb} KB)`);
|
|
239
|
+
|
|
240
|
+
// ── Create zip archive with POSIX path separators ─────────────────
|
|
241
|
+
console.log("[pack-clean] Creating zip archive with POSIX paths...");
|
|
242
|
+
rmSync(zipArchivePath, { force: true });
|
|
243
|
+
|
|
244
|
+
const zipFiles = releaseFiles.map((file) => {
|
|
245
|
+
const rel = toPosix(relative(releaseDir, file));
|
|
246
|
+
return { path: rel, content: readFileSync(file) };
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
createZip(zipFiles, zipArchivePath);
|
|
250
|
+
|
|
251
|
+
// Verify zip entries use POSIX separators
|
|
252
|
+
const zipEntryNames = readZipEntryNames(zipArchivePath);
|
|
253
|
+
const badZipEntries = zipEntryNames.filter((name) => name.includes("\\"));
|
|
254
|
+
if (badZipEntries.length > 0) {
|
|
255
|
+
console.error("[pack-clean] Backslash path separators found in zip entries:");
|
|
256
|
+
for (const name of badZipEntries.slice(0, 20)) {
|
|
257
|
+
console.error(` ${name}`);
|
|
258
|
+
}
|
|
259
|
+
rmSync(zipArchivePath, { force: true });
|
|
260
|
+
process.exit(1);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
const badZipForbidden = zipEntryNames.filter(isForbidden);
|
|
264
|
+
if (badZipForbidden.length > 0) {
|
|
265
|
+
console.error("[pack-clean] Forbidden files in zip archive:");
|
|
266
|
+
for (const entry of badZipForbidden) {
|
|
267
|
+
console.error(` ${entry}`);
|
|
268
|
+
}
|
|
269
|
+
rmSync(zipArchivePath, { force: true });
|
|
270
|
+
process.exit(1);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
const zipSizeKb = (statSync(zipArchivePath).size / 1024).toFixed(1);
|
|
274
|
+
console.log(`[pack-clean] zip Archive: ${zipArchivePath} (${zipSizeKb} KB, ${zipEntryNames.length} entries)`);
|
|
120
275
|
console.log("[pack-clean] OK");
|
|
121
276
|
|
|
122
277
|
function listFiles(dir) {
|