@wrongstack/tools 0.313.0 → 0.316.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/dist/_edit-match.d.ts +3 -2
- package/dist/_output-spool.d.ts +4 -3
- package/dist/_shell-pick.d.ts +2 -1
- package/dist/_spawn-stream.d.ts +2 -1
- package/dist/_syntax-check.d.ts +2 -1
- package/dist/audit.js +10 -4055
- package/dist/bash-kill-guard.d.ts +3 -6
- package/dist/bash.js +9 -2308
- package/dist/batch-tool-use.js +2 -187
- package/dist/browser/index.js +25 -1339
- package/dist/browser/network-guard-proxy.d.ts +2 -1
- package/dist/builtin.js +69 -33949
- package/dist/chunk-22B7GE5T.js +260 -0
- package/dist/chunk-2VSZC6QM.js +113 -0
- package/dist/chunk-3PVYQFRQ.js +472 -0
- package/dist/chunk-42BHI7TN.js +337 -0
- package/dist/chunk-4BHUSMNL.js +104 -0
- package/dist/chunk-4I4LWFOO.js +431 -0
- package/dist/chunk-4UESMQDP.js +122 -0
- package/dist/chunk-553IQUMO.js +478 -0
- package/dist/chunk-5H4GSJAI.js +223 -0
- package/dist/chunk-6RNIFLG4.js +305 -0
- package/dist/chunk-7EY5DCRO.js +434 -0
- package/dist/chunk-A6BRWIXN.js +317 -0
- package/dist/chunk-AHHDWNXC.js +200 -0
- package/dist/chunk-BSLVR56S.js +199 -0
- package/dist/chunk-BTSFTGIL.js +7 -0
- package/dist/chunk-D33S4RQG.js +222 -0
- package/dist/chunk-DA4KNE2H.js +4412 -0
- package/dist/chunk-DR7JOQTC.js +520 -0
- package/dist/chunk-EEOFQDT6.js +898 -0
- package/dist/chunk-EJWSCQ6D.js +137 -0
- package/dist/chunk-EMRGN4KM.js +170 -0
- package/dist/chunk-FQZOHLG3.js +89 -0
- package/dist/chunk-GOIZNNOA.js +221 -0
- package/dist/chunk-HFXSNKCV.js +196 -0
- package/dist/chunk-HYX2TZ7O.js +1272 -0
- package/dist/chunk-I64BS3VC.js +15 -0
- package/dist/chunk-IVWV2UM6.js +955 -0
- package/dist/chunk-IXMNTNW5.js +238 -0
- package/dist/chunk-IYK7XGDZ.js +892 -0
- package/dist/chunk-J5SKD6LN.js +235 -0
- package/dist/chunk-JNNVRHMV.js +2111 -0
- package/dist/chunk-JOHXDF3D.js +295 -0
- package/dist/chunk-JRJJX3L5.js +424 -0
- package/dist/chunk-K3WNSHUF.js +1371 -0
- package/dist/chunk-L25CVUVR.js +2830 -0
- package/dist/chunk-LIJQMYYX.js +59 -0
- package/dist/chunk-LWKUTMD5.js +197 -0
- package/dist/chunk-MQX2KXVW.js +54 -0
- package/dist/chunk-MZZN7VCD.js +359 -0
- package/dist/chunk-NBDZMMG2.js +169 -0
- package/dist/chunk-NKJW5GFO.js +366 -0
- package/dist/chunk-NKNOBUBU.js +522 -0
- package/dist/chunk-O4MBV4AX.js +134 -0
- package/dist/chunk-OUBDL3WH.js +3515 -0
- package/dist/chunk-PQKK5JH7.js +101 -0
- package/dist/chunk-PY2IOS7C.js +1000 -0
- package/dist/chunk-Q5JPPCZC.js +12 -0
- package/dist/chunk-QBV725YG.js +128 -0
- package/dist/chunk-QZQSPUBF.js +315 -0
- package/dist/chunk-RGISBZBD.js +231 -0
- package/dist/chunk-RJZQWEMS.js +84 -0
- package/dist/chunk-RXISBFR5.js +315 -0
- package/dist/chunk-S5CCLZL3.js +99 -0
- package/dist/chunk-SDLVH5RZ.js +178 -0
- package/dist/chunk-T2F4S7U4.js +1723 -0
- package/dist/chunk-TEBQ4VPZ.js +163 -0
- package/dist/chunk-U4D5S3VQ.js +21 -0
- package/dist/chunk-U4VAATKP.js +193 -0
- package/dist/chunk-UCYQTZ7F.js +113 -0
- package/dist/chunk-UKU2ZX4C.js +203 -0
- package/dist/chunk-UNSKG6DC.js +159 -0
- package/dist/chunk-V43EQ6VR.js +68 -0
- package/dist/chunk-VB3MJGCV.js +610 -0
- package/dist/chunk-VFKPWSOX.js +154 -0
- package/dist/chunk-VJNHFZ7J.js +190 -0
- package/dist/chunk-VXECAJJ7.js +1091 -0
- package/dist/chunk-WHIIIREL.js +223 -0
- package/dist/chunk-Z6DAKQ3U.js +1 -0
- package/dist/chunk-ZJQ4IGXK.js +474 -0
- package/dist/chunk-ZLTFLC25.js +385 -0
- package/dist/circuit-breaker.js +3 -191
- package/dist/codebase-index/ast-invariant-engine.d.ts +5 -4
- package/dist/codebase-index/binary-frame.d.ts +0 -2
- package/dist/codebase-index/bm25.d.ts +1 -1
- package/dist/codebase-index/circuit-breaker.d.ts +2 -1
- package/dist/codebase-index/codebase-ast-replace-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-impact-analysis-tool.d.ts +4 -3
- package/dist/codebase-index/codebase-invariant-check-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-repo-map-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-skeleton-tool.d.ts +3 -2
- package/dist/codebase-index/codebase-targeted-test-tool.d.ts +3 -2
- package/dist/codebase-index/content-hash.d.ts +0 -2
- package/dist/codebase-index/import-extractor.d.ts +0 -13
- package/dist/codebase-index/index-service.d.ts +1 -1
- package/dist/codebase-index/index.js +84 -13688
- package/dist/codebase-index/languages.d.ts +2 -1
- package/dist/codebase-index/module-roots.d.ts +3 -2
- package/dist/codebase-index/parser-batch.d.ts +0 -5
- package/dist/codebase-index/parser-dispatch.d.ts +2 -1
- package/dist/codebase-index/parser-output.d.ts +4 -3
- package/dist/codebase-index/parser-worker-pool.d.ts +0 -2
- package/dist/codebase-index/parser-worker-script.d.ts +1 -16
- package/dist/codebase-index/parser-worker-script.js +8 -3995
- package/dist/codebase-index/project-server-client-state.d.ts +2 -2
- package/dist/codebase-index/project-server-endpoint.d.ts +0 -15
- package/dist/codebase-index/project-server.js +60 -8548
- package/dist/codebase-index/tree-sitter/visitor.d.ts +2 -1
- package/dist/codebase-index/tree-sitter-parser.d.ts +0 -2
- package/dist/codebase-index/ts-parser.d.ts +1 -1
- package/dist/codebase-index/vector-search.d.ts +2 -1
- package/dist/codebase-index/wal-maintenance.d.ts +3 -2
- package/dist/codebase-index/worker.js +20 -8416
- package/dist/codebase-index/writer-admin.d.ts +0 -1
- package/dist/codebase-index/writer-search.d.ts +0 -9
- package/dist/design.js +2 -299
- package/dist/diff.js +4 -256
- package/dist/document.js +4 -222
- package/dist/e2e.js +5 -476
- package/dist/edit.js +14 -10200
- package/dist/exec.js +16 -3131
- package/dist/fetch.js +8 -398
- package/dist/format.js +10 -3750
- package/dist/generic-parser-VUXDIAOZ.js +13 -0
- package/dist/git.js +4 -380
- package/dist/glob.js +6 -280
- package/dist/go-parser-JXYKJHRC.js +469 -0
- package/dist/grep.js +7 -604
- package/dist/index.js +318 -35387
- package/dist/install.js +10 -4091
- package/dist/json-parser-IBHZ4BZC.js +213 -0
- package/dist/json.js +4 -665
- package/dist/kanban-contract-actions.d.ts +1 -1
- package/dist/kanban-detail-actions.d.ts +2 -1
- package/dist/kanban-presence.d.ts +2 -1
- package/dist/kanban-serializer.d.ts +0 -8
- package/dist/kanban-split-task-handler.d.ts +2 -2
- package/dist/kanban-tool-types.d.ts +2 -1
- package/dist/kanban.js +3 -2096
- package/dist/languages/diagnostics.d.ts +2 -1
- package/dist/languages/index.js +23 -4664
- package/dist/languages/legacy-bridge.d.ts +3 -2
- package/dist/languages/types.d.ts +2 -1
- package/dist/legacy-bridge-7M6QLUQN.js +18 -0
- package/dist/lint.js +10 -3706
- package/dist/logs.js +4 -287
- package/dist/memory.js +6 -215
- package/dist/mode.js +3 -99
- package/dist/outdated.js +4 -5170
- package/dist/pack.js +65 -33897
- package/dist/parser-dispatch-QH4XPGG6.js +14 -0
- package/dist/patch.js +13 -9935
- package/dist/plan.js +5 -3286
- package/dist/process-registry.js +8 -651
- package/dist/ps-slash.js +12 -1439
- package/dist/pwsh.d.ts +0 -1
- package/dist/py-parser-IS4HKYMF.js +16 -0
- package/dist/read.js +13 -9816
- package/dist/replace.js +15 -10014
- package/dist/rs-parser-XHUUO2KZ.js +81 -0
- package/dist/scaffold.js +4 -253
- package/dist/search.js +5 -585
- package/dist/session-kanban-sync.d.ts +0 -6
- package/dist/session-kanban.js +30 -972
- package/dist/skill.js +2 -215
- package/dist/spawn-background.d.ts +1 -1
- package/dist/task.js +5 -3410
- package/dist/test.js +10 -3773
- package/dist/todo.js +4 -2891
- package/dist/tool-help.js +3 -123
- package/dist/tool-search.js +3 -79
- package/dist/tool-tier.js +68 -33984
- package/dist/tool-use.js +2 -93
- package/dist/tree-sitter-parser-AIERW3YI.js +15 -0
- package/dist/tree.js +4 -288
- package/dist/ts-parser-RCTYKSVI.js +232 -0
- package/dist/typecheck.js +10 -3753
- package/dist/win32.js +6 -60
- package/dist/write.js +14 -9882
- package/dist/yaml-parser-4SAXV3PL.js +157 -0
- package/package.json +5 -5
|
@@ -0,0 +1,522 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getProcessRegistry
|
|
3
|
+
} from "./chunk-3PVYQFRQ.js";
|
|
4
|
+
|
|
5
|
+
// src/process-registry-persistent.ts
|
|
6
|
+
import * as fs from "node:fs/promises";
|
|
7
|
+
import * as os from "node:os";
|
|
8
|
+
import { wstackGlobalRoot } from "@wrongstack/core/utils";
|
|
9
|
+
import * as path from "node:path";
|
|
10
|
+
var REGISTRY_FILE = "process-registry.json";
|
|
11
|
+
function toErrorMessage(err) {
|
|
12
|
+
return err instanceof Error ? err.message : String(err);
|
|
13
|
+
}
|
|
14
|
+
function emitStructuredLog(level, event, message, error) {
|
|
15
|
+
const payload = {
|
|
16
|
+
level,
|
|
17
|
+
event,
|
|
18
|
+
message,
|
|
19
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
20
|
+
};
|
|
21
|
+
if (error !== void 0) {
|
|
22
|
+
payload.error = toErrorMessage(error);
|
|
23
|
+
}
|
|
24
|
+
console.log(JSON.stringify(payload));
|
|
25
|
+
}
|
|
26
|
+
var HEARTBEAT_INTERVAL_MS = 5e3;
|
|
27
|
+
var STALE_THRESHOLD_MS = 3e4;
|
|
28
|
+
var LOCK_STALE_MS = 3e4;
|
|
29
|
+
var LOCKFILE = ".process-registry.lock";
|
|
30
|
+
function generateInstanceId() {
|
|
31
|
+
const hostname2 = os.hostname();
|
|
32
|
+
const pid = process.pid;
|
|
33
|
+
const random = Math.random().toString(36).slice(2, 8);
|
|
34
|
+
return `${hostname2}:${pid}:${random}`;
|
|
35
|
+
}
|
|
36
|
+
function isNodeError(err) {
|
|
37
|
+
return typeof err === "object" && err !== null && "code" in err;
|
|
38
|
+
}
|
|
39
|
+
async function acquireLock(lockfilePath, timeoutMs = 5e3) {
|
|
40
|
+
const start = Date.now();
|
|
41
|
+
const pidStr = String(process.pid);
|
|
42
|
+
const hostStr = os.hostname();
|
|
43
|
+
try {
|
|
44
|
+
await fs.mkdir(path.dirname(lockfilePath), { recursive: true });
|
|
45
|
+
} catch {
|
|
46
|
+
}
|
|
47
|
+
while (Date.now() - start < timeoutMs) {
|
|
48
|
+
try {
|
|
49
|
+
await fs.writeFile(lockfilePath, `${pidStr}:${hostStr}:${Date.now()}`, { flag: "wx" });
|
|
50
|
+
return async () => {
|
|
51
|
+
try {
|
|
52
|
+
await fs.unlink(lockfilePath);
|
|
53
|
+
} catch {
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
} catch (err) {
|
|
57
|
+
if (isNodeError(err) && err.code === "EEXIST") {
|
|
58
|
+
try {
|
|
59
|
+
const content = await fs.readFile(lockfilePath, "utf-8");
|
|
60
|
+
const parts = content.split(":");
|
|
61
|
+
const lockPid = parseInt(parts[0] ?? "0", 10);
|
|
62
|
+
const lockTs = Number(parts[parts.length - 1]);
|
|
63
|
+
const staleByAge = Number.isFinite(lockTs) && Date.now() - lockTs > LOCK_STALE_MS;
|
|
64
|
+
let holderDead = false;
|
|
65
|
+
if (process.platform !== "win32" && Number.isFinite(lockPid) && lockPid > 0) {
|
|
66
|
+
try {
|
|
67
|
+
process.kill(lockPid, 0);
|
|
68
|
+
} catch {
|
|
69
|
+
holderDead = true;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
if (holderDead || staleByAge) {
|
|
73
|
+
await fs.unlink(lockfilePath).catch(() => {
|
|
74
|
+
});
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
await fs.unlink(lockfilePath).catch(() => {
|
|
79
|
+
});
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
throw err;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
throw new Error(`Failed to acquire lock after ${timeoutMs}ms`);
|
|
89
|
+
}
|
|
90
|
+
function freshRegistryData() {
|
|
91
|
+
return {
|
|
92
|
+
version: 1,
|
|
93
|
+
instances: /* @__PURE__ */ new Map(),
|
|
94
|
+
protectedPatterns: ["wrongstack", "node"],
|
|
95
|
+
lastCleanup: Date.now()
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
async function readRegistryFile(filePath) {
|
|
99
|
+
let content;
|
|
100
|
+
try {
|
|
101
|
+
content = await fs.readFile(filePath, "utf-8");
|
|
102
|
+
} catch (err) {
|
|
103
|
+
if (isNodeError(err) && err.code === "ENOENT") return freshRegistryData();
|
|
104
|
+
throw err;
|
|
105
|
+
}
|
|
106
|
+
try {
|
|
107
|
+
const parsed = JSON.parse(content);
|
|
108
|
+
if (!parsed || typeof parsed !== "object") return freshRegistryData();
|
|
109
|
+
const base = freshRegistryData();
|
|
110
|
+
return {
|
|
111
|
+
version: 1,
|
|
112
|
+
instances: Array.isArray(parsed.instances) ? new Map(parsed.instances) : base.instances,
|
|
113
|
+
protectedPatterns: Array.isArray(parsed.protectedPatterns) ? parsed.protectedPatterns : base.protectedPatterns,
|
|
114
|
+
lastCleanup: typeof parsed.lastCleanup === "number" ? parsed.lastCleanup : base.lastCleanup
|
|
115
|
+
};
|
|
116
|
+
} catch {
|
|
117
|
+
return freshRegistryData();
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function writeRegistryFile(filePath, data) {
|
|
121
|
+
const tmpPath = `${filePath}.tmp.${process.pid}.${Date.now().toString(36)}.${Math.random().toString(36).slice(2, 8)}`;
|
|
122
|
+
const content = JSON.stringify(
|
|
123
|
+
data,
|
|
124
|
+
(_k, v) => {
|
|
125
|
+
if (v instanceof Map) {
|
|
126
|
+
return Array.from(v.entries());
|
|
127
|
+
}
|
|
128
|
+
return v;
|
|
129
|
+
},
|
|
130
|
+
2
|
|
131
|
+
);
|
|
132
|
+
await fs.writeFile(tmpPath, content, "utf-8");
|
|
133
|
+
await fs.rename(tmpPath, filePath);
|
|
134
|
+
}
|
|
135
|
+
var PersistentProcessRegistry = class {
|
|
136
|
+
instanceId;
|
|
137
|
+
registryPath;
|
|
138
|
+
lockPath;
|
|
139
|
+
baseRegistry;
|
|
140
|
+
heartbeatInterval = null;
|
|
141
|
+
cleanupInterval = null;
|
|
142
|
+
heartbeatRunning = false;
|
|
143
|
+
cleanupRunning = false;
|
|
144
|
+
isShuttingDown = false;
|
|
145
|
+
onProcessExit = () => {
|
|
146
|
+
this.runInBackground(
|
|
147
|
+
this.syncToPersistent(),
|
|
148
|
+
"process_registry.exit_sync_failed",
|
|
149
|
+
"PersistentProcessRegistry: exit sync failed"
|
|
150
|
+
);
|
|
151
|
+
};
|
|
152
|
+
constructor(baseRegistry) {
|
|
153
|
+
this.instanceId = generateInstanceId();
|
|
154
|
+
const globalRoot = wstackGlobalRoot();
|
|
155
|
+
this.registryPath = path.join(globalRoot, REGISTRY_FILE);
|
|
156
|
+
this.lockPath = path.join(globalRoot, LOCKFILE);
|
|
157
|
+
this.baseRegistry = baseRegistry ?? getProcessRegistry();
|
|
158
|
+
this.ensureDirectory().catch((err) => {
|
|
159
|
+
emitStructuredLog(
|
|
160
|
+
"warn",
|
|
161
|
+
"process_registry.dir_create_failed",
|
|
162
|
+
"PersistentProcessRegistry: failed to create .wrongstack directory",
|
|
163
|
+
err
|
|
164
|
+
);
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async ensureDirectory() {
|
|
168
|
+
const dir = path.dirname(this.registryPath);
|
|
169
|
+
try {
|
|
170
|
+
await fs.mkdir(dir, { recursive: true });
|
|
171
|
+
} catch (err) {
|
|
172
|
+
if (!isNodeError(err) || err.code !== "EEXIST") throw err;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
runInBackground(operation, event, message) {
|
|
176
|
+
void operation.catch((err) => {
|
|
177
|
+
emitStructuredLog("warn", event, message, err);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Start the heartbeat and periodic cleanup tasks.
|
|
182
|
+
*/
|
|
183
|
+
start() {
|
|
184
|
+
if (this.heartbeatInterval) return;
|
|
185
|
+
this.isShuttingDown = false;
|
|
186
|
+
this.heartbeat();
|
|
187
|
+
this.heartbeatInterval = setInterval(() => {
|
|
188
|
+
this.heartbeat();
|
|
189
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
190
|
+
this.heartbeatInterval.unref?.();
|
|
191
|
+
this.cleanupInterval = setInterval(() => {
|
|
192
|
+
this.cleanup();
|
|
193
|
+
}, STALE_THRESHOLD_MS);
|
|
194
|
+
this.cleanupInterval.unref?.();
|
|
195
|
+
this.registerMainProcess();
|
|
196
|
+
process.on("exit", this.onProcessExit);
|
|
197
|
+
}
|
|
198
|
+
/**
|
|
199
|
+
* Stop the heartbeat and clean up.
|
|
200
|
+
*/
|
|
201
|
+
stop() {
|
|
202
|
+
this.isShuttingDown = true;
|
|
203
|
+
if (this.heartbeatInterval) {
|
|
204
|
+
clearInterval(this.heartbeatInterval);
|
|
205
|
+
this.heartbeatInterval = null;
|
|
206
|
+
}
|
|
207
|
+
if (this.cleanupInterval) {
|
|
208
|
+
clearInterval(this.cleanupInterval);
|
|
209
|
+
this.cleanupInterval = null;
|
|
210
|
+
}
|
|
211
|
+
process.off("exit", this.onProcessExit);
|
|
212
|
+
this.runInBackground(
|
|
213
|
+
this.syncToPersistent(),
|
|
214
|
+
"process_registry.stop_sync_failed",
|
|
215
|
+
"PersistentProcessRegistry: stop sync failed"
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Register the main WrongStack process as protected.
|
|
220
|
+
*/
|
|
221
|
+
registerMainProcess() {
|
|
222
|
+
const mainPid = process.pid;
|
|
223
|
+
this.runInBackground(
|
|
224
|
+
this.updatePersistentEntry({
|
|
225
|
+
pid: mainPid,
|
|
226
|
+
name: "wrongstack-main",
|
|
227
|
+
command: process.argv.slice(0, 3).join(" "),
|
|
228
|
+
startedAt: Date.now(),
|
|
229
|
+
lastHeartbeat: Date.now(),
|
|
230
|
+
instanceId: this.instanceId,
|
|
231
|
+
hostname: os.hostname(),
|
|
232
|
+
protected: true,
|
|
233
|
+
spawnMode: "main",
|
|
234
|
+
parentPid: process.ppid,
|
|
235
|
+
platform: process.platform
|
|
236
|
+
}),
|
|
237
|
+
"process_registry.main_register_failed",
|
|
238
|
+
"PersistentProcessRegistry: failed to register main process"
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
/**
|
|
242
|
+
* Register a spawned child process with the persistent registry.
|
|
243
|
+
*/
|
|
244
|
+
registerChildProcess(pid, name, command, sessionId, spawnMode = "spawn") {
|
|
245
|
+
const entry = {
|
|
246
|
+
pid,
|
|
247
|
+
name,
|
|
248
|
+
command,
|
|
249
|
+
startedAt: Date.now(),
|
|
250
|
+
lastHeartbeat: Date.now(),
|
|
251
|
+
instanceId: this.instanceId,
|
|
252
|
+
hostname: os.hostname(),
|
|
253
|
+
protected: true,
|
|
254
|
+
// All WrongStack child processes are protected by default
|
|
255
|
+
spawnMode,
|
|
256
|
+
parentPid: process.pid,
|
|
257
|
+
platform: process.platform
|
|
258
|
+
};
|
|
259
|
+
if (sessionId) {
|
|
260
|
+
entry.sessionId = sessionId;
|
|
261
|
+
}
|
|
262
|
+
this.runInBackground(
|
|
263
|
+
this.updatePersistentEntry(entry),
|
|
264
|
+
"process_registry.child_register_failed",
|
|
265
|
+
"PersistentProcessRegistry: failed to register child process"
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Update or add an entry in the persistent registry.
|
|
270
|
+
*/
|
|
271
|
+
async updatePersistentEntry(entry) {
|
|
272
|
+
const release = await acquireLock(this.lockPath);
|
|
273
|
+
try {
|
|
274
|
+
const data = await readRegistryFile(this.registryPath);
|
|
275
|
+
data.instances.set(String(entry.pid), entry);
|
|
276
|
+
this.baseRegistry.register({
|
|
277
|
+
pid: entry.pid,
|
|
278
|
+
name: entry.name,
|
|
279
|
+
command: entry.command,
|
|
280
|
+
startedAt: entry.startedAt,
|
|
281
|
+
sessionId: entry.sessionId,
|
|
282
|
+
protected: entry.protected,
|
|
283
|
+
child: null
|
|
284
|
+
});
|
|
285
|
+
await writeRegistryFile(this.registryPath, data);
|
|
286
|
+
} finally {
|
|
287
|
+
await release();
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Unregister a process from the persistent registry.
|
|
292
|
+
*/
|
|
293
|
+
async unregister(pid) {
|
|
294
|
+
const release = await acquireLock(this.lockPath);
|
|
295
|
+
try {
|
|
296
|
+
const data = await readRegistryFile(this.registryPath);
|
|
297
|
+
data.instances.delete(String(pid));
|
|
298
|
+
await writeRegistryFile(this.registryPath, data);
|
|
299
|
+
} finally {
|
|
300
|
+
await release();
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
/**
|
|
304
|
+
* Send heartbeat to mark all this instance's processes as alive.
|
|
305
|
+
*/
|
|
306
|
+
heartbeat() {
|
|
307
|
+
if (this.isShuttingDown || this.heartbeatRunning) return;
|
|
308
|
+
this.heartbeatRunning = true;
|
|
309
|
+
void this.syncToPersistent().catch((err) => {
|
|
310
|
+
emitStructuredLog(
|
|
311
|
+
"warn",
|
|
312
|
+
"process_registry.heartbeat_failed",
|
|
313
|
+
"PersistentProcessRegistry: heartbeat failed",
|
|
314
|
+
err
|
|
315
|
+
);
|
|
316
|
+
}).finally(() => {
|
|
317
|
+
this.heartbeatRunning = false;
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
cleanup() {
|
|
321
|
+
if (this.isShuttingDown || this.cleanupRunning) return;
|
|
322
|
+
this.cleanupRunning = true;
|
|
323
|
+
void this.cleanupStaleEntries().catch((err) => {
|
|
324
|
+
emitStructuredLog(
|
|
325
|
+
"warn",
|
|
326
|
+
"process_registry.periodic_cleanup_failed",
|
|
327
|
+
"PersistentProcessRegistry: periodic cleanup failed",
|
|
328
|
+
err
|
|
329
|
+
);
|
|
330
|
+
}).finally(() => {
|
|
331
|
+
this.cleanupRunning = false;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
/**
|
|
335
|
+
* Sync this instance's processes to the persistent registry.
|
|
336
|
+
*/
|
|
337
|
+
async syncToPersistent() {
|
|
338
|
+
const release = await acquireLock(this.lockPath);
|
|
339
|
+
try {
|
|
340
|
+
const data = await readRegistryFile(this.registryPath);
|
|
341
|
+
const now = Date.now();
|
|
342
|
+
const updatedInstances = /* @__PURE__ */ new Map();
|
|
343
|
+
for (const [_pidStr, entry] of data.instances) {
|
|
344
|
+
if (entry.instanceId === this.instanceId) {
|
|
345
|
+
entry.lastHeartbeat = now;
|
|
346
|
+
}
|
|
347
|
+
if (entry.instanceId === this.instanceId || now - entry.lastHeartbeat < STALE_THRESHOLD_MS) {
|
|
348
|
+
updatedInstances.set(_pidStr, entry);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
data.instances = updatedInstances;
|
|
352
|
+
data.lastCleanup = now;
|
|
353
|
+
await writeRegistryFile(this.registryPath, data);
|
|
354
|
+
} catch (err) {
|
|
355
|
+
emitStructuredLog(
|
|
356
|
+
"warn",
|
|
357
|
+
"process_registry.sync_failed",
|
|
358
|
+
"PersistentProcessRegistry: sync failed",
|
|
359
|
+
err
|
|
360
|
+
);
|
|
361
|
+
} finally {
|
|
362
|
+
await release();
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
/**
|
|
366
|
+
* Remove entries for processes that are no longer running.
|
|
367
|
+
*/
|
|
368
|
+
async cleanupStaleEntries() {
|
|
369
|
+
const release = await acquireLock(this.lockPath);
|
|
370
|
+
try {
|
|
371
|
+
const data = await readRegistryFile(this.registryPath);
|
|
372
|
+
const now = Date.now();
|
|
373
|
+
const stalePids = [];
|
|
374
|
+
for (const [_pidStr, entry] of data.instances) {
|
|
375
|
+
const age = now - entry.lastHeartbeat;
|
|
376
|
+
if (age > STALE_THRESHOLD_MS) {
|
|
377
|
+
try {
|
|
378
|
+
if (process.platform !== "win32") {
|
|
379
|
+
process.kill(entry.pid, 0);
|
|
380
|
+
} else {
|
|
381
|
+
emitStructuredLog(
|
|
382
|
+
"debug",
|
|
383
|
+
"process_registry.stale_pid_check",
|
|
384
|
+
`PersistentProcessRegistry: checking stale pid ${entry.pid} (${age}ms old)`
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
} catch {
|
|
388
|
+
stalePids.push(_pidStr);
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (stalePids.length > 0) {
|
|
393
|
+
for (const pidStr of stalePids) {
|
|
394
|
+
data.instances.delete(pidStr);
|
|
395
|
+
}
|
|
396
|
+
await writeRegistryFile(this.registryPath, data);
|
|
397
|
+
}
|
|
398
|
+
} catch (err) {
|
|
399
|
+
emitStructuredLog(
|
|
400
|
+
"warn",
|
|
401
|
+
"process_registry.cleanup_failed",
|
|
402
|
+
"PersistentProcessRegistry: cleanup failed",
|
|
403
|
+
err
|
|
404
|
+
);
|
|
405
|
+
} finally {
|
|
406
|
+
await release();
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
/**
|
|
410
|
+
* Check if a PID belongs to a WrongStack process and should be protected.
|
|
411
|
+
*/
|
|
412
|
+
async isProtectedPid(pid) {
|
|
413
|
+
const release = await acquireLock(this.lockPath);
|
|
414
|
+
try {
|
|
415
|
+
const data = await readRegistryFile(this.registryPath);
|
|
416
|
+
const entry = data.instances.get(String(pid));
|
|
417
|
+
if (!entry) return false;
|
|
418
|
+
if (Date.now() - entry.lastHeartbeat > STALE_THRESHOLD_MS) {
|
|
419
|
+
return false;
|
|
420
|
+
}
|
|
421
|
+
return entry.protected;
|
|
422
|
+
} finally {
|
|
423
|
+
await release();
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
/**
|
|
427
|
+
* Get all protected PIDs from all WrongStack instances.
|
|
428
|
+
*/
|
|
429
|
+
async getAllProtectedPids() {
|
|
430
|
+
const release = await acquireLock(this.lockPath);
|
|
431
|
+
try {
|
|
432
|
+
const data = await readRegistryFile(this.registryPath);
|
|
433
|
+
const now = Date.now();
|
|
434
|
+
const protectedPids = [];
|
|
435
|
+
for (const [_pidStr, entry] of data.instances) {
|
|
436
|
+
if (entry.protected && now - entry.lastHeartbeat < STALE_THRESHOLD_MS) {
|
|
437
|
+
protectedPids.push(entry.pid);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return protectedPids;
|
|
441
|
+
} finally {
|
|
442
|
+
await release();
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
/**
|
|
446
|
+
* Get complete status of all tracked processes across all instances.
|
|
447
|
+
*/
|
|
448
|
+
async getGlobalStatus() {
|
|
449
|
+
const release = await acquireLock(this.lockPath);
|
|
450
|
+
try {
|
|
451
|
+
const data = await readRegistryFile(this.registryPath);
|
|
452
|
+
const now = Date.now();
|
|
453
|
+
const instances = /* @__PURE__ */ new Map();
|
|
454
|
+
let protectedCount = 0;
|
|
455
|
+
let staleCount = 0;
|
|
456
|
+
for (const [_pidStr, entry] of data.instances) {
|
|
457
|
+
const instanceEntries = instances.get(entry.instanceId) ?? [];
|
|
458
|
+
instanceEntries.push(entry);
|
|
459
|
+
instances.set(entry.instanceId, instanceEntries);
|
|
460
|
+
if (entry.protected) protectedCount++;
|
|
461
|
+
if (now - entry.lastHeartbeat > STALE_THRESHOLD_MS) staleCount++;
|
|
462
|
+
}
|
|
463
|
+
return {
|
|
464
|
+
instances,
|
|
465
|
+
totalProcesses: data.instances.size,
|
|
466
|
+
protectedCount,
|
|
467
|
+
staleCount
|
|
468
|
+
};
|
|
469
|
+
} finally {
|
|
470
|
+
await release();
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Get the instance ID for this process.
|
|
475
|
+
*/
|
|
476
|
+
getInstanceId() {
|
|
477
|
+
return this.instanceId;
|
|
478
|
+
}
|
|
479
|
+
/**
|
|
480
|
+
* Check if a kill command should be blocked.
|
|
481
|
+
* Returns true if the kill should be blocked (target is a WrongStack process).
|
|
482
|
+
*/
|
|
483
|
+
async shouldBlockKill(pid) {
|
|
484
|
+
const protectedPids = await this.getAllProtectedPids();
|
|
485
|
+
return protectedPids.includes(pid);
|
|
486
|
+
}
|
|
487
|
+
/**
|
|
488
|
+
* Add a pattern-based protection rule.
|
|
489
|
+
* Processes whose command matches any protected pattern are protected.
|
|
490
|
+
*/
|
|
491
|
+
async addProtectedPattern(pattern) {
|
|
492
|
+
const release = await acquireLock(this.lockPath);
|
|
493
|
+
try {
|
|
494
|
+
const data = await readRegistryFile(this.registryPath);
|
|
495
|
+
if (!data.protectedPatterns.includes(pattern)) {
|
|
496
|
+
data.protectedPatterns.push(pattern);
|
|
497
|
+
await writeRegistryFile(this.registryPath, data);
|
|
498
|
+
}
|
|
499
|
+
} finally {
|
|
500
|
+
await release();
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
var _persistentRegistry;
|
|
505
|
+
function getPersistentProcessRegistry() {
|
|
506
|
+
if (!_persistentRegistry) {
|
|
507
|
+
_persistentRegistry = new PersistentProcessRegistry();
|
|
508
|
+
}
|
|
509
|
+
return _persistentRegistry;
|
|
510
|
+
}
|
|
511
|
+
function resetPersistentProcessRegistry() {
|
|
512
|
+
if (_persistentRegistry) {
|
|
513
|
+
_persistentRegistry.stop();
|
|
514
|
+
_persistentRegistry = void 0;
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
export {
|
|
519
|
+
getPersistentProcessRegistry,
|
|
520
|
+
resetPersistentProcessRegistry
|
|
521
|
+
};
|
|
522
|
+
//# sourceMappingURL=chunk-NKNOBUBU.js.map
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
tryLegacyCodeOperation
|
|
3
|
+
} from "./chunk-PQKK5JH7.js";
|
|
4
|
+
import {
|
|
5
|
+
spawnStream
|
|
6
|
+
} from "./chunk-22B7GE5T.js";
|
|
7
|
+
import {
|
|
8
|
+
normalizeCommandOutput,
|
|
9
|
+
safeResolveReal
|
|
10
|
+
} from "./chunk-D33S4RQG.js";
|
|
11
|
+
|
|
12
|
+
// src/lint.ts
|
|
13
|
+
var lintTool = {
|
|
14
|
+
name: "lint",
|
|
15
|
+
category: "Code Quality",
|
|
16
|
+
description: "Run the project linter (primarily Biome in this repo). Detects style violations, potential bugs, and formatting issues.",
|
|
17
|
+
usageHint: "RUN OFTEN DURING DEVELOPMENT:\n\n- `fix: true` will automatically correct what it can.\n- Target specific files or globs when you only want to check part of the project.\nThis is a fast and important quality gate. Use it before typecheck in most workflows.",
|
|
18
|
+
permission: "confirm",
|
|
19
|
+
mutating: false,
|
|
20
|
+
timeoutMs: 6e4,
|
|
21
|
+
capabilities: ["shell.restricted"],
|
|
22
|
+
icon: "code",
|
|
23
|
+
inputSchema: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
files: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: 'Files/patterns: single path, comma-separated list, or glob (e.g. "src/**/*.ts")'
|
|
29
|
+
},
|
|
30
|
+
fix: { type: "boolean", description: "Auto-fix fixable issues (default: false)" },
|
|
31
|
+
linter: {
|
|
32
|
+
type: "string",
|
|
33
|
+
enum: ["biome", "eslint", "tslint", "auto"],
|
|
34
|
+
description: "Linter to use (default: auto-detect)"
|
|
35
|
+
},
|
|
36
|
+
cwd: { type: "string", description: "Working directory (default: cwd)" }
|
|
37
|
+
}
|
|
38
|
+
},
|
|
39
|
+
async execute(input, ctx, opts) {
|
|
40
|
+
let final;
|
|
41
|
+
const executeStream = lintTool.executeStream;
|
|
42
|
+
if (!executeStream) throw new Error("lintTool: stream execution unavailable");
|
|
43
|
+
for await (const ev of executeStream(input, ctx, opts)) {
|
|
44
|
+
if (ev.type === "final") final = ev.output;
|
|
45
|
+
}
|
|
46
|
+
if (!final) throw new Error("lint: stream ended without final event");
|
|
47
|
+
return final;
|
|
48
|
+
},
|
|
49
|
+
async *executeStream(input, ctx, opts) {
|
|
50
|
+
const cwd = input.cwd ? await safeResolveReal(input.cwd, ctx) : ctx.cwd;
|
|
51
|
+
const linter = input.linter ?? "auto";
|
|
52
|
+
if (linter === "auto" && !input.files) {
|
|
53
|
+
const bridge = await tryLegacyCodeOperation("lint", {
|
|
54
|
+
cwd,
|
|
55
|
+
projectRoot: ctx.projectRoot,
|
|
56
|
+
signal: opts.signal
|
|
57
|
+
});
|
|
58
|
+
if (bridge?.run) {
|
|
59
|
+
const run = bridge.run;
|
|
60
|
+
yield {
|
|
61
|
+
type: "final",
|
|
62
|
+
output: {
|
|
63
|
+
linter: bridge.language,
|
|
64
|
+
files_checked: 0,
|
|
65
|
+
errors: run.summary.errors,
|
|
66
|
+
warnings: run.summary.warnings,
|
|
67
|
+
output: normalizeCommandOutput(run.output || run.error || ""),
|
|
68
|
+
fix_applied: false,
|
|
69
|
+
truncated: run.truncated
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
const detected = linter === "auto" ? await detectLinter(cwd) : linter;
|
|
76
|
+
if (!detected) {
|
|
77
|
+
yield {
|
|
78
|
+
type: "final",
|
|
79
|
+
output: {
|
|
80
|
+
linter: "none",
|
|
81
|
+
files_checked: 0,
|
|
82
|
+
errors: 0,
|
|
83
|
+
warnings: 0,
|
|
84
|
+
output: "No linter found (biome.json, .eslintrc, tslint.json)",
|
|
85
|
+
fix_applied: false,
|
|
86
|
+
truncated: false
|
|
87
|
+
}
|
|
88
|
+
};
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
yield { type: "log", text: `Running ${detected}\u2026`, data: { linter: detected } };
|
|
92
|
+
const args = ["lint"];
|
|
93
|
+
if (input.fix) args.push("--write");
|
|
94
|
+
if (input.files) {
|
|
95
|
+
const files = Array.isArray(input.files) ? input.files : input.files.split(",");
|
|
96
|
+
args.push("--", ...files.map((f) => f.trim()));
|
|
97
|
+
}
|
|
98
|
+
const cmd = detected === "biome" ? "biome" : detected;
|
|
99
|
+
const result = yield* spawnStream({ cmd, args, cwd, signal: opts.signal, maxBytes: 1e5 });
|
|
100
|
+
const errors = [...result.stdout.matchAll(/\berror\b/gi)].length;
|
|
101
|
+
const warnings = [...result.stdout.matchAll(/\bwarning\b/gi)].length;
|
|
102
|
+
yield {
|
|
103
|
+
type: "final",
|
|
104
|
+
output: {
|
|
105
|
+
linter: detected,
|
|
106
|
+
files_checked: input.files ? Array.isArray(input.files) ? input.files.length : input.files.split(",").length : 0,
|
|
107
|
+
errors,
|
|
108
|
+
warnings,
|
|
109
|
+
output: normalizeCommandOutput(result.stdout),
|
|
110
|
+
fix_applied: input.fix ?? false,
|
|
111
|
+
truncated: result.truncated
|
|
112
|
+
}
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
async function detectLinter(cwd) {
|
|
117
|
+
const { stat } = await import("node:fs/promises");
|
|
118
|
+
const checks = ["biome.json", ".eslintrc.json", "tslint.json", ".eslintrc.js", "tsconfig.json"];
|
|
119
|
+
for (const f of checks) {
|
|
120
|
+
try {
|
|
121
|
+
await stat(`${cwd}/${f}`);
|
|
122
|
+
if (f.includes("biome")) return "biome";
|
|
123
|
+
if (f.includes("eslint")) return "eslint";
|
|
124
|
+
if (f.includes("tslint")) return "tslint";
|
|
125
|
+
} catch {
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return "biome";
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export {
|
|
132
|
+
lintTool
|
|
133
|
+
};
|
|
134
|
+
//# sourceMappingURL=chunk-O4MBV4AX.js.map
|