@sentry/warden 0.0.0

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.
Files changed (199) hide show
  1. package/.agents/skills/find-bugs/SKILL.md +75 -0
  2. package/.agents/skills/vercel-react-best-practices/AGENTS.md +2934 -0
  3. package/.agents/skills/vercel-react-best-practices/SKILL.md +136 -0
  4. package/.agents/skills/vercel-react-best-practices/rules/advanced-event-handler-refs.md +55 -0
  5. package/.agents/skills/vercel-react-best-practices/rules/advanced-init-once.md +42 -0
  6. package/.agents/skills/vercel-react-best-practices/rules/advanced-use-latest.md +39 -0
  7. package/.agents/skills/vercel-react-best-practices/rules/async-api-routes.md +38 -0
  8. package/.agents/skills/vercel-react-best-practices/rules/async-defer-await.md +80 -0
  9. package/.agents/skills/vercel-react-best-practices/rules/async-dependencies.md +51 -0
  10. package/.agents/skills/vercel-react-best-practices/rules/async-parallel.md +28 -0
  11. package/.agents/skills/vercel-react-best-practices/rules/async-suspense-boundaries.md +99 -0
  12. package/.agents/skills/vercel-react-best-practices/rules/bundle-barrel-imports.md +59 -0
  13. package/.agents/skills/vercel-react-best-practices/rules/bundle-conditional.md +31 -0
  14. package/.agents/skills/vercel-react-best-practices/rules/bundle-defer-third-party.md +49 -0
  15. package/.agents/skills/vercel-react-best-practices/rules/bundle-dynamic-imports.md +35 -0
  16. package/.agents/skills/vercel-react-best-practices/rules/bundle-preload.md +50 -0
  17. package/.agents/skills/vercel-react-best-practices/rules/client-event-listeners.md +74 -0
  18. package/.agents/skills/vercel-react-best-practices/rules/client-localstorage-schema.md +71 -0
  19. package/.agents/skills/vercel-react-best-practices/rules/client-passive-event-listeners.md +48 -0
  20. package/.agents/skills/vercel-react-best-practices/rules/client-swr-dedup.md +56 -0
  21. package/.agents/skills/vercel-react-best-practices/rules/js-batch-dom-css.md +107 -0
  22. package/.agents/skills/vercel-react-best-practices/rules/js-cache-function-results.md +80 -0
  23. package/.agents/skills/vercel-react-best-practices/rules/js-cache-property-access.md +28 -0
  24. package/.agents/skills/vercel-react-best-practices/rules/js-cache-storage.md +70 -0
  25. package/.agents/skills/vercel-react-best-practices/rules/js-combine-iterations.md +32 -0
  26. package/.agents/skills/vercel-react-best-practices/rules/js-early-exit.md +50 -0
  27. package/.agents/skills/vercel-react-best-practices/rules/js-hoist-regexp.md +45 -0
  28. package/.agents/skills/vercel-react-best-practices/rules/js-index-maps.md +37 -0
  29. package/.agents/skills/vercel-react-best-practices/rules/js-length-check-first.md +49 -0
  30. package/.agents/skills/vercel-react-best-practices/rules/js-min-max-loop.md +82 -0
  31. package/.agents/skills/vercel-react-best-practices/rules/js-set-map-lookups.md +24 -0
  32. package/.agents/skills/vercel-react-best-practices/rules/js-tosorted-immutable.md +57 -0
  33. package/.agents/skills/vercel-react-best-practices/rules/rendering-activity.md +26 -0
  34. package/.agents/skills/vercel-react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
  35. package/.agents/skills/vercel-react-best-practices/rules/rendering-conditional-render.md +40 -0
  36. package/.agents/skills/vercel-react-best-practices/rules/rendering-content-visibility.md +38 -0
  37. package/.agents/skills/vercel-react-best-practices/rules/rendering-hoist-jsx.md +46 -0
  38. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
  39. package/.agents/skills/vercel-react-best-practices/rules/rendering-hydration-suppress-warning.md +30 -0
  40. package/.agents/skills/vercel-react-best-practices/rules/rendering-svg-precision.md +28 -0
  41. package/.agents/skills/vercel-react-best-practices/rules/rendering-usetransition-loading.md +75 -0
  42. package/.agents/skills/vercel-react-best-practices/rules/rerender-defer-reads.md +39 -0
  43. package/.agents/skills/vercel-react-best-practices/rules/rerender-dependencies.md +45 -0
  44. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state-no-effect.md +40 -0
  45. package/.agents/skills/vercel-react-best-practices/rules/rerender-derived-state.md +29 -0
  46. package/.agents/skills/vercel-react-best-practices/rules/rerender-functional-setstate.md +74 -0
  47. package/.agents/skills/vercel-react-best-practices/rules/rerender-lazy-state-init.md +58 -0
  48. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo-with-default-value.md +38 -0
  49. package/.agents/skills/vercel-react-best-practices/rules/rerender-memo.md +44 -0
  50. package/.agents/skills/vercel-react-best-practices/rules/rerender-move-effect-to-event.md +45 -0
  51. package/.agents/skills/vercel-react-best-practices/rules/rerender-simple-expression-in-memo.md +35 -0
  52. package/.agents/skills/vercel-react-best-practices/rules/rerender-transitions.md +40 -0
  53. package/.agents/skills/vercel-react-best-practices/rules/rerender-use-ref-transient-values.md +73 -0
  54. package/.agents/skills/vercel-react-best-practices/rules/server-after-nonblocking.md +73 -0
  55. package/.agents/skills/vercel-react-best-practices/rules/server-auth-actions.md +96 -0
  56. package/.agents/skills/vercel-react-best-practices/rules/server-cache-lru.md +41 -0
  57. package/.agents/skills/vercel-react-best-practices/rules/server-cache-react.md +76 -0
  58. package/.agents/skills/vercel-react-best-practices/rules/server-dedup-props.md +65 -0
  59. package/.agents/skills/vercel-react-best-practices/rules/server-parallel-fetching.md +83 -0
  60. package/.agents/skills/vercel-react-best-practices/rules/server-serialization.md +38 -0
  61. package/.claude/settings.json +57 -0
  62. package/.claude/settings.local.json +88 -0
  63. package/.claude/skills/agent-prompt/SKILL.md +54 -0
  64. package/.claude/skills/agent-prompt/references/agentic-patterns.md +94 -0
  65. package/.claude/skills/agent-prompt/references/anti-patterns.md +140 -0
  66. package/.claude/skills/agent-prompt/references/context-design.md +124 -0
  67. package/.claude/skills/agent-prompt/references/core-principles.md +75 -0
  68. package/.claude/skills/agent-prompt/references/model-guidance.md +118 -0
  69. package/.claude/skills/agent-prompt/references/output-formats.md +98 -0
  70. package/.claude/skills/agent-prompt/references/skill-structure.md +115 -0
  71. package/.claude/skills/agent-prompt/references/system-prompts.md +115 -0
  72. package/.claude/skills/notseer/SKILL.md +131 -0
  73. package/.claude/skills/skill-writer/SKILL.md +140 -0
  74. package/.claude/skills/testing-guidelines/SKILL.md +132 -0
  75. package/.claude/skills/warden-skill/SKILL.md +250 -0
  76. package/.claude/skills/warden-skill/references/config-schema.md +133 -0
  77. package/.dex/config.toml +2 -0
  78. package/.github/workflows/ci.yml +33 -0
  79. package/.github/workflows/release.yml +54 -0
  80. package/.github/workflows/warden.yml +40 -0
  81. package/AGENTS.md +89 -0
  82. package/CONTRIBUTING.md +60 -0
  83. package/LICENSE +105 -0
  84. package/README.md +43 -0
  85. package/SPEC.md +263 -0
  86. package/action.yml +87 -0
  87. package/assets/favicon.png +0 -0
  88. package/assets/warden-icon-bw.svg +5 -0
  89. package/assets/warden-icon-purple.png +0 -0
  90. package/assets/warden-icon-purple.svg +5 -0
  91. package/docs/.claude/settings.local.json +11 -0
  92. package/docs/astro.config.mjs +43 -0
  93. package/docs/package.json +19 -0
  94. package/docs/pnpm-lock.yaml +4000 -0
  95. package/docs/public/favicon.svg +5 -0
  96. package/docs/src/components/Code.astro +141 -0
  97. package/docs/src/components/PackageManagerTabs.astro +183 -0
  98. package/docs/src/components/Terminal.astro +212 -0
  99. package/docs/src/layouts/Base.astro +380 -0
  100. package/docs/src/pages/cli.astro +167 -0
  101. package/docs/src/pages/config.astro +394 -0
  102. package/docs/src/pages/guide.astro +449 -0
  103. package/docs/src/pages/index.astro +490 -0
  104. package/docs/src/styles/global.css +551 -0
  105. package/docs/tsconfig.json +3 -0
  106. package/docs/vercel.json +5 -0
  107. package/eslint.config.js +33 -0
  108. package/package.json +73 -0
  109. package/src/action/index.ts +1 -0
  110. package/src/action/main.ts +868 -0
  111. package/src/cli/args.test.ts +477 -0
  112. package/src/cli/args.ts +415 -0
  113. package/src/cli/commands/add.ts +447 -0
  114. package/src/cli/commands/init.test.ts +136 -0
  115. package/src/cli/commands/init.ts +132 -0
  116. package/src/cli/commands/setup-app/browser.ts +38 -0
  117. package/src/cli/commands/setup-app/credentials.ts +45 -0
  118. package/src/cli/commands/setup-app/manifest.ts +48 -0
  119. package/src/cli/commands/setup-app/server.ts +172 -0
  120. package/src/cli/commands/setup-app.ts +156 -0
  121. package/src/cli/commands/sync.ts +114 -0
  122. package/src/cli/context.ts +131 -0
  123. package/src/cli/files.test.ts +155 -0
  124. package/src/cli/files.ts +89 -0
  125. package/src/cli/fix.test.ts +310 -0
  126. package/src/cli/fix.ts +387 -0
  127. package/src/cli/git.test.ts +119 -0
  128. package/src/cli/git.ts +318 -0
  129. package/src/cli/index.ts +14 -0
  130. package/src/cli/main.ts +672 -0
  131. package/src/cli/output/box.ts +235 -0
  132. package/src/cli/output/formatters.test.ts +187 -0
  133. package/src/cli/output/formatters.ts +269 -0
  134. package/src/cli/output/icons.ts +13 -0
  135. package/src/cli/output/index.ts +44 -0
  136. package/src/cli/output/ink-runner.tsx +337 -0
  137. package/src/cli/output/jsonl.test.ts +347 -0
  138. package/src/cli/output/jsonl.ts +126 -0
  139. package/src/cli/output/reporter.ts +435 -0
  140. package/src/cli/output/tasks.ts +374 -0
  141. package/src/cli/output/tty.test.ts +117 -0
  142. package/src/cli/output/tty.ts +60 -0
  143. package/src/cli/output/verbosity.test.ts +40 -0
  144. package/src/cli/output/verbosity.ts +31 -0
  145. package/src/cli/terminal.test.ts +148 -0
  146. package/src/cli/terminal.ts +301 -0
  147. package/src/config/index.ts +3 -0
  148. package/src/config/loader.test.ts +313 -0
  149. package/src/config/loader.ts +103 -0
  150. package/src/config/schema.ts +168 -0
  151. package/src/config/writer.test.ts +119 -0
  152. package/src/config/writer.ts +84 -0
  153. package/src/diff/classify.test.ts +162 -0
  154. package/src/diff/classify.ts +92 -0
  155. package/src/diff/coalesce.test.ts +208 -0
  156. package/src/diff/coalesce.ts +133 -0
  157. package/src/diff/context.test.ts +226 -0
  158. package/src/diff/context.ts +201 -0
  159. package/src/diff/index.ts +4 -0
  160. package/src/diff/parser.test.ts +212 -0
  161. package/src/diff/parser.ts +149 -0
  162. package/src/event/context.ts +132 -0
  163. package/src/event/index.ts +2 -0
  164. package/src/event/schedule-context.ts +101 -0
  165. package/src/examples/examples.integration.test.ts +66 -0
  166. package/src/examples/index.test.ts +101 -0
  167. package/src/examples/index.ts +122 -0
  168. package/src/examples/setup.ts +25 -0
  169. package/src/index.ts +115 -0
  170. package/src/output/dedup.test.ts +419 -0
  171. package/src/output/dedup.ts +607 -0
  172. package/src/output/github-checks.test.ts +300 -0
  173. package/src/output/github-checks.ts +476 -0
  174. package/src/output/github-issues.ts +329 -0
  175. package/src/output/index.ts +5 -0
  176. package/src/output/issue-renderer.ts +197 -0
  177. package/src/output/renderer.test.ts +727 -0
  178. package/src/output/renderer.ts +217 -0
  179. package/src/output/stale.test.ts +375 -0
  180. package/src/output/stale.ts +155 -0
  181. package/src/output/types.ts +34 -0
  182. package/src/sdk/index.ts +1 -0
  183. package/src/sdk/runner.test.ts +806 -0
  184. package/src/sdk/runner.ts +1232 -0
  185. package/src/skills/index.ts +36 -0
  186. package/src/skills/loader.test.ts +300 -0
  187. package/src/skills/loader.ts +423 -0
  188. package/src/skills/remote.test.ts +704 -0
  189. package/src/skills/remote.ts +604 -0
  190. package/src/triggers/matcher.test.ts +277 -0
  191. package/src/triggers/matcher.ts +152 -0
  192. package/src/types/index.ts +194 -0
  193. package/src/utils/async.ts +18 -0
  194. package/src/utils/index.test.ts +84 -0
  195. package/src/utils/index.ts +50 -0
  196. package/tsconfig.json +25 -0
  197. package/vitest.config.ts +8 -0
  198. package/vitest.integration.config.ts +11 -0
  199. package/warden.toml +19 -0
@@ -0,0 +1,435 @@
1
+ import chalk from 'chalk';
2
+ import figures from 'figures';
3
+ import type { SkillReport, Finding, FileChange, UsageStats } from '../../types/index.js';
4
+ import { Verbosity } from './verbosity.js';
5
+ import { type OutputMode, timestamp } from './tty.js';
6
+ import {
7
+ formatDuration,
8
+ formatFindingCounts,
9
+ formatFindingCountsPlain,
10
+ formatUsage,
11
+ formatUsagePlain,
12
+ countBySeverity,
13
+ } from './formatters.js';
14
+ import { BoxRenderer } from './box.js';
15
+ import { ICON_CHECK } from './icons.js';
16
+
17
+ const VERSION = '0.1.0';
18
+
19
+ /**
20
+ * ASCII art logo for TTY header.
21
+ */
22
+ const LOGO = `
23
+ __ __ _
24
+ / / /\\ \\ \\__ _ _ __ __| | ___ _ __
25
+ \\ \\/ \\/ / _\` | '__/ _\` |/ _ \\ '_ \\
26
+ \\ /\\ / (_| | | | (_| | __/ | | |
27
+ \\/ \\/ \\__,_|_| \\__,_|\\___|_| |_|
28
+ `.trimStart();
29
+
30
+ /**
31
+ * Callbacks for skill runner progress reporting.
32
+ */
33
+ export interface SkillRunnerCallbacks {
34
+ /** Start time of the skill execution (for elapsed time calculations) */
35
+ skillStartTime?: number;
36
+ onFileStart?: (file: string, index: number, total: number) => void;
37
+ onHunkStart?: (file: string, hunkNum: number, total: number, lineRange: string) => void;
38
+ onHunkComplete?: (file: string, hunkNum: number, findings: Finding[]) => void;
39
+ onFileComplete?: (file: string, index: number, total: number) => void;
40
+ }
41
+
42
+ /**
43
+ * Main reporter class for CLI output.
44
+ * Handles different verbosity levels and TTY/non-TTY modes.
45
+ */
46
+ export class Reporter {
47
+ readonly mode: OutputMode;
48
+ readonly verbosity: Verbosity;
49
+
50
+ constructor(mode: OutputMode, verbosity: Verbosity) {
51
+ this.mode = mode;
52
+ this.verbosity = verbosity;
53
+ }
54
+
55
+ /**
56
+ * Output to stderr (status messages).
57
+ */
58
+ private log(message: string): void {
59
+ if (this.verbosity === Verbosity.Quiet) {
60
+ return;
61
+ }
62
+ console.error(message);
63
+ }
64
+
65
+ /**
66
+ * Output to stderr with timestamp (CI mode).
67
+ */
68
+ private logCI(message: string): void {
69
+ console.error(`[${timestamp()}] warden: ${message}`);
70
+ }
71
+
72
+ /**
73
+ * Print the header with logo and version.
74
+ */
75
+ header(): void {
76
+ if (this.verbosity === Verbosity.Quiet) {
77
+ return;
78
+ }
79
+
80
+ if (this.mode.isTTY) {
81
+ this.log('');
82
+ for (const line of LOGO.split('\n')) {
83
+ this.log(chalk.dim(line));
84
+ }
85
+ this.log(chalk.dim(`v${VERSION}`));
86
+ this.log('');
87
+ } else {
88
+ this.logCI(`Warden v${VERSION}`);
89
+ }
90
+ }
91
+
92
+ /**
93
+ * Start the context section (e.g., "Analyzing changes from HEAD~3...")
94
+ */
95
+ startContext(description: string): void {
96
+ if (this.verbosity === Verbosity.Quiet) {
97
+ return;
98
+ }
99
+
100
+ if (this.mode.isTTY) {
101
+ this.log(chalk.dim(description));
102
+ this.log('');
103
+ } else {
104
+ this.logCI(description);
105
+ }
106
+ }
107
+
108
+ /**
109
+ * Display the list of files being analyzed.
110
+ */
111
+ contextFiles(files: FileChange[]): void {
112
+ if (this.verbosity === Verbosity.Quiet) {
113
+ return;
114
+ }
115
+
116
+ const totalChunks = files.reduce((sum, f) => sum + (f.chunks ?? 0), 0);
117
+
118
+ if (this.mode.isTTY) {
119
+ this.log(
120
+ chalk.bold('FILES') +
121
+ chalk.cyan(` ${files.length} files`) +
122
+ chalk.dim(` · ${totalChunks} chunks`)
123
+ );
124
+
125
+ // Show up to 10 files
126
+ const displayFiles = files.slice(0, 10);
127
+ for (const file of displayFiles) {
128
+ let statusSymbol: string;
129
+ if (file.status === 'added') {
130
+ statusSymbol = chalk.green('+');
131
+ } else if (file.status === 'removed') {
132
+ statusSymbol = chalk.red('-');
133
+ } else {
134
+ statusSymbol = chalk.yellow('~');
135
+ }
136
+ const chunkInfo = file.chunks ? chalk.dim(` (${file.chunks} chunk${file.chunks !== 1 ? 's' : ''})`) : '';
137
+ this.log(` ${statusSymbol} ${file.filename}${chunkInfo}`);
138
+ }
139
+
140
+ if (files.length > 10) {
141
+ this.log(chalk.dim(` ... and ${files.length - 10} more`));
142
+ }
143
+
144
+ this.log('');
145
+ } else {
146
+ this.logCI(`Found ${files.length} changed files with ${totalChunks} chunks`);
147
+ }
148
+ }
149
+
150
+ /**
151
+ * Aggregate usage stats from multiple reports.
152
+ */
153
+ private aggregateUsage(reports: SkillReport[]): UsageStats | undefined {
154
+ const usages = reports.map((r) => r.usage).filter((u): u is UsageStats => u !== undefined);
155
+ if (usages.length === 0) return undefined;
156
+
157
+ return usages.reduce((acc, u) => ({
158
+ inputTokens: acc.inputTokens + u.inputTokens,
159
+ outputTokens: acc.outputTokens + u.outputTokens,
160
+ cacheReadInputTokens: (acc.cacheReadInputTokens ?? 0) + (u.cacheReadInputTokens ?? 0),
161
+ cacheCreationInputTokens: (acc.cacheCreationInputTokens ?? 0) + (u.cacheCreationInputTokens ?? 0),
162
+ costUSD: acc.costUSD + u.costUSD,
163
+ }));
164
+ }
165
+
166
+ /**
167
+ * Render the summary section.
168
+ */
169
+ renderSummary(reports: SkillReport[], totalDuration: number): void {
170
+ const allFindings: Finding[] = [];
171
+ let totalFailedHunks = 0;
172
+ for (const report of reports) {
173
+ allFindings.push(...report.findings);
174
+ totalFailedHunks += report.failedHunks ?? 0;
175
+ }
176
+ const counts = countBySeverity(allFindings);
177
+ const totalUsage = this.aggregateUsage(reports);
178
+
179
+ if (this.verbosity === Verbosity.Quiet) {
180
+ // Quiet mode: just output the summary line
181
+ const countStr = formatFindingCountsPlain(counts);
182
+ console.log(countStr);
183
+ return;
184
+ }
185
+
186
+ if (this.mode.isTTY) {
187
+ this.log(chalk.bold('SUMMARY'));
188
+ this.log(formatFindingCounts(counts));
189
+ if (totalFailedHunks > 0) {
190
+ this.log(chalk.yellow(`${figures.warning} ${totalFailedHunks} chunk${totalFailedHunks === 1 ? '' : 's'} failed to analyze`));
191
+ }
192
+ const durationLine = `Analysis completed in ${formatDuration(totalDuration)}`;
193
+ if (totalUsage) {
194
+ this.log(chalk.dim(`${durationLine} · ${formatUsage(totalUsage)}`));
195
+ } else {
196
+ this.log(chalk.dim(durationLine));
197
+ }
198
+ } else {
199
+ this.logCI(`Summary: ${formatFindingCountsPlain(counts)}`);
200
+ if (totalFailedHunks > 0) {
201
+ this.logCI(`WARN: ${totalFailedHunks} chunk(s) failed to analyze`);
202
+ }
203
+ if (totalUsage) {
204
+ this.logCI(`Usage: ${formatUsagePlain(totalUsage)}`);
205
+ }
206
+ this.logCI(`Total time: ${formatDuration(totalDuration)}`);
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Display the configuration section with triggers.
212
+ */
213
+ configTriggers(
214
+ loaded: number,
215
+ matched: number,
216
+ triggers: { name: string; skill: string }[]
217
+ ): void {
218
+ if (this.verbosity === Verbosity.Quiet) {
219
+ return;
220
+ }
221
+
222
+ if (this.mode.isTTY) {
223
+ this.log(
224
+ chalk.bold('CONFIG') +
225
+ chalk.cyan(` ${loaded} triggers`) +
226
+ chalk.dim(` · ${matched} matched`)
227
+ );
228
+
229
+ // Show matched triggers
230
+ for (const trigger of triggers) {
231
+ this.log(` ${chalk.green(ICON_CHECK)} ${trigger.name} ${chalk.dim(`(${trigger.skill})`)}`);
232
+ }
233
+
234
+ this.log('');
235
+ } else {
236
+ this.logCI(`Config: ${loaded} triggers, ${matched} matched`);
237
+ for (const trigger of triggers) {
238
+ this.logCI(` ${trigger.name} (${trigger.skill})`);
239
+ }
240
+ }
241
+ }
242
+
243
+ /**
244
+ * Log a step message.
245
+ */
246
+ step(message: string): void {
247
+ if (this.verbosity === Verbosity.Quiet) {
248
+ return;
249
+ }
250
+
251
+ if (this.mode.isTTY) {
252
+ this.log(`${chalk.cyan(figures.arrowRight)} ${message}`);
253
+ } else {
254
+ this.logCI(message);
255
+ }
256
+ }
257
+
258
+ /**
259
+ * Log a success message.
260
+ */
261
+ success(message: string): void {
262
+ if (this.verbosity === Verbosity.Quiet) {
263
+ return;
264
+ }
265
+
266
+ if (this.mode.isTTY) {
267
+ this.log(`${chalk.green(ICON_CHECK)} ${message}`);
268
+ } else {
269
+ this.logCI(message);
270
+ }
271
+ }
272
+
273
+ /**
274
+ * Log a file creation message (green "Created" prefix, no icon).
275
+ */
276
+ created(filename: string): void {
277
+ if (this.verbosity === Verbosity.Quiet) {
278
+ return;
279
+ }
280
+
281
+ if (this.mode.isTTY) {
282
+ this.log(`${chalk.green('Created')} ${filename}`);
283
+ } else {
284
+ this.logCI(`Created ${filename}`);
285
+ }
286
+ }
287
+
288
+ /**
289
+ * Log a skipped file message (yellow "Skipped" prefix with reason).
290
+ */
291
+ skipped(filename: string, reason?: string): void {
292
+ if (this.verbosity === Verbosity.Quiet) {
293
+ return;
294
+ }
295
+
296
+ const suffix = reason ? chalk.dim(` (${reason})`) : '';
297
+ if (this.mode.isTTY) {
298
+ this.log(`${chalk.yellow('Skipped')} ${filename}${suffix}`);
299
+ } else {
300
+ this.logCI(`Skipped ${filename}${reason ? ` (${reason})` : ''}`);
301
+ }
302
+ }
303
+
304
+ /**
305
+ * Log a warning message.
306
+ */
307
+ warning(message: string): void {
308
+ if (this.verbosity === Verbosity.Quiet) {
309
+ return;
310
+ }
311
+
312
+ if (this.mode.isTTY) {
313
+ this.log(`${chalk.yellow(figures.warning)} ${message}`);
314
+ } else {
315
+ this.logCI(`WARN: ${message}`);
316
+ }
317
+ }
318
+
319
+ /**
320
+ * Log an error message.
321
+ * Errors are always shown, even in quiet mode.
322
+ */
323
+ error(message: string): void {
324
+ if (this.mode.isTTY) {
325
+ console.error(`${chalk.red(figures.cross)} ${message}`);
326
+ } else {
327
+ console.error(`[${timestamp()}] warden: ERROR: ${message}`);
328
+ }
329
+ }
330
+
331
+ /**
332
+ * Log a debug message.
333
+ */
334
+ debug(message: string): void {
335
+ if (this.verbosity < Verbosity.Debug) {
336
+ return;
337
+ }
338
+
339
+ if (this.mode.isTTY) {
340
+ this.log(chalk.dim(`[debug] ${message}`));
341
+ } else {
342
+ this.logCI(`DEBUG: ${message}`);
343
+ }
344
+ }
345
+
346
+ /**
347
+ * Log a hint/tip message.
348
+ */
349
+ tip(message: string): void {
350
+ if (this.verbosity === Verbosity.Quiet) {
351
+ return;
352
+ }
353
+
354
+ if (this.mode.isTTY) {
355
+ this.log(chalk.dim(`Tip: ${message}`));
356
+ }
357
+ // No tips in CI mode
358
+ }
359
+
360
+ /**
361
+ * Log plain text (no prefix).
362
+ */
363
+ text(message: string): void {
364
+ if (this.verbosity === Verbosity.Quiet) {
365
+ return;
366
+ }
367
+
368
+ if (this.mode.isTTY) {
369
+ this.log(message);
370
+ } else {
371
+ this.logCI(message);
372
+ }
373
+ }
374
+
375
+ /**
376
+ * Log bold text.
377
+ */
378
+ bold(message: string): void {
379
+ if (this.verbosity === Verbosity.Quiet) {
380
+ return;
381
+ }
382
+
383
+ if (this.mode.isTTY) {
384
+ this.log(chalk.bold(message));
385
+ } else {
386
+ this.logCI(message);
387
+ }
388
+ }
389
+
390
+ /**
391
+ * Output a blank line.
392
+ */
393
+ blank(): void {
394
+ if (this.verbosity === Verbosity.Quiet) {
395
+ return;
396
+ }
397
+ this.log('');
398
+ }
399
+
400
+ /**
401
+ * Render an empty state box (e.g., "No changes found").
402
+ */
403
+ renderEmptyState(message: string, tip?: string): void {
404
+ if (this.verbosity === Verbosity.Quiet) {
405
+ console.log(message);
406
+ return;
407
+ }
408
+
409
+ if (this.mode.isTTY) {
410
+ const box = new BoxRenderer({
411
+ title: 'warden',
412
+ mode: this.mode,
413
+ });
414
+
415
+ box.header();
416
+ box.blank();
417
+ box.content(`${chalk.yellow(figures.warning)} ${message}`);
418
+ if (tip) {
419
+ box.blank();
420
+ box.content(chalk.dim(`Tip: ${tip}`));
421
+ }
422
+ box.blank();
423
+ box.footer();
424
+
425
+ for (const line of box.render()) {
426
+ this.log(line);
427
+ }
428
+ } else {
429
+ this.logCI(`WARN: ${message}`);
430
+ if (tip) {
431
+ this.logCI(`Tip: ${tip}`);
432
+ }
433
+ }
434
+ }
435
+ }