@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,44 @@
1
+ export { Verbosity, parseVerbosity } from './verbosity.js';
2
+ export { type OutputMode, detectOutputMode, timestamp } from './tty.js';
3
+ export { Reporter, type SkillRunnerCallbacks } from './reporter.js';
4
+ export {
5
+ pluralize,
6
+ formatDuration,
7
+ formatElapsed,
8
+ formatSeverityBadge,
9
+ formatSeverityDot,
10
+ formatSeverityPlain,
11
+ formatFindingCounts,
12
+ formatFindingCountsPlain,
13
+ formatProgress,
14
+ formatLocation,
15
+ formatFileStats,
16
+ formatFindingCompact,
17
+ truncate,
18
+ padRight,
19
+ countBySeverity,
20
+ formatCost,
21
+ formatTokens,
22
+ formatUsage,
23
+ formatUsagePlain,
24
+ } from './formatters.js';
25
+ export {
26
+ runSkillTask,
27
+ runSkillTasks,
28
+ type SkillTaskResult,
29
+ type SkillTaskOptions,
30
+ type RunTasksOptions,
31
+ type SkillProgressCallbacks,
32
+ type SkillState,
33
+ type FileState,
34
+ } from './tasks.js';
35
+ export { runSkillTasksWithInk } from './ink-runner.js';
36
+ export { BoxRenderer, type BoxOptions } from './box.js';
37
+ export {
38
+ writeJsonlReport,
39
+ getRunLogsDir,
40
+ getRunLogPath,
41
+ type JsonlRecord,
42
+ type JsonlRunMetadata,
43
+ } from './jsonl.js';
44
+ export { ICON_CHECK, ICON_SKIPPED, SPINNER_FRAMES } from './icons.js';
@@ -0,0 +1,337 @@
1
+ /**
2
+ * Ink-based skill runner with real-time progress display.
3
+ */
4
+
5
+ import React, { useState, useEffect } from 'react';
6
+ import { render, Box, Text, Static } from 'ink';
7
+ import {
8
+ runSkillTask,
9
+ type SkillTaskOptions,
10
+ type SkillTaskResult,
11
+ type RunTasksOptions,
12
+ type SkillProgressCallbacks,
13
+ type SkillState,
14
+ type FileState,
15
+ } from './tasks.js';
16
+ import { formatDuration, truncate, countBySeverity, formatSeverityDot } from './formatters.js';
17
+ import { Verbosity } from './verbosity.js';
18
+ import { ICON_CHECK, ICON_SKIPPED, SPINNER_FRAMES } from './icons.js';
19
+ import figures from 'figures';
20
+
21
+ type StaticItem = { type: 'header' } | { type: 'skill'; skill: SkillState };
22
+
23
+ interface SkillRunnerProps {
24
+ skills: SkillState[];
25
+ completedItems: SkillState[];
26
+ }
27
+
28
+ function Spinner(): React.ReactElement {
29
+ const [frame, setFrame] = useState(0);
30
+
31
+ useEffect(() => {
32
+ const timer = setInterval(() => {
33
+ setFrame((f) => (f + 1) % SPINNER_FRAMES.length);
34
+ }, 80);
35
+ return () => clearInterval(timer);
36
+ }, []);
37
+
38
+ return <Text color="yellow">{SPINNER_FRAMES[frame]}</Text>;
39
+ }
40
+
41
+ function FileProgress({ file }: { file: FileState }): React.ReactElement | null {
42
+ if (file.status === 'pending') return null;
43
+
44
+ const filename = truncate(file.filename, 50);
45
+
46
+ if (file.status === 'done') {
47
+ const counts = countBySeverity(file.findings);
48
+ const hasFindings = file.findings.length > 0;
49
+
50
+ return (
51
+ <Box>
52
+ <Text color="green">{ICON_CHECK}</Text>
53
+ <Text> {filename}</Text>
54
+ {hasFindings && (
55
+ <Text>
56
+ {' '}
57
+ {counts.critical > 0 && <Text>{formatSeverityDot('critical')} {counts.critical} </Text>}
58
+ {counts.high > 0 && <Text>{formatSeverityDot('high')} {counts.high} </Text>}
59
+ {counts.medium > 0 && <Text>{formatSeverityDot('medium')} {counts.medium} </Text>}
60
+ {counts.low > 0 && <Text>{formatSeverityDot('low')} {counts.low} </Text>}
61
+ {counts.info > 0 && <Text>{formatSeverityDot('info')} {counts.info}</Text>}
62
+ </Text>
63
+ )}
64
+ </Box>
65
+ );
66
+ }
67
+
68
+ // Running
69
+ return (
70
+ <Box>
71
+ <Spinner />
72
+ <Text> {filename} [{file.currentHunk}/{file.totalHunks}]</Text>
73
+ </Box>
74
+ );
75
+ }
76
+
77
+ function CompletedSkill({ skill }: { skill: SkillState }): React.ReactElement {
78
+ const duration = skill.durationMs ? formatDuration(skill.durationMs) : '';
79
+
80
+ if (skill.status === 'skipped') {
81
+ return (
82
+ <Box>
83
+ <Text color="yellow">{ICON_SKIPPED}</Text>
84
+ <Text> {skill.displayName}</Text>
85
+ <Text dimColor> [skipped]</Text>
86
+ </Box>
87
+ );
88
+ }
89
+
90
+ if (skill.status === 'error') {
91
+ return (
92
+ <Box flexDirection="column">
93
+ <Box>
94
+ <Text color="red">{'\u2717'}</Text>
95
+ <Text> {skill.displayName}</Text>
96
+ {duration && <Text dimColor> [{duration}]</Text>}
97
+ </Box>
98
+ {skill.error && <Text color="red"> Error: {skill.error}</Text>}
99
+ </Box>
100
+ );
101
+ }
102
+
103
+ return (
104
+ <Box>
105
+ <Text color="green">{ICON_CHECK}</Text>
106
+ <Text> {skill.displayName}</Text>
107
+ {duration && <Text dimColor> [{duration}]</Text>}
108
+ </Box>
109
+ );
110
+ }
111
+
112
+ function RunningSkill({ skill }: { skill: SkillState }): React.ReactElement {
113
+ const visibleFiles = skill.files.filter((f) => f.status !== 'pending');
114
+
115
+ return (
116
+ <Box flexDirection="column">
117
+ <Box>
118
+ <Spinner />
119
+ <Text> {skill.displayName}</Text>
120
+ </Box>
121
+ {visibleFiles.map((file) => (
122
+ <Box key={file.filename} marginLeft={2}>
123
+ <FileProgress file={file} />
124
+ </Box>
125
+ ))}
126
+ </Box>
127
+ );
128
+ }
129
+
130
+ function SkillRunner({ skills, completedItems }: SkillRunnerProps): React.ReactElement {
131
+ const running = skills.filter((s) => s.status === 'running');
132
+ const pending = skills.filter((s) => s.status === 'pending');
133
+
134
+ // Build static items: header first, then completed skills
135
+ const staticItems: StaticItem[] = [
136
+ { type: 'header' },
137
+ ...completedItems.map((skill) => ({ type: 'skill' as const, skill })),
138
+ ];
139
+
140
+ return (
141
+ <>
142
+ {/* Static content: header + completed skills */}
143
+ <Static items={staticItems}>
144
+ {(item) => {
145
+ switch (item.type) {
146
+ case 'header':
147
+ return (
148
+ <Text key="header" bold>
149
+ SKILLS
150
+ </Text>
151
+ );
152
+ case 'skill':
153
+ return <CompletedSkill key={item.skill.name} skill={item.skill} />;
154
+ }
155
+ }}
156
+ </Static>
157
+
158
+ {/* Dynamic content: running + pending */}
159
+ <Box flexDirection="column">
160
+ {running.map((skill) => (
161
+ <RunningSkill key={skill.name} skill={skill} />
162
+ ))}
163
+ {pending.map((skill) => (
164
+ <Text key={skill.name} dimColor>
165
+ {'\u25CB'} {skill.displayName}
166
+ </Text>
167
+ ))}
168
+ </Box>
169
+ </>
170
+ );
171
+ }
172
+
173
+ /** No-op callbacks for quiet mode. */
174
+ // eslint-disable-next-line @typescript-eslint/no-empty-function
175
+ const noop = () => {};
176
+ const noopCallbacks: SkillProgressCallbacks = {
177
+ onSkillStart: noop,
178
+ onSkillUpdate: noop,
179
+ onFileUpdate: noop,
180
+ onSkillComplete: noop,
181
+ onSkillSkipped: noop,
182
+ onSkillError: noop,
183
+ };
184
+
185
+ /**
186
+ * Run skill tasks with Ink-based real-time progress display.
187
+ */
188
+ export async function runSkillTasksWithInk(
189
+ tasks: SkillTaskOptions[],
190
+ options: RunTasksOptions
191
+ ): Promise<SkillTaskResult[]> {
192
+ const { verbosity, concurrency } = options;
193
+
194
+ if (tasks.length === 0 || verbosity === Verbosity.Quiet) {
195
+ // No tasks or quiet mode - run without UI
196
+ const results: SkillTaskResult[] = [];
197
+ for (const task of tasks) {
198
+ const result = await runSkillTask(task, 5, noopCallbacks);
199
+ results.push(result);
200
+ }
201
+ return results;
202
+ }
203
+
204
+ // Track skill states
205
+ const skillStates: SkillState[] = [];
206
+ const completedItems: SkillState[] = [];
207
+ const completedNames = new Set<string>();
208
+
209
+ // Create Ink instance
210
+ const { rerender, unmount } = render(
211
+ <SkillRunner skills={skillStates} completedItems={completedItems} />,
212
+ { stdout: process.stderr }
213
+ );
214
+
215
+ const updateUI = () => {
216
+ rerender(<SkillRunner skills={[...skillStates]} completedItems={[...completedItems]} />);
217
+ };
218
+
219
+ // Callbacks to update state
220
+ const callbacks: SkillProgressCallbacks = {
221
+ onSkillStart: (skill) => {
222
+ skillStates.push(skill);
223
+ updateUI();
224
+ },
225
+ onSkillUpdate: (name, updates) => {
226
+ const idx = skillStates.findIndex((s) => s.name === name);
227
+ const existing = skillStates[idx];
228
+ if (idx >= 0 && existing) {
229
+ const updated = { ...existing, ...updates };
230
+ skillStates[idx] = updated;
231
+
232
+ // If skill just completed, add to completedItems (only once)
233
+ if (updates.status === 'done' && !completedNames.has(name)) {
234
+ completedNames.add(name);
235
+ completedItems.push(updated);
236
+ }
237
+
238
+ updateUI();
239
+ }
240
+ },
241
+ onFileUpdate: (skillName, filename, updates) => {
242
+ const skill = skillStates.find((s) => s.name === skillName);
243
+ if (skill) {
244
+ const file = skill.files.find((f) => f.filename === filename);
245
+ if (file) {
246
+ Object.assign(file, updates);
247
+ updateUI();
248
+ }
249
+ }
250
+ },
251
+ onSkillComplete: () => {
252
+ updateUI();
253
+ },
254
+ onSkillSkipped: (name) => {
255
+ const task = tasks.find((t) => t.name === name);
256
+ const state: SkillState = {
257
+ name,
258
+ displayName: task?.displayName ?? name,
259
+ status: 'skipped',
260
+ files: [],
261
+ findings: [],
262
+ };
263
+ skillStates.push(state);
264
+
265
+ if (!completedNames.has(name)) {
266
+ completedNames.add(name);
267
+ completedItems.push(state);
268
+ }
269
+
270
+ updateUI();
271
+ },
272
+ onSkillError: (name, error) => {
273
+ const idx = skillStates.findIndex((s) => s.name === name);
274
+ const existing = skillStates[idx];
275
+ let state: SkillState;
276
+
277
+ if (idx >= 0 && existing) {
278
+ state = { ...existing, status: 'error', error };
279
+ skillStates[idx] = state;
280
+ } else {
281
+ const task = tasks.find((t) => t.name === name);
282
+ state = {
283
+ name,
284
+ displayName: task?.displayName ?? name,
285
+ status: 'error',
286
+ error,
287
+ files: [],
288
+ findings: [],
289
+ };
290
+ skillStates.push(state);
291
+ }
292
+
293
+ if (!completedNames.has(name)) {
294
+ completedNames.add(name);
295
+ completedItems.push(state);
296
+ }
297
+
298
+ updateUI();
299
+ },
300
+ // Warn about large prompts - write directly to stderr to avoid Ink interference
301
+ onLargePrompt: (_skillName, filename, lineRange, chars, estimatedTokens) => {
302
+ const location = `${filename}:${lineRange}`;
303
+ const size = `${Math.round(chars / 1000)}k chars (~${Math.round(estimatedTokens / 1000)}k tokens)`;
304
+ process.stderr.write(`\x1b[33m${figures.warning}\x1b[0m Large prompt for ${location}: ${size}\n`);
305
+ },
306
+ // Debug mode: show prompt sizes
307
+ onPromptSize: verbosity >= Verbosity.Debug
308
+ ? (_skillName, filename, lineRange, systemChars, userChars, totalChars, estimatedTokens) => {
309
+ const location = `${filename}:${lineRange}`;
310
+ process.stderr.write(`\x1b[2m[debug] Prompt for ${location}: system=${systemChars}, user=${userChars}, total=${totalChars} chars (~${estimatedTokens} tokens)\x1b[0m\n`);
311
+ }
312
+ : undefined,
313
+ };
314
+
315
+ const fileConcurrency = 5;
316
+ const results: SkillTaskResult[] = [];
317
+
318
+ if (concurrency <= 1) {
319
+ for (const task of tasks) {
320
+ const result = await runSkillTask(task, fileConcurrency, callbacks);
321
+ results.push(result);
322
+ }
323
+ } else {
324
+ for (let i = 0; i < tasks.length; i += concurrency) {
325
+ const batch = tasks.slice(i, i + concurrency);
326
+ const batchResults = await Promise.all(
327
+ batch.map((task) => runSkillTask(task, fileConcurrency, callbacks))
328
+ );
329
+ results.push(...batchResults);
330
+ }
331
+ }
332
+
333
+ // Cleanup
334
+ unmount();
335
+
336
+ return results;
337
+ }