@cortexkit/opencode-magic-context 0.40.1 → 0.41.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/README.md +1 -1
- package/dist/{announcement-5eg4t64s.js → announcement-3g9xg433.js} +2 -2
- package/dist/config/schema/magic-context.d.ts +15 -0
- package/dist/config/schema/magic-context.d.ts.map +1 -1
- package/dist/{conflict-warning-hook-f406ggbw.js → conflict-warning-hook-rf17b6et.js} +4 -4
- package/dist/{derive-budgets-xk8cy5e6.js → derive-budgets-bb7mrpzs.js} +3 -3
- package/dist/features/builtin-commands/commands.d.ts +35 -2
- package/dist/features/builtin-commands/commands.d.ts.map +1 -1
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts +12 -16
- package/dist/features/magic-context/compartment-chunk-embedding.d.ts.map +1 -1
- package/dist/features/magic-context/context-authority.d.ts +10 -0
- package/dist/features/magic-context/context-authority.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/classify.d.ts +1 -0
- package/dist/features/magic-context/dreamer/classify.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/lease.d.ts +12 -27
- package/dist/features/magic-context/dreamer/lease.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts +1 -1
- package/dist/features/magic-context/dreamer/map-memories-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/map-memories.d.ts +2 -0
- package/dist/features/magic-context/dreamer/map-memories.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/memory-claim-safety.d.ts +9 -0
- package/dist/features/magic-context/dreamer/memory-claim-safety.d.ts.map +1 -0
- package/dist/features/magic-context/dreamer/task-executor.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/task-registry.d.ts +2 -0
- package/dist/features/magic-context/dreamer/task-registry.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts +7 -2
- package/dist/features/magic-context/dreamer/verify-prompt.d.ts.map +1 -1
- package/dist/features/magic-context/dreamer/verify.d.ts +13 -10
- package/dist/features/magic-context/dreamer/verify.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-failure.d.ts +1 -1
- package/dist/features/magic-context/memory/embedding-failure.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-local.d.ts +12 -2
- package/dist/features/magic-context/memory/embedding-local.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding-synapse.d.ts +10 -1
- package/dist/features/magic-context/memory/embedding-synapse.d.ts.map +1 -1
- package/dist/features/magic-context/memory/embedding.d.ts.map +1 -1
- package/dist/features/magic-context/memory/project-identity.d.ts +4 -0
- package/dist/features/magic-context/memory/project-identity.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory-verifications.d.ts +5 -1
- package/dist/features/magic-context/memory/storage-memory-verifications.d.ts.map +1 -1
- package/dist/features/magic-context/memory/storage-memory.d.ts.map +1 -1
- package/dist/features/magic-context/memory/transformers-web-entry.d.ts +2 -0
- package/dist/features/magic-context/memory/transformers-web-entry.d.ts.map +1 -0
- package/dist/features/magic-context/message-index.d.ts.map +1 -1
- package/dist/features/magic-context/migrations.d.ts.map +1 -1
- package/dist/features/magic-context/project-embedding-registry.d.ts +3 -0
- package/dist/features/magic-context/project-embedding-registry.d.ts.map +1 -1
- package/dist/features/magic-context/search.d.ts +18 -0
- package/dist/features/magic-context/search.d.ts.map +1 -1
- package/dist/features/magic-context/storage-db.d.ts +1 -1
- package/dist/features/magic-context/storage-db.d.ts.map +1 -1
- package/dist/features/magic-context/storage-identity-merge.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-persisted.d.ts +29 -3
- package/dist/features/magic-context/storage-meta-persisted.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta-session.d.ts.map +1 -1
- package/dist/features/magic-context/storage-meta.d.ts +1 -1
- package/dist/features/magic-context/storage-meta.d.ts.map +1 -1
- package/dist/features/magic-context/storage-ops.d.ts +2 -0
- package/dist/features/magic-context/storage-ops.d.ts.map +1 -1
- package/dist/features/magic-context/storage-tags.d.ts +7 -0
- package/dist/features/magic-context/storage-tags.d.ts.map +1 -1
- package/dist/features/magic-context/storage.d.ts +2 -2
- package/dist/features/magic-context/storage.d.ts.map +1 -1
- package/dist/hooks/magic-context/boundary-execution.d.ts +3 -0
- package/dist/hooks/magic-context/boundary-execution.d.ts.map +1 -1
- package/dist/hooks/magic-context/channel2-delivery.d.ts +2 -4
- package/dist/hooks/magic-context/channel2-delivery.d.ts.map +1 -1
- package/dist/hooks/magic-context/command-handler.d.ts +7 -0
- package/dist/hooks/magic-context/command-handler.d.ts.map +1 -1
- package/dist/hooks/magic-context/compaction-marker-manager.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-incremental.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-runner-recomp.d.ts.map +1 -1
- package/dist/hooks/magic-context/compartment-trigger.d.ts +1 -0
- package/dist/hooks/magic-context/compartment-trigger.d.ts.map +1 -1
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts +15 -14
- package/dist/hooks/magic-context/ctx-reduce-nudge.d.ts.map +1 -1
- package/dist/hooks/magic-context/emergency-drop.d.ts +6 -8
- package/dist/hooks/magic-context/emergency-drop.d.ts.map +1 -1
- package/dist/hooks/magic-context/event-resolvers.d.ts +2 -2
- package/dist/hooks/magic-context/execute-status.d.ts +4 -1
- package/dist/hooks/magic-context/execute-status.d.ts.map +1 -1
- package/dist/hooks/magic-context/format-embed-failure.d.ts.map +1 -1
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts +6 -0
- package/dist/hooks/magic-context/heuristic-cleanup.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook-handlers.d.ts +21 -8
- package/dist/hooks/magic-context/hook-handlers.d.ts.map +1 -1
- package/dist/hooks/magic-context/hook.d.ts +13 -0
- package/dist/hooks/magic-context/hook.d.ts.map +1 -1
- package/dist/hooks/magic-context/inject-compartments.d.ts +6 -16
- package/dist/hooks/magic-context/inject-compartments.d.ts.map +1 -1
- package/dist/hooks/magic-context/maintenance-authority.d.ts +3 -0
- package/dist/hooks/magic-context/maintenance-authority.d.ts.map +1 -0
- package/dist/hooks/magic-context/module-transport.d.ts.map +1 -1
- package/dist/hooks/magic-context/module-wire.d.ts.map +1 -1
- package/dist/hooks/magic-context/protected-tail-boundary.d.ts.map +1 -1
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts +1 -0
- package/dist/hooks/magic-context/read-session-true-raw-tokens.d.ts.map +1 -1
- package/dist/hooks/magic-context/rust-mode-transform.d.ts +9 -0
- package/dist/hooks/magic-context/rust-mode-transform.d.ts.map +1 -1
- package/dist/hooks/magic-context/strip-content.d.ts +8 -0
- package/dist/hooks/magic-context/strip-content.d.ts.map +1 -1
- package/dist/hooks/magic-context/stripped-command.d.ts +19 -0
- package/dist/hooks/magic-context/stripped-command.d.ts.map +1 -0
- package/dist/hooks/magic-context/supersession-reclaim.d.ts +11 -0
- package/dist/hooks/magic-context/supersession-reclaim.d.ts.map +1 -1
- package/dist/hooks/magic-context/tag-messages.d.ts +6 -0
- package/dist/hooks/magic-context/tag-messages.d.ts.map +1 -1
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts +11 -0
- package/dist/hooks/magic-context/tail-hygiene-walk.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-operations.d.ts +1 -1
- package/dist/hooks/magic-context/transform-operations.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts +8 -1
- package/dist/hooks/magic-context/transform-postprocess-phase.d.ts.map +1 -1
- package/dist/hooks/magic-context/transform.d.ts.map +1 -1
- package/dist/{index-mg1yx1ph.js → index-2d4kf2ve.js} +2 -2
- package/dist/{index-mmrhhk9a.js → index-37yzyeca.js} +36 -8
- package/dist/index-4x691bz5.js +369 -0
- package/dist/{index-3dkzzh0t.js → index-9j3tx8hy.js} +1 -1
- package/dist/{index-sw8gnq34.js → index-b9j85d29.js} +2 -2
- package/dist/{index-ehpzdy0v.js → index-d1mxrd3p.js} +7 -1
- package/dist/{index-x64cmpej.js → index-dh0vfghk.js} +817 -438
- package/dist/{index-qf20mv7e.js → index-dmvy29z0.js} +1 -1
- package/dist/{index-1h9t9xb5.js → index-eq9wr75c.js} +2 -2
- package/dist/{index-pck9vadr.js → index-vnz6jvdc.js} +1 -1
- package/dist/{index-rw0qrkvq.js → index-z893kjyt.js} +17 -4
- package/dist/{index-btj2qhmc.js → index-zwjhba2r.js} +3 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3230 -2422
- package/dist/{model-resolution-r9jbcz86.js → model-resolution-6w6wzw18.js} +3 -1
- package/dist/plugin/dream-timer.d.ts.map +1 -1
- package/dist/plugin/embedding-routing.d.ts.map +1 -1
- package/dist/plugin/hooks/create-session-hooks.d.ts +14 -0
- package/dist/plugin/hooks/create-session-hooks.d.ts.map +1 -1
- package/dist/plugin/rpc-handlers.d.ts +10 -0
- package/dist/plugin/rpc-handlers.d.ts.map +1 -1
- package/dist/{recomp-orchestrator-dtgww0f0.js → recomp-orchestrator-w7xg48e3.js} +7 -7
- package/dist/{safe-notification-target-tepzc2xa.js → safe-notification-target-2a8py95v.js} +3 -3
- package/dist/{send-session-notification-6v5b72b4.js → send-session-notification-bw7eex6w.js} +4 -4
- package/dist/shared/data-path.d.ts +12 -3
- package/dist/shared/data-path.d.ts.map +1 -1
- package/dist/shared/logger.d.ts.map +1 -1
- package/dist/shared/model-resolution.d.ts +1 -0
- package/dist/shared/model-resolution.d.ts.map +1 -1
- package/dist/shared/rpc-types.d.ts +5 -0
- package/dist/shared/rpc-types.d.ts.map +1 -1
- package/dist/shared/rust-mode-status.d.ts +3 -0
- package/dist/shared/rust-mode-status.d.ts.map +1 -0
- package/dist/shared/status-detail-text.d.ts.map +1 -1
- package/dist/shared/subagent-runner.d.ts +4 -0
- package/dist/shared/subagent-runner.d.ts.map +1 -1
- package/dist/shared/test-temp-dir.d.ts +40 -0
- package/dist/shared/test-temp-dir.d.ts.map +1 -0
- package/dist/shared/write-transaction-timing.d.ts +11 -0
- package/dist/shared/write-transaction-timing.d.ts.map +1 -0
- package/dist/{storage-meta-session-yvtbaw7c.js → storage-meta-session-gr4zn7sq.js} +4 -4
- package/dist/{task-config-a1qmj9bx.js → task-config-gv6s04fp.js} +2 -2
- package/dist/tools/ctx-expand/tools.d.ts.map +1 -1
- package/dist/tools/ctx-memory/tools.d.ts.map +1 -1
- package/dist/tools/ctx-search/tools.d.ts.map +1 -1
- package/dist/transformers-web.js +24570 -0
- package/dist/transformers.node-b1wa0hgx.js +28808 -0
- package/dist/tui/data/context-db.d.ts +10 -3
- package/dist/tui/data/context-db.d.ts.map +1 -1
- package/dist/tui-compiled/data/context-db.d.ts +10 -3
- package/dist/tui-compiled/data/context-db.d.ts.map +1 -1
- package/package.json +7 -3
- package/src/config/index.test.ts +47 -4
- package/src/config/schema/magic-context.test.ts +20 -0
- package/src/config/schema/magic-context.ts +8 -0
- package/src/features/magic-context/dreamer/task-registry.ts +2 -0
- package/src/shared/conflict-detector.test.ts +6 -19
- package/src/shared/conflict-fixer.test.ts +5 -8
- package/src/shared/data-path.test.ts +44 -4
- package/src/shared/data-path.ts +54 -11
- package/src/shared/logger.test.ts +145 -35
- package/src/shared/logger.ts +124 -6
- package/src/shared/model-resolution.test.ts +37 -0
- package/src/shared/model-resolution.ts +7 -0
- package/src/shared/redaction.ts +2 -2
- package/src/shared/rpc-types.ts +5 -0
- package/src/shared/rust-mode-status.ts +3 -0
- package/src/shared/status-detail-text.test.ts +13 -0
- package/src/shared/status-detail-text.ts +2 -0
- package/src/shared/subagent-runner.ts +4 -0
- package/src/shared/test-temp-dir.test.ts +57 -0
- package/src/shared/test-temp-dir.ts +165 -0
- package/src/shared/write-transaction-timing.ts +25 -0
- package/src/tui/data/context-db.test.ts +31 -1
- package/src/tui/data/context-db.ts +17 -61
- package/src/tui/index.tsx +36 -5
- package/src/tui-compiled/data/context-db.ts +17 -61
- package/src/tui-compiled/index.tsx +229 -191
- package/dist/index-8pqfcghm.js +0 -81
- /package/dist/{index-rpgg35ms.js → index-8sseka65.js} +0 -0
|
@@ -18,45 +18,121 @@ type LoggerScenarioResult = {
|
|
|
18
18
|
};
|
|
19
19
|
};
|
|
20
20
|
|
|
21
|
+
type RotationScenarioResult = {
|
|
22
|
+
firstCurrent: string;
|
|
23
|
+
firstPredecessorFirstByte: number;
|
|
24
|
+
current: string;
|
|
25
|
+
predecessorFirstByte: number;
|
|
26
|
+
predecessorSize: number;
|
|
27
|
+
currentSize: number;
|
|
28
|
+
currentMode: number;
|
|
29
|
+
predecessorMode: number;
|
|
30
|
+
files: string[];
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
type HotPathScenarioResult = {
|
|
34
|
+
statCalls: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
21
37
|
const loggerScenario = `
|
|
22
|
-
import
|
|
38
|
+
import fs from "node:fs";
|
|
23
39
|
import * as path from "node:path";
|
|
40
|
+
import { mock } from "bun:test";
|
|
24
41
|
|
|
25
42
|
const root = process.env.LOGGER_SCENARIO_ROOT;
|
|
26
43
|
const loggerModuleUrl = process.env.LOGGER_MODULE_URL;
|
|
27
|
-
|
|
44
|
+
const scenario = process.env.LOGGER_SCENARIO;
|
|
45
|
+
if (!root || !loggerModuleUrl || !scenario) {
|
|
46
|
+
throw new Error("logger scenario environment is incomplete");
|
|
47
|
+
}
|
|
28
48
|
|
|
29
|
-
const logger = await import(loggerModuleUrl);
|
|
30
49
|
const logPath = path.join(root, "nested", "magic-context.log");
|
|
31
50
|
process.env.MAGIC_CONTEXT_LOG_PATH = logPath;
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
logger.flushLogger();
|
|
51
|
+
let statCalls = 0;
|
|
52
|
+
if (scenario === "hot-path") {
|
|
53
|
+
mock.module("node:fs", () => ({
|
|
54
|
+
...fs,
|
|
55
|
+
statSync: (...args) => {
|
|
56
|
+
statCalls++;
|
|
57
|
+
return fs.statSync(...args);
|
|
58
|
+
},
|
|
59
|
+
}));
|
|
42
60
|
}
|
|
43
61
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
62
|
+
const logger = await import(loggerModuleUrl);
|
|
63
|
+
|
|
64
|
+
if (scenario === "rotation") {
|
|
65
|
+
const maxLogBytes = 32 * 1024 * 1024;
|
|
66
|
+
fs.mkdirSync(path.dirname(logPath), { recursive: true });
|
|
67
|
+
fs.writeFileSync(logPath, Buffer.alloc(maxLogBytes, "o"));
|
|
68
|
+
fs.chmodSync(logPath, 0o600);
|
|
69
|
+
fs.writeFileSync(logPath + ".1", "prior predecessor");
|
|
70
|
+
|
|
71
|
+
logger.log("after first rotation");
|
|
72
|
+
logger.flushLogger();
|
|
73
|
+
const firstCurrent = fs.readFileSync(logPath, "utf8");
|
|
74
|
+
const firstPredecessorFirstByte = fs.readFileSync(logPath + ".1")[0];
|
|
75
|
+
|
|
76
|
+
// Switch paths once so the logger rechecks the primary path after this
|
|
77
|
+
// external replacement, as a different plugin process would require.
|
|
78
|
+
fs.writeFileSync(logPath, Buffer.alloc(maxLogBytes, "n"));
|
|
79
|
+
process.env.MAGIC_CONTEXT_LOG_PATH = path.join(root, "alternate.log");
|
|
80
|
+
logger.log("alternate path");
|
|
81
|
+
logger.flushLogger();
|
|
82
|
+
process.env.MAGIC_CONTEXT_LOG_PATH = logPath;
|
|
83
|
+
logger.log("after second rotation");
|
|
84
|
+
logger.flushLogger();
|
|
85
|
+
|
|
86
|
+
const predecessorPath = logPath + ".1";
|
|
87
|
+
console.log(JSON.stringify({
|
|
88
|
+
firstCurrent,
|
|
89
|
+
firstPredecessorFirstByte,
|
|
90
|
+
current: fs.readFileSync(logPath, "utf8"),
|
|
91
|
+
predecessorFirstByte: fs.readFileSync(predecessorPath)[0],
|
|
92
|
+
predecessorSize: fs.statSync(predecessorPath).size,
|
|
93
|
+
currentSize: fs.statSync(logPath).size,
|
|
94
|
+
currentMode: fs.statSync(logPath).mode & 0o777,
|
|
95
|
+
predecessorMode: fs.statSync(predecessorPath).mode & 0o777,
|
|
96
|
+
files: fs.readdirSync(path.dirname(logPath)).filter((name) => name.startsWith("magic-context.log")).sort(),
|
|
97
|
+
}));
|
|
98
|
+
} else if (scenario === "hot-path") {
|
|
99
|
+
for (let index = 0; index < 20; index++) {
|
|
100
|
+
logger.log("hot-path-" + index);
|
|
101
|
+
logger.flushLogger();
|
|
102
|
+
}
|
|
103
|
+
console.log(JSON.stringify({ statCalls }));
|
|
104
|
+
} else {
|
|
105
|
+
logger.log("first");
|
|
106
|
+
logger.log(
|
|
107
|
+
"tokens.input=45000 api_key=sk-proj-abcdefghijklmnopqrstuvwxyzABCDEFGH",
|
|
108
|
+
{ authorization: "Bearer abcdefghijklmnop", totalInputTokens: 132000 },
|
|
109
|
+
);
|
|
110
|
+
logger.flushLogger();
|
|
111
|
+
const healthyDiagnostics = logger.getLoggerDiagnostics();
|
|
112
|
+
|
|
113
|
+
const logDirectory = path.dirname(logPath);
|
|
114
|
+
if (scenario === "recovery") {
|
|
115
|
+
fs.rmSync(logDirectory, { recursive: true, force: true });
|
|
116
|
+
logger.log("second");
|
|
117
|
+
logger.flushLogger();
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// A directory used as the file target makes append fail deterministically on every platform.
|
|
121
|
+
const failedPath = path.join(root, "unwritable-log-target");
|
|
122
|
+
fs.mkdirSync(failedPath, { recursive: true });
|
|
123
|
+
process.env.MAGIC_CONTEXT_LOG_PATH = failedPath;
|
|
124
|
+
logger.log("failed write");
|
|
125
|
+
logger.flushLogger();
|
|
126
|
+
const failedDiagnostics = logger.getLoggerDiagnostics();
|
|
127
|
+
|
|
128
|
+
const content = fs.existsSync(logPath) ? fs.readFileSync(logPath, "utf8") : "";
|
|
129
|
+
console.log(JSON.stringify({
|
|
130
|
+
exists: fs.existsSync(logPath),
|
|
131
|
+
content,
|
|
132
|
+
healthyDiagnostics,
|
|
133
|
+
failedDiagnostics,
|
|
134
|
+
}));
|
|
135
|
+
}
|
|
60
136
|
`;
|
|
61
137
|
|
|
62
138
|
const scenarioRoots: string[] = [];
|
|
@@ -67,9 +143,9 @@ afterEach(() => {
|
|
|
67
143
|
}
|
|
68
144
|
});
|
|
69
145
|
|
|
70
|
-
async function runLoggerScenario(
|
|
71
|
-
scenario: "recovery" | "diagnostics",
|
|
72
|
-
): Promise<
|
|
146
|
+
async function runLoggerScenario<T>(
|
|
147
|
+
scenario: "recovery" | "diagnostics" | "rotation" | "hot-path",
|
|
148
|
+
): Promise<T> {
|
|
73
149
|
const root = mkdtempSync(path.join(os.tmpdir(), "magic-context-logger-test-"));
|
|
74
150
|
scenarioRoots.push(root);
|
|
75
151
|
const child = Bun.spawn({
|
|
@@ -91,19 +167,53 @@ async function runLoggerScenario(
|
|
|
91
167
|
new Response(child.stderr).text(),
|
|
92
168
|
]);
|
|
93
169
|
expect(exitCode, stderr).toBe(0);
|
|
94
|
-
return JSON.parse(stdout.trim()) as
|
|
170
|
+
return JSON.parse(stdout.trim()) as T;
|
|
95
171
|
}
|
|
96
172
|
|
|
97
173
|
describe("logger", () => {
|
|
98
174
|
test("recreates a log directory removed while the process is running", async () => {
|
|
99
|
-
const result = await runLoggerScenario("recovery");
|
|
175
|
+
const result = await runLoggerScenario<LoggerScenarioResult>("recovery");
|
|
100
176
|
|
|
101
177
|
expect(result.exists).toBe(true);
|
|
102
178
|
expect(result.content).toContain("second");
|
|
103
179
|
});
|
|
104
180
|
|
|
181
|
+
test("redacts secrets while preserving numeric diagnostics", async () => {
|
|
182
|
+
const result = await runLoggerScenario<LoggerScenarioResult>("diagnostics");
|
|
183
|
+
|
|
184
|
+
expect(result.content).toContain("tokens.input=45000");
|
|
185
|
+
expect(result.content).toContain('"totalInputTokens":132000');
|
|
186
|
+
expect(result.content).toContain("<REDACTED:api_key>");
|
|
187
|
+
expect(result.content).toContain("<REDACTED:authorization>");
|
|
188
|
+
expect(result.content).not.toContain("abcdefghijklmnopqrstuvwxyzABCDEFGH");
|
|
189
|
+
expect(result.content).not.toContain("abcdefghijklmnop");
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
test("rotates at the 32 MiB bound with one private predecessor", async () => {
|
|
193
|
+
const result = await runLoggerScenario<RotationScenarioResult>("rotation");
|
|
194
|
+
const maxLogBytes = 32 * 1024 * 1024;
|
|
195
|
+
|
|
196
|
+
expect(result.firstCurrent).toContain("after first rotation");
|
|
197
|
+
expect(result.firstPredecessorFirstByte).toBe("o".charCodeAt(0));
|
|
198
|
+
expect(result.current).toContain("after second rotation");
|
|
199
|
+
expect(result.predecessorFirstByte).toBe("n".charCodeAt(0));
|
|
200
|
+
expect(result.predecessorSize).toBeLessThanOrEqual(maxLogBytes);
|
|
201
|
+
expect(result.currentSize).toBeLessThanOrEqual(maxLogBytes);
|
|
202
|
+
expect(result.files).toEqual(["magic-context.log", "magic-context.log.1"]);
|
|
203
|
+
if (process.platform !== "win32") {
|
|
204
|
+
expect(result.currentMode).toBe(0o600);
|
|
205
|
+
expect(result.predecessorMode).toBe(0o600);
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("uses cached size between periodic rotation checks", async () => {
|
|
210
|
+
const result = await runLoggerScenario<HotPathScenarioResult>("hot-path");
|
|
211
|
+
|
|
212
|
+
expect(result.statCalls).toBeLessThanOrEqual(1);
|
|
213
|
+
});
|
|
214
|
+
|
|
105
215
|
test("reports swallowed writes while healthy writes leave the counter at zero", async () => {
|
|
106
|
-
const result = await runLoggerScenario("diagnostics");
|
|
216
|
+
const result = await runLoggerScenario<LoggerScenarioResult>("diagnostics");
|
|
107
217
|
|
|
108
218
|
expect(result.healthyDiagnostics).toEqual({
|
|
109
219
|
swallowedWriteCount: 0,
|
package/src/shared/logger.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { getMagicContextLogPath } from "./data-path";
|
|
4
|
+
import { sanitizeConfigValue, sanitizeDiagnosticText } from "./redaction";
|
|
4
5
|
|
|
5
6
|
const isTestEnv = process.env.NODE_ENV === "test";
|
|
6
7
|
|
|
@@ -8,6 +9,12 @@ let buffer: string[] = [];
|
|
|
8
9
|
let flushTimer: ReturnType<typeof setTimeout> | null = null;
|
|
9
10
|
const FLUSH_INTERVAL_MS = 500;
|
|
10
11
|
const BUFFER_SIZE_LIMIT = 50;
|
|
12
|
+
const MAX_LOG_FILE_BYTES = 32 * 1024 * 1024;
|
|
13
|
+
const SIZE_CHECK_INTERVAL_FLUSHES = 64;
|
|
14
|
+
|
|
15
|
+
let activeLogFile: string | null = null;
|
|
16
|
+
let activeLogSize: number | null = null;
|
|
17
|
+
let flushesSinceSizeCheck = 0;
|
|
11
18
|
|
|
12
19
|
export interface LoggerDiagnostics {
|
|
13
20
|
swallowedWriteCount: number;
|
|
@@ -22,7 +29,9 @@ let lastErrorTime: string | null = null;
|
|
|
22
29
|
function recordSwallowedWrite(error: unknown): void {
|
|
23
30
|
try {
|
|
24
31
|
swallowedWriteCount++;
|
|
25
|
-
lastErrorMessage =
|
|
32
|
+
lastErrorMessage = sanitizeDiagnosticText(
|
|
33
|
+
error instanceof Error ? error.message : String(error),
|
|
34
|
+
);
|
|
26
35
|
lastErrorTime = new Date().toISOString();
|
|
27
36
|
} catch {
|
|
28
37
|
// Diagnostics must not make the logger throw either.
|
|
@@ -33,19 +42,126 @@ function ensureDir(filePath: string): void {
|
|
|
33
42
|
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
34
43
|
}
|
|
35
44
|
|
|
45
|
+
function isMissingFile(error: unknown): boolean {
|
|
46
|
+
return (
|
|
47
|
+
typeof error === "object" &&
|
|
48
|
+
error !== null &&
|
|
49
|
+
"code" in error &&
|
|
50
|
+
(error as { code?: unknown }).code === "ENOENT"
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getCurrentLogSize(logFile: string): number {
|
|
55
|
+
if (
|
|
56
|
+
activeLogFile === logFile &&
|
|
57
|
+
activeLogSize !== null &&
|
|
58
|
+
flushesSinceSizeCheck < SIZE_CHECK_INTERVAL_FLUSHES
|
|
59
|
+
) {
|
|
60
|
+
return activeLogSize;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
try {
|
|
64
|
+
const stat = fs.statSync(logFile);
|
|
65
|
+
if (!stat.isFile()) {
|
|
66
|
+
throw new Error(`Magic Context log path is not a regular file: ${logFile}`);
|
|
67
|
+
}
|
|
68
|
+
// Logs can contain diagnostic context, so tighten files created by older
|
|
69
|
+
// versions before copying or appending any new content.
|
|
70
|
+
fs.chmodSync(logFile, 0o600);
|
|
71
|
+
activeLogFile = logFile;
|
|
72
|
+
activeLogSize = stat.size;
|
|
73
|
+
flushesSinceSizeCheck = 0;
|
|
74
|
+
return stat.size;
|
|
75
|
+
} catch (error) {
|
|
76
|
+
if (!isMissingFile(error)) throw error;
|
|
77
|
+
activeLogFile = logFile;
|
|
78
|
+
activeLogSize = 0;
|
|
79
|
+
flushesSinceSizeCheck = 0;
|
|
80
|
+
return 0;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function capLogData(data: string): string {
|
|
85
|
+
if (Buffer.byteLength(data) <= MAX_LOG_FILE_BYTES) return data;
|
|
86
|
+
|
|
87
|
+
// A single diagnostic payload must not defeat the on-disk bound. Cutting a
|
|
88
|
+
// Buffer can split UTF-8, so trim any replacement character expansion too.
|
|
89
|
+
let bounded = Buffer.from(data).subarray(0, MAX_LOG_FILE_BYTES).toString("utf8");
|
|
90
|
+
while (Buffer.byteLength(bounded) > MAX_LOG_FILE_BYTES) {
|
|
91
|
+
bounded = bounded.slice(0, -1);
|
|
92
|
+
}
|
|
93
|
+
return bounded;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function writeBoundedPredecessor(logFile: string, predecessorPath: string, size: number): void {
|
|
97
|
+
const predecessorFd = fs.openSync(predecessorPath, "w", 0o600);
|
|
98
|
+
try {
|
|
99
|
+
// `openSync(..., "w")` preserves an existing file's mode, so set it
|
|
100
|
+
// before any copied diagnostic text reaches the predecessor.
|
|
101
|
+
fs.fchmodSync(predecessorFd, 0o600);
|
|
102
|
+
const bytesToCopy = Math.min(size, MAX_LOG_FILE_BYTES);
|
|
103
|
+
const sourceFd = fs.openSync(logFile, "r");
|
|
104
|
+
try {
|
|
105
|
+
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, bytesToCopy));
|
|
106
|
+
let remaining = bytesToCopy;
|
|
107
|
+
let position = Math.max(0, size - bytesToCopy);
|
|
108
|
+
while (remaining > 0) {
|
|
109
|
+
const bytesRead = fs.readSync(
|
|
110
|
+
sourceFd,
|
|
111
|
+
chunk,
|
|
112
|
+
0,
|
|
113
|
+
Math.min(chunk.length, remaining),
|
|
114
|
+
position,
|
|
115
|
+
);
|
|
116
|
+
if (bytesRead === 0) break;
|
|
117
|
+
fs.writeSync(predecessorFd, chunk, 0, bytesRead);
|
|
118
|
+
remaining -= bytesRead;
|
|
119
|
+
position += bytesRead;
|
|
120
|
+
}
|
|
121
|
+
} finally {
|
|
122
|
+
fs.closeSync(sourceFd);
|
|
123
|
+
}
|
|
124
|
+
} finally {
|
|
125
|
+
fs.closeSync(predecessorFd);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function rotateLogFile(logFile: string, size: number): void {
|
|
130
|
+
const predecessorPath = `${logFile}.1`;
|
|
131
|
+
writeBoundedPredecessor(logFile, predecessorPath, size);
|
|
132
|
+
// Keep the current inode in place so diagnostic readers can observe a
|
|
133
|
+
// shorter log during rotation instead of seeing the path disappear.
|
|
134
|
+
fs.truncateSync(logFile, 0);
|
|
135
|
+
activeLogSize = 0;
|
|
136
|
+
flushesSinceSizeCheck = 0;
|
|
137
|
+
}
|
|
138
|
+
|
|
36
139
|
function flush(): void {
|
|
37
140
|
if (flushTimer) {
|
|
38
141
|
clearTimeout(flushTimer);
|
|
39
142
|
flushTimer = null;
|
|
40
143
|
}
|
|
41
144
|
if (buffer.length === 0) return;
|
|
42
|
-
const
|
|
145
|
+
const bufferedData = buffer.join("");
|
|
43
146
|
buffer = [];
|
|
44
147
|
try {
|
|
148
|
+
const data = capLogData(bufferedData);
|
|
45
149
|
const logFile = getMagicContextLogPath();
|
|
46
150
|
ensureDir(logFile);
|
|
47
|
-
|
|
151
|
+
let currentSize = getCurrentLogSize(logFile);
|
|
152
|
+
const dataSize = Buffer.byteLength(data);
|
|
153
|
+
if (currentSize > 0 && currentSize + dataSize > MAX_LOG_FILE_BYTES) {
|
|
154
|
+
rotateLogFile(logFile, currentSize);
|
|
155
|
+
currentSize = 0;
|
|
156
|
+
}
|
|
157
|
+
fs.appendFileSync(logFile, data, { encoding: "utf8", mode: 0o600 });
|
|
158
|
+
activeLogFile = logFile;
|
|
159
|
+
activeLogSize = currentSize + dataSize;
|
|
160
|
+
flushesSinceSizeCheck++;
|
|
48
161
|
} catch (error) {
|
|
162
|
+
activeLogFile = null;
|
|
163
|
+
activeLogSize = null;
|
|
164
|
+
flushesSinceSizeCheck = 0;
|
|
49
165
|
recordSwallowedWrite(error);
|
|
50
166
|
}
|
|
51
167
|
}
|
|
@@ -66,9 +182,11 @@ export function log(message: string, data?: unknown): void {
|
|
|
66
182
|
data === undefined
|
|
67
183
|
? ""
|
|
68
184
|
: data instanceof Error
|
|
69
|
-
? ` ${
|
|
70
|
-
|
|
71
|
-
|
|
185
|
+
? ` ${sanitizeDiagnosticText(
|
|
186
|
+
`${data.message}${data.stack ? `\n${data.stack}` : ""}`,
|
|
187
|
+
)}`
|
|
188
|
+
: ` ${JSON.stringify(sanitizeConfigValue(data))}`;
|
|
189
|
+
buffer.push(`[${timestamp}] ${sanitizeDiagnosticText(message)}${serialized}\n`);
|
|
72
190
|
if (buffer.length >= BUFFER_SIZE_LIMIT) {
|
|
73
191
|
flush();
|
|
74
192
|
} else {
|
|
@@ -4,10 +4,29 @@ import {
|
|
|
4
4
|
normalizeModelEntry,
|
|
5
5
|
resolveDreamerTaskModel,
|
|
6
6
|
resolveFallbackEntries,
|
|
7
|
+
resolveHistorianAgentOverrides,
|
|
7
8
|
resolveHistorianModel,
|
|
8
9
|
} from "./model-resolution";
|
|
9
10
|
|
|
10
11
|
describe("model-resolution", () => {
|
|
12
|
+
test("keeps historian temperature opt-in while preserving explicit overrides", () => {
|
|
13
|
+
expect(resolveHistorianAgentOverrides(undefined)).toEqual({
|
|
14
|
+
maxTokens: 32_000,
|
|
15
|
+
});
|
|
16
|
+
expect(
|
|
17
|
+
resolveHistorianAgentOverrides({
|
|
18
|
+
temperature: 0.2,
|
|
19
|
+
maxTokens: 16_000,
|
|
20
|
+
opencode: { model: "google/flash", variant: "low" },
|
|
21
|
+
}),
|
|
22
|
+
).toEqual({
|
|
23
|
+
temperature: 0.2,
|
|
24
|
+
maxTokens: 16_000,
|
|
25
|
+
model: "google/flash",
|
|
26
|
+
variant: "low",
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
|
|
11
30
|
test("normalizes string and object entries to the same model identity", () => {
|
|
12
31
|
expect(normalizeModelEntry("anthropic/sonnet", "opencode")).toEqual({
|
|
13
32
|
model: "anthropic/sonnet",
|
|
@@ -36,6 +55,24 @@ describe("model-resolution", () => {
|
|
|
36
55
|
]);
|
|
37
56
|
});
|
|
38
57
|
|
|
58
|
+
test("keeps the flash calibration available only as an explicit override", () => {
|
|
59
|
+
expect(
|
|
60
|
+
resolveHistorianAgentOverrides({
|
|
61
|
+
opencode: { model: { model: "google/flash", variant: "fast" } },
|
|
62
|
+
pi: { model: "pi/ignored" },
|
|
63
|
+
two_pass: true,
|
|
64
|
+
}),
|
|
65
|
+
).toEqual({
|
|
66
|
+
maxTokens: 32_000,
|
|
67
|
+
two_pass: true,
|
|
68
|
+
model: "google/flash",
|
|
69
|
+
variant: "fast",
|
|
70
|
+
});
|
|
71
|
+
expect(
|
|
72
|
+
resolveHistorianAgentOverrides({ temperature: 0.1, maxTokens: 12_000 }),
|
|
73
|
+
).toMatchObject({ temperature: 0.1, maxTokens: 12_000 });
|
|
74
|
+
});
|
|
75
|
+
|
|
39
76
|
test("does not inherit a block qualifier into unqualified fallbacks", () => {
|
|
40
77
|
const config = {
|
|
41
78
|
historian: {
|
|
@@ -225,3 +225,10 @@ export function resolveOpenCodeAgentOverrides(agent: unknown): Record<string, un
|
|
|
225
225
|
...(primary?.qualifier ? { variant: primary.qualifier } : {}),
|
|
226
226
|
};
|
|
227
227
|
}
|
|
228
|
+
|
|
229
|
+
export function resolveHistorianAgentOverrides(agent: unknown): Record<string, unknown> {
|
|
230
|
+
return {
|
|
231
|
+
maxTokens: 32_000,
|
|
232
|
+
...resolveOpenCodeAgentOverrides(agent),
|
|
233
|
+
};
|
|
234
|
+
}
|
package/src/shared/redaction.ts
CHANGED
|
@@ -108,7 +108,7 @@ export function isSecretKey(key: string): boolean {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
export function sanitizePathString(value: string): string {
|
|
111
|
-
const home = homedir();
|
|
111
|
+
const home = process.env.HOME || process.env.USERPROFILE || homedir();
|
|
112
112
|
const username = userInfo().username;
|
|
113
113
|
let sanitized = value;
|
|
114
114
|
if (home) {
|
|
@@ -132,7 +132,7 @@ const SECRET_TEXT_PATTERNS: Array<{
|
|
|
132
132
|
replacement: "<ANTHROPIC_API_KEY_REDACTED>",
|
|
133
133
|
},
|
|
134
134
|
{
|
|
135
|
-
pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{
|
|
135
|
+
pattern: /\bsk-(?:proj-)?[A-Za-z0-9_-]{12,}/g,
|
|
136
136
|
replacement: "<OPENAI_API_KEY_REDACTED>",
|
|
137
137
|
},
|
|
138
138
|
{
|
package/src/shared/rpc-types.ts
CHANGED
|
@@ -141,12 +141,17 @@ export interface SidebarSnapshot {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
export interface StatusDetail extends SidebarSnapshot {
|
|
144
|
+
/** True when Rust authority has rerouted host tool and historian paths to the module. */
|
|
145
|
+
hostBackendsModuleSide?: boolean;
|
|
144
146
|
/** User-owned model profile selected for this project, or null for the base config. */
|
|
145
147
|
activeProfile: string | null;
|
|
146
148
|
tagCounter: number;
|
|
147
149
|
activeTags: number;
|
|
148
150
|
droppedTags: number;
|
|
149
151
|
totalTags: number;
|
|
152
|
+
/** False when Rust authority supplies only the exact total; active/dropped host-mirror
|
|
153
|
+
* counts are not presented as module truth. Omitted by older RPC servers means true. */
|
|
154
|
+
tagCountsAuthoritative?: boolean;
|
|
150
155
|
activeBytes: number;
|
|
151
156
|
lastResponseTime: number;
|
|
152
157
|
lastNudgeTokens: number;
|
|
@@ -79,4 +79,17 @@ describe("formatStatusDetailMarkdown", () => {
|
|
|
79
79
|
- **Tags:** 4 active, 1 dropped; 2 pending drops
|
|
80
80
|
- **Execute threshold:** 65.0%`);
|
|
81
81
|
});
|
|
82
|
+
|
|
83
|
+
test("shows module-routed host paths only in Rust-mode chat fallback", () => {
|
|
84
|
+
const rustStatus = formatStatusDetailMarkdown({
|
|
85
|
+
...STATUS_FIXTURE,
|
|
86
|
+
hostBackendsModuleSide: true,
|
|
87
|
+
});
|
|
88
|
+
const tsStatus = formatStatusDetailMarkdown(STATUS_FIXTURE);
|
|
89
|
+
|
|
90
|
+
expect(rustStatus).toContain(
|
|
91
|
+
"Host backends → MODULE: ctx_memory, ctx_note; historian: module-side",
|
|
92
|
+
);
|
|
93
|
+
expect(tsStatus).not.toContain("Host backends → MODULE");
|
|
94
|
+
});
|
|
82
95
|
});
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { StatusDetail } from "./rpc-types";
|
|
2
|
+
import { RUST_MODE_HOST_PATHS_LINE } from "./rust-mode-status";
|
|
2
3
|
|
|
3
4
|
function formatCount(value: number): string {
|
|
4
5
|
return Math.round(value).toLocaleString();
|
|
@@ -39,6 +40,7 @@ export function formatStatusDetailMarkdown(detail: StatusDetail): string {
|
|
|
39
40
|
`- **Usage:** ${detail.usagePercentage.toFixed(1)}% (${formatCount(detail.inputTokens)} / ${usableLimit})`,
|
|
40
41
|
`- **Cache lane:** ${formatCacheLane(detail)}`,
|
|
41
42
|
`- **Historian:** ${[historianState, ...historianDetails].join("; ")}`,
|
|
43
|
+
...(detail.hostBackendsModuleSide ? [`- ${RUST_MODE_HOST_PATHS_LINE}`] : []),
|
|
42
44
|
`- **Memory:** ${formatCount(detail.memoryCount)} active; ${formatCount(detail.memoryBlockCount)} injected`,
|
|
43
45
|
`- **Tags:** ${formatCount(detail.activeTags)} active, ${formatCount(detail.droppedTags)} dropped; ${formatCount(detail.pendingOpsCount)} pending drops`,
|
|
44
46
|
`- **Execute threshold:** ${detail.executeThreshold.toFixed(1)}%${detail.executeThresholdClamped ? " (clamped)" : ""}`,
|
|
@@ -67,6 +67,10 @@ export interface SubagentRunOptions {
|
|
|
67
67
|
model?: string | undefined;
|
|
68
68
|
fallbackModels?: readonly ModelInput[];
|
|
69
69
|
timeoutMs?: number | undefined;
|
|
70
|
+
/** Sampling temperature for the provider request when the harness can enforce it. */
|
|
71
|
+
temperature?: number | undefined;
|
|
72
|
+
/** Requested output-token budget for the provider request. */
|
|
73
|
+
maxOutputTokens?: number | undefined;
|
|
70
74
|
cwd?: string | undefined;
|
|
71
75
|
signal?: AbortSignal | undefined;
|
|
72
76
|
/**
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it } from "bun:test";
|
|
2
|
+
import { existsSync, mkdirSync, utimesSync } from "node:fs";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import {
|
|
5
|
+
cleanupTestTempDir,
|
|
6
|
+
createTestTempDir,
|
|
7
|
+
sweepStaleTestTempDirs,
|
|
8
|
+
withTestTempDir,
|
|
9
|
+
} from "./test-temp-dir";
|
|
10
|
+
|
|
11
|
+
const tempDirs: string[] = [];
|
|
12
|
+
|
|
13
|
+
afterEach(() => {
|
|
14
|
+
for (const directory of tempDirs.splice(0)) cleanupTestTempDir(directory);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
function createFixtureRoot(): string {
|
|
18
|
+
const { dir } = createTestTempDir("mc-test-temp-dir-helper-");
|
|
19
|
+
tempDirs.push(dir);
|
|
20
|
+
return dir;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("test temp directories", () => {
|
|
24
|
+
it("removes a root when its fixture callback throws", () => {
|
|
25
|
+
let directory = "";
|
|
26
|
+
|
|
27
|
+
expect(() =>
|
|
28
|
+
withTestTempDir("mc-test-temp-dir-helper-", (tempDir) => {
|
|
29
|
+
directory = tempDir;
|
|
30
|
+
throw new Error("fixture setup failed");
|
|
31
|
+
}),
|
|
32
|
+
).toThrow("fixture setup failed");
|
|
33
|
+
|
|
34
|
+
expect(existsSync(directory)).toBe(false);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("sweeps only aged directories with recognized prefixes", () => {
|
|
38
|
+
const root = createFixtureRoot();
|
|
39
|
+
const staleRecognized = join(root, "mc-config-secret-stale");
|
|
40
|
+
const freshRecognized = join(root, "mc-config-secret-fresh");
|
|
41
|
+
const staleBystander = join(root, "unrelated-tool-stale");
|
|
42
|
+
const nowMs = Date.now();
|
|
43
|
+
|
|
44
|
+
for (const directory of [staleRecognized, freshRecognized, staleBystander]) {
|
|
45
|
+
mkdirSync(directory);
|
|
46
|
+
}
|
|
47
|
+
utimesSync(staleRecognized, new Date(nowMs - 7_200_000), new Date(nowMs - 7_200_000));
|
|
48
|
+
utimesSync(staleBystander, new Date(nowMs - 7_200_000), new Date(nowMs - 7_200_000));
|
|
49
|
+
|
|
50
|
+
const removed = sweepStaleTestTempDirs({ tempDir: root, nowMs });
|
|
51
|
+
|
|
52
|
+
expect(removed).toEqual([staleRecognized]);
|
|
53
|
+
expect(existsSync(staleRecognized)).toBe(false);
|
|
54
|
+
expect(existsSync(freshRecognized)).toBe(true);
|
|
55
|
+
expect(existsSync(staleBystander)).toBe(true);
|
|
56
|
+
});
|
|
57
|
+
});
|