Skip to content

Commit ebae8a1

Browse files
authored
feat: add dosu hooks for Claude Code knowledge injection (#74)
* feat: add dosu hooks for Claude Code knowledge injection Turns the validated prototype into a production 'dosu hooks' command surface that injects Dosu knowledge into Claude Code automatically and non-blockingly. - user-prompt-submit: create a knowledge ticket (fire-and-forget) + inject a short 'lookup started' note - post-tool-use: poll (cooldown-throttled) and inject the route map exactly once - stop (opt-in): non-blocking last-chance delivery - install/uninstall/doctor for claude-code; writes a bare 'dosu hooks <event>' command into .claude/settings.local.json only (never tracked config) - in-CLI fake ticket API + unit/integration tests; silent-no-op-on-failure so a hook never disrupts the agent - save nudge when the server reports a knowledge gap (save_recommended) * fix: guard Stop against bare save-nudge and tighten installer detection Review follow-ups on the dosu hooks surface: - Stop hook only blocks for real knowledge; a knowledge-gap ticket (empty context) is consumed but not blocked on, so the agent is never held open just for a save nudge - isDosuHookCommand requires 'dosu' to be the command name (optionally path-prefixed), not merely an argument — so a user hook whose argument contains 'dosu hooks' is no longer misidentified and deleted on uninstall - fake-server: handle mid-stream request errors so a socket error can't crash the dev server * chore: drop the test-only fake ticket server (not shipped) fake-server.ts + its two tests were test/dev-only infra: no production code imported them, so they were already tree-shaken out of the published bundle (bin/dosu.js). Removing them slims the changeset with zero behavior change. Unit tests still cover the hook runtime + the §3.6 wire-contract parsing, and coverage stays above the 95/90/80/95 gate (95.23/90.55/98.11/95.23). * refactor: self-document hooks install/uninstall and validate the agent Constrain the <agent> positional to the supported set (claude-code) via commander .choices() so an unknown agent fails fast with a clear message, and add --help examples for install/uninstall. * refactor: drop plan/MVP/demo references from hooks comments and messages Strip 'plan §x' comment references, the 'The MVP supports' wording in user-facing errors (now 'Supported agents/scopes'), and demo/lineage notes. Behavior unchanged except the reworded error strings. --------- Co-authored-by: Caspian Zhao <user.name>
1 parent 4a88ac1 commit ebae8a1

12 files changed

Lines changed: 2006 additions & 2 deletions

src/cli/cli.test.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,4 +109,24 @@ describe("CLI", () => {
109109
expect(cmd?.options.find((o) => o.long === "--tail")).toBeDefined();
110110
expect(cmd?.options.find((o) => o.long === "--clear")).toBeDefined();
111111
});
112+
113+
it("has hooks command with entrypoint and lifecycle subcommands", () => {
114+
const program = createProgram();
115+
const cmd = program.commands.find((c) => c.name() === "hooks");
116+
expect(cmd).toBeDefined();
117+
const names = cmd?.commands.map((c) => c.name()) ?? [];
118+
expect(names).toEqual(
119+
expect.arrayContaining([
120+
"user-prompt-submit",
121+
"post-tool-use",
122+
"stop",
123+
"status",
124+
"install",
125+
"uninstall",
126+
"doctor",
127+
]),
128+
);
129+
const install = cmd?.commands.find((c) => c.name() === "install");
130+
expect(install?.options.find((o) => o.long === "--with-stop")).toBeDefined();
131+
});
112132
});

src/cli/cli.ts

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { analyticsCommand } from "../commands/analytics";
88
import { askCommand } from "../commands/ask";
99
import { deploymentsCommand } from "../commands/deployments";
1010
import { docsCommand } from "../commands/docs";
11+
import { hooksCommand } from "../commands/hooks";
1112
import { insightsCommand } from "../commands/insights";
1213
import { integrationsCommand } from "../commands/integrations";
1314
import { knowledgeCommand } from "../commands/knowledge";
@@ -33,6 +34,17 @@ import { checkForSkillUpdates } from "../version/skill-update-check";
3334
import { checkForUpdates } from "../version/update-check";
3435
import { getVersionString } from "../version/version";
3536

37+
/**
38+
* Hook entrypoints are auto-invoked by Claude Code on every turn and must stay
39+
* fast and stdout-clean. Skip the update checks for them (their stderr notices
40+
* are noise on the hot path and the background fetch can delay process exit).
41+
*/
42+
const HOOK_ENTRYPOINTS = new Set(["user-prompt-submit", "post-tool-use", "stop"]);
43+
function isHookEntrypointInvocation(argv: string[]): boolean {
44+
const i = argv.indexOf("hooks");
45+
return i >= 0 && HOOK_ENTRYPOINTS.has(argv[i + 1] ?? "");
46+
}
47+
3648
export function createProgram(): Command {
3749
const program = new Command();
3850

@@ -44,8 +56,10 @@ export function createProgram(): Command {
4456
.hook("preAction", (thisCommand) => {
4557
const opts = thisCommand.optsWithGlobals();
4658
logger.init({ debug: opts.debug });
47-
checkForUpdates();
48-
checkForSkillUpdates();
59+
if (!isHookEntrypointInvocation(process.argv)) {
60+
checkForUpdates();
61+
checkForSkillUpdates();
62+
}
4963
})
5064
.action(async () => {
5165
// Default: launch TUI when no subcommand given
@@ -242,6 +256,7 @@ export function createProgram(): Command {
242256
program.addCommand(askCommand());
243257
program.addCommand(deploymentsCommand());
244258
program.addCommand(docsCommand());
259+
program.addCommand(hooksCommand());
245260
program.addCommand(insightsCommand());
246261
program.addCommand(integrationsCommand());
247262
program.addCommand(knowledgeCommand());

0 commit comments

Comments
 (0)