@walwal-harness/cli 7.1.7 → 7.1.9

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 (89) hide show
  1. package/HR-Resource/index.json +19 -43
  2. package/HR-Resource/marketing-marketing-cross-border-ecommerce/SKILL.md +108 -223
  3. package/HR-Resource/marketing-marketing-global-marketplace-operator/SKILL.md +175 -0
  4. package/HR-Resource/marketing-marketing-google-seo-specialist/SKILL.md +91 -0
  5. package/HR-Resource/marketing-marketing-instagram-commerce-strategist/SKILL.md +81 -0
  6. package/HR-Resource/marketing-marketing-korea-global-market-localization-strategist/SKILL.md +186 -0
  7. package/HR-Resource/marketing-marketing-livestream-commerce-coach/SKILL.md +88 -272
  8. package/HR-Resource/marketing-marketing-naver-seo-specialist/SKILL.md +88 -0
  9. package/HR-Resource/marketing-marketing-short-video-editing-coach/SKILL.md +99 -395
  10. package/HR-Resource/marketing-marketing-youtube-shopping-strategist/SKILL.md +82 -0
  11. package/README.md +41 -0
  12. package/apps/harness-dashboard/app/api/env/route.ts +62 -0
  13. package/apps/harness-dashboard/app/api/log/route.ts +19 -0
  14. package/apps/harness-dashboard/app/api/missions/route.ts +15 -0
  15. package/apps/harness-dashboard/app/api/snapshot/route.ts +15 -0
  16. package/apps/harness-dashboard/app/api/stream/route.ts +71 -0
  17. package/apps/harness-dashboard/app/globals.css +17 -0
  18. package/apps/harness-dashboard/app/layout.tsx +29 -0
  19. package/apps/harness-dashboard/app/page.tsx +35 -0
  20. package/apps/harness-dashboard/components/Drawer.tsx +97 -0
  21. package/apps/harness-dashboard/components/Header.tsx +40 -0
  22. package/apps/harness-dashboard/components/MissionTimeline.tsx +210 -0
  23. package/apps/harness-dashboard/components/OrgTree.tsx +330 -0
  24. package/apps/harness-dashboard/components/Scene.tsx +563 -0
  25. package/apps/harness-dashboard/components/Wordmark.tsx +14 -0
  26. package/apps/harness-dashboard/components/drawer/AgentLogTab.tsx +56 -0
  27. package/apps/harness-dashboard/components/drawer/GotchasTab.tsx +89 -0
  28. package/apps/harness-dashboard/components/drawer/HypothesisTab.tsx +42 -0
  29. package/apps/harness-dashboard/components/drawer/IncidentsTab.tsx +50 -0
  30. package/apps/harness-dashboard/components/drawer/MissionDocTab.tsx +92 -0
  31. package/apps/harness-dashboard/components/drawer/MissionFlowTab.tsx +517 -0
  32. package/apps/harness-dashboard/components/drawer/OwnerHistoryTab.tsx +156 -0
  33. package/apps/harness-dashboard/components/drawer/RoomMetricsTab.tsx +90 -0
  34. package/apps/harness-dashboard/components/drawer/TracksTab.tsx +47 -0
  35. package/apps/harness-dashboard/components/three/ArchiveBoxes.tsx +95 -0
  36. package/apps/harness-dashboard/components/three/DeptBoard.tsx +175 -0
  37. package/apps/harness-dashboard/components/three/Floor3D.tsx +404 -0
  38. package/apps/harness-dashboard/components/three/GoalCard3D.tsx +123 -0
  39. package/apps/harness-dashboard/components/three/HypothesisCards.tsx +71 -0
  40. package/apps/harness-dashboard/components/three/MeetingWhiteboard.tsx +98 -0
  41. package/apps/harness-dashboard/components/three/Minifig3D.tsx +391 -0
  42. package/apps/harness-dashboard/components/three/OpsSiren.tsx +99 -0
  43. package/apps/harness-dashboard/components/three/Stage3D.tsx +104 -0
  44. package/apps/harness-dashboard/components/three/TrackRibbons.tsx +81 -0
  45. package/apps/harness-dashboard/hooks/useHarnessStream.ts +53 -0
  46. package/apps/harness-dashboard/lib/__tests__/company-flow.test.ts +270 -0
  47. package/apps/harness-dashboard/lib/__tests__/harness-state.test.ts +194 -0
  48. package/apps/harness-dashboard/lib/__tests__/i18n.test.ts +16 -0
  49. package/apps/harness-dashboard/lib/__tests__/iso.test.ts +42 -0
  50. package/apps/harness-dashboard/lib/__tests__/safe-json.test.ts +46 -0
  51. package/apps/harness-dashboard/lib/__tests__/state-mapping.test.ts +159 -0
  52. package/apps/harness-dashboard/lib/__tests__/watcher.test.ts +48 -0
  53. package/apps/harness-dashboard/lib/agent-roster.ts +42 -0
  54. package/apps/harness-dashboard/lib/furniture.ts +165 -0
  55. package/apps/harness-dashboard/lib/harness-root.ts +20 -0
  56. package/apps/harness-dashboard/lib/harness-state.ts +1162 -0
  57. package/apps/harness-dashboard/lib/i18n.ts +26 -0
  58. package/apps/harness-dashboard/lib/iso.ts +150 -0
  59. package/apps/harness-dashboard/lib/log-filter.ts +23 -0
  60. package/apps/harness-dashboard/lib/markdown.tsx +306 -0
  61. package/apps/harness-dashboard/lib/path-planning.ts +72 -0
  62. package/apps/harness-dashboard/lib/safe-json.ts +24 -0
  63. package/apps/harness-dashboard/lib/state-mapping.ts +126 -0
  64. package/apps/harness-dashboard/lib/types.ts +329 -0
  65. package/apps/harness-dashboard/lib/watcher.ts +64 -0
  66. package/apps/harness-dashboard/next-env.d.ts +6 -0
  67. package/apps/harness-dashboard/next.config.mjs +9 -0
  68. package/apps/harness-dashboard/package-lock.json +4280 -0
  69. package/apps/harness-dashboard/package.json +38 -0
  70. package/apps/harness-dashboard/playwright.config.ts +23 -0
  71. package/apps/harness-dashboard/postcss.config.js +6 -0
  72. package/apps/harness-dashboard/tailwind.config.ts +49 -0
  73. package/apps/harness-dashboard/tsconfig.json +24 -0
  74. package/apps/harness-dashboard/vitest.config.ts +15 -0
  75. package/assets/templates/AGENTS-ko /341/204/207/341/205/251/341/206/250/341/204/211/341/205/241/341/204/207/341/205/251/341/206/253.md" +114 -0
  76. package/package.json +11 -1
  77. package/scripts/harness-dashboard-up.sh +24 -4
  78. package/HR-Resource/engineering-engineering-feishu-integration-developer/SKILL.md +0 -615
  79. package/HR-Resource/engineering-engineering-wechat-mini-program-developer/SKILL.md +0 -367
  80. package/HR-Resource/marketing-marketing-baidu-seo-specialist/SKILL.md +0 -243
  81. package/HR-Resource/marketing-marketing-bilibili-content-strategist/SKILL.md +0 -216
  82. package/HR-Resource/marketing-marketing-china-ecommerce-operator/SKILL.md +0 -300
  83. package/HR-Resource/marketing-marketing-china-market-localization-strategist/SKILL.md +0 -300
  84. package/HR-Resource/marketing-marketing-douyin-strategist/SKILL.md +0 -166
  85. package/HR-Resource/marketing-marketing-kuaishou-strategist/SKILL.md +0 -240
  86. package/HR-Resource/marketing-marketing-wechat-official-account/SKILL.md +0 -162
  87. package/HR-Resource/marketing-marketing-weibo-strategist/SKILL.md +0 -257
  88. package/HR-Resource/marketing-marketing-xiaohongshu-specialist/SKILL.md +0 -155
  89. package/HR-Resource/marketing-marketing-zhihu-strategist/SKILL.md +0 -179
@@ -0,0 +1,126 @@
1
+ import type { AgentId, Dept, MinifigState } from "./types";
2
+
3
+ export interface ProgressSlice {
4
+ current_agent?: AgentId | null;
5
+ next_agent?: AgentId | null;
6
+ agent_status?: string;
7
+ failure?: { agent?: AgentId | null; message?: string | null } | null;
8
+ meetings?: { active?: AgentId[] };
9
+ service_ops?: {
10
+ incident?: { open?: Array<{ id: string; dept?: string }> };
11
+ monitor?: { stream_active?: boolean; stream_target?: string | null };
12
+ requested_mode?: string | null;
13
+ };
14
+ // v6 NEXUS parallel mode (CLAUDE.md §Parallel Tracks). When tracks have
15
+ // owners with status=running, those agents are concurrently working —
16
+ // not just current_agent. The dashboard treats every running owner as
17
+ // typing so the parallel reality is visible.
18
+ conductor?: {
19
+ state?: string;
20
+ current_action?: string | null;
21
+ tracks?: Array<{ owner?: string; status?: string }>;
22
+ };
23
+ }
24
+
25
+ export interface DeriveContext {
26
+ // Distinct departments with at least one open incident — pre-computed by
27
+ // harness-state.ts so the mapping doesn't iterate the array per agent.
28
+ incidentDepts?: Set<string>;
29
+ // The agent's own department (passed in because we map by department, not
30
+ // by the bare AgentId).
31
+ dept?: Dept | "Operations";
32
+ }
33
+
34
+ // `conductor.current_action` carries strings like:
35
+ // "spawn:generator-frontend"
36
+ // "spawn:coo-developer:F12-006"
37
+ // "spawn:generator-backend (foundation F-101)"
38
+ // Pull out the agent id from the head.
39
+ export function parseSpawnAgent(action: string | null | undefined): string | null {
40
+ if (!action) return null;
41
+ const m = /^spawn:([a-z][a-z0-9-]*)/i.exec(action);
42
+ return m ? m[1].toLowerCase() : null;
43
+ }
44
+
45
+ function isAgentRunningTrack(
46
+ agentId: string,
47
+ conductor: ProgressSlice["conductor"]
48
+ ): boolean {
49
+ const tracks = conductor?.tracks ?? [];
50
+ return tracks.some(
51
+ (t) =>
52
+ t.owner === agentId &&
53
+ (t.status === "running" || t.status === "in_progress" || t.status === "active")
54
+ );
55
+ }
56
+
57
+ function isAgentQueuedTrack(
58
+ agentId: string,
59
+ conductor: ProgressSlice["conductor"]
60
+ ): boolean {
61
+ const tracks = conductor?.tracks ?? [];
62
+ return tracks.some((t) => t.owner === agentId && t.status === "pending");
63
+ }
64
+
65
+ // Priority: red-alert > talking > typing > queued > idle.
66
+ // red-alert wins because incidents/failures must dominate the visual field.
67
+ // talking wins over typing because meetings teleport overrides desk presence.
68
+ // typing now reflects v6 NEXUS *parallel* mode — multiple agents can be
69
+ // concurrently working, all of them get the typing aura.
70
+ // queued makes autonomous handoff visible: when the company already assigned
71
+ // a next department, that department sits at its desk even before the worker
72
+ // process emits its first log line.
73
+ export function deriveMinifigState(
74
+ agentId: AgentId,
75
+ progress: ProgressSlice | null,
76
+ ctx: DeriveContext = {}
77
+ ): MinifigState {
78
+ if (!progress) return "idle";
79
+ if (progress.failure?.agent === agentId) return "red-alert";
80
+ if (ctx.dept && ctx.incidentDepts && ctx.incidentDepts.size > 0) {
81
+ if (ctx.dept === "Operations" || ctx.incidentDepts.has(ctx.dept)) {
82
+ return "red-alert";
83
+ }
84
+ }
85
+ const inMeeting = progress.meetings?.active?.includes(agentId) ?? false;
86
+ if (inMeeting) return "talking";
87
+
88
+ // Parallel-typing signals (any one of these means the agent is working).
89
+ if (
90
+ progress.current_agent === agentId &&
91
+ progress.agent_status === "running"
92
+ ) {
93
+ return "typing";
94
+ }
95
+ if (isAgentRunningTrack(agentId, progress.conductor)) {
96
+ return "typing";
97
+ }
98
+ if (parseSpawnAgent(progress.conductor?.current_action) === agentId) {
99
+ return "typing";
100
+ }
101
+ // G-006: service-ops co-spawned in monitor mode is actively streaming.
102
+ if (
103
+ agentId === "service-ops" &&
104
+ progress.service_ops?.monitor?.stream_active === true
105
+ ) {
106
+ return "typing";
107
+ }
108
+ if (
109
+ progress.next_agent === agentId &&
110
+ progress.agent_status !== "blocked" &&
111
+ progress.agent_status !== "failed"
112
+ ) {
113
+ return "queued";
114
+ }
115
+ if (isAgentQueuedTrack(agentId, progress.conductor)) {
116
+ return "queued";
117
+ }
118
+
119
+ return "idle";
120
+ }
121
+
122
+ export function pickTalkingPreview(agentId: AgentId): string {
123
+ // Sprint 2 will pull this from progress.log; for Sprint 1 we surface a stub
124
+ // so the speech-bubble component has a sensible default.
125
+ return `${agentId}…`;
126
+ }
@@ -0,0 +1,329 @@
1
+ export type AgentId =
2
+ | "dispatcher"
3
+ | "brainstormer"
4
+ | "conductor"
5
+ | "meeting-manager"
6
+ | "planner"
7
+ | "coo-developer"
8
+ | "documentationer"
9
+ | "cto"
10
+ | "generator-backend"
11
+ | "generator-frontend"
12
+ | "generator-designer"
13
+ | "generator-devops"
14
+ | "cqo"
15
+ | "evaluator-functional"
16
+ | "evaluator-visual"
17
+ | "evaluator-code-quality"
18
+ | "evaluator-architecture"
19
+ | "evaluator-security"
20
+ | "service-ops";
21
+
22
+ export type RoomId =
23
+ | "ceo"
24
+ | "meeting"
25
+ | "coo"
26
+ | "cto-team"
27
+ | "cqo-team"
28
+ | "service-ops"
29
+ | "archive";
30
+
31
+ export type Dept = "CEO" | "Meeting" | "Planner" | "CTO" | "CQO" | "Operations";
32
+
33
+ export type MinifigState = "idle" | "queued" | "typing" | "talking" | "red-alert";
34
+
35
+ export interface AgentState {
36
+ id: AgentId;
37
+ name: string;
38
+ dept: Dept;
39
+ /** The agent's *current* room (after meeting teleport). */
40
+ room: RoomId;
41
+ /** The agent's home room — never changes. */
42
+ homeRoom: RoomId;
43
+ minifigState: MinifigState;
44
+ lastActivity: string | null;
45
+ talkingPreview?: string;
46
+ alertReason?: string;
47
+ }
48
+
49
+ export interface RoomMetrics {
50
+ sprint_number?: number;
51
+ last_review?: string | null;
52
+ last_audit?: string | null;
53
+ last_check?: string | null;
54
+ sprint_verdict?: "pending" | "PASS" | "FAIL";
55
+ open_alerts?: number;
56
+ open_arch_risks?: number;
57
+ open_regressions?: number;
58
+ cadence?: MeetingCadence;
59
+ next_scheduled?: string | null;
60
+ active_tracks?: number;
61
+ active_hypothesis?: number;
62
+ active_workers?: number;
63
+ open_incidents?: number;
64
+ pass_rate?: number | null;
65
+ contract_signed?: { be?: boolean; fe?: boolean };
66
+ eval_scores?: EvalScores | null;
67
+ }
68
+
69
+ export interface SeatLayout {
70
+ seats: number;
71
+ occupants: AgentId[];
72
+ overflow: number;
73
+ }
74
+
75
+ export interface RoomState {
76
+ id: RoomId;
77
+ label_ko: string;
78
+ label_en: string;
79
+ dept: Dept | "Archive";
80
+ metrics?: RoomMetrics;
81
+ seatLayout?: SeatLayout;
82
+ }
83
+
84
+ export interface GoalCard {
85
+ id: string;
86
+ title: string;
87
+ description_truncated?: string;
88
+ adherence: number | null;
89
+ }
90
+
91
+ export interface ArchiveEntry {
92
+ dir: string;
93
+ label: string;
94
+ result: "PASS" | "FAIL" | "unknown";
95
+ ts: string | null;
96
+ }
97
+
98
+ export interface ArchiveStat {
99
+ sprintCount: number;
100
+ recent: ArchiveEntry[];
101
+ all: ArchiveEntry[];
102
+ }
103
+
104
+ export type MeetingCadence = "light" | "normal" | "heavy";
105
+ export type MeetingType =
106
+ | "standup"
107
+ | "sprint-review"
108
+ | "spec-review"
109
+ | "incident-war-room"
110
+ | "all-hands"
111
+ | "followup-review";
112
+
113
+ export interface CurrentMeeting {
114
+ type: MeetingType;
115
+ topic?: string;
116
+ convened_at?: string | null;
117
+ }
118
+
119
+ export interface MeetingsState {
120
+ active: AgentId[];
121
+ cadence: MeetingCadence;
122
+ next_scheduled: string | null;
123
+ current: CurrentMeeting | null;
124
+ }
125
+
126
+ export type TrackStatus = "dispatched" | "in_progress" | "joined" | "blocked";
127
+
128
+ export interface ParallelTrack {
129
+ id: string;
130
+ from_meeting: string;
131
+ to_dept: Dept | "Operations" | "Multi";
132
+ to_room: RoomId;
133
+ status: TrackStatus;
134
+ label?: string;
135
+ }
136
+
137
+ export type IncidentSeverity = "low" | "medium" | "high" | "critical";
138
+
139
+ export interface IncidentEntry {
140
+ id: string;
141
+ dept: Dept | "Operations" | "Multi" | string;
142
+ severity: IncidentSeverity;
143
+ message?: string;
144
+ ts?: string | null;
145
+ }
146
+
147
+ export type HypothesisVerdict = "pending" | "valid" | "invalid";
148
+
149
+ export interface HypothesisEntry {
150
+ id: string;
151
+ brief: string;
152
+ verdict: HypothesisVerdict;
153
+ ts?: string | null;
154
+ }
155
+
156
+ export type EscalationReason =
157
+ | "three-fail"
158
+ | "incident"
159
+ | "goal-violation"
160
+ | "budget"
161
+ | "other";
162
+
163
+ export interface EscalationEntry {
164
+ id: string;
165
+ reason: EscalationReason | string;
166
+ message?: string;
167
+ ts?: string | null;
168
+ }
169
+
170
+ export type Pipeline = "FULLSTACK" | "FE-ONLY" | "BE-ONLY" | "META_REFACTOR" | null;
171
+
172
+ export interface ContractSnapshot {
173
+ sprint_number: number | null;
174
+ pipeline: Pipeline;
175
+ api_version: string | null;
176
+ feature_total: number;
177
+ feature_passed: number;
178
+ feature_failed: number;
179
+ contract_signed: { be: boolean; fe: boolean };
180
+ }
181
+
182
+ export interface FeatureSummary {
183
+ id: string;
184
+ title: string;
185
+ status: "ready" | "blocked" | "in_progress" | "passed" | "failed" | "unknown";
186
+ passes: string[];
187
+ }
188
+
189
+ export interface EvalScores {
190
+ functional?: number | null;
191
+ visual?: number | null;
192
+ code_quality?: number | null;
193
+ architecture?: number | null;
194
+ security?: number | null;
195
+ }
196
+
197
+ export interface ErrorBanner {
198
+ level: "info" | "warn" | "error";
199
+ message_ko: string;
200
+ message_en: string;
201
+ }
202
+
203
+ export interface WorkerSnapshot {
204
+ team: number | string;
205
+ feature: string;
206
+ title?: string;
207
+ agent: AgentId | string;
208
+ phase?: string;
209
+ status: "spawned" | "recorded" | "running" | "idle" | "blocked" | "unknown";
210
+ pid?: number | null;
211
+ prompt?: string | null;
212
+ log?: string | null;
213
+ progress: number | null;
214
+ summary: string;
215
+ next_material?: string | null;
216
+ }
217
+
218
+ export interface MeetingRecord {
219
+ id: string;
220
+ path: string;
221
+ ts: string | null;
222
+ title: string;
223
+ verdict?: string | null;
224
+ summary: string;
225
+ content: string;
226
+ }
227
+
228
+ export interface OpsServiceHealth {
229
+ name: string;
230
+ host: string;
231
+ port: number;
232
+ status: "ok" | "degraded" | "down" | "unknown";
233
+ port_state?: string | null;
234
+ health_status?: number | null;
235
+ health_path?: string | null;
236
+ recent_errors?: number | null;
237
+ }
238
+
239
+ export interface EnvKeySummary {
240
+ key: string;
241
+ category: "secret" | "endpoint" | "port" | "mode" | "other";
242
+ masked: string;
243
+ }
244
+
245
+ export interface EnvFileSummary {
246
+ path: string;
247
+ updated_at: string | null;
248
+ key_count: number;
249
+ keys: EnvKeySummary[];
250
+ }
251
+
252
+ export interface OperationsDashboard {
253
+ workers: WorkerSnapshot[];
254
+ features: FeatureSummary[];
255
+ recentMeetings: MeetingRecord[];
256
+ opsHealth: OpsServiceHealth[];
257
+ envFiles: EnvFileSummary[];
258
+ }
259
+
260
+ export interface HarnessSnapshot {
261
+ version: string;
262
+ ts: string;
263
+ projectName: string;
264
+ projectPath: string;
265
+ agents: AgentState[];
266
+ rooms: RoomState[];
267
+ goal: GoalCard | null;
268
+ archive: ArchiveStat;
269
+ meetings: MeetingsState;
270
+ tracks: ParallelTrack[];
271
+ incidents: IncidentEntry[];
272
+ hypothesis: HypothesisEntry[];
273
+ escalations: EscalationEntry[];
274
+ contract: ContractSnapshot;
275
+ evalScores: EvalScores | null;
276
+ errorBanner: ErrorBanner | null;
277
+ dashboard: OperationsDashboard;
278
+ missions: MissionDoc[];
279
+ ownerHistory: OwnerPromptEntry[];
280
+ gotchas: GotchaEntry[];
281
+ }
282
+
283
+ // Real harness mission documents
284
+ export interface WorkerDocEntry {
285
+ name: string;
286
+ content: string;
287
+ status: "COMPLETE" | "IN_PROGRESS" | "unknown";
288
+ owner: "cto" | "cqo" | "coo" | "cdo" | "ops" | "unknown";
289
+ }
290
+
291
+ export interface MissionDoc {
292
+ missionId: string;
293
+ ts: string;
294
+ type: "hotfix" | "feature" | "unknown";
295
+ label: string;
296
+ ceo: string | null;
297
+ cto: string | null;
298
+ cqo: string | null;
299
+ coo: string | null;
300
+ cdo: string | null;
301
+ ops: string | null;
302
+ workers: WorkerDocEntry[];
303
+ cxxPresent: string[];
304
+ }
305
+
306
+ export interface OwnerPromptEntry {
307
+ ts: string;
308
+ content: string;
309
+ type: "goal" | "hot-fix" | "other";
310
+ }
311
+
312
+ export interface GotchaEntry {
313
+ id: string;
314
+ title: string;
315
+ content: string;
316
+ tags: string[];
317
+ }
318
+
319
+ export type OrgRole = "owner" | "ceo" | "coo" | "cdo" | "cto" | "cqo" | "ops";
320
+
321
+ export interface OrgNodeDef {
322
+ id: string;
323
+ role: OrgRole;
324
+ label: string;
325
+ sublabel?: string;
326
+ status: MinifigState;
327
+ activity: string | null;
328
+ agentIds: AgentId[];
329
+ }
@@ -0,0 +1,64 @@
1
+ import chokidar from "chokidar";
2
+ import path from "node:path";
3
+
4
+ export interface WatchHandle {
5
+ close(): Promise<void>;
6
+ }
7
+
8
+ export interface WatchOptions {
9
+ /** Coalesce rapid-fire events within this window (ms). */
10
+ debounceMs?: number;
11
+ }
12
+
13
+ // Watches the .harness/ directory for changes that affect the dashboard
14
+ // snapshot — progress.json (state), progress.log (activity), actions/* (sprint
15
+ // artifacts), archive/* (sprint history).
16
+ export function watchHarness(
17
+ rootDir: string,
18
+ onChange: (changedPath: string) => void,
19
+ opts: WatchOptions = {}
20
+ ): WatchHandle {
21
+ const debounceMs = opts.debounceMs ?? 50;
22
+ const harnessDir = path.join(rootDir, ".harness");
23
+ const watcher = chokidar.watch(
24
+ [
25
+ path.join(harnessDir, "progress.json"),
26
+ path.join(harnessDir, "progress.log"),
27
+ path.join(harnessDir, "actions"),
28
+ path.join(harnessDir, "archive"),
29
+ ],
30
+ {
31
+ ignoreInitial: true,
32
+ awaitWriteFinish: { stabilityThreshold: 100, pollInterval: 50 },
33
+ // Vitest creates and deletes temp harness dirs quickly; polling avoids
34
+ // macOS FSEvents EMFILE noise after close().
35
+ usePolling: process.env.VITEST === "true",
36
+ // Don't crawl deeply into archive — recent dirs only.
37
+ depth: 4,
38
+ }
39
+ );
40
+
41
+ let pending: NodeJS.Timeout | null = null;
42
+ let lastPath = "";
43
+
44
+ const fire = (changedPath: string) => {
45
+ lastPath = changedPath;
46
+ if (pending) return;
47
+ pending = setTimeout(() => {
48
+ pending = null;
49
+ onChange(lastPath);
50
+ }, debounceMs);
51
+ };
52
+
53
+ watcher.on("add", fire).on("change", fire).on("unlink", fire);
54
+
55
+ return {
56
+ async close() {
57
+ if (pending) {
58
+ clearTimeout(pending);
59
+ pending = null;
60
+ }
61
+ await watcher.close();
62
+ },
63
+ };
64
+ }
@@ -0,0 +1,6 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+ /// <reference path="./.next/types/routes.d.ts" />
4
+
5
+ // NOTE: This file should not be edited
6
+ // see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
@@ -0,0 +1,9 @@
1
+ /** @type {import('next').NextConfig} */
2
+ const nextConfig = {
3
+ reactStrictMode: true,
4
+ experimental: {
5
+ typedRoutes: false,
6
+ },
7
+ };
8
+
9
+ export default nextConfig;