Skip to content

Commit 8d70668

Browse files
committed
Refactor test temporary directory management
- Introduced a new utility module `test_temp.ts` to handle temporary directory creation and cleanup for tests. - Replaced direct calls to `Deno.makeTempDir` and `Deno.remove` with utility functions `makeTestTempDir`, `removePathIfPresent`, and `withTestTempDir` across multiple test files. - Ensured that temporary directories are created under a shared `.test-tmp` directory to avoid cluttering the project root. - Improved cleanup logic to remove the test temp root if it becomes empty after test execution.
1 parent 1c01943 commit 8d70668

17 files changed

Lines changed: 388 additions & 399 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Deno cache and coverage artifacts
22
.deno/
33
coverage/
4+
.test-tmp/
45

56
# Editor and OS artifacts
67
.DS_Store

.kato/test-tmp/in-chat-record-commands/new.md

Lines changed: 0 additions & 10 deletions
This file was deleted.

apps/daemon/src/orchestrator/daemon_runtime.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1370,9 +1370,10 @@ async function applyControlCommandsForEvent(
13701370
);
13711371
const activeOnResolvedDestination = active &&
13721372
active.outputPath === resolvedDestination;
1373-
const existingRecordingId = sessionEventState.destinationRecordingIds.get(
1374-
resolvedDestination,
1375-
);
1373+
const existingRecordingId = sessionEventState.destinationRecordingIds
1374+
.get(
1375+
resolvedDestination,
1376+
);
13761377
const recordingIdToUse = existingRecordingId ??
13771378
(activeOnResolvedDestination ? active.recordingId : undefined) ??
13781379
crypto.randomUUID();

deno.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
"fmt:check": "deno fmt --check deno.json main.ts main_test.ts apps shared tests",
1111
"lint": "deno lint main.ts main_test.ts apps shared tests",
1212
"check": "deno check main.ts main_test.ts apps/**/*.ts shared/**/*.ts tests/**/*.ts",
13-
"test": "deno test --allow-read --allow-write=.kato --allow-env=KATO_LOGGING_OPERATIONAL_LEVEL,KATO_LOGGING_AUDIT_LEVEL,HOME,USERPROFILE main_test.ts tests/**/*.ts",
13+
"test": "deno test --allow-read --allow-write=.test-tmp --allow-env=KATO_LOGGING_OPERATIONAL_LEVEL,KATO_LOGGING_AUDIT_LEVEL,HOME,USERPROFILE main_test.ts tests/**/*.ts",
1414
"ci": "deno task fmt:check && deno task lint && deno task check --frozen && deno task test --frozen"
1515
},
1616
"imports": {

dev-docs/notes/conv.2026.2026-02-27-per-workspace-daemons

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,4 +62,41 @@ I also removed the now-obsolete open question about CLI export directory behavio
6262
No tests were run since this was a doc-only edit.
6363

6464
1. If you want, I can make one more pass on the note to lock the non-existent-directory rule (for example: existing dir only vs trailing slash also counts as dir).
65-
2. If you want to start implementation, the next sensible step is updating the config contract and runtime config loader.
65+
2. If you want to start implementation, the next sensible step is updating the config contract and runtime config loader.
66+
67+
# User_2026-02-27_2203_46
68+
69+
#1 yes.
70+
71+
I will start a new conversation for implementation.
72+
73+
**Locking directory-path rule in docs**
74+
75+
# gpt-5.3-codex_2026-02-27_2203_57
76+
77+
I’m locking the directory-path rule in the task note so implementation has a deterministic contract: existing directory always counts, and a non-existent path counts as a directory only when the user explicitly signals it with a trailing separator.
78+
79+
# gpt-5.3-codex_2026-02-27_2203_59
80+
81+
I’m patching the note now in the path rules, tests, and risk section so the implementation conversation can treat that behavior as settled.
82+
83+
**Verifying nested bullet formatting**
84+
85+
# gpt-5.3-codex_2026-02-27_2204_14
86+
87+
I’m checking the edited section once so the rule reads cleanly and doesn’t conflict with the rest of the task note.
88+
89+
**Summarizing update readiness**
90+
91+
# gpt-5.3-codex_2026-02-27_2204_19
92+
93+
I updated [task.2026.2026-02-27-per-workspace-daemons.md](/home/djradon/hub/spectacular-voyage/kato/dev-docs/notes/task.2026.2026-02-27-per-workspace-daemons.md) to lock the directory-detection rule.
94+
95+
The note now treats directory arguments as:
96+
- existing directory: always a directory
97+
- non-existent path ending with a path separator: directory
98+
- otherwise: file path
99+
100+
I also reflected that in the tests, acceptance criteria, and risk/mitigation text so the implementation thread can treat it as settled.
101+
102+
No tests were run since this was a doc-only edit.

dev-docs/notes/task.2026.2026-02-27-per-workspace-daemons.md

Lines changed: 32 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
id: 3hcvyoruw53sn36me1xjvfz
33
title: 2026 02 27 per Workspace Daemons
44
desc: ''
5-
updated: 1772257805807
5+
updated: 1772259284723
66
created: 1772252011318
77
---
88

@@ -50,21 +50,21 @@ Add the minimum config surface needed to make workspace-local setups practical:
5050

5151
Add two optional runtime-config fields:
5252

53-
- `defaultOutputPath?: string`
53+
- `defaultOutputDir?: string`
5454
- `filenameTemplate?: string`
5555

5656
Recommended semantics:
5757

58-
- `defaultOutputPath` is the base directory used when Kato needs to generate a
58+
- `defaultOutputDir` is the base directory used when Kato needs to generate a
5959
destination for a pathless command.
6060
- `filenameTemplate` is the filename pattern used under that directory.
6161
- If either field is omitted, Kato falls back to the current behavior so older
6262
configs remain valid.
6363

64-
Recommended default values (to preserve current behavior):
64+
Recommended default values:
6565

66-
- `defaultOutputPath: ~/.kato/recordings`
67-
- `filenameTemplate: "{provider}-{shortSessionId}-{YYYY}{MM}{DD}-{HH}{mm}{ss}.md"`
66+
- `defaultOutputDir: .kato/recordings`
67+
- `filenameTemplate: "conv.{YYYY}-{MM}-{DD}_{HH}{mm}-{snippetSlug}-{provider}.md"`
6868

6969
## Config Template For New Workspaces
7070

@@ -92,7 +92,7 @@ Important path rule:
9292

9393
- Relative paths in the template should be copied into the new workspace config
9494
as relative values, not resolved against `~/.kato/` at scaffold time.
95-
- That way a template value like `defaultOutputPath: dev-docs/notes` remains
95+
- That way a template value like `defaultOutputDir: dev-docs/notes` remains
9696
portable and resolves relative to each workspace config after initialization.
9797

9898
## Filename Template Contract
@@ -128,8 +128,8 @@ Rules:
128128

129129
- Generated paths should resolve as:
130130
`join(resolvedDefaultOutputDir, renderedFilename)`.
131-
- If `defaultOutputPath` is absolute, use it directly.
132-
- If `defaultOutputPath` is relative, resolve it against the config root
131+
- If `defaultOutputDir` is absolute, use it directly.
132+
- If `defaultOutputDir` is relative, resolve it against the config root
133133
(`katoDir` / the directory containing the runtime config), not against the
134134
daemon process cwd.
135135
- Explicit relative command arguments should also resolve against that same
@@ -141,13 +141,18 @@ Rules:
141141
`::export` when a path argument is present but names a directory.
142142
- The same directory-argument behavior should apply to CLI
143143
`kato export <session> --output <dir>`.
144+
- Directory detection rule:
145+
- [ ] an existing directory always counts as a directory
146+
- [ ] a non-existent path counts as a directory only if the raw argument ends
147+
with a path separator
148+
- [ ] otherwise, treat it as a file path
144149
- The final generated path still goes through the existing write-path policy
145150
gate.
146151
- `allowedWriteRoots` remains the hard security boundary.
147152

148153
## Scope
149154

150-
- [ ] Add `defaultOutputPath` and `filenameTemplate` to the runtime config
155+
- [ ] Add `defaultOutputDir` and `filenameTemplate` to the runtime config
151156
contract.
152157
- [ ] Add a global config-template file that `kato init` can use to seed new
153158
workspace configs.
@@ -171,7 +176,7 @@ Rules:
171176
- [ ] Update
172177
[/home/djradon/hub/spectacular-voyage/kato/shared/src/contracts/config.ts](/home/djradon/hub/spectacular-voyage/kato/shared/src/contracts/config.ts)
173178
to add:
174-
- [ ] `defaultOutputPath?: string`
179+
- [ ] `defaultOutputDir?: string`
175180
- [ ] `filenameTemplate?: string`
176181

177182
### 2. Extend runtime config parsing/defaults
@@ -180,8 +185,8 @@ Rules:
180185
[/home/djradon/hub/spectacular-voyage/kato/apps/daemon/src/config/runtime_config.ts](/home/djradon/hub/spectacular-voyage/kato/apps/daemon/src/config/runtime_config.ts)
181186
to:
182187
- [ ] validate both fields as strings when present
183-
- [ ] expand `~` for `defaultOutputPath` during load
184-
- [ ] resolve/normalize relative `defaultOutputPath` against `katoDir`
188+
- [ ] expand `~` for `defaultOutputDir` during load
189+
- [ ] resolve/normalize relative `defaultOutputDir` against `katoDir`
185190
- [ ] preserve home shorthand when generating default config (same style as
186191
existing path fields)
187192
- [ ] clone both fields correctly
@@ -208,7 +213,7 @@ Rules:
208213
- [ ] replace `resolveDefaultRecordingRootDir()`
209214
- [ ] replace `makeDefaultRecordingDestinationPath(...)`
210215
- [ ] introduce a config-aware helper that builds the generated destination
211-
from `defaultOutputPath` + `filenameTemplate`
216+
from `defaultOutputDir` + `filenameTemplate`
212217
- [ ] use that helper anywhere pathless `::init`, `::record`, `::capture`,
213218
or `::export` currently falls back to a generated path
214219
- [ ] generate timestamp pieces from local time, not `toISOString()`
@@ -231,7 +236,7 @@ Rules:
231236
### 6. Surface new defaults in `kato init`
232237

233238
- [ ] Ensure the generated config written by `kato init` includes
234-
`defaultOutputPath` and `filenameTemplate`.
239+
`defaultOutputDir` and `filenameTemplate`.
235240
- [ ] If the global config template exists, use it to seed the new workspace
236241
config before writing the file.
237242

@@ -242,7 +247,7 @@ Rules:
242247
- [ ] accept valid values
243248
- [ ] reject invalid types
244249
- [ ] preserve backward compatibility when fields are missing
245-
- [ ] verify relative `defaultOutputPath` resolves against config location
250+
- [ ] verify relative `defaultOutputDir` resolves against config location
246251
- [ ] verify `filenameTemplate` loads and clones correctly
247252
- [ ] Update
248253
[/home/djradon/hub/spectacular-voyage/kato/tests/daemon-cli_test.ts](/home/djradon/hub/spectacular-voyage/kato/tests/daemon-cli_test.ts)
@@ -267,6 +272,8 @@ Rules:
267272
filename
268273
- [ ] explicit directory-path `::export` uses that directory plus generated
269274
filename
275+
- [ ] non-existent path with trailing separator is treated as a directory
276+
- [ ] non-existent path without trailing separator is treated as a file path
270277
- [ ] filename template rendering uses expected tokens, including
271278
`snippetSlug`
272279
- [ ] local-time date-part tokens render correctly
@@ -283,18 +290,21 @@ Rules:
283290

284291
## Acceptance Criteria
285292

286-
- [ ] A runtime config can define `defaultOutputPath` and
293+
- [ ] A runtime config can define `defaultOutputDir` and
287294
`filenameTemplate`.
288295
- [ ] Pathless `::init`, `::record`, and `::capture` use those defaults instead
289296
of the current hard-coded `~/.kato/recordings/...` path.
290-
- [ ] Pathless `::export` uses the same generator and `defaultOutputPath`.
297+
- [ ] Pathless `::export` uses the same generator and `defaultOutputDir`.
291298
- [ ] Explicit relative command paths are accepted and resolve against the
292299
workspace config root.
293300
- [ ] If an explicit output argument resolves to a directory, that directory is
294301
used as the output root and the filename is still generated.
295302
- [ ] CLI `kato export <id> --output <dir>` uses that directory as the output
296303
root and still generates the filename.
297-
- [ ] Relative `defaultOutputPath` is resolved against config location, not
304+
- [ ] Existing directories are treated as directory arguments, and non-existent
305+
paths are treated as directory arguments only when the raw input ends with a
306+
path separator.
307+
- [ ] Relative `defaultOutputDir` is resolved against config location, not
298308
daemon cwd.
299309
- [ ] `kato init` can seed a new workspace config from a reusable template in
300310
`~/.kato/kato-config.template.yaml`, when present.
@@ -306,7 +316,7 @@ Rules:
306316

307317
## Risks and Mitigations
308318

309-
- Risk: `defaultOutputPath` is ambiguous (file path vs directory path).
319+
- Risk: `defaultOutputDir` is ambiguous (file path vs directory path).
310320
Mitigation: define it explicitly as a base directory for generated
311321
destinations.
312322
- Risk: template surface grows into a mini DSL.
@@ -318,8 +328,8 @@ Rules:
318328
write-path policy gate on the final path.
319329
- Risk: directory-path detection may be ambiguous for non-existent targets.
320330
Mitigation: define the contract explicitly (existing directory always counts
321-
as directory; trailing-separator handling may also opt into directory mode if
322-
desired).
331+
as directory; non-existent path requires a trailing separator to opt into
332+
directory mode).
323333
- Risk: a broken global config template could silently create bad workspace
324334
configs.
325335
Mitigation: validate the template before use and fail init clearly on errors.

tests/daemon-cli_test.ts

Lines changed: 7 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
type RuntimeConfigStoreLike,
2121
type WritePathPolicyGateLike,
2222
} from "../apps/daemon/src/mod.ts";
23+
import { makeTestTempDir, removePathIfPresent } from "./test_temp.ts";
2324

2425
function makeRuntimeHarness(runtimeDir: string) {
2526
const stdout: string[] = [];
@@ -681,11 +682,7 @@ Deno.test("runDaemonCli uses control queue and status snapshot stores", async ()
681682
});
682683

683684
Deno.test("runDaemonCli queues export and handles clean in CLI", async () => {
684-
await Deno.mkdir(".kato/test-tmp", { recursive: true });
685-
const rootDir = await Deno.makeTempDir({
686-
dir: ".kato/test-tmp",
687-
prefix: "daemon-cli-clean-",
688-
});
685+
const rootDir = await makeTestTempDir("daemon-cli-clean-");
689686
const runtimeDir = `${rootDir}/runtime`;
690687

691688
try {
@@ -755,16 +752,12 @@ Deno.test("runDaemonCli queues export and handles clean in CLI", async () => {
755752
assertEquals(await Deno.readTextFile(auditLogPath), "");
756753
assertEquals(await Deno.readTextFile(exportsLogPath), "");
757754
} finally {
758-
await Deno.remove(rootDir, { recursive: true });
755+
await removePathIfPresent(rootDir);
759756
}
760757
});
761758

762759
Deno.test("runDaemonCli clean --sessions removes old persisted session artifacts", async () => {
763-
await Deno.mkdir(".kato/test-tmp", { recursive: true });
764-
const rootDir = await Deno.makeTempDir({
765-
dir: ".kato/test-tmp",
766-
prefix: "daemon-cli-clean-sessions-",
767-
});
760+
const rootDir = await makeTestTempDir("daemon-cli-clean-sessions-");
768761
const runtimeDir = `${rootDir}/runtime`;
769762

770763
try {
@@ -821,16 +814,12 @@ Deno.test("runDaemonCli clean --sessions removes old persisted session artifacts
821814
await Deno.stat(recentMetaPath);
822815
await Deno.stat(recentTwinPath);
823816
} finally {
824-
await Deno.remove(rootDir, { recursive: true });
817+
await removePathIfPresent(rootDir);
825818
}
826819
});
827820

828821
Deno.test("runDaemonCli clean --sessions dry-run reports candidate counts", async () => {
829-
await Deno.mkdir(".kato/test-tmp", { recursive: true });
830-
const rootDir = await Deno.makeTempDir({
831-
dir: ".kato/test-tmp",
832-
prefix: "daemon-cli-clean-sessions-dry-",
833-
});
822+
const rootDir = await makeTestTempDir("daemon-cli-clean-sessions-dry-");
834823
const runtimeDir = `${rootDir}/runtime`;
835824

836825
try {
@@ -870,7 +859,7 @@ Deno.test("runDaemonCli clean --sessions dry-run reports candidate counts", asyn
870859
await Deno.stat(oldMetaPath);
871860
await Deno.stat(oldTwinPath);
872861
} finally {
873-
await Deno.remove(rootDir, { recursive: true });
862+
await removePathIfPresent(rootDir);
874863
}
875864
});
876865

tests/daemon-control-plane_test.ts

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,12 @@ import {
44
DaemonControlRequestFileStore,
55
DaemonStatusSnapshotFileStore,
66
} from "../apps/daemon/src/mod.ts";
7+
import { withTestTempDir } from "./test_temp.ts";
78

89
async function withTempRuntimeDir(
910
run: (runtimeDir: string) => Promise<void>,
1011
): Promise<void> {
11-
await Deno.mkdir(".kato/test-tmp", { recursive: true });
12-
const runtimeDir = await Deno.makeTempDir({
13-
dir: ".kato/test-tmp",
14-
prefix: "daemon-control-plane-",
15-
});
16-
17-
try {
18-
await run(runtimeDir);
19-
} finally {
20-
await Deno.remove(runtimeDir, { recursive: true });
21-
}
12+
await withTestTempDir("daemon-control-plane-", run);
2213
}
2314

2415
Deno.test("DaemonStatusSnapshotFileStore persists and loads snapshots", async () => {

0 commit comments

Comments
 (0)