@towles/tool 0.0.96 → 0.0.103

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.
@@ -1,7 +1,7 @@
1
- import { Flags } from "@oclif/core";
1
+ import { defineCommand } from "citty";
2
2
  import { colors } from "consola/utils";
3
3
  import consola from "consola";
4
- import { BaseCommand } from "./base.js";
4
+ import { debugArg } from "./shared.js";
5
5
  import {
6
6
  CLAUDE_SETTINGS_PATH,
7
7
  loadClaudeSettings,
@@ -9,159 +9,135 @@ import {
9
9
  saveClaudeSettings,
10
10
  } from "../lib/install/claude-settings.js";
11
11
 
12
- /**
13
- * Install and configure towles-tool with Claude Code
14
- */
15
- export default class Install extends BaseCommand {
16
- static override description =
17
- "Configure Claude Code settings and optionally enable observability";
18
-
19
- static override examples = [
20
- {
21
- description: "Configure Claude Code settings",
22
- command: "<%= config.bin %> <%= command.id %>",
23
- },
24
- {
25
- description: "Include OTEL setup instructions",
26
- command: "<%= config.bin %> <%= command.id %> --observability",
27
- },
28
- ];
29
-
30
- static override flags = {
31
- ...BaseCommand.baseFlags,
32
- observability: Flags.boolean({
33
- char: "o",
12
+ export default defineCommand({
13
+ meta: {
14
+ name: "install",
15
+ description: "Configure Claude Code settings and optionally enable observability",
16
+ },
17
+ args: {
18
+ debug: debugArg,
19
+ observability: {
20
+ type: "boolean",
21
+ alias: "o",
34
22
  description: "Show OTEL setup instructions and configure SubagentStop hook",
35
23
  default: false,
36
- }),
37
- };
38
-
39
- async run(): Promise<void> {
40
- const { flags } = await this.parse(Install);
41
-
42
- this.log(colors.bold("\nšŸ”§ towles-tool install\n"));
24
+ },
25
+ },
26
+ async run({ args }) {
27
+ consola.log(colors.bold("\nšŸ”§ towles-tool install\n"));
43
28
 
44
- // Load or create Claude settings
45
29
  const existing = loadClaudeSettings(CLAUDE_SETTINGS_PATH);
46
30
  if (Object.keys(existing).length > 0) {
47
- this.log(colors.dim(`Found existing Claude settings at ${CLAUDE_SETTINGS_PATH}`));
31
+ consola.log(colors.dim(`Found existing Claude settings at ${CLAUDE_SETTINGS_PATH}`));
48
32
  } else {
49
- this.log(colors.dim(`No Claude settings file found, will create one`));
33
+ consola.log(colors.dim(`No Claude settings file found, will create one`));
50
34
  }
51
35
 
52
- // Apply recommended settings
53
36
  const { settings, changes } = applyRecommendedSettings(existing);
54
37
 
55
38
  for (const change of changes) {
56
- this.log(colors.green(`āœ“ ${change}`));
39
+ consola.log(colors.green(`āœ“ ${change}`));
57
40
  }
58
41
 
59
- // Report already-correct settings
60
42
  if (!changes.some((c) => c.includes("cleanupPeriodDays"))) {
61
- this.log(colors.dim("āœ“ cleanupPeriodDays already set to 99999"));
43
+ consola.log(colors.dim("āœ“ cleanupPeriodDays already set to 99999"));
62
44
  }
63
45
  if (!changes.some((c) => c.includes("alwaysThinkingEnabled"))) {
64
- this.log(colors.dim("āœ“ alwaysThinkingEnabled already set to true"));
46
+ consola.log(colors.dim("āœ“ alwaysThinkingEnabled already set to true"));
65
47
  }
66
48
 
67
- // Save settings if anything changed
68
49
  if (changes.length > 0) {
69
50
  saveClaudeSettings(CLAUDE_SETTINGS_PATH, settings);
70
- this.log(colors.green(`\nāœ“ Saved Claude settings to ${CLAUDE_SETTINGS_PATH}`));
51
+ consola.log(colors.green(`\nāœ“ Saved Claude settings to ${CLAUDE_SETTINGS_PATH}`));
71
52
  }
72
53
 
73
- // Show observability setup if requested
74
- if (flags.observability) {
75
- this.log(colors.bold("\nšŸ“Š Observability Setup\n"));
76
- this.showOtelInstructions();
54
+ if (args.observability) {
55
+ consola.log(colors.bold("\nšŸ“Š Observability Setup\n"));
56
+ showOtelInstructions();
77
57
  }
78
58
 
79
- // Install Claude plugins
80
- this.log(colors.bold("\nšŸ“¦ Claude Plugins\n"));
81
- await this.ensureClaudePlugins();
59
+ consola.log(colors.bold("\nšŸ“¦ Claude Plugins\n"));
60
+ await ensureClaudePlugins();
82
61
 
83
- this.log(colors.bold(colors.green("\nāœ… Installation complete!\n")));
62
+ consola.log(colors.bold(colors.green("\nāœ… Installation complete!\n")));
63
+ },
64
+ });
65
+
66
+ async function ensureClaudePlugins(): Promise<void> {
67
+ const { x } = await import("tinyexec");
68
+
69
+ const requiredPlugins = [
70
+ {
71
+ id: "tt@towles-tool",
72
+ name: "tt-core",
73
+ marketplaceUrl: "https://github.com/ChrisTowles/towles-tool",
74
+ marketplace: "towles-tool",
75
+ },
76
+ {
77
+ id: "code-simplifier@claude-plugins-official",
78
+ name: "code-simplifier",
79
+ },
80
+ ];
81
+
82
+ let installedIds = new Set<string>();
83
+ try {
84
+ const result = await x("claude", ["plugin", "list", "--json"]);
85
+ const plugins: { id: string }[] = JSON.parse(result.stdout);
86
+ installedIds = new Set(plugins.map((p) => p.id));
87
+ } catch {
88
+ consola.log(colors.yellow("⚠ Could not list Claude plugins"));
84
89
  }
85
90
 
86
- private async ensureClaudePlugins(): Promise<void> {
87
- const { x } = await import("tinyexec");
88
-
89
- const requiredPlugins = [
90
- {
91
- id: "tt@towles-tool",
92
- name: "tt-core",
93
- marketplaceUrl: "https://github.com/ChrisTowles/towles-tool",
94
- marketplace: "towles-tool",
95
- },
96
- {
97
- id: "code-simplifier@claude-plugins-official",
98
- name: "code-simplifier",
99
- },
100
- ];
101
-
102
- // Get installed plugins
103
- let installedIds = new Set<string>();
104
- try {
105
- const result = await x("claude", ["plugin", "list", "--json"]);
106
- const plugins: { id: string }[] = JSON.parse(result.stdout);
107
- installedIds = new Set(plugins.map((p) => p.id));
108
- } catch {
109
- this.log(colors.yellow("⚠ Could not list Claude plugins"));
91
+ for (const plugin of requiredPlugins) {
92
+ if (plugin.marketplaceUrl && !installedIds.has(plugin.id)) {
93
+ try {
94
+ await x("claude", ["plugin", "marketplace", "add", plugin.marketplaceUrl]);
95
+ consola.log(colors.dim(` Added marketplace: ${plugin.marketplace}`));
96
+ } catch {
97
+ // marketplace may already be added
98
+ }
110
99
  }
100
+ }
111
101
 
112
- // Ensure marketplaces are added first
113
- for (const plugin of requiredPlugins) {
114
- if (plugin.marketplaceUrl && !installedIds.has(plugin.id)) {
115
- try {
116
- await x("claude", ["plugin", "marketplace", "add", plugin.marketplaceUrl]);
117
- this.log(colors.dim(` Added marketplace: ${plugin.marketplace}`));
118
- } catch {
119
- // marketplace may already be added
120
- }
121
- }
102
+ for (const plugin of requiredPlugins) {
103
+ if (installedIds.has(plugin.id)) {
104
+ consola.log(colors.dim(`āœ“ ${plugin.name} already installed`));
105
+ continue;
122
106
  }
123
107
 
124
- // Install missing plugins
125
- for (const plugin of requiredPlugins) {
126
- if (installedIds.has(plugin.id)) {
127
- this.log(colors.dim(`āœ“ ${plugin.name} already installed`));
128
- continue;
129
- }
108
+ const answer = await consola.prompt(`Install ${plugin.name} plugin?`, {
109
+ type: "confirm",
110
+ initial: true,
111
+ });
130
112
 
131
- const answer = await consola.prompt(`Install ${plugin.name} plugin?`, {
132
- type: "confirm",
133
- initial: true,
134
- });
135
-
136
- if (answer) {
137
- const result = await x("claude", ["plugin", "install", plugin.id, "--scope", "user"]);
138
- if (result.exitCode === 0) {
139
- this.log(colors.green(`āœ“ ${plugin.name} installed`));
140
- } else {
141
- if (result.stdout) this.log(result.stdout);
142
- if (result.stderr) this.log(colors.dim(result.stderr));
143
- this.log(colors.yellow(`⚠ ${plugin.name} install exited with code ${result.exitCode}`));
144
- }
113
+ if (answer) {
114
+ const result = await x("claude", ["plugin", "install", plugin.id, "--scope", "user"]);
115
+ if (result.exitCode === 0) {
116
+ consola.log(colors.green(`āœ“ ${plugin.name} installed`));
145
117
  } else {
146
- this.log(colors.dim(` Skipped ${plugin.name}`));
118
+ if (result.stdout) consola.log(result.stdout);
119
+ if (result.stderr) consola.log(colors.dim(result.stderr));
120
+ consola.log(colors.yellow(`⚠ ${plugin.name} install exited with code ${result.exitCode}`));
147
121
  }
122
+ } else {
123
+ consola.log(colors.dim(` Skipped ${plugin.name}`));
148
124
  }
149
125
  }
126
+ }
150
127
 
151
- private showOtelInstructions(): void {
152
- this.log(colors.cyan("Add these environment variables to your shell profile:\n"));
128
+ function showOtelInstructions(): void {
129
+ consola.log(colors.cyan("Add these environment variables to your shell profile:\n"));
153
130
 
154
- consola.box(`export CLAUDE_CODE_ENABLE_TELEMETRY=1
131
+ consola.box(`export CLAUDE_CODE_ENABLE_TELEMETRY=1
155
132
  export OTEL_METRICS_EXPORTER=otlp
156
133
  export OTEL_LOGS_EXPORTER=otlp
157
134
  export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317`);
158
135
 
159
- this.log("");
160
- this.log(
161
- colors.dim("For more info, see: https://github.com/anthropics/claude-code-monitoring-guide"),
162
- );
163
- this.log("");
164
- this.log(colors.cyan("Quick cost analysis (no setup required):"));
165
- this.log(colors.dim(" npx ccusage@latest --breakdown"));
166
- }
136
+ consola.log("");
137
+ consola.log(
138
+ colors.dim("For more info, see: https://github.com/anthropics/claude-code-monitoring-guide"),
139
+ );
140
+ consola.log("");
141
+ consola.log(colors.cyan("Quick cost analysis (no setup required):"));
142
+ consola.log(colors.dim(" npx ccusage@latest --breakdown"));
167
143
  }
@@ -1,9 +1,10 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
+ import { defineCommand } from "citty";
4
5
  import consola from "consola";
5
6
  import { colors } from "consola/utils";
6
- import { BaseCommand } from "../base.js";
7
+ import { withSettings, debugArg } from "../shared.js";
7
8
  import { JOURNAL_TYPES } from "../../types/journal.js";
8
9
  import {
9
10
  createJournalContent,
@@ -13,29 +14,21 @@ import {
13
14
  openInEditor,
14
15
  } from "../../lib/journal/index.js";
15
16
 
16
- /**
17
- * Create or open daily notes journal file
18
- */
19
- export default class DailyNotes extends BaseCommand {
20
- static override aliases = ["today"];
21
- static override description = "Weekly files with daily sections for ongoing work and notes";
22
-
23
- static override examples = [
24
- {
25
- description: "Open weekly notes for today",
26
- command: "<%= config.bin %> <%= command.id %>",
27
- },
28
- { description: "Using alias", command: "<%= config.bin %> today" },
29
- ];
30
-
31
- async run(): Promise<void> {
32
- await this.parse(DailyNotes);
17
+ export default defineCommand({
18
+ meta: {
19
+ name: "daily-notes",
20
+ description: "Weekly files with daily sections for ongoing work and notes",
21
+ },
22
+ args: {
23
+ debug: debugArg,
24
+ },
25
+ async run({ args }) {
26
+ const { settings } = await withSettings(args.debug);
33
27
 
34
28
  try {
35
- const journalSettings = this.userSettings.journalSettings;
29
+ const journalSettings = settings.journalSettings;
36
30
  const templateDir = journalSettings.templateDir;
37
31
 
38
- // Ensure templates exist on first run
39
32
  ensureTemplatesExist(templateDir);
40
33
 
41
34
  const currentDate = new Date();
@@ -46,7 +39,6 @@ export default class DailyNotes extends BaseCommand {
46
39
  title: "",
47
40
  });
48
41
 
49
- // Ensure journal directory exists
50
42
  ensureDirectoryExists(path.dirname(fileInfo.fullPath));
51
43
 
52
44
  if (existsSync(fileInfo.fullPath)) {
@@ -58,7 +50,7 @@ export default class DailyNotes extends BaseCommand {
58
50
  }
59
51
 
60
52
  await openInEditor({
61
- editor: this.userSettings.preferredEditor,
53
+ editor: settings.preferredEditor,
62
54
  filePath: fileInfo.fullPath,
63
55
  folderPath: journalSettings.baseFolder,
64
56
  });
@@ -66,5 +58,5 @@ export default class DailyNotes extends BaseCommand {
66
58
  consola.warn(`Error creating daily-notes file:`, error);
67
59
  process.exit(1);
68
60
  }
69
- }
70
- }
61
+ },
62
+ });
@@ -0,0 +1,10 @@
1
+ import { defineCommand } from "citty";
2
+
3
+ export default defineCommand({
4
+ meta: { name: "journal", description: "Journal and note-taking commands" },
5
+ subCommands: {
6
+ "daily-notes": () => import("./daily-notes.js").then((m) => m.default),
7
+ note: () => import("./note.js").then((m) => m.default),
8
+ meeting: () => import("./meeting.js").then((m) => m.default),
9
+ },
10
+ });
@@ -1,10 +1,10 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
- import { Args } from "@oclif/core";
4
+ import { defineCommand } from "citty";
5
5
  import consola from "consola";
6
6
  import { colors } from "consola/utils";
7
- import { BaseCommand } from "../base.js";
7
+ import { withSettings, debugArg } from "../shared.js";
8
8
  import { JOURNAL_TYPES } from "../../types/journal.js";
9
9
  import {
10
10
  createMeetingContent,
@@ -14,42 +14,25 @@ import {
14
14
  openInEditor,
15
15
  } from "../../lib/journal/index.js";
16
16
 
17
- /**
18
- * Create or open meeting notes file
19
- */
20
- export default class Meeting extends BaseCommand {
21
- static override description = "Structured meeting notes with agenda and action items";
22
-
23
- static override args = {
24
- title: Args.string({
25
- description: "Meeting title",
17
+ export default defineCommand({
18
+ meta: { name: "meeting", description: "Structured meeting notes with agenda and action items" },
19
+ args: {
20
+ debug: debugArg,
21
+ title: {
22
+ type: "positional",
26
23
  required: false,
27
- }),
28
- };
29
-
30
- static override examples = [
31
- {
32
- description: "Create meeting note (prompts for title)",
33
- command: "<%= config.bin %> <%= command.id %>",
34
- },
35
- {
36
- description: "Create with title",
37
- command: '<%= config.bin %> <%= command.id %> "Sprint Planning"',
24
+ description: "Meeting title",
38
25
  },
39
- { description: "Using alias", command: '<%= config.bin %> m "Standup"' },
40
- ];
41
-
42
- async run(): Promise<void> {
43
- const { args } = await this.parse(Meeting);
26
+ },
27
+ async run({ args }) {
28
+ const { settings } = await withSettings(args.debug);
44
29
 
45
30
  try {
46
- const journalSettings = this.userSettings.journalSettings;
31
+ const journalSettings = settings.journalSettings;
47
32
  const templateDir = journalSettings.templateDir;
48
33
 
49
- // Ensure templates exist on first run
50
34
  ensureTemplatesExist(templateDir);
51
35
 
52
- // Prompt for title if not provided
53
36
  let title = args.title || "";
54
37
  if (title.trim().length === 0) {
55
38
  title = await consola.prompt(`Enter meeting title:`, {
@@ -65,7 +48,6 @@ export default class Meeting extends BaseCommand {
65
48
  title,
66
49
  });
67
50
 
68
- // Ensure journal directory exists
69
51
  ensureDirectoryExists(path.dirname(fileInfo.fullPath));
70
52
 
71
53
  if (existsSync(fileInfo.fullPath)) {
@@ -77,7 +59,7 @@ export default class Meeting extends BaseCommand {
77
59
  }
78
60
 
79
61
  await openInEditor({
80
- editor: this.userSettings.preferredEditor,
62
+ editor: settings.preferredEditor,
81
63
  filePath: fileInfo.fullPath,
82
64
  folderPath: journalSettings.baseFolder,
83
65
  });
@@ -85,5 +67,5 @@ export default class Meeting extends BaseCommand {
85
67
  consola.warn(`Error creating meeting file:`, error);
86
68
  process.exit(1);
87
69
  }
88
- }
89
- }
70
+ },
71
+ });
@@ -1,10 +1,10 @@
1
1
  import { existsSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import process from "node:process";
4
- import { Args } from "@oclif/core";
4
+ import { defineCommand } from "citty";
5
5
  import consola from "consola";
6
6
  import { colors } from "consola/utils";
7
- import { BaseCommand } from "../base.js";
7
+ import { withSettings, debugArg } from "../shared.js";
8
8
  import { JOURNAL_TYPES } from "../../types/journal.js";
9
9
  import {
10
10
  createNoteContent,
@@ -14,42 +14,25 @@ import {
14
14
  openInEditor,
15
15
  } from "../../lib/journal/index.js";
16
16
 
17
- /**
18
- * Create or open general-purpose note file
19
- */
20
- export default class Note extends BaseCommand {
21
- static override description = "General-purpose notes with structured sections";
22
-
23
- static override args = {
24
- title: Args.string({
25
- description: "Note title",
17
+ export default defineCommand({
18
+ meta: { name: "note", description: "General-purpose notes with structured sections" },
19
+ args: {
20
+ debug: debugArg,
21
+ title: {
22
+ type: "positional",
26
23
  required: false,
27
- }),
28
- };
29
-
30
- static override examples = [
31
- {
32
- description: "Create note (prompts for title)",
33
- command: "<%= config.bin %> <%= command.id %>",
34
- },
35
- {
36
- description: "Create with title",
37
- command: '<%= config.bin %> <%= command.id %> "Research Notes"',
24
+ description: "Note title",
38
25
  },
39
- { description: "Using alias", command: '<%= config.bin %> n "Ideas"' },
40
- ];
41
-
42
- async run(): Promise<void> {
43
- const { args } = await this.parse(Note);
26
+ },
27
+ async run({ args }) {
28
+ const { settings } = await withSettings(args.debug);
44
29
 
45
30
  try {
46
- const journalSettings = this.userSettings.journalSettings;
31
+ const journalSettings = settings.journalSettings;
47
32
  const templateDir = journalSettings.templateDir;
48
33
 
49
- // Ensure templates exist on first run
50
34
  ensureTemplatesExist(templateDir);
51
35
 
52
- // Prompt for title if not provided
53
36
  let title = args.title || "";
54
37
  if (title.trim().length === 0) {
55
38
  title = await consola.prompt(`Enter note title:`, {
@@ -65,7 +48,6 @@ export default class Note extends BaseCommand {
65
48
  title,
66
49
  });
67
50
 
68
- // Ensure journal directory exists
69
51
  ensureDirectoryExists(path.dirname(fileInfo.fullPath));
70
52
 
71
53
  if (existsSync(fileInfo.fullPath)) {
@@ -77,7 +59,7 @@ export default class Note extends BaseCommand {
77
59
  }
78
60
 
79
61
  await openInEditor({
80
- editor: this.userSettings.preferredEditor,
62
+ editor: settings.preferredEditor,
81
63
  filePath: fileInfo.fullPath,
82
64
  folderPath: journalSettings.baseFolder,
83
65
  });
@@ -85,5 +67,5 @@ export default class Note extends BaseCommand {
85
67
  consola.warn(`Error creating note file:`, error);
86
68
  process.exit(1);
87
69
  }
88
- }
89
- }
70
+ },
71
+ });
@@ -0,0 +1,21 @@
1
+ import type { SettingsFile } from "../config/settings.js";
2
+ import { loadSettings } from "../config/settings.js";
3
+
4
+ export interface CommandContext {
5
+ settingsFile: SettingsFile;
6
+ settings: SettingsFile["settings"];
7
+ debug: boolean;
8
+ }
9
+
10
+ export async function withSettings(debug = false): Promise<CommandContext> {
11
+ const settingsFile = await loadSettings();
12
+ return { settingsFile, settings: settingsFile.settings, debug };
13
+ }
14
+
15
+ /** Common debug flag definition for citty args */
16
+ export const debugArg = {
17
+ type: "boolean" as const,
18
+ alias: "d",
19
+ description: "Enable debug output",
20
+ default: false,
21
+ };
@@ -1,9 +1,14 @@
1
1
  import { describe, expect, it, vi, beforeEach } from "vitest";
2
+ import type { Mock } from "vitest";
2
3
 
3
4
  import { resolveTemplate } from "./templates";
4
5
  import type { TemplateFsDeps, TokenValues } from "./templates";
5
6
 
6
- function createMockFs(): TemplateFsDeps {
7
+ function createMockFs(): TemplateFsDeps & {
8
+ readFileSync: Mock;
9
+ writeFileSync: Mock;
10
+ mkdirSync: Mock;
11
+ } {
7
12
  return {
8
13
  readFileSync: vi.fn().mockReturnValue(""),
9
14
  writeFileSync: vi.fn(),
@@ -12,7 +17,7 @@ function createMockFs(): TemplateFsDeps {
12
17
  }
13
18
 
14
19
  describe("resolveTemplate", () => {
15
- let mockFs: TemplateFsDeps;
20
+ let mockFs: ReturnType<typeof createMockFs>;
16
21
 
17
22
  beforeEach(() => {
18
23
  mockFs = createMockFs();
@@ -25,13 +30,13 @@ describe("resolveTemplate", () => {
25
30
  };
26
31
 
27
32
  it("replaces all token placeholders in template", () => {
28
- vi.mocked(mockFs.readFileSync).mockReturnValue(
33
+ mockFs.readFileSync.mockReturnValue(
29
34
  "Scope: {{SCOPE_PATH}}\nDir: {{ISSUE_DIR}}\nBranch: {{MAIN_BRANCH}}",
30
35
  );
31
36
 
32
37
  resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
33
38
 
34
- const writtenContent = vi.mocked(mockFs.writeFileSync).mock.calls[0][1];
39
+ const writtenContent = mockFs.writeFileSync.mock.calls[0][1];
35
40
  expect(writtenContent).toContain("/home/user/project");
36
41
  expect(writtenContent).toContain("/tmp/issues/42");
37
42
  expect(writtenContent).toContain("main");
@@ -43,16 +48,16 @@ describe("resolveTemplate", () => {
43
48
  ...tokens,
44
49
  REVIEW_FEEDBACK: "Needs more tests",
45
50
  };
46
- vi.mocked(mockFs.readFileSync).mockReturnValue("Feedback: {{REVIEW_FEEDBACK}}");
51
+ mockFs.readFileSync.mockReturnValue("Feedback: {{REVIEW_FEEDBACK}}");
47
52
 
48
53
  resolveTemplate("review.md", tokensWithFeedback, "/tmp/issues/42", mockFs);
49
54
 
50
- const writtenContent = vi.mocked(mockFs.writeFileSync).mock.calls[0][1];
55
+ const writtenContent = mockFs.writeFileSync.mock.calls[0][1];
51
56
  expect(writtenContent).toContain("Needs more tests");
52
57
  });
53
58
 
54
59
  it("creates output directory recursively", () => {
55
- vi.mocked(mockFs.readFileSync).mockReturnValue("template content");
60
+ mockFs.readFileSync.mockReturnValue("template content");
56
61
 
57
62
  resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
58
63
 
@@ -60,7 +65,7 @@ describe("resolveTemplate", () => {
60
65
  });
61
66
 
62
67
  it("writes resolved template to issue dir", () => {
63
- vi.mocked(mockFs.readFileSync).mockReturnValue("simple content");
68
+ mockFs.readFileSync.mockReturnValue("simple content");
64
69
 
65
70
  resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
66
71
 
@@ -72,7 +77,7 @@ describe("resolveTemplate", () => {
72
77
  });
73
78
 
74
79
  it("returns relative path from cwd", () => {
75
- vi.mocked(mockFs.readFileSync).mockReturnValue("content");
80
+ mockFs.readFileSync.mockReturnValue("content");
76
81
 
77
82
  const result = resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
78
83
  // Should be a relative path (not starting with /)
@@ -80,11 +85,11 @@ describe("resolveTemplate", () => {
80
85
  });
81
86
 
82
87
  it("handles multiple occurrences of the same token", () => {
83
- vi.mocked(mockFs.readFileSync).mockReturnValue("{{MAIN_BRANCH}} and {{MAIN_BRANCH}} again");
88
+ mockFs.readFileSync.mockReturnValue("{{MAIN_BRANCH}} and {{MAIN_BRANCH}} again");
84
89
 
85
90
  resolveTemplate("plan.md", tokens, "/tmp/issues/42", mockFs);
86
91
 
87
- const writtenContent = vi.mocked(mockFs.writeFileSync).mock.calls[0][1];
92
+ const writtenContent = mockFs.writeFileSync.mock.calls[0][1];
88
93
  expect(writtenContent).toBe("main and main again");
89
94
  });
90
95
  });