howtofixcli 0.7.13 → 0.7.15
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/chunk-27TRKD65.js +2 -0
- package/dist/chunk-3FYCFLNK.js +1 -0
- package/dist/chunk-56SEAGAQ.js +1 -0
- package/dist/chunk-6FUWMLWO.js +1 -0
- package/dist/chunk-7CHDNTH6.js +1 -0
- package/dist/chunk-7DTQKDO4.js +2 -0
- package/dist/chunk-AGGIYXN7.js +2 -0
- package/dist/chunk-AUDMRGWF.js +189 -0
- package/dist/chunk-CPJKKHGH.js +1 -0
- package/dist/chunk-DAPXGSGD.js +12 -0
- package/dist/chunk-DOD7MUGX.js +1 -0
- package/dist/chunk-DV2BBBBJ.js +2 -0
- package/dist/chunk-EIBOS5KO.js +5 -0
- package/dist/chunk-EYWOVCSS.js +6 -0
- package/dist/chunk-FPXVDP32.js +8 -0
- package/dist/chunk-GXB7RZHH.js +1 -0
- package/dist/chunk-HT3MFLAV.js +34 -0
- package/dist/chunk-ILDDDLRC.js +1 -0
- package/dist/chunk-JFYVRWJI.js +2 -0
- package/dist/chunk-JMKK26AT.js +2 -0
- package/dist/chunk-LAHVHRCS.js +2 -0
- package/dist/chunk-LOOFOBTS.js +1 -0
- package/dist/chunk-LSJC73DG.js +1 -0
- package/dist/chunk-MOKUBKE7.js +1 -0
- package/dist/chunk-N345KWWK.js +1 -0
- package/dist/chunk-ONZLNFQ2.js +3 -0
- package/dist/chunk-ORJ2QZBG.js +3 -0
- package/dist/chunk-PCALBZOO.js +2 -0
- package/dist/chunk-SX5VE6XA.js +110 -0
- package/dist/chunk-TSEKPGK4.js +1 -0
- package/dist/chunk-UNK5HRQR.js +1 -0
- package/dist/chunk-UTYFLLSM.js +1 -0
- package/dist/chunk-V2RPZMLK.js +1 -0
- package/dist/chunk-V3R4FYTJ.js +1 -0
- package/dist/chunk-V656XVXV.js +8 -0
- package/dist/chunk-VLMQSD25.js +1 -0
- package/dist/chunk-WKUJ7CFO.js +1 -0
- package/dist/chunk-WOT6VMZA.js +1 -0
- package/dist/chunk-WUPU6LTD.js +4 -0
- package/dist/chunk-WX3V5P7U.js +5 -0
- package/dist/chunk-Y7YM5R75.js +5 -0
- package/dist/chunk-YW7NORH3.js +2 -0
- package/dist/main.js +99 -540
- package/package.json +17 -64
- package/dist/account-credential.js +0 -136
- package/dist/account-credential.test.js +0 -38
- package/dist/activity.js +0 -124
- package/dist/agent-defs.js +0 -237
- package/dist/agent-limiter.js +0 -68
- package/dist/agents.js +0 -554
- package/dist/anim.js +0 -361
- package/dist/args.js +0 -228
- package/dist/at-file.js +0 -28
- package/dist/brand-extension.js +0 -87
- package/dist/brand.js +0 -76
- package/dist/browser.js +0 -58
- package/dist/cache-report.js +0 -139
- package/dist/checkpoint.js +0 -437
- package/dist/claims.js +0 -167
- package/dist/cli-auth.js +0 -142
- package/dist/cli-checkin.js +0 -120
- package/dist/cli-credentials.js +0 -254
- package/dist/clipboard.js +0 -149
- package/dist/collapse.js +0 -129
- package/dist/commands.js +0 -294
- package/dist/compaction-guard.js +0 -81
- package/dist/context-extension.js +0 -17
- package/dist/dcp/commands.js +0 -172
- package/dist/dcp/compress-state.js +0 -139
- package/dist/dcp/compress.js +0 -329
- package/dist/dcp/dcp-extension.js +0 -295
- package/dist/dcp/dedup.js +0 -93
- package/dist/dcp/ids.js +0 -189
- package/dist/dcp/index.js +0 -25
- package/dist/dcp/middle-prune.js +0 -76
- package/dist/dcp/prompts.js +0 -68
- package/dist/dcp/protected-content.js +0 -221
- package/dist/dcp/protected.js +0 -161
- package/dist/dcp/prune.js +0 -418
- package/dist/dcp/state.js +0 -176
- package/dist/device-id.js +0 -90
- package/dist/device-id.test.js +0 -23
- package/dist/errors.js +0 -63
- package/dist/extensions.js +0 -48
- package/dist/fatal.js +0 -35
- package/dist/feed.js +0 -498
- package/dist/fs-atomic.js +0 -54
- package/dist/gateway-errors.js +0 -90
- package/dist/git.js +0 -230
- package/dist/goal-extension.js +0 -88
- package/dist/goal.js +0 -222
- package/dist/hooks-extension.js +0 -176
- package/dist/hooks.js +0 -443
- package/dist/howtofix-account.js +0 -735
- package/dist/hud-line.js +0 -153
- package/dist/hwid.js +0 -130
- package/dist/interrupt.js +0 -60
- package/dist/json-events.js +0 -50
- package/dist/level-style.js +0 -112
- package/dist/loop-guard.js +0 -95
- package/dist/lsp-tools.js +0 -32
- package/dist/lsp.js +0 -429
- package/dist/mcp-config.js +0 -85
- package/dist/mcp-lifecycle.js +0 -147
- package/dist/model-catalog.js +0 -217
- package/dist/model-facts.js +0 -35
- package/dist/model-resolve.js +0 -65
- package/dist/notes.js +0 -78
- package/dist/oauth-login.js +0 -568
- package/dist/paths.js +0 -61
- package/dist/permissions.js +0 -298
- package/dist/plan-mode.js +0 -363
- package/dist/preset-command.js +0 -227
- package/dist/preset-extension.js +0 -35
- package/dist/preset-mcp.js +0 -292
- package/dist/presets.js +0 -727
- package/dist/prompt-files.js +0 -103
- package/dist/prompt-text.js +0 -41
- package/dist/proxy.js +0 -137
- package/dist/quality/ast.js +0 -196
- package/dist/quality/cognitive.js +0 -83
- package/dist/quality/cyclomatic.js +0 -30
- package/dist/quality/halstead.js +0 -83
- package/dist/quality/index.js +0 -18
- package/dist/quality/length.js +0 -3
- package/dist/quality/maintainability.js +0 -15
- package/dist/quality/nesting.js +0 -57
- package/dist/quality/report.js +0 -803
- package/dist/quality/smells.js +0 -67
- package/dist/quality-tool.js +0 -63
- package/dist/read-only-commands.js +0 -597
- package/dist/repo-map.js +0 -292
- package/dist/rtk.js +0 -193
- package/dist/sandbox.js +0 -105
- package/dist/schema.js +0 -214
- package/dist/session-lifecycle.js +0 -19
- package/dist/session-retention.js +0 -111
- package/dist/session.js +0 -447
- package/dist/sessions.js +0 -100
- package/dist/settings.js +0 -148
- package/dist/skills.js +0 -35
- package/dist/slop-tool.js +0 -96
- package/dist/spend-cap.js +0 -43
- package/dist/subagent.js +0 -171
- package/dist/tar.js +0 -146
- package/dist/theme.js +0 -79
- package/dist/thinking.js +0 -45
- package/dist/todo.js +0 -114
- package/dist/tool-loadout.js +0 -97
- package/dist/tool-render.js +0 -769
- package/dist/tools.js +0 -176
- package/dist/ui.js +0 -4778
- package/dist/update.js +0 -551
- package/dist/usage-segment.js +0 -69
- package/dist/web-tools.js +0 -46
- package/dist/web.js +0 -265
- package/dist/welcome.js +0 -179
- package/dist/workflow-engine.js +0 -246
- package/dist/workflow-progress.js +0 -446
- package/dist/workflow-view.js +0 -195
- package/dist/workflow.js +0 -349
package/dist/schema.js
DELETED
|
@@ -1,214 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* A small JSON Schema validator, sufficient for the `schema` option of
|
|
3
|
-
* `agent()` in a workflow script.
|
|
4
|
-
*
|
|
5
|
-
* Why not a library: `ajv` is not installed and the SDK does not export a
|
|
6
|
-
* validator, so a dependency would have to be vendored into a repo whose rule is
|
|
7
|
-
* not to re-vendor the SDK. The supported subset is the one the workflow spec names
|
|
8
|
-
* for structured output — `type`, `properties`, `required`, `items`, `enum`,
|
|
9
|
-
* `anyOf`/`oneOf`/`allOf`, `$defs`/`definitions`, `additionalProperties` — which
|
|
10
|
-
* is small enough to implement honestly, and the failure messages matter more
|
|
11
|
-
* than breadth here: they are fed back to the model as a repair turn.
|
|
12
|
-
*
|
|
13
|
-
* Validation is intentionally permissive about keywords it does not understand
|
|
14
|
-
* (they are ignored rather than failing the value), because the contract is that
|
|
15
|
-
* a reply validating against the authored schema is accepted.
|
|
16
|
-
*/
|
|
17
|
-
const isObject = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
18
|
-
function typeOf(value) {
|
|
19
|
-
if (value === null)
|
|
20
|
-
return "null";
|
|
21
|
-
if (Array.isArray(value))
|
|
22
|
-
return "array";
|
|
23
|
-
if (Number.isInteger(value))
|
|
24
|
-
return "integer";
|
|
25
|
-
return typeof value;
|
|
26
|
-
}
|
|
27
|
-
function matchesType(value, want) {
|
|
28
|
-
const actual = typeOf(value);
|
|
29
|
-
if (want === "number")
|
|
30
|
-
return actual === "number" || actual === "integer";
|
|
31
|
-
if (want === "integer")
|
|
32
|
-
return actual === "integer";
|
|
33
|
-
return actual === want;
|
|
34
|
-
}
|
|
35
|
-
/** Resolve a `$ref` against the document root (`#/$defs/name` or `#/definitions/name`). */
|
|
36
|
-
function resolveRef(root, ref) {
|
|
37
|
-
if (typeof ref !== "string" || !ref.startsWith("#/"))
|
|
38
|
-
return undefined;
|
|
39
|
-
let node = root;
|
|
40
|
-
for (const rawPart of ref.slice(2).split("/")) {
|
|
41
|
-
const part = rawPart.replace(/~1/g, "/").replace(/~0/g, "~");
|
|
42
|
-
if (!isObject(node) && !Array.isArray(node))
|
|
43
|
-
return undefined;
|
|
44
|
-
node = node[part];
|
|
45
|
-
}
|
|
46
|
-
return isObject(node) ? node : undefined;
|
|
47
|
-
}
|
|
48
|
-
function validateNode(value, schema, root, path, issues) {
|
|
49
|
-
if (!isObject(schema))
|
|
50
|
-
return;
|
|
51
|
-
if (schema.$ref !== undefined) {
|
|
52
|
-
const target = resolveRef(root, schema.$ref);
|
|
53
|
-
if (target)
|
|
54
|
-
validateNode(value, target, root, path, issues);
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
// Combinators. `allOf` must hold everywhere; `anyOf`/`oneOf` need a passing branch.
|
|
58
|
-
if (Array.isArray(schema.allOf)) {
|
|
59
|
-
for (const branch of schema.allOf)
|
|
60
|
-
validateNode(value, branch, root, path, issues);
|
|
61
|
-
}
|
|
62
|
-
for (const keyword of ["anyOf", "oneOf"]) {
|
|
63
|
-
const branches = schema[keyword];
|
|
64
|
-
if (!Array.isArray(branches) || branches.length === 0)
|
|
65
|
-
continue;
|
|
66
|
-
const passes = branches.filter((branch) => {
|
|
67
|
-
const scratch = [];
|
|
68
|
-
validateNode(value, branch, root, path, scratch);
|
|
69
|
-
return scratch.length === 0;
|
|
70
|
-
}).length;
|
|
71
|
-
if (keyword === "anyOf" && passes === 0)
|
|
72
|
-
issues.push({ path, message: "does not match any allowed shape" });
|
|
73
|
-
if (keyword === "oneOf" && passes !== 1)
|
|
74
|
-
issues.push({ path, message: `must match exactly one shape (matched ${passes})` });
|
|
75
|
-
}
|
|
76
|
-
const declaredType = schema.type;
|
|
77
|
-
if (declaredType !== undefined) {
|
|
78
|
-
const wanted = Array.isArray(declaredType) ? declaredType.map(String) : [String(declaredType)];
|
|
79
|
-
if (!wanted.some((want) => matchesType(value, want))) {
|
|
80
|
-
issues.push({ path, message: `expected ${wanted.join(" or ")}, got ${typeOf(value)}` });
|
|
81
|
-
return; // A wrong type makes the remaining checks noise.
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
if (Array.isArray(schema.enum) && !schema.enum.some((option) => JSON.stringify(option) === JSON.stringify(value))) {
|
|
85
|
-
issues.push({ path, message: `must be one of ${schema.enum.map((option) => JSON.stringify(option)).join(", ")}` });
|
|
86
|
-
}
|
|
87
|
-
if (isObject(value)) {
|
|
88
|
-
const properties = isObject(schema.properties) ? schema.properties : {};
|
|
89
|
-
if (Array.isArray(schema.required)) {
|
|
90
|
-
for (const key of schema.required.map(String)) {
|
|
91
|
-
if (!(key in value))
|
|
92
|
-
issues.push({ path: path ? `${path}.${key}` : key, message: "is required" });
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
for (const [key, propertySchema] of Object.entries(properties)) {
|
|
96
|
-
if (key in value)
|
|
97
|
-
validateNode(value[key], propertySchema, root, path ? `${path}.${key}` : key, issues);
|
|
98
|
-
}
|
|
99
|
-
if (schema.additionalProperties === false) {
|
|
100
|
-
for (const key of Object.keys(value)) {
|
|
101
|
-
if (!(key in properties))
|
|
102
|
-
issues.push({ path: path ? `${path}.${key}` : key, message: "is not an allowed property" });
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
else if (isObject(schema.additionalProperties)) {
|
|
106
|
-
for (const [key, entry] of Object.entries(value)) {
|
|
107
|
-
if (!(key in properties))
|
|
108
|
-
validateNode(entry, schema.additionalProperties, root, path ? `${path}.${key}` : key, issues);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
if (Array.isArray(value) && schema.items !== undefined) {
|
|
113
|
-
const items = schema.items;
|
|
114
|
-
if (Array.isArray(items)) {
|
|
115
|
-
// Tuple form: validate positionally.
|
|
116
|
-
value.forEach((entry, index) => {
|
|
117
|
-
if (index < items.length)
|
|
118
|
-
validateNode(entry, items[index], root, `${path}[${index}]`, issues);
|
|
119
|
-
});
|
|
120
|
-
}
|
|
121
|
-
else {
|
|
122
|
-
value.forEach((entry, index) => validateNode(entry, items, root, `${path}[${index}]`, issues));
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
if (typeof value === "string") {
|
|
126
|
-
if (typeof schema.minLength === "number" && value.length < schema.minLength) {
|
|
127
|
-
issues.push({ path, message: `is shorter than ${schema.minLength} characters` });
|
|
128
|
-
}
|
|
129
|
-
if (typeof schema.pattern === "string") {
|
|
130
|
-
try {
|
|
131
|
-
if (!new RegExp(schema.pattern).test(value))
|
|
132
|
-
issues.push({ path, message: `does not match ${schema.pattern}` });
|
|
133
|
-
}
|
|
134
|
-
catch {
|
|
135
|
-
// An invalid pattern is the schema author's problem, not the value's.
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
if (typeof value === "number") {
|
|
140
|
-
if (typeof schema.minimum === "number" && value < schema.minimum)
|
|
141
|
-
issues.push({ path, message: `is below ${schema.minimum}` });
|
|
142
|
-
if (typeof schema.maximum === "number" && value > schema.maximum)
|
|
143
|
-
issues.push({ path, message: `is above ${schema.maximum}` });
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
/** Every way `value` fails `schema`. Empty means it validates. */
|
|
147
|
-
export function validateSchema(value, schema) {
|
|
148
|
-
if (!isObject(schema))
|
|
149
|
-
return [];
|
|
150
|
-
const issues = [];
|
|
151
|
-
validateNode(value, schema, schema, "", issues);
|
|
152
|
-
return issues;
|
|
153
|
-
}
|
|
154
|
-
/** One line per issue, for a repair turn. */
|
|
155
|
-
export function formatIssues(issues) {
|
|
156
|
-
return issues.map((issue) => `- ${issue.path || "(root)"}: ${issue.message}`).join("\n");
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* Pull a JSON value out of a model reply.
|
|
160
|
-
*
|
|
161
|
-
* Models wrap JSON in prose or fences often enough that a bare `JSON.parse` is
|
|
162
|
-
* not a fair test of whether they followed the schema. Only the first complete
|
|
163
|
-
* value is taken; anything before or after it is ignored.
|
|
164
|
-
*/
|
|
165
|
-
export function extractJson(text) {
|
|
166
|
-
const trimmed = text.trim();
|
|
167
|
-
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(trimmed);
|
|
168
|
-
const candidates = [trimmed, fenced?.[1]?.trim()].filter((candidate) => Boolean(candidate));
|
|
169
|
-
for (const candidate of candidates) {
|
|
170
|
-
try {
|
|
171
|
-
return JSON.parse(candidate);
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
/* try to find a balanced value inside */
|
|
175
|
-
}
|
|
176
|
-
for (let start = 0; start < candidate.length; start++) {
|
|
177
|
-
const opener = candidate[start];
|
|
178
|
-
if (opener !== "{" && opener !== "[")
|
|
179
|
-
continue;
|
|
180
|
-
const closer = opener === "{" ? "}" : "]";
|
|
181
|
-
let depth = 0;
|
|
182
|
-
let inString = false;
|
|
183
|
-
let escaped = false;
|
|
184
|
-
for (let index = start; index < candidate.length; index++) {
|
|
185
|
-
const char = candidate[index];
|
|
186
|
-
if (inString) {
|
|
187
|
-
if (escaped)
|
|
188
|
-
escaped = false;
|
|
189
|
-
else if (char === "\\")
|
|
190
|
-
escaped = true;
|
|
191
|
-
else if (char === '"')
|
|
192
|
-
inString = false;
|
|
193
|
-
continue;
|
|
194
|
-
}
|
|
195
|
-
if (char === '"')
|
|
196
|
-
inString = true;
|
|
197
|
-
else if (char === opener)
|
|
198
|
-
depth++;
|
|
199
|
-
else if (char === closer) {
|
|
200
|
-
depth--;
|
|
201
|
-
if (depth === 0) {
|
|
202
|
-
try {
|
|
203
|
-
return JSON.parse(candidate.slice(start, index + 1));
|
|
204
|
-
}
|
|
205
|
-
catch {
|
|
206
|
-
break;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
return undefined;
|
|
214
|
-
}
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Close extension-owned resources before disposing an agent session.
|
|
3
|
-
*
|
|
4
|
-
* AgentSession.dispose() disconnects the agent itself, but extension resources
|
|
5
|
-
* (including MCP transports) are released by the session_shutdown event. The
|
|
6
|
-
* SDK exposes the runner so callers can deliver that lifecycle event even when
|
|
7
|
-
* a session is replaced outside the interactive mode.
|
|
8
|
-
*
|
|
9
|
-
* The returned promise settles once the shutdown has been delivered and the
|
|
10
|
-
* session is disposed. Callers must await it: dropping the promise leaves the
|
|
11
|
-
* MCP processes and tool registrations alive after the configuration that
|
|
12
|
-
* created them is gone.
|
|
13
|
-
*/
|
|
14
|
-
export function disposeAgentSession(session) {
|
|
15
|
-
return session.extensionRunner
|
|
16
|
-
.emit({ type: "session_shutdown", reason: "quit" })
|
|
17
|
-
.catch(() => undefined)
|
|
18
|
-
.finally(() => session.dispose());
|
|
19
|
-
}
|
|
@@ -1,111 +0,0 @@
|
|
|
1
|
-
import { readdir, rmdir, stat, unlink } from "node:fs/promises";
|
|
2
|
-
import { dirname, join, resolve } from "node:path";
|
|
3
|
-
import { RETENTION_DAYS } from "./checkpoint.js";
|
|
4
|
-
import { AGENT_DIR } from "./paths.js";
|
|
5
|
-
/**
|
|
6
|
-
* Unlink session files idle for longer than `RETENTION_DAYS`, across every
|
|
7
|
-
* project directory — the store's whole problem is that it is per-cwd and
|
|
8
|
-
* nothing ever looked outside the current one.
|
|
9
|
-
*
|
|
10
|
-
* The SDK writes `<agentDir>/sessions/--<encoded-cwd>--/<timestamp>_<id>.jsonl`
|
|
11
|
-
* and never removes one: `SessionManager` only appends, the TUI's delete is a
|
|
12
|
-
* single user-chosen session (and prefers `trash`), and the CLI had no sweep at
|
|
13
|
-
* all, so a machine that uses the product for a year keeps a transcript of every
|
|
14
|
-
* folder it was ever pointed at. Rewind's shadow store already sweeps at 30 days
|
|
15
|
-
* (checkpoint.ts, `RETENTION_DAYS`) — same window here on purpose, so "old" means
|
|
16
|
-
* one thing across both stores and a session whose snapshots are gone does not
|
|
17
|
-
* linger as a transcript.
|
|
18
|
-
*
|
|
19
|
-
* Age is the file's mtime, not the timestamp in its name: a session resumed last
|
|
20
|
-
* week was written last week and must survive, while its filename still says the
|
|
21
|
-
* day it was created. `keep` is the stronger rule on top of that — `--continue`
|
|
22
|
-
* and `/resume` exist to bring an old session back, so the file this process just
|
|
23
|
-
* opened is kept whatever its age.
|
|
24
|
-
*
|
|
25
|
-
* Housekeeping, so it never throws and never reports to the user: every step is
|
|
26
|
-
* per-file, an error only leaves the store larger than it needed to be. Files are
|
|
27
|
-
* unlinked rather than trashed — the TUI's `trash` path is a person deleting one
|
|
28
|
-
* session they chose, this is a bulk sweep they did not ask to watch.
|
|
29
|
-
*/
|
|
30
|
-
export async function sweepStaleSessions(options = {}) {
|
|
31
|
-
const result = { removed: [], failed: [], removedDirs: [] };
|
|
32
|
-
const root = join(options.agentDir ?? AGENT_DIR, "sessions");
|
|
33
|
-
const cutoff = (options.now ?? Date.now()) - RETENTION_DAYS * 24 * 60 * 60 * 1000;
|
|
34
|
-
const keep = options.keep ? resolve(options.keep) : undefined;
|
|
35
|
-
let projects;
|
|
36
|
-
try {
|
|
37
|
-
projects = await readdir(root, { withFileTypes: true });
|
|
38
|
-
}
|
|
39
|
-
catch {
|
|
40
|
-
// No store, or an unreadable one: nothing to sweep and nothing to say.
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
43
|
-
for (const project of projects) {
|
|
44
|
-
if (!project.isDirectory())
|
|
45
|
-
continue;
|
|
46
|
-
const dir = join(root, project.name);
|
|
47
|
-
let files;
|
|
48
|
-
try {
|
|
49
|
-
files = await readdir(dir);
|
|
50
|
-
}
|
|
51
|
-
catch {
|
|
52
|
-
continue;
|
|
53
|
-
}
|
|
54
|
-
// What will still be in the directory when the sweep is done. Sidecars a
|
|
55
|
-
// stale session left behind (`plan.md`, `plan_mode.json`) count and keep the
|
|
56
|
-
// directory on purpose: they are project state a later session of the same
|
|
57
|
-
// folder still restores.
|
|
58
|
-
const surviving = files.filter((file) => !file.endsWith(".jsonl"));
|
|
59
|
-
for (const file of files) {
|
|
60
|
-
if (!file.endsWith(".jsonl"))
|
|
61
|
-
continue;
|
|
62
|
-
const path = join(dir, file);
|
|
63
|
-
let stale;
|
|
64
|
-
try {
|
|
65
|
-
if (keep && resolve(path) === keep)
|
|
66
|
-
stale = false;
|
|
67
|
-
else {
|
|
68
|
-
// mtime, not the timestamp in the name: a session resumed last week
|
|
69
|
-
// was written last week, whatever its filename says.
|
|
70
|
-
stale = (await stat(path)).mtimeMs < cutoff;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
catch {
|
|
74
|
-
// Raced with another sweeper (two processes starting at once).
|
|
75
|
-
stale = false;
|
|
76
|
-
}
|
|
77
|
-
if (!stale) {
|
|
78
|
-
surviving.push(file);
|
|
79
|
-
continue;
|
|
80
|
-
}
|
|
81
|
-
try {
|
|
82
|
-
await unlink(path);
|
|
83
|
-
result.removed.push(path);
|
|
84
|
-
}
|
|
85
|
-
catch {
|
|
86
|
-
// A directory called `x.jsonl`, a permission wall, a file another
|
|
87
|
-
// process holds oddly: count it and keep going.
|
|
88
|
-
result.failed.push(path);
|
|
89
|
-
surviving.push(file);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
// Drop the per-project directory when nothing is left in it, so the encoded
|
|
93
|
-
// cwd names stop piling up too.
|
|
94
|
-
if (surviving.length === 0) {
|
|
95
|
-
// Never drop the directory the live session writes into. On a fresh start
|
|
96
|
-
// the session file does not exist yet, so the directory looks empty and
|
|
97
|
-
// the sweep removed it — the next prompt then failed with ENOENT because
|
|
98
|
-
// the writer's parent had been deleted underneath it.
|
|
99
|
-
if (keep && dirname(keep) === dir)
|
|
100
|
-
continue;
|
|
101
|
-
try {
|
|
102
|
-
await rmdir(dir);
|
|
103
|
-
result.removedDirs.push(dir);
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
// Non-empty after all, or racing a writer: the directory stays.
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
return result;
|
|
111
|
-
}
|