@cleocode/core 2026.9.2 → 2026.9.4
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/dispatch/suggested-next.d.ts.map +1 -1
- package/dist/dispatch/suggested-next.js +7 -1
- package/dist/dispatch/suggested-next.js.map +1 -1
- package/dist/docs/export-document.js +84 -0
- package/dist/docs/export-document.js.map +2 -2
- package/dist/hygiene/validate-spawn-readiness.d.ts +88 -4
- package/dist/hygiene/validate-spawn-readiness.d.ts.map +1 -1
- package/dist/hygiene/validate-spawn-readiness.js +138 -16
- package/dist/hygiene/validate-spawn-readiness.js.map +1 -1
- package/dist/injection.d.ts.map +1 -1
- package/dist/injection.js +10 -2
- package/dist/injection.js.map +1 -1
- package/dist/internal.d.ts +1 -1
- package/dist/internal.d.ts.map +1 -1
- package/dist/internal.js +1 -1
- package/dist/internal.js.map +1 -1
- package/dist/llm/plugin-facade.js +84 -0
- package/dist/llm/plugin-facade.js.map +2 -2
- package/dist/release/open.d.ts +11 -18
- package/dist/release/open.d.ts.map +1 -1
- package/dist/release/open.js +135 -7
- package/dist/release/open.js.map +1 -1
- package/dist/selfimprove/fix-gen.js +84 -0
- package/dist/selfimprove/fix-gen.js.map +2 -2
- package/dist/setup/sections/brain.d.ts +1 -1
- package/dist/setup/sections/brain.js +1 -1
- package/dist/system/bridge-mode.d.ts +1 -1
- package/dist/system/bridge-mode.js +1 -1
- package/dist/tasks/index.d.ts +1 -1
- package/dist/tasks/index.d.ts.map +1 -1
- package/dist/tasks/index.js +1 -1
- package/dist/tasks/index.js.map +1 -1
- package/dist/tasks/tool-cache.d.ts +134 -9
- package/dist/tasks/tool-cache.d.ts.map +1 -1
- package/dist/tasks/tool-cache.js +193 -36
- package/dist/tasks/tool-cache.js.map +1 -1
- package/package.json +13 -13
- package/templates/CLEO-INJECTION.md +6 -6
- package/templates/workflows/release-prepare.yml.tmpl +67 -2
- package/templates/workflows/release-publish.yml.tmpl +1 -1
- package/templates/workflows/release-rollback.yml.tmpl +18 -2
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Controls how BRAIN context is surfaced inside AGENTS.md by toggling
|
|
5
5
|
* `brain.memoryBridge.mode` across three operator-visible values:
|
|
6
6
|
*
|
|
7
|
-
* - `'digest'` — AGENTS.md gets a `cleo memory digest
|
|
7
|
+
* - `'digest'` — AGENTS.md gets a `cleo memory digest` directive
|
|
8
8
|
* (no `.cleo/memory-bridge.md` written). Wire value stays
|
|
9
9
|
* `'cli'` so existing project configs continue to resolve
|
|
10
10
|
* correctly; the wizard label was renamed to "digest" to
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Controls how BRAIN context is surfaced inside AGENTS.md by toggling
|
|
5
5
|
* `brain.memoryBridge.mode` across three operator-visible values:
|
|
6
6
|
*
|
|
7
|
-
* - `'digest'` — AGENTS.md gets a `cleo memory digest
|
|
7
|
+
* - `'digest'` — AGENTS.md gets a `cleo memory digest` directive
|
|
8
8
|
* (no `.cleo/memory-bridge.md` written). Wire value stays
|
|
9
9
|
* `'cli'` so existing project configs continue to resolve
|
|
10
10
|
* correctly; the wizard label was renamed to "digest" to
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* where one sibling silently ignored the gate.
|
|
8
8
|
*
|
|
9
9
|
* Behaviour:
|
|
10
|
-
* - `'cli'` — AGENTS.md gets a `cleo memory digest
|
|
10
|
+
* - `'cli'` — AGENTS.md gets a `cleo memory digest` directive; no `.md` files written.
|
|
11
11
|
* - `'file'` — legacy `@.cleo/memory-bridge.md` + `@.cleo/nexus-bridge.md` injection.
|
|
12
12
|
*
|
|
13
13
|
* The resolver never throws. On any read/parse failure it returns the safe
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* where one sibling silently ignored the gate.
|
|
8
8
|
*
|
|
9
9
|
* Behaviour:
|
|
10
|
-
* - `'cli'` — AGENTS.md gets a `cleo memory digest
|
|
10
|
+
* - `'cli'` — AGENTS.md gets a `cleo memory digest` directive; no `.md` files written.
|
|
11
11
|
* - `'file'` — legacy `@.cleo/memory-bridge.md` + `@.cleo/nexus-bridge.md` injection.
|
|
12
12
|
*
|
|
13
13
|
* The resolver never throws. On any read/parse failure it returns the safe
|
package/dist/tasks/index.d.ts
CHANGED
|
@@ -32,7 +32,7 @@ export { type AppendSeverityAttestationOptions, appendSignedSeverityAttestation,
|
|
|
32
32
|
export { showTask, type TaskDetail, taskExists, taskShow, taskShowIvtrHistory, taskShowOperation, taskShowWithHistory, } from './show.js';
|
|
33
33
|
export { taskSyncLinks, taskSyncLinksRemove, taskSyncReconcile } from './sync-ops.js';
|
|
34
34
|
export { taskAnalyze, taskBatchValidate, taskBlockers, taskCancel, taskClaim, taskComplexityEstimate, taskDepends, taskDeps, taskDepsCycles, taskDepsOverview, taskDepsTree, taskDepsValidate, taskExport, taskHistory, taskImpact, taskImport, taskLint, taskNext, taskPromote, taskRelates, taskRelatesAdd, taskRelatesFind, taskRelatesRemove, taskReopen, taskReorder, taskReparent, taskRestore, taskSlice, taskStats, taskTree, taskUnarchive, taskUnclaim, } from './task-ops.js';
|
|
35
|
-
export { cacheEntryPath, captureDirtyFingerprint, captureHead, clearToolCache, computeCacheKey, type RunToolOptions, readCacheEntry, runToolCached, type ToolCacheEntry, type ToolRunResult, writeCacheEntry, } from './tool-cache.js';
|
|
35
|
+
export { cacheEntryPath, captureDirtyFingerprint, captureHead, clearToolCache, computeCacheKey, isEntryUsable, type RunToolOptions, readCacheEntry, runToolCached, TOOL_RUN_IDENTITY_FIELDS, type ToolCacheEntry, type ToolRunIdentity, type ToolRunResult, writeCacheEntry, } from './tool-cache.js';
|
|
36
36
|
export { type CanonicalTool, listValidToolNames, type ResolutionSource, type ResolvedToolCommand, type ResolveToolResult, resolveToolCommand, } from './tool-resolver.js';
|
|
37
37
|
export { type AcquireSlotOptions, acquireGlobalSlot, defaultMaxConcurrent, isHolderAlive, listSlotHolders, type ReleaseSlotFn, readHolder, reapOrphanedSlots, reapSlotIfOrphaned, resolveMaxConcurrent, type SlotHolder, semaphoreDir, } from './tool-semaphore.js';
|
|
38
38
|
export { taskUpdate, type UpdateTaskOptions, type UpdateTaskResult, updateTask } from './update.js';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":"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"}
|
package/dist/tasks/index.js
CHANGED
|
@@ -42,7 +42,7 @@ export { showTask, taskExists, taskShow, taskShowIvtrHistory, taskShowOperation,
|
|
|
42
42
|
export { taskSyncLinks, taskSyncLinksRemove, taskSyncReconcile } from './sync-ops.js';
|
|
43
43
|
export { taskAnalyze, taskBatchValidate, taskBlockers, taskCancel, taskClaim, taskComplexityEstimate, taskDepends, taskDeps, taskDepsCycles, taskDepsOverview, taskDepsTree, taskDepsValidate, taskExport, taskHistory, taskImpact, taskImport, taskLint, taskNext, taskPromote, taskRelates, taskRelatesAdd, taskRelatesFind, taskRelatesRemove, taskReopen, taskReorder, taskReparent, taskRestore, taskSlice, taskStats, taskTree, taskUnarchive, taskUnclaim, } from './task-ops.js';
|
|
44
44
|
// Tool result cache + cross-process semaphore (T1534 / ADR-061)
|
|
45
|
-
export { cacheEntryPath, captureDirtyFingerprint, captureHead, clearToolCache, computeCacheKey, readCacheEntry, runToolCached, writeCacheEntry, } from './tool-cache.js';
|
|
45
|
+
export { cacheEntryPath, captureDirtyFingerprint, captureHead, clearToolCache, computeCacheKey, isEntryUsable, readCacheEntry, runToolCached, TOOL_RUN_IDENTITY_FIELDS, writeCacheEntry, } from './tool-cache.js';
|
|
46
46
|
// Project-agnostic tool resolution (T1534 / ADR-061)
|
|
47
47
|
export { listValidToolNames, resolveToolCommand, } from './tool-resolver.js';
|
|
48
48
|
// Cross-process global per-tool concurrency semaphore (T1534 / ADR-061)
|
package/dist/tasks/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/tasks/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -37,8 +37,24 @@ import { type AcquireSlotOptions } from './tool-semaphore.js';
|
|
|
37
37
|
* @task T1534
|
|
38
38
|
*/
|
|
39
39
|
export interface ToolCacheEntry {
|
|
40
|
-
/**
|
|
41
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Schema version for forwards compatibility.
|
|
42
|
+
*
|
|
43
|
+
* Bumped 1 -> 2 by gh#1419, which added {@link ToolCacheEntry.executionRoot}
|
|
44
|
+
* to the run's identity. Every entry written before that field existed is
|
|
45
|
+
* refused wholesale by {@link readCacheEntry} on this one comparison.
|
|
46
|
+
*
|
|
47
|
+
* That is the mechanism gh#1380 and gh#1404 each rebuilt by hand. Both
|
|
48
|
+
* needed to retire entries already on disk in consumers' caches, and both
|
|
49
|
+
* did it with a bespoke null-check on the field they had just added —
|
|
50
|
+
* `exitCode === null`, then `head === null` — while this field, whose entire
|
|
51
|
+
* purpose is to retire incompatible entries, stayed at 1 through both.
|
|
52
|
+
* Two defects, two hand-written retirement clauses, one unused version
|
|
53
|
+
* counter sitting between them.
|
|
54
|
+
*
|
|
55
|
+
* @task T12190 (gh#1419)
|
|
56
|
+
*/
|
|
57
|
+
schemaVersion: 2;
|
|
42
58
|
/** Cache key (also encoded in the filename). */
|
|
43
59
|
key: string;
|
|
44
60
|
/** Canonical tool name from the resolver. */
|
|
@@ -55,6 +71,26 @@ export interface ToolCacheEntry {
|
|
|
55
71
|
head: string | null;
|
|
56
72
|
/** sha256 of `git status --porcelain` (uncommitted tree fingerprint). */
|
|
57
73
|
dirtyFingerprint: string | null;
|
|
74
|
+
/**
|
|
75
|
+
* Absolute, symlink-resolved path of the tree the tool actually ran in.
|
|
76
|
+
*
|
|
77
|
+
* Part of the run's IDENTITY (see {@link TOOL_RUN_IDENTITY_FIELDS}), so it
|
|
78
|
+
* is in the cache key: two clean worktrees at the same HEAD no longer
|
|
79
|
+
* collide. They used to by construction — a clean tree's
|
|
80
|
+
* `dirtyFingerprint` is the empty-input hash in every worktree, so the old
|
|
81
|
+
* key `{canonical, cmd, args, head, dirtyFingerprint}` was byte-identical
|
|
82
|
+
* across them.
|
|
83
|
+
*
|
|
84
|
+
* Stored as well as keyed, so a hit can be AUDITED. Before this field,
|
|
85
|
+
* `ToolCacheEntry` carried no directory at all: a field survey of 259 tool
|
|
86
|
+
* entries found 0 that could say which tree produced them, and 8 that had
|
|
87
|
+
* been produced in worktrees since deleted — every one `exitCode: 0` and
|
|
88
|
+
* still servable, which makes the evidence unfalsifiable rather than merely
|
|
89
|
+
* stale. Nobody can go and look.
|
|
90
|
+
*
|
|
91
|
+
* @task T12190 (gh#1419)
|
|
92
|
+
*/
|
|
93
|
+
executionRoot: string;
|
|
58
94
|
/** Process exit code. */
|
|
59
95
|
exitCode: number | null;
|
|
60
96
|
/**
|
|
@@ -290,21 +326,110 @@ export declare function defaultSpawnTimeoutMs(canonical: string): number;
|
|
|
290
326
|
* @task T12105 (gh#1193)
|
|
291
327
|
*/
|
|
292
328
|
export declare function resolveSpawnTimeoutMs(canonical: string, env?: NodeJS.ProcessEnv): number;
|
|
329
|
+
/**
|
|
330
|
+
* The fields that establish WHICH RUN a cache entry describes.
|
|
331
|
+
*
|
|
332
|
+
* This list is the single source of truth for three things that must agree
|
|
333
|
+
* and previously did not, because each was maintained by hand:
|
|
334
|
+
*
|
|
335
|
+
* 1. what {@link computeCacheKey} hashes,
|
|
336
|
+
* 2. what {@link readCacheEntry} requires before serving an entry,
|
|
337
|
+
* 3. what `runToolCached` requires before persisting one.
|
|
338
|
+
*
|
|
339
|
+
* ## Why this exists rather than a fourth guard clause
|
|
340
|
+
*
|
|
341
|
+
* Three defects in three releases, each invisible to the detector written for
|
|
342
|
+
* the one before it:
|
|
343
|
+
*
|
|
344
|
+
* | | gh#1380 (2026.9.2) | gh#1404 (2026.9.3) | gh#1419 (this) |
|
|
345
|
+
* |---|---|---|---|
|
|
346
|
+
* | signature | `exitCode: null` | real code, `head: null` | well-formed, genuinely succeeded |
|
|
347
|
+
* | wrong how | never ran | key cannot rotate | ran, on the wrong tree |
|
|
348
|
+
* | detector | all-null | real code + null head | neither matches |
|
|
349
|
+
*
|
|
350
|
+
* Each fix added one `if` naming one field, to two separate guards — the read
|
|
351
|
+
* path and the write path — that encode the same rule in different code with
|
|
352
|
+
* nothing asserting they agree. A fourth defect means a fourth pair of `if`s,
|
|
353
|
+
* and the pair for defect five is the one somebody edits only half of.
|
|
354
|
+
*
|
|
355
|
+
* The recurring mistake is not any of the three fields. It is that a field
|
|
356
|
+
* could be added to {@link ToolCacheEntry} without being added to the key:
|
|
357
|
+
* `executionRoot` was threaded all the way through execution by T12112 and
|
|
358
|
+
* never reached `computeCacheKey`, so the tool correctly ran in worktree A
|
|
359
|
+
* and its result was then served to worktree B under an identical key. The
|
|
360
|
+
* execution half of that fix landed; the caching half did not.
|
|
361
|
+
*
|
|
362
|
+
* Deriving all three consumers from this one array makes that specific
|
|
363
|
+
* mistake unavailable: a field added here is keyed, required on read, and
|
|
364
|
+
* required on write, together or not at all.
|
|
365
|
+
*
|
|
366
|
+
* ## What is deliberately NOT here
|
|
367
|
+
*
|
|
368
|
+
* `exitCode` is a RESULT, not an identity — it is what the run produced, not
|
|
369
|
+
* which run it was. It has its own non-null requirement in
|
|
370
|
+
* {@link isEntryUsable} (gh#1380: an unknown outcome must never be cached).
|
|
371
|
+
* Putting it here would key the cache on its own answer.
|
|
372
|
+
*
|
|
373
|
+
* @task T12190 (gh#1419)
|
|
374
|
+
*/
|
|
375
|
+
export declare const TOOL_RUN_IDENTITY_FIELDS: readonly ["canonical", "cmd", "args", "head", "dirtyFingerprint", "executionRoot"];
|
|
376
|
+
/**
|
|
377
|
+
* The identity half of a {@link ToolCacheEntry} — the inputs that determine
|
|
378
|
+
* which run it is, independent of what that run produced.
|
|
379
|
+
*
|
|
380
|
+
* @task T12190 (gh#1419)
|
|
381
|
+
*/
|
|
382
|
+
export type ToolRunIdentity = Pick<ToolCacheEntry, (typeof TOOL_RUN_IDENTITY_FIELDS)[number]>;
|
|
293
383
|
/**
|
|
294
384
|
* Compute the cache key for a resolved tool command + repo state.
|
|
295
385
|
*
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
386
|
+
* The key covers exactly {@link TOOL_RUN_IDENTITY_FIELDS} — canonical tool
|
|
387
|
+
* name, resolved command and args, git HEAD, dirty-tree fingerprint, and the
|
|
388
|
+
* normalised execution root. Fields are projected in the order that array
|
|
389
|
+
* declares, so the hashed payload is a function of the array and adding a
|
|
390
|
+
* field there changes every key by construction.
|
|
391
|
+
*
|
|
392
|
+
* `executionRoot` is a REQUIRED parameter, deliberately. A defaulted one
|
|
393
|
+
* would let every callsite that was not updated keep computing the old,
|
|
394
|
+
* colliding key while the type-checker reported success — which is the exact
|
|
395
|
+
* shape of the defect this fixes, since `executionRoot` already existed and
|
|
396
|
+
* already reached this function's caller.
|
|
301
397
|
*
|
|
302
398
|
* Using `createHash('sha256')` makes the key collision-resistant and bounded
|
|
303
|
-
* to
|
|
399
|
+
* to 32 hex chars regardless of input size.
|
|
304
400
|
*
|
|
305
401
|
* @task T1534
|
|
402
|
+
* @task T12190 (gh#1419)
|
|
403
|
+
*/
|
|
404
|
+
export declare function computeCacheKey(command: ResolvedToolCommand, head: string | null, dirtyFingerprint: string | null, executionRoot: string): string;
|
|
405
|
+
/**
|
|
406
|
+
* Whether a cache entry may be SERVED or PERSISTED as a real result.
|
|
407
|
+
*
|
|
408
|
+
* One predicate, used by both {@link readCacheEntry} and the persist guard in
|
|
409
|
+
* `runToolCached`, so the two cannot drift apart. Two rules:
|
|
410
|
+
*
|
|
411
|
+
* 1. every field in {@link TOOL_RUN_IDENTITY_FIELDS} is present and
|
|
412
|
+
* non-null — an entry that cannot say which run it describes is not
|
|
413
|
+
* evidence of anything;
|
|
414
|
+
* 2. `exitCode` is non-null — a run that produced no exit code records that
|
|
415
|
+
* we do not know what happened, and an unknown must never be cached
|
|
416
|
+
* (gh#1380).
|
|
417
|
+
*
|
|
418
|
+
* Rule 1 subsumes gh#1404's `head === null` clause without naming `head`:
|
|
419
|
+
* when the tool runs off a non-git root both git fields are null on EVERY
|
|
420
|
+
* run, so the key is a function of the command alone and nothing a developer
|
|
421
|
+
* does to the source can rotate it. That is not a cache, it is a hardcoded
|
|
422
|
+
* answer with a filename — and unlike gh#1380 it holds a real exit code,
|
|
423
|
+
* which can be a PASS. A poisoned red is investigated within minutes; nobody
|
|
424
|
+
* debugs a passing gate.
|
|
425
|
+
*
|
|
426
|
+
* The cost is stated rather than hidden: a project whose CLEO root is not a
|
|
427
|
+
* git checkout gets no tool-result caching at all. Such projects are not
|
|
428
|
+
* getting valid caching today — they are getting one answer forever.
|
|
429
|
+
*
|
|
430
|
+
* @task T12190 (gh#1419)
|
|
306
431
|
*/
|
|
307
|
-
export declare function
|
|
432
|
+
export declare function isEntryUsable(entry: Partial<ToolCacheEntry>): boolean;
|
|
308
433
|
/**
|
|
309
434
|
* Capture the repo's git HEAD sha. Returns `null` when the directory is not
|
|
310
435
|
* a git checkout or the command fails.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tool-cache.d.ts","sourceRoot":"","sources":["../../src/tasks/tool-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;
|
|
1
|
+
{"version":3,"file":"tool-cache.d.ts","sourceRoot":"","sources":["../../src/tasks/tool-cache.ts"],"names":[],"mappings":"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"}
|
package/dist/tasks/tool-cache.js
CHANGED
|
@@ -31,8 +31,8 @@
|
|
|
31
31
|
*/
|
|
32
32
|
import { execFileSync, spawn } from 'node:child_process';
|
|
33
33
|
import { createHash } from 'node:crypto';
|
|
34
|
-
import { existsSync, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
35
|
-
import { join } from 'node:path';
|
|
34
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, realpathSync, renameSync, rmSync, writeFileSync, } from 'node:fs';
|
|
35
|
+
import { join, resolve } from 'node:path';
|
|
36
36
|
import { ExitCode } from '@cleocode/contracts';
|
|
37
37
|
import { CleoError } from '../errors.js';
|
|
38
38
|
import { withLock } from '../store/lock.js';
|
|
@@ -156,30 +156,152 @@ export function resolveSpawnTimeoutMs(canonical, env = process.env) {
|
|
|
156
156
|
// ---------------------------------------------------------------------------
|
|
157
157
|
// Cache key derivation
|
|
158
158
|
// ---------------------------------------------------------------------------
|
|
159
|
+
/**
|
|
160
|
+
* The fields that establish WHICH RUN a cache entry describes.
|
|
161
|
+
*
|
|
162
|
+
* This list is the single source of truth for three things that must agree
|
|
163
|
+
* and previously did not, because each was maintained by hand:
|
|
164
|
+
*
|
|
165
|
+
* 1. what {@link computeCacheKey} hashes,
|
|
166
|
+
* 2. what {@link readCacheEntry} requires before serving an entry,
|
|
167
|
+
* 3. what `runToolCached` requires before persisting one.
|
|
168
|
+
*
|
|
169
|
+
* ## Why this exists rather than a fourth guard clause
|
|
170
|
+
*
|
|
171
|
+
* Three defects in three releases, each invisible to the detector written for
|
|
172
|
+
* the one before it:
|
|
173
|
+
*
|
|
174
|
+
* | | gh#1380 (2026.9.2) | gh#1404 (2026.9.3) | gh#1419 (this) |
|
|
175
|
+
* |---|---|---|---|
|
|
176
|
+
* | signature | `exitCode: null` | real code, `head: null` | well-formed, genuinely succeeded |
|
|
177
|
+
* | wrong how | never ran | key cannot rotate | ran, on the wrong tree |
|
|
178
|
+
* | detector | all-null | real code + null head | neither matches |
|
|
179
|
+
*
|
|
180
|
+
* Each fix added one `if` naming one field, to two separate guards — the read
|
|
181
|
+
* path and the write path — that encode the same rule in different code with
|
|
182
|
+
* nothing asserting they agree. A fourth defect means a fourth pair of `if`s,
|
|
183
|
+
* and the pair for defect five is the one somebody edits only half of.
|
|
184
|
+
*
|
|
185
|
+
* The recurring mistake is not any of the three fields. It is that a field
|
|
186
|
+
* could be added to {@link ToolCacheEntry} without being added to the key:
|
|
187
|
+
* `executionRoot` was threaded all the way through execution by T12112 and
|
|
188
|
+
* never reached `computeCacheKey`, so the tool correctly ran in worktree A
|
|
189
|
+
* and its result was then served to worktree B under an identical key. The
|
|
190
|
+
* execution half of that fix landed; the caching half did not.
|
|
191
|
+
*
|
|
192
|
+
* Deriving all three consumers from this one array makes that specific
|
|
193
|
+
* mistake unavailable: a field added here is keyed, required on read, and
|
|
194
|
+
* required on write, together or not at all.
|
|
195
|
+
*
|
|
196
|
+
* ## What is deliberately NOT here
|
|
197
|
+
*
|
|
198
|
+
* `exitCode` is a RESULT, not an identity — it is what the run produced, not
|
|
199
|
+
* which run it was. It has its own non-null requirement in
|
|
200
|
+
* {@link isEntryUsable} (gh#1380: an unknown outcome must never be cached).
|
|
201
|
+
* Putting it here would key the cache on its own answer.
|
|
202
|
+
*
|
|
203
|
+
* @task T12190 (gh#1419)
|
|
204
|
+
*/
|
|
205
|
+
export const TOOL_RUN_IDENTITY_FIELDS = [
|
|
206
|
+
'canonical',
|
|
207
|
+
'cmd',
|
|
208
|
+
'args',
|
|
209
|
+
'head',
|
|
210
|
+
'dirtyFingerprint',
|
|
211
|
+
'executionRoot',
|
|
212
|
+
];
|
|
213
|
+
/**
|
|
214
|
+
* Normalise an execution root to a stable, comparable absolute path.
|
|
215
|
+
*
|
|
216
|
+
* Symlinks are resolved so that two spellings of one directory produce one
|
|
217
|
+
* key — the safe direction, since collapsing an alias can only merge entries
|
|
218
|
+
* that genuinely describe the same tree, while failing to collapse it would
|
|
219
|
+
* split one tree's cache across spellings. A path that cannot be resolved
|
|
220
|
+
* (it has been deleted) falls back to lexical resolution rather than
|
|
221
|
+
* throwing: callers of {@link computeCacheKey} must be able to compute the
|
|
222
|
+
* key of a run whose directory is already gone, precisely so
|
|
223
|
+
* {@link readCacheEntry} can then refuse it.
|
|
224
|
+
*
|
|
225
|
+
* @task T12190 (gh#1419)
|
|
226
|
+
*/
|
|
227
|
+
function normalizeExecutionRoot(executionRoot) {
|
|
228
|
+
try {
|
|
229
|
+
return realpathSync(executionRoot);
|
|
230
|
+
}
|
|
231
|
+
catch {
|
|
232
|
+
return resolve(executionRoot);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
159
235
|
/**
|
|
160
236
|
* Compute the cache key for a resolved tool command + repo state.
|
|
161
237
|
*
|
|
162
|
-
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
165
|
-
*
|
|
166
|
-
*
|
|
238
|
+
* The key covers exactly {@link TOOL_RUN_IDENTITY_FIELDS} — canonical tool
|
|
239
|
+
* name, resolved command and args, git HEAD, dirty-tree fingerprint, and the
|
|
240
|
+
* normalised execution root. Fields are projected in the order that array
|
|
241
|
+
* declares, so the hashed payload is a function of the array and adding a
|
|
242
|
+
* field there changes every key by construction.
|
|
243
|
+
*
|
|
244
|
+
* `executionRoot` is a REQUIRED parameter, deliberately. A defaulted one
|
|
245
|
+
* would let every callsite that was not updated keep computing the old,
|
|
246
|
+
* colliding key while the type-checker reported success — which is the exact
|
|
247
|
+
* shape of the defect this fixes, since `executionRoot` already existed and
|
|
248
|
+
* already reached this function's caller.
|
|
167
249
|
*
|
|
168
250
|
* Using `createHash('sha256')` makes the key collision-resistant and bounded
|
|
169
|
-
* to
|
|
251
|
+
* to 32 hex chars regardless of input size.
|
|
170
252
|
*
|
|
171
253
|
* @task T1534
|
|
254
|
+
* @task T12190 (gh#1419)
|
|
172
255
|
*/
|
|
173
|
-
export function computeCacheKey(command, head, dirtyFingerprint) {
|
|
174
|
-
const
|
|
256
|
+
export function computeCacheKey(command, head, dirtyFingerprint, executionRoot) {
|
|
257
|
+
const identity = {
|
|
175
258
|
canonical: command.canonical,
|
|
176
259
|
cmd: command.cmd,
|
|
177
260
|
args: command.args,
|
|
178
261
|
head,
|
|
179
262
|
dirtyFingerprint,
|
|
180
|
-
|
|
263
|
+
executionRoot: normalizeExecutionRoot(executionRoot),
|
|
264
|
+
};
|
|
265
|
+
// Projected through TOOL_RUN_IDENTITY_FIELDS rather than written as an
|
|
266
|
+
// object literal: the literal is what drifted from the entry shape before.
|
|
267
|
+
const payload = JSON.stringify(TOOL_RUN_IDENTITY_FIELDS.map((f) => identity[f]));
|
|
181
268
|
return createHash('sha256').update(payload).digest('hex').slice(0, 32);
|
|
182
269
|
}
|
|
270
|
+
/**
|
|
271
|
+
* Whether a cache entry may be SERVED or PERSISTED as a real result.
|
|
272
|
+
*
|
|
273
|
+
* One predicate, used by both {@link readCacheEntry} and the persist guard in
|
|
274
|
+
* `runToolCached`, so the two cannot drift apart. Two rules:
|
|
275
|
+
*
|
|
276
|
+
* 1. every field in {@link TOOL_RUN_IDENTITY_FIELDS} is present and
|
|
277
|
+
* non-null — an entry that cannot say which run it describes is not
|
|
278
|
+
* evidence of anything;
|
|
279
|
+
* 2. `exitCode` is non-null — a run that produced no exit code records that
|
|
280
|
+
* we do not know what happened, and an unknown must never be cached
|
|
281
|
+
* (gh#1380).
|
|
282
|
+
*
|
|
283
|
+
* Rule 1 subsumes gh#1404's `head === null` clause without naming `head`:
|
|
284
|
+
* when the tool runs off a non-git root both git fields are null on EVERY
|
|
285
|
+
* run, so the key is a function of the command alone and nothing a developer
|
|
286
|
+
* does to the source can rotate it. That is not a cache, it is a hardcoded
|
|
287
|
+
* answer with a filename — and unlike gh#1380 it holds a real exit code,
|
|
288
|
+
* which can be a PASS. A poisoned red is investigated within minutes; nobody
|
|
289
|
+
* debugs a passing gate.
|
|
290
|
+
*
|
|
291
|
+
* The cost is stated rather than hidden: a project whose CLEO root is not a
|
|
292
|
+
* git checkout gets no tool-result caching at all. Such projects are not
|
|
293
|
+
* getting valid caching today — they are getting one answer forever.
|
|
294
|
+
*
|
|
295
|
+
* @task T12190 (gh#1419)
|
|
296
|
+
*/
|
|
297
|
+
export function isEntryUsable(entry) {
|
|
298
|
+
for (const field of TOOL_RUN_IDENTITY_FIELDS) {
|
|
299
|
+
const value = entry[field];
|
|
300
|
+
if (value === null || value === undefined)
|
|
301
|
+
return false;
|
|
302
|
+
}
|
|
303
|
+
return entry.exitCode !== null && entry.exitCode !== undefined;
|
|
304
|
+
}
|
|
183
305
|
/**
|
|
184
306
|
* Maximum bytes retained from a child's stdout / stderr stream during
|
|
185
307
|
* spawn. The tool-evidence atom only needs the trailing 512 bytes for
|
|
@@ -467,23 +589,37 @@ export function readCacheEntry(projectRoot, key) {
|
|
|
467
589
|
return null;
|
|
468
590
|
try {
|
|
469
591
|
const parsed = JSON.parse(readFileSync(path, 'utf-8'));
|
|
470
|
-
|
|
592
|
+
// Schema gate. Bumped to 2 by gh#1419; every entry written before
|
|
593
|
+
// `executionRoot` was part of a run's identity is refused here, on this
|
|
594
|
+
// one line. gh#1380 and gh#1404 each needed exactly this and each
|
|
595
|
+
// reimplemented it as a bespoke null-check on their own field, while this
|
|
596
|
+
// counter sat unused at 1 through both.
|
|
597
|
+
if (parsed.schemaVersion !== 2 || parsed.key !== key)
|
|
471
598
|
return null;
|
|
472
599
|
// A placeholder written by `runToolCached` to satisfy proper-lockfile's
|
|
473
600
|
// "file must exist" requirement. It carries no real result — treat as
|
|
474
|
-
// a miss until the lock holder writes the entry.
|
|
601
|
+
// a miss until the lock holder writes the entry. Checked before the
|
|
602
|
+
// identity gate below because a placeholder legitimately has none.
|
|
475
603
|
if (parsed.pending === true)
|
|
476
604
|
return null;
|
|
477
|
-
//
|
|
478
|
-
//
|
|
479
|
-
//
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
//
|
|
483
|
-
//
|
|
484
|
-
//
|
|
485
|
-
//
|
|
486
|
-
|
|
605
|
+
// The identity + result gate (gh#1380, gh#1404, gh#1419), derived from
|
|
606
|
+
// TOOL_RUN_IDENTITY_FIELDS rather than written out field by field, and
|
|
607
|
+
// shared verbatim with the persist guard in `runToolCached`.
|
|
608
|
+
if (!isEntryUsable(parsed))
|
|
609
|
+
return null;
|
|
610
|
+
// gh#1419: refuse a hit whose originating tree is GONE.
|
|
611
|
+
//
|
|
612
|
+
// Keying on `executionRoot` stops one live worktree from being served
|
|
613
|
+
// another live worktree's result. It does not address the worse case the
|
|
614
|
+
// field survey actually found: 8 entries produced in worktrees that had
|
|
615
|
+
// since been deleted, all `exitCode: 0`, all still servable. A stale
|
|
616
|
+
// result is merely wrong; a result whose tree no longer exists is
|
|
617
|
+
// UNFALSIFIABLE — there is nowhere left to go and check what ran.
|
|
618
|
+
//
|
|
619
|
+
// This is the one rule that is genuinely read-only. At write time the
|
|
620
|
+
// directory exists by construction, so it cannot live in `isEntryUsable`
|
|
621
|
+
// alongside the rules both paths share.
|
|
622
|
+
if (!existsSync(parsed.executionRoot))
|
|
487
623
|
return null;
|
|
488
624
|
return parsed;
|
|
489
625
|
}
|
|
@@ -550,7 +686,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
550
686
|
const bypassCache = opts.bypassCache ?? process.env['CLEO_EVIDENCE_FRESH'] === '1';
|
|
551
687
|
const head = await captureHead(executionRoot);
|
|
552
688
|
const dirtyFingerprint = await captureDirtyFingerprint(executionRoot);
|
|
553
|
-
const key = computeCacheKey(command, head, dirtyFingerprint);
|
|
689
|
+
const key = computeCacheKey(command, head, dirtyFingerprint, executionRoot);
|
|
554
690
|
// Fast path — fresh cache hit
|
|
555
691
|
if (!bypassCache) {
|
|
556
692
|
const existing = readCacheEntry(projectRoot, key);
|
|
@@ -585,7 +721,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
585
721
|
ensureCacheDir(projectRoot);
|
|
586
722
|
const cachePath = cacheEntryPath(projectRoot, key);
|
|
587
723
|
if (!existsSync(cachePath)) {
|
|
588
|
-
writeFileSync(cachePath, JSON.stringify({ schemaVersion:
|
|
724
|
+
writeFileSync(cachePath, JSON.stringify({ schemaVersion: 2, key, pending: true }), 'utf-8');
|
|
589
725
|
}
|
|
590
726
|
const releaseSemaphore = opts.skipGlobalSemaphore
|
|
591
727
|
? undefined
|
|
@@ -640,7 +776,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
640
776
|
lockBusy: false,
|
|
641
777
|
executionRoot,
|
|
642
778
|
entry: {
|
|
643
|
-
schemaVersion:
|
|
779
|
+
schemaVersion: 2,
|
|
644
780
|
key,
|
|
645
781
|
canonical: command.canonical,
|
|
646
782
|
displayName: command.displayName,
|
|
@@ -649,6 +785,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
649
785
|
source: command.source,
|
|
650
786
|
head,
|
|
651
787
|
dirtyFingerprint,
|
|
788
|
+
executionRoot,
|
|
652
789
|
exitCode: null,
|
|
653
790
|
signal: result.signal,
|
|
654
791
|
stdoutTail: '',
|
|
@@ -659,7 +796,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
659
796
|
};
|
|
660
797
|
}
|
|
661
798
|
const entry = {
|
|
662
|
-
schemaVersion:
|
|
799
|
+
schemaVersion: 2,
|
|
663
800
|
key,
|
|
664
801
|
canonical: command.canonical,
|
|
665
802
|
displayName: command.displayName,
|
|
@@ -668,6 +805,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
668
805
|
source: command.source,
|
|
669
806
|
head,
|
|
670
807
|
dirtyFingerprint,
|
|
808
|
+
executionRoot,
|
|
671
809
|
exitCode: result.exitCode,
|
|
672
810
|
signal: result.signal,
|
|
673
811
|
stdoutTail: tailString(result.stdout, tailBytes),
|
|
@@ -675,14 +813,32 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
675
813
|
durationMs,
|
|
676
814
|
capturedAt: new Date().toISOString(),
|
|
677
815
|
};
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
//
|
|
683
|
-
//
|
|
684
|
-
//
|
|
685
|
-
|
|
816
|
+
// Persist only a usable entry. `isEntryUsable` is the SAME predicate
|
|
817
|
+
// `readCacheEntry` applies, which is the point: these two guards used
|
|
818
|
+
// to be independent transcriptions of one rule — `exitCode !== null
|
|
819
|
+
// && head !== null`, written out twice — so a field added to one had
|
|
820
|
+
// to be remembered into the other. gh#1380 and gh#1404 each edited
|
|
821
|
+
// both, and nothing checked that they still agreed.
|
|
822
|
+
//
|
|
823
|
+
// What the shared predicate refuses, and why none of it is an
|
|
824
|
+
// overcorrection:
|
|
825
|
+
// - unknown outcome (`exitCode: null`) — a signal kill, or a
|
|
826
|
+
// failure to start. The `timedOut` branch above already returns
|
|
827
|
+
// without writing for this reason; an OOM kill that is not a CLEO
|
|
828
|
+
// timeout (gh#1381) had no equivalent guard and fell through here.
|
|
829
|
+
// - unrotatable key (`head: null`) — costs a non-git CLEO root all
|
|
830
|
+
// caching. Stated rather than hidden: those projects are not
|
|
831
|
+
// getting valid caching today, they are getting ONE answer
|
|
832
|
+
// forever, and a slow correct answer beats a fast fabricated one.
|
|
833
|
+
// - unattributable run (`executionRoot` absent) — cannot occur on
|
|
834
|
+
// this path, and is required here anyway so that read and write
|
|
835
|
+
// stay ONE rule rather than two that happen to match today.
|
|
836
|
+
//
|
|
837
|
+
// Deliberately narrow in the other direction too: refusing, say, all
|
|
838
|
+
// non-zero exits would trade a fabricated pass for a permanent cache
|
|
839
|
+
// miss on healthy projects — the same overcorrection pointed the
|
|
840
|
+
// other way.
|
|
841
|
+
if (isEntryUsable(entry)) {
|
|
686
842
|
writeCacheEntry(projectRoot, entry);
|
|
687
843
|
}
|
|
688
844
|
return {
|
|
@@ -720,7 +876,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
720
876
|
lockBusy: true,
|
|
721
877
|
executionRoot,
|
|
722
878
|
entry: {
|
|
723
|
-
schemaVersion:
|
|
879
|
+
schemaVersion: 2,
|
|
724
880
|
key,
|
|
725
881
|
canonical: command.canonical,
|
|
726
882
|
displayName: command.displayName,
|
|
@@ -729,6 +885,7 @@ export async function runToolCached(command, projectRoot, opts = {}) {
|
|
|
729
885
|
source: command.source,
|
|
730
886
|
head,
|
|
731
887
|
dirtyFingerprint,
|
|
888
|
+
executionRoot,
|
|
732
889
|
exitCode: null,
|
|
733
890
|
stdoutTail: '',
|
|
734
891
|
stderrTail: '',
|