diff --git a/docs/superpowers/plans/2026-08-23-workflow-run-watcher-clock-independent-test.md b/docs/superpowers/plans/2026-08-23-workflow-run-watcher-clock-independent-test.md deleted file mode 100644 index 894aa6fb..00000000 --- a/docs/superpowers/plans/2026-08-23-workflow-run-watcher-clock-independent-test.md +++ /dev/null @@ -1,69 +0,0 @@ -# Workflow Run Watcher Clock-Independent Test Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Prevent `workflow-run-watcher.test.ts` from expiring as wall-clock time advances. - -**Architecture:** Keep production code unchanged. Anchor every synthetic workflow timestamp to one captured current -time while preserving the fixture's existing event offsets, then verify the recent-summary assertion against the -real watcher. - -**Tech Stack:** TypeScript, Vitest, Node.js - ---- - -### Task 1: Make the completed-run fixture relative to test time - -**Files:** - -- Modify: `test/workflow-run-watcher.test.ts:17-100` - -- [x] **Step 1: Confirm the fixed fixture fails after its recent window expires** - -Run: - -```bash -npm test -- test/workflow-run-watcher.test.ts -``` - -Expected: FAIL in `getRecentRunSummaries omits agents[] (lightweight snapshot)` because `summaries` is empty. - -- [x] **Step 2: Anchor the fixture to the current test time** - -At the start of `sampleRunJson()`, capture a start time fifteen minutes before the call: - -```typescript -const startTime = Date.now() - 15 * 60_000; -``` - -Replace the fixed ISO timestamp with `new Date(startTime).toISOString()`, replace the fixed `startTime` property -with the variable, and express each workflow agent's `startedAt`, `queuedAt`, and `lastProgressAt` as its existing -millisecond offset from `startTime`. - -- [x] **Step 3: Verify the focused test passes** - -Run: - -```bash -npm test -- test/workflow-run-watcher.test.ts -``` - -Expected: 19 tests pass with no failures. - -- [x] **Step 4: Verify formatting and the CI gate** - -Run: - -```bash -npx prettier --check test/workflow-run-watcher.test.ts -npm run test:ci -``` - -Expected: formatting passes and the CI unit/integration suite has no failures. - -- [x] **Step 5: Commit the correction** - -```bash -git add test/workflow-run-watcher.test.ts docs/superpowers/plans/2026-08-23-workflow-run-watcher-clock-independent-test.md -git commit -m "test(workflows): keep recent-run fixture clock-independent" -``` diff --git a/docs/superpowers/specs/2026-08-23-workflow-run-watcher-clock-independent-test-design.md b/docs/superpowers/specs/2026-08-23-workflow-run-watcher-clock-independent-test-design.md deleted file mode 100644 index f440595f..00000000 --- a/docs/superpowers/specs/2026-08-23-workflow-run-watcher-clock-independent-test-design.md +++ /dev/null @@ -1,24 +0,0 @@ -# Workflow Run Watcher Clock-Independent Test Design - -## Problem - -`getRecentRunSummaries omits agents[] (lightweight snapshot)` uses a workflow fixture whose activity timestamps -are fixed in June 2026. The test requests a 100,000-minute recent window, so it began failing once wall-clock time -moved beyond that window even though the implementation had not changed. - -## Design - -Keep production code unchanged. Build the synthetic run from one captured `Date.now()` value and express its -timestamp, start time, queue times, progress times, and completion times as offsets from that value. Preserve the -existing ordering and duration relationships between workflow events, so the fixture remains representative while -its completed run always falls within the test's recent window. - -Do not widen the window or replace `getRecentRunSummaries()` with an unfiltered API: either choice would weaken or -eventually reintroduce the regression. Do not install fake timers, because the watcher uses asynchronous discovery -and timer behavior that this assertion does not need to control. - -## Verification - -- Confirm the existing fixed fixture fails because the run falls outside the recent window. -- Run `test/workflow-run-watcher.test.ts` after converting the fixture to relative timestamps. -- Run formatting and the CI unit/integration gate before pushing the updated PR. diff --git a/test/workflow-run-watcher.test.ts b/test/workflow-run-watcher.test.ts index 965a418e..04acc3f7 100644 --- a/test/workflow-run-watcher.test.ts +++ b/test/workflow-run-watcher.test.ts @@ -17,13 +17,22 @@ const PROJECT_HASH = '-home-arkon-default-claudeman'; const SESSION_UUID = '388113c8-cd01-4e80-93a8-3be66ab1519b'; const RUN_ID = 'wf_test1234-abc'; +/** + * The fixture's epochs are anchored to "now", never pinned, because the recency + * assertions below compare them against `Date.now()`. A frozen epoch plus a fixed + * window is a time bomb: the original fixture's newest activity sat at + * 2026-06-14T20:06:40Z, and `getRecentRunSummaries(100000)` — that argument is + * MINUTES, i.e. 69.4 days — stopped matching it on 2026-08-23T06:46:40Z, turning + * CI red on a suite nobody had touched. Offsets from the anchor are preserved + * verbatim, so every parsed duration and ordering assertion is unchanged. + */ +const RUN_ANCHOR = Date.now() - 601_000; + /** A run JSON shaped like a real (killed) run: all three agent states + the bloat fields. */ function sampleRunJson() { - const startTime = Date.now() - 15 * 60_000; - return { runId: RUN_ID, - timestamp: new Date(startTime).toISOString(), + timestamp: new Date(RUN_ANCHOR).toISOString(), taskId: 'task_abc', // --- bloat fields that MUST be stripped --- script: 'export const meta = {};\n'.repeat(5000), // ~110KB @@ -37,7 +46,7 @@ function sampleRunJson() { workflowName: 'review-open-prs', status: 'killed', error: 'user stopped the task', - startTime, + startTime: RUN_ANCHOR, defaultModel: 'claude-opus-4-8[1m]', totalTokens: 109703, totalToolCalls: 44, @@ -57,13 +66,13 @@ function sampleRunJson() { agentId: 'a6c0e282c3f5ac0bf', model: 'claude-opus-4-8[1m]', state: 'done', - startedAt: startTime + 1002, - queuedAt: startTime + 962, + startedAt: RUN_ANCHOR + 1002, + queuedAt: RUN_ANCHOR + 962, attempt: 1, lastToolName: 'StructuredOutput', lastToolSummary: 'Does the profile setting make the allowlist dead config', promptPreview: 'You are reviewing a pull request...', - lastProgressAt: startTime + 525143, + lastProgressAt: RUN_ANCHOR + 525_143, tokens: 104703, toolCalls: 41, durationMs: 524140, @@ -78,12 +87,12 @@ function sampleRunJson() { agentId: 'a1234567890abcdef', model: 'claude-opus-4-8[1m]', state: 'progress', - startedAt: startTime + 11000, - queuedAt: startTime + 970, + startedAt: RUN_ANCHOR + 11_000, + queuedAt: RUN_ANCHOR + 970, attempt: 1, lastToolName: 'Read', promptPreview: 'Review PR 127...', - lastProgressAt: startTime + 601000, + lastProgressAt: RUN_ANCHOR + 601_000, tokens: 5000, toolCalls: 3, }, @@ -95,9 +104,9 @@ function sampleRunJson() { phaseTitle: 'Verify', model: 'claude-opus-4-8[1m]', state: 'start', - queuedAt: startTime + 980, + queuedAt: RUN_ANCHOR + 980, promptPreview: 'Verify finding x...', - lastProgressAt: startTime + 980, + lastProgressAt: RUN_ANCHOR + 980, }, ], };