feat(DIS-101): introduce pino structured logger, remove console.* #45

Merged
dev merged 2 commits from phase-1/pino-logger into main 2026-04-09 15:40:47 +00:00
8 changed files with 334 additions and 28 deletions
Showing only changes of commit 1cafaabe2a - Show all commits

247
package-lock.json generated
View file

@ -12,15 +12,18 @@
"cross-spawn": "^7.0.6",
"discord.js": "^14.16.0",
"dotenv": "^16.4.0",
"pino": "^10.3.1",
"yaml": "^2.6.0"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.0",
"@types/cross-spawn": "^6.0.6",
"@types/node": "^22.0.0",
"@types/pino": "^7.0.4",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.57.0",
"pino-pretty": "^13.1.3",
"typescript": "^5.7.0",
"vitest": "^4.1.4"
},
@ -831,6 +834,12 @@
"node": ">= 8"
}
},
"node_modules/@pinojs/redact": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/@pinojs/redact/-/redact-0.4.0.tgz",
"integrity": "sha512-k2ENnmBugE/rzQfEcdWHcCY+/FM3VLzH9cYEsbdsoqrvzAKRhUZeRNhAZvB8OitQJ1TBed3yqWtdjzS6wJKBwg==",
"license": "MIT"
},
"node_modules/@rollup/rollup-android-arm-eabi": {
"version": "4.60.1",
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.1.tgz",
@ -1275,6 +1284,16 @@
"undici-types": "~6.21.0"
}
},
"node_modules/@types/pino": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/@types/pino/-/pino-7.0.4.tgz",
"integrity": "sha512-yKw1UbZOTe7vP1xMQT+oz3FexwgIpBTrM+AC62vWgAkNRULgLTJWfYX+H5/sKPm8VXFbIcXkC3VZPyuaNioZFg==",
"dev": true,
"license": "MIT",
"dependencies": {
"pino": "*"
}
},
"node_modules/@types/ws": {
"version": "8.18.1",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz",
@ -1730,6 +1749,15 @@
"node": ">=12"
}
},
"node_modules/atomic-sleep": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz",
"integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==",
"license": "MIT",
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/balanced-match": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
@ -1891,6 +1919,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/colorette": {
"version": "2.0.20",
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
"dev": true,
"license": "MIT"
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@ -1919,6 +1954,16 @@
"node": ">= 8"
}
},
"node_modules/dateformat": {
"version": "4.6.3",
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
"dev": true,
"license": "MIT",
"engines": {
"node": "*"
}
},
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@ -2330,6 +2375,13 @@
"node": ">=12.0.0"
}
},
"node_modules/fast-copy": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.2.tgz",
"integrity": "sha512-ybA6PDXIXOXivLJK/z9e+Otk7ve13I4ckBvGO5I2RRmBU1gMHLVDJYEuJYhGwez7YNlYji2M2DvVU+a9mSFDlw==",
"dev": true,
"license": "MIT"
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -2350,6 +2402,13 @@
"dev": true,
"license": "MIT"
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
"dev": true,
"license": "MIT"
},
"node_modules/fastq": {
"version": "1.20.1",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
@ -2569,6 +2628,13 @@
"node": ">=8"
}
},
"node_modules/help-me": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
"dev": true,
"license": "MIT"
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
@ -2689,6 +2755,16 @@
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"license": "ISC"
},
"node_modules/joycon": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/js-yaml": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
@ -2903,6 +2979,15 @@
],
"license": "MIT"
},
"node_modules/on-exit-leak-free": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz",
"integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==",
"license": "MIT",
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@ -3031,6 +3116,81 @@
"url": "https://github.com/sponsors/jonschlinkert"
}
},
"node_modules/pino": {
"version": "10.3.1",
"resolved": "https://registry.npmjs.org/pino/-/pino-10.3.1.tgz",
"integrity": "sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==",
"license": "MIT",
"dependencies": {
"@pinojs/redact": "^0.4.0",
"atomic-sleep": "^1.0.0",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pino-std-serializers": "^7.0.0",
"process-warning": "^5.0.0",
"quick-format-unescaped": "^4.0.3",
"real-require": "^0.2.0",
"safe-stable-stringify": "^2.3.1",
"sonic-boom": "^4.0.1",
"thread-stream": "^4.0.0"
},
"bin": {
"pino": "bin.js"
}
},
"node_modules/pino-abstract-transport": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-3.0.0.tgz",
"integrity": "sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==",
"license": "MIT",
"dependencies": {
"split2": "^4.0.0"
}
},
"node_modules/pino-pretty": {
"version": "13.1.3",
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
"integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
"dev": true,
"license": "MIT",
"dependencies": {
"colorette": "^2.0.7",
"dateformat": "^4.6.3",
"fast-copy": "^4.0.0",
"fast-safe-stringify": "^2.1.1",
"help-me": "^5.0.0",
"joycon": "^3.1.1",
"minimist": "^1.2.6",
"on-exit-leak-free": "^2.1.0",
"pino-abstract-transport": "^3.0.0",
"pump": "^3.0.0",
"secure-json-parse": "^4.0.0",
"sonic-boom": "^4.0.1",
"strip-json-comments": "^5.0.2"
},
"bin": {
"pino-pretty": "bin.js"
}
},
"node_modules/pino-pretty/node_modules/strip-json-comments": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
"integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=14.16"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/pino-std-serializers": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-7.1.0.tgz",
"integrity": "sha512-BndPH67/JxGExRgiX1dX0w1FvZck5Wa4aal9198SrRhZjH3GxKQUKIBnYJTdj2HDN3UQAS06HlfcSbQj2OHmaw==",
"license": "MIT"
},
"node_modules/postcss": {
"version": "8.5.9",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.9.tgz",
@ -3097,6 +3257,22 @@
"node": ">= 0.8.0"
}
},
"node_modules/process-warning": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.0.0.tgz",
"integrity": "sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "MIT"
},
"node_modules/pump": {
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz",
@ -3138,6 +3314,12 @@
],
"license": "MIT"
},
"node_modules/quick-format-unescaped": {
"version": "4.0.4",
"resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz",
"integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==",
"license": "MIT"
},
"node_modules/rc": {
"version": "1.2.8",
"resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz",
@ -3167,6 +3349,15 @@
"node": ">= 6"
}
},
"node_modules/real-require": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/real-require/-/real-require-0.2.0.tgz",
"integrity": "sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==",
"license": "MIT",
"engines": {
"node": ">= 12.13.0"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -3294,6 +3485,32 @@
],
"license": "MIT"
},
"node_modules/safe-stable-stringify": {
"version": "2.5.0",
"resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz",
"integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==",
"license": "MIT",
"engines": {
"node": ">=10"
}
},
"node_modules/secure-json-parse": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz",
"integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==",
"dev": true,
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fastify"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fastify"
}
],
"license": "BSD-3-Clause"
},
"node_modules/semver": {
"version": "7.7.4",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz",
@ -3379,6 +3596,15 @@
"simple-concat": "^1.0.0"
}
},
"node_modules/sonic-boom": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz",
"integrity": "sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==",
"license": "MIT",
"dependencies": {
"atomic-sleep": "^1.0.0"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@ -3389,6 +3615,15 @@
"node": ">=0.10.0"
}
},
"node_modules/split2": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz",
"integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==",
"license": "ISC",
"engines": {
"node": ">= 10.x"
}
},
"node_modules/stackback": {
"version": "0.0.2",
"resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
@ -3482,6 +3717,18 @@
"dev": true,
"license": "MIT"
},
"node_modules/thread-stream": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-4.0.0.tgz",
"integrity": "sha512-4iMVL6HAINXWf1ZKZjIPcz5wYaOdPhtO8ATvZ+Xqp3BTdaqtAwQkNmKORqcIo5YkQqGXq5cwfswDwMqqQNrpJA==",
"license": "MIT",
"dependencies": {
"real-require": "^0.2.0"
},
"engines": {
"node": ">=20"
}
},
"node_modules/tinybench": {
"version": "2.9.0",
"resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",

View file

@ -11,7 +11,9 @@
"lint": "eslint src --ext .ts"
},
"vitest": {
"include": ["tests/**/*.test.ts"],
"include": [
"tests/**/*.test.ts"
],
"environment": "node"
},
"dependencies": {
@ -19,15 +21,18 @@
"cross-spawn": "^7.0.6",
"discord.js": "^14.16.0",
"dotenv": "^16.4.0",
"pino": "^10.3.1",
"yaml": "^2.6.0"
},
"devDependencies": {
"@types/better-sqlite3": "^7.6.0",
"@types/cross-spawn": "^6.0.6",
"@types/node": "^22.0.0",
"@types/pino": "^7.0.4",
"@typescript-eslint/eslint-plugin": "^8.0.0",
"@typescript-eslint/parser": "^8.0.0",
"eslint": "^8.57.0",
"pino-pretty": "^13.1.3",
"typescript": "^5.7.0",
"vitest": "^4.1.4"
},

View file

@ -11,6 +11,9 @@ import { DisclawDatabase } from "./db/database";
import { DisclawConfig, EnvConfig } from "./config/loader";
import { newAgentCommand, handleNewAgent } from "./commands/new-agent";
import { routeMessage } from "./router";
import { childLogger } from "./runtime/logger";
const log = childLogger("bot");
export async function startBot(
envConfig: EnvConfig,
@ -31,18 +34,18 @@ export async function startBot(
// --- Event: Ready ---
client.once("ready", async () => {
if (!client.user) return;
console.log(`Bot online as ${client.user.tag}`);
log.info({ tag: client.user.tag }, "Bot online");
const guild = envConfig.DISCORD_GUILD_ID
? client.guilds.cache.get(envConfig.DISCORD_GUILD_ID)
: client.guilds.cache.first();
if (!guild) {
console.error("No guild found. Is the bot added to a server?");
log.error("No guild found. Is the bot added to a server?");
process.exit(1);
}
console.log(`Connected to guild: ${guild.name} (${guild.id})`);
log.info({ guildName: guild.name, guildId: guild.id }, "Connected to guild");
// --- Ensure management channel exists ---
const managementName = disclawConfig.management_channel;
@ -55,7 +58,7 @@ export async function startBot(
);
if (existingChannel) {
managementChannelId = existingWorkspace.channel_id;
console.log(`Management channel found: #${managementName}`);
log.info({ channelName: managementName }, "Management channel found");
} else {
// Channel was deleted from Discord, recreate it
existingWorkspace = undefined;
@ -80,7 +83,7 @@ export async function startBot(
);
managementChannelId = found.id;
mgmtChannel = found;
console.log(`Adopted existing channel #${managementName}`);
log.info({ channelName: managementName }, "Adopted existing channel");
} else {
const newChannel = await guild.channels.create({
name: managementName,
@ -95,7 +98,7 @@ export async function startBot(
);
managementChannelId = newChannel.id;
mgmtChannel = newChannel;
console.log(`Created management channel #${managementName}`);
log.info({ channelName: managementName }, "Created management channel");
}
// Send welcome message using the direct channel reference
@ -118,9 +121,9 @@ export async function startBot(
body: [newAgentCommand.toJSON()],
}
);
console.log("Slash commands registered.");
log.info("Slash commands registered.");
} catch (err) {
console.error("Failed to register slash commands:", err);
log.error({ err }, "Failed to register slash commands");
}
});

View file

@ -3,6 +3,9 @@ import * as os from "os";
import * as path from "path";
import * as dotenv from "dotenv";
import * as YAML from "yaml";
import { childLogger } from "../runtime/logger";
const log = childLogger("config");
export interface DisclawConfig {
workspaces_root: string;
@ -37,10 +40,9 @@ export function expandWorkspacesRoot(raw: string, repoRoot: string): string {
const isInsideRepo = !rel.startsWith("..") && !path.isAbsolute(rel);
if (isInsideRepo) {
console.warn(
"[DisClaw] WARNING: workspaces_root lies inside the DisClaw repository (" +
resolved +
"). " +
log.warn(
{ workspacesRoot: resolved },
"workspaces_root lies inside the DisClaw repository. " +
"Claude Code walks parent directories and concatenates every CLAUDE.md it finds. " +
"A workspace under the repo root will inherit the project CLAUDE.md, " +
"leaking DisClaw system instructions into every agent context. " +

View file

@ -3,17 +3,20 @@ import { loadEnv, loadDisclawConfig } from "./config/loader";
import { DisclawDatabase } from "./db/database";
import { startBot } from "./bot";
import { acquireLock } from "./runtime/lockfile";
import { childLogger } from "./runtime/logger";
const log = childLogger("main");
const ROOT_DIR = path.resolve(__dirname, "..");
async function main(): Promise<void> {
console.log("DisClaw starting...");
log.info("DisClaw starting...");
// 1. Load configuration
const envConfig = loadEnv(ROOT_DIR);
const disclawConfig = loadDisclawConfig(ROOT_DIR);
console.log(
`Config loaded. Workspaces root: ${disclawConfig.workspaces_root}`
log.info(
{ workspacesRoot: disclawConfig.workspaces_root },
"Config loaded"
);
// 2. Acquire startup lock (before DB init)
@ -24,14 +27,14 @@ async function main(): Promise<void> {
// 3. Initialize database
const dbPath = path.join(ROOT_DIR, "data", "disclaw.db");
const db = new DisclawDatabase(dbPath);
console.log(`Database initialized at ${dbPath}`);
log.info({ dbPath }, "Database initialized");
// 4. Start Discord bot
await startBot(envConfig, disclawConfig, db);
// Graceful shutdown
const shutdown = (): void => {
console.log("\nShutting down...");
log.info("Shutting down...");
db.close();
lockRelease();
process.exit(0);
@ -42,6 +45,6 @@ async function main(): Promise<void> {
}
main().catch((err) => {
console.error("Fatal error:", err);
log.error({ err }, "Fatal error");
process.exit(1);
});

16
src/runtime/logger.ts Normal file
View file

@ -0,0 +1,16 @@
import pino from "pino";
export const rootLogger = pino({
level: process.env.LOG_LEVEL ?? "info",
transport:
process.env.NODE_ENV !== "production"
? { target: "pino-pretty" }
: undefined,
});
/**
* Returns a child logger that includes `component` in every log entry.
*/
export function childLogger(component: string): pino.Logger {
return rootLogger.child({ component });
}

31
tests/unit/logger.test.ts Normal file
View file

@ -0,0 +1,31 @@
import { describe, it, expect } from "vitest";
import { childLogger, rootLogger } from "../../src/runtime/logger";
import pino from "pino";
describe("childLogger", () => {
it("returns a pino logger instance", () => {
const logger = childLogger("runner");
expect(logger).toBeDefined();
expect(typeof logger.info).toBe("function");
expect(typeof logger.error).toBe("function");
});
it("sets component binding on child logger", () => {
const logger = childLogger("runner");
// pino child loggers expose their bindings via .bindings()
const bindings = (logger as pino.Logger).bindings();
expect(bindings.component).toBe("runner");
});
it("rootLogger is a pino logger", () => {
expect(typeof rootLogger.info).toBe("function");
expect(typeof rootLogger.child).toBe("function");
});
it("different component names produce distinct bindings", () => {
const runnerLog = childLogger("runner");
const botLog = childLogger("bot");
expect(runnerLog.bindings().component).toBe("runner");
expect(botLog.bindings().component).toBe("bot");
});
});

View file

@ -2,6 +2,7 @@ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import * as os from "os";
import * as path from "path";
import { expandWorkspacesRoot } from "../../src/config/loader";
import { rootLogger } from "../../src/runtime/logger";
const HOME = os.homedir();
// Use an absolute path that is portable across Linux, macOS, and Windows.
@ -10,7 +11,7 @@ const REPO_ROOT = path.join(os.tmpdir(), "fake-disclaw-repo");
describe("expandWorkspacesRoot", () => {
beforeEach(() => {
vi.spyOn(console, "warn").mockImplementation(() => undefined);
vi.spyOn(rootLogger, "warn").mockImplementation(() => undefined);
});
afterEach(() => {
@ -40,24 +41,22 @@ describe("expandWorkspacesRoot", () => {
expect(path.isAbsolute(result)).toBe(true);
});
it("(d) triggers console.warn when workspaces_root is inside the repo", () => {
it("(d) triggers logger.warn when workspaces_root is inside the repo", () => {
expandWorkspacesRoot("workspaces", REPO_ROOT);
expect(console.warn).toHaveBeenCalledOnce();
expect(console.warn).toHaveBeenCalledWith(
expect.stringContaining("[DisClaw] WARNING")
);
expect(console.warn).toHaveBeenCalledWith(
expect(rootLogger.warn).toHaveBeenCalledOnce();
expect(rootLogger.warn).toHaveBeenCalledWith(
expect.objectContaining({ workspacesRoot: expect.stringContaining(REPO_ROOT) }),
expect.stringContaining("CLAUDE.md")
);
});
it("(d) does NOT warn when workspaces_root is outside the repo", () => {
expandWorkspacesRoot("~/.disclaw/workspaces", REPO_ROOT);
expect(console.warn).not.toHaveBeenCalled();
expect(rootLogger.warn).not.toHaveBeenCalled();
});
it("(d) does NOT warn when an absolute path outside the repo is given", () => {
expandWorkspacesRoot("/tmp/disclaw-workspaces", REPO_ROOT);
expect(console.warn).not.toHaveBeenCalled();
expect(rootLogger.warn).not.toHaveBeenCalled();
});
});