@multiplier-labs/stepflow 0.3.1 → 0.3.4

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 (112) hide show
  1. package/.codekin/reports/code-review/2026-04-11_code-review-daily.md +146 -0
  2. package/.codekin/reports/code-review/2026-04-13_code-review-daily.md +74 -0
  3. package/.codekin/reports/comments/2026-04-04_comment-assessment.md +179 -0
  4. package/.codekin/reports/comments/2026-04-11_comment-assessment.md +161 -0
  5. package/.codekin/reports/comments/2026-04-14_comment-assessment.md +34 -0
  6. package/.codekin/reports/complexity/2026-04-03_complexity-report.md +152 -0
  7. package/.codekin/reports/complexity/2026-04-11_complexity-report.md +152 -0
  8. package/.codekin/reports/complexity/2026-04-14_complexity-report.md +34 -0
  9. package/.codekin/reports/dependencies/2026-04-04_dependency-health.md +94 -0
  10. package/.codekin/reports/dependencies/2026-04-11_dependency-health.md +93 -0
  11. package/.codekin/reports/docs-audit/2026-04-04_docs-audit.md +10 -0
  12. package/.codekin/reports/docs-audit/2026-04-11_docs-audit.md +138 -0
  13. package/.codekin/reports/docs-audit/2026-04-13_docs-audit.md +121 -0
  14. package/.codekin/reports/security/2026-04-03_security-audit.md +208 -0
  15. package/.codekin/reports/security/2026-04-11_security-audit.md +297 -0
  16. package/.github/CODEOWNERS +15 -0
  17. package/.github/dependabot.yml +23 -0
  18. package/.github/workflows/ci.yml +38 -0
  19. package/.github/workflows/publish.yml +17 -5
  20. package/.github/workflows/security.yml +29 -0
  21. package/CHANGELOG.md +34 -0
  22. package/README.md +24 -0
  23. package/SECURITY.md +40 -0
  24. package/dist/{chunk-FQCT3I2A.js → chunk-5XJBAPXF.js} +527 -551
  25. package/dist/chunk-5XJBAPXF.js.map +1 -0
  26. package/dist/{chunk-U5QGN3RT.js → chunk-FMPJBK2N.js} +67 -55
  27. package/dist/chunk-FMPJBK2N.js.map +1 -0
  28. package/dist/{chunk-GXY66V7Q.js → chunk-JPXPB7U5.js} +52 -2
  29. package/dist/chunk-JPXPB7U5.js.map +1 -0
  30. package/dist/{chunk-XCF4PLN7.js → chunk-U3PCVK6O.js} +276 -61
  31. package/dist/chunk-U3PCVK6O.js.map +1 -0
  32. package/dist/events/index.d.ts +60 -7
  33. package/dist/events/index.js +1 -1
  34. package/dist/{index-CPfGsmrp.d.ts → index-Cd0IZ40L.d.ts} +56 -17
  35. package/dist/index.d.ts +15 -23
  36. package/dist/index.js +784 -547
  37. package/dist/index.js.map +1 -1
  38. package/dist/scheduler/index.d.ts +4 -4
  39. package/dist/scheduler/index.js +2 -2
  40. package/dist/storage/index.d.ts +322 -56
  41. package/dist/storage/index.js +4 -2
  42. package/dist/{types-BlwFEGzj.d.ts → types-DSEjiLMj.d.ts} +16 -15
  43. package/dist/{types-DzkEqnQJ.d.ts → types-DZUdWmCc.d.ts} +2 -2
  44. package/dist/{types-BVShq7zT.d.ts → types-K5Gjk3H_.d.ts} +4 -4
  45. package/docs/stepflow-api-reference.md +5 -4
  46. package/package.json +12 -8
  47. package/src/core/engine/concurrency.ts +229 -0
  48. package/src/core/engine/lifecycle.ts +176 -0
  49. package/src/core/engine.test.ts +536 -522
  50. package/src/core/engine.ts +116 -235
  51. package/src/core/index.ts +3 -3
  52. package/src/core/orchestrator/persistence.ts +165 -0
  53. package/src/core/orchestrator/plan-resolution.ts +140 -0
  54. package/src/core/orchestrator/state-transitions.ts +109 -0
  55. package/src/core/orchestrator/step-runner.ts +427 -0
  56. package/src/core/orchestrator.ts +57 -395
  57. package/src/core/types.ts +7 -29
  58. package/src/events/index.ts +4 -4
  59. package/src/events/memory.test.ts +39 -41
  60. package/src/events/memory.ts +7 -10
  61. package/src/events/socketio.test.ts +112 -162
  62. package/src/events/socketio.ts +20 -27
  63. package/src/events/types.ts +16 -19
  64. package/src/events/webhook.test.ts +489 -235
  65. package/src/events/webhook.ts +355 -139
  66. package/src/index.ts +19 -19
  67. package/src/planning/evaluate-condition.test.ts +153 -0
  68. package/src/planning/evaluate-condition.ts +105 -0
  69. package/src/planning/index.ts +6 -3
  70. package/src/planning/planner.ts +45 -152
  71. package/src/planning/planning.test.ts +303 -366
  72. package/src/planning/registry.test.ts +204 -251
  73. package/src/planning/registry.ts +20 -106
  74. package/src/planning/types.ts +23 -27
  75. package/src/scheduler/cron.test.ts +149 -150
  76. package/src/scheduler/cron.ts +58 -69
  77. package/src/scheduler/index.ts +4 -14
  78. package/src/scheduler/postgres-persistence.ts +89 -105
  79. package/src/scheduler/sqlite-persistence.test.ts +96 -96
  80. package/src/scheduler/sqlite-persistence.ts +40 -22
  81. package/src/scheduler/types.ts +4 -9
  82. package/src/storage/index.ts +5 -5
  83. package/src/storage/memory.test.ts +133 -183
  84. package/src/storage/memory.ts +21 -42
  85. package/src/storage/postgres-core.ts +550 -0
  86. package/src/storage/postgres-migration.test.ts +69 -0
  87. package/src/storage/postgres-transaction-pagination.test.ts +107 -0
  88. package/src/storage/postgres.ts +222 -937
  89. package/src/storage/sqlite-core.ts +151 -0
  90. package/src/storage/sqlite.test.ts +330 -281
  91. package/src/storage/sqlite.ts +225 -170
  92. package/src/storage/types.ts +33 -58
  93. package/src/utils/errors.test.ts +78 -80
  94. package/src/utils/errors.ts +27 -57
  95. package/src/utils/id.ts +3 -3
  96. package/src/utils/index.ts +6 -4
  97. package/src/utils/logger.test.ts +52 -58
  98. package/src/utils/logger.ts +4 -4
  99. package/src/utils/pinned-fetch.test.ts +84 -0
  100. package/src/utils/pinned-fetch.ts +118 -0
  101. package/src/utils/postgres-deps.ts +20 -4
  102. package/src/utils/retry.test.ts +47 -67
  103. package/src/utils/retry.ts +9 -9
  104. package/src/utils/safe-json.test.ts +166 -0
  105. package/src/utils/safe-json.ts +107 -0
  106. package/src/utils/webhook-signature.test.ts +111 -0
  107. package/src/utils/webhook-signature.ts +74 -0
  108. package/dist/chunk-FQCT3I2A.js.map +0 -1
  109. package/dist/chunk-GXY66V7Q.js.map +0 -1
  110. package/dist/chunk-U5QGN3RT.js.map +0 -1
  111. package/dist/chunk-XCF4PLN7.js.map +0 -1
  112. package/src/scheduler/postgres-persistence.test.ts +0 -906
@@ -0,0 +1,146 @@
1
+ # Daily Code Review: stepflow
2
+
3
+ **Date**: 2026-04-11T02:46:56.903Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: 592ed81f-2d62-4608-bf34-98470c42c55b
7
+ **Session**: 99646d28-b2e3-4d3a-bcdf-1478c0bc9eff
8
+
9
+ ---
10
+
11
+ Let me verify the most critical findings before writing the final report.Now I have enough detail to write the full report.
12
+
13
+ ---
14
+
15
+ ## Daily Automated Code Review — `@multiplier-labs/stepflow` v0.2.6
16
+
17
+ **Date**: 2026-04-11 | **Commit**: `bd16961` (Initial commit — only commit in repo)
18
+
19
+ ---
20
+
21
+ ### Critical
22
+
23
+ **None found.**
24
+
25
+ ---
26
+
27
+ ### Warnings
28
+
29
+ **[W1] Checkpoint resume derives completedStepKeys from context keys — fragile assumption**
30
+ `src/core/engine.ts:569`
31
+ ```typescript
32
+ const completedStepKeys = new Set(Object.keys(run.context));
33
+ ```
34
+ `run.context` stores step results keyed by step key. If a step writes to context before throwing, its key will appear in context but the step didn't complete. On resume, that step would be silently skipped. There is no separate `completedSteps` log to distinguish "wrote result" from "finished cleanly."
35
+ **Recommendation**: Track a dedicated `completedSteps: string[]` field in the persisted run record, written atomically with the step result, and use that set during resume.
36
+
37
+ ---
38
+
39
+ **[W2] `isBlockedIp` does not cover IPv6 reserved ranges beyond loopback and link-local**
40
+ `src/events/webhook.ts:533–557`
41
+ The resolved-IP check (`isBlockedIp`) handles IPv4 private ranges well, and blocks `::1` and `fe80:` for IPv6. However it does not block:
42
+ - `fc00::/7` — Unique Local Addresses (ULA, the IPv6 equivalent of RFC 1918)
43
+ - `ff00::/8` — Multicast
44
+ - `2001:db8::/32` — Documentation range
45
+ - `::ffff:0:0/96` — IPv4-mapped IPv6 addresses (could bypass IPv4 private range checks)
46
+
47
+ `isBlockedHost` is more aggressive (blocks all bracket-wrapped IPv6 literals), so the static check is safe. But if `validateResolvedHost` receives a resolved IPv6 address in any format other than `[addr]`, the gap applies.
48
+ **Recommendation**: In `isBlockedIp`, add IPv4-mapped check (`::ffff:` prefix) and ULA check (`/^f[cd]/i`).
49
+
50
+ ---
51
+
52
+ **[W3] `enqueueRequest` doesn't guard against `maxConcurrentRequests <= 0`**
53
+ `src/events/webhook.ts:475–479`
54
+ ```typescript
55
+ if (this.activeRequests < this.maxConcurrentRequests) {
56
+ execute();
57
+ } else {
58
+ this.requestQueue.push(execute);
59
+ }
60
+ ```
61
+ If `maxConcurrentRequests` is set to `0` (or negative), the condition is never true and all requests queue forever — effectively a deadlock. There is no constructor validation.
62
+ **Recommendation**: Add `if (maxConcurrentRequests <= 0) throw new Error('maxConcurrentRequests must be > 0')` in the constructor.
63
+
64
+ ---
65
+
66
+ **[W4] `withRetry` abort throws a generic `Error('Aborted')` instead of `WorkflowCanceledError`**
67
+ `src/utils/retry.ts:83–85`
68
+ ```typescript
69
+ if (opts.signal?.aborted) {
70
+ throw new Error('Aborted');
71
+ }
72
+ ```
73
+ The `sleep()` helper (line 58) correctly throws `WorkflowCanceledError` on abort, but the pre-attempt abort check throws a plain `Error`. Downstream error classification (e.g. `instanceof WorkflowCanceledError`) will fail for aborts caught before the first sleep.
74
+ **Recommendation**: Change to `throw new WorkflowCanceledError('run')` to match the `sleep()` path.
75
+
76
+ ---
77
+
78
+ **[W5] Hook errors log without run context**
79
+ `src/core/orchestrator.ts:258`
80
+ ```typescript
81
+ logger.error('afterRun hook failed:', hookError);
82
+ ```
83
+ The error is intentionally swallowed (correct behavior), but the log line has no `runId` or `kind`, making production debugging unnecessarily hard.
84
+ **Recommendation**: `logger.error(\`afterRun hook failed for run ${runId}:\`, hookError)`
85
+
86
+ ---
87
+
88
+ **[W6] Integration and edge-case test coverage gaps**
89
+
90
+ No integration tests exercise the full lifecycle across multiple adapters. Specific gaps:
91
+ - Resume of a partially-completed run (directly relevant to W1 above)
92
+ - `maxConcurrency` queuing with priority ordering
93
+ - Webhook retries under network failure simulations
94
+ - Scheduler DST-boundary cron behavior
95
+ - Hook error recovery scenarios (`beforeRun`, `afterRun`, `onStepError` throwing)
96
+ - SQLite transaction rollback verification
97
+
98
+ The current test suite is unit-level and covers the happy path well, but these edge cases are where durable-execution bugs hide.
99
+
100
+ ---
101
+
102
+ ### Info
103
+
104
+ **[I1] CI workflow publishes without running tests**
105
+ `.github/workflows/publish.yml:23–29`
106
+ The publish job runs `npm ci` → `npm run build` → `npm publish`. There is no `npm test` or `npm run typecheck` step. A broken release could be published if tests are skipped.
107
+ **Recommendation**: Add `- run: npm test` and `- run: npm run typecheck` between Install and Publish steps.
108
+
109
+ ---
110
+
111
+ **[I2] `calculateRetryDelay` is defined but never called internally**
112
+ `src/utils/retry.ts:115–121`
113
+ `withRetry` computes delay inline (`currentDelay * opts.backoff`) rather than calling `calculateRetryDelay`. The exported helper is a public API, but the internal inconsistency means the two can drift.
114
+ **Recommendation**: Refactor `withRetry` to call `calculateRetryDelay` for the delay computation, or document that they are intentionally separate.
115
+
116
+ ---
117
+
118
+ **[I3] Peer dependency lower bounds are not tested**
119
+ `package.json:57–71`
120
+ `better-sqlite3>=11`, `kysely>=0.27`, `pg>=8.13` are wide ranges. Only the devDependency pinned versions are tested. If a consumer uses the minimum allowed version and it has a breaking API difference, the error surface is silent.
121
+ **Recommendation**: Add a CI matrix job that installs minimum peer dependency versions and runs the test suite.
122
+
123
+ ---
124
+
125
+ **[I4] Short webhook secrets not warned against**
126
+ `src/events/webhook.ts:24`
127
+ The `secret` field accepts any string. A very short secret (< 16 bytes) produces a weak HMAC. The JSDoc example (`process.env.WEBHOOK_SECRET`) is good, but there is no runtime warning or minimum-length enforcement.
128
+ **Recommendation**: Add a `secret.length < 16` warning log in `signPayload` at minimum, or throw in the constructor.
129
+
130
+ ---
131
+
132
+ **[I5] `retryDelay` in webhook config is not applied with backoff**
133
+ `src/events/webhook.ts` — webhook retries use a fixed `retryDelay` with no backoff, while the core `withRetry` utility supports exponential backoff. This inconsistency means repeated webhook failures hammer the endpoint at a fixed rate.
134
+ **Recommendation**: Either apply backoff to webhook retries (using the existing `withRetry` utility) or document the fixed-delay intent.
135
+
136
+ ---
137
+
138
+ ### Summary
139
+
140
+ | Severity | Count |
141
+ |---|---|
142
+ | Critical | 0 |
143
+ | Warning | 6 |
144
+ | Info | 5 |
145
+
146
+ The codebase is well-structured with good TypeScript discipline, solid SSRF protection, and thoughtful SQLite query design. The top priorities are **W1** (checkpoint resume correctness) and **W4** (abort error type mismatch), both of which are correctness bugs rather than just style issues. **W2** is a security hardening gap. The CI publish-without-tests gap (**I1**) is a process risk worth closing quickly.
@@ -0,0 +1,74 @@
1
+ # Daily Code Review: stepflow
2
+
3
+ **Date**: 2026-04-13T01:32:56.537Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: 12fb929b-3da3-4e1b-a8fc-e9b497a0eb42
7
+ **Session**: c3af596a-a1f0-43c3-922e-8ed0cd0562f0
8
+
9
+ ---
10
+
11
+ ---
12
+
13
+ ## Daily Code Review — 2026-04-13
14
+
15
+ **Project:** `@multiplier-labs/stepflow` — TypeScript workflow orchestration library
16
+ **Coverage:** 93.88% | **Recent changes:** Initial commit only (no incremental changes in 7 days)
17
+
18
+ ---
19
+
20
+ ### CRITICAL (4)
21
+
22
+ **C1 — Unhandled `JSON.parse()` in all storage adapters**
23
+ `src/storage/sqlite.ts`, `src/storage/postgres.ts`, `src/scheduler/*-persistence.ts`
24
+ Every row deserialization call is bare `JSON.parse()`. Corrupted DB rows will throw unhandled `SyntaxError` and crash the process. Wrap all calls in try-catch with safe fallbacks.
25
+
26
+ **C2 — DNS lookup blocks event loop on every webhook send**
27
+ `src/events/webhook.ts` ~line 417
28
+ `validateResolvedHost()` calls `dns.lookup()` with no timeout, no caching. A stalling DNS resolver can hang the entire event loop for 30+ seconds. Add a per-call timeout (via `AbortController`) and a short-TTL cache.
29
+
30
+ **C3 — No `shutdown()` method; timers are untracked**
31
+ `src/core/engine.ts` ~lines 369–374
32
+ `setTimeout` / `setImmediate` handles in `launchRun()` are never stored or cleared. The process cannot exit cleanly while delayed runs are pending. Implement `WorkflowEngine.shutdown()` that cancels all tracked handles.
33
+
34
+ **C4 — Race condition in `waitForRun`**
35
+ `src/core/engine.ts` ~lines 480–527
36
+ Storage is polled _before_ subscribing to events. A run completing in that window emits an event that is never received, causing an infinite wait. Fix: subscribe first, then check storage; unsubscribe immediately if terminal.
37
+
38
+ ---
39
+
40
+ ### WARNING (10)
41
+
42
+ | # | File | Issue |
43
+ |---|------|-------|
44
+ | W1 | `package.json` deps | `npm audit`: `picomatch` (HIGH ReDoS) + `brace-expansion` (MODERATE hang). Run `npm audit fix`. |
45
+ | W2 | `src/storage/postgres.ts:13-16` | 48 `any` casts — concentrated in lazy Kysely imports. Use conditional types or `unknown` + guards. |
46
+ | W3 | `src/events/webhook.ts` | Webhook secret length not enforced despite docs recommending ≥ 32 bytes. Validate in `addEndpoint()`. |
47
+ | W4 | `src/events/webhook.ts` | DNS validation is outside the retry loop — DNS failures skip all retry logic. Move inside loop. |
48
+ | W5 | `src/scheduler/cron.ts:96-97` | `start()` after `stop()` can create duplicate subscriptions. Unconditionally unsubscribe at top of `start()`. |
49
+ | W6 | `src/storage/postgres.ts` | PG connection pool not closed if adapter goes out of scope without explicit `close()`. Document + warn. |
50
+ | W7 | `src/core/engine.ts:405-412` | No depth/count limit on `spawnChild()` — recursive workflows can spawn unboundedly. Add `maxChildDepth`. |
51
+ | W8 | `src/core/orchestrator.ts:87-93` | `emitEvent()` called without error handling — transport failures can abort workflow execution. Wrap in try-catch. |
52
+ | W9 | `src/scheduler/cron.ts` | Invalid persisted cron strings crash the scheduler on startup. Catch `CronExpressionParser.parse()` errors. |
53
+ | W10 | `src/storage/sqlite.ts` | SQLite single-writer limitation not documented. Add prominent warning; recommend Postgres for multi-worker. |
54
+
55
+ ---
56
+
57
+ ### INFO (5)
58
+
59
+ - **I1** — Test files are very large (engine.test.ts: 1364 lines). Split by feature for maintainability.
60
+ - **I2** — Resume/checkpoint semantics undocumented for distributed use (single-process only).
61
+ - **I3** — `registerWorkflow()` doesn't validate `kind` format or duplicate step keys.
62
+ - **I4** — Error stacks sanitized on write but not on read — defense-in-depth gap.
63
+ - **I5** — No circuit breaker for consistently failing webhook endpoints.
64
+
65
+ ---
66
+
67
+ **Top 5 priorities:**
68
+ 1. `JSON.parse()` error handling in all storage adapters (C1)
69
+ 2. `WorkflowEngine.shutdown()` with timer cleanup (C3)
70
+ 3. Fix subscribe-then-check ordering in `waitForRun` (C4)
71
+ 4. `npm audit fix` for dep vulnerabilities (W1)
72
+ 5. DNS caching + timeout in webhook transport (C2/W4)
73
+
74
+ Report saved to `.codekin/reports/code-review/2026-04-13_code-review-daily.md`.
@@ -0,0 +1,179 @@
1
+ # Comment Assessment: stepflow
2
+
3
+ **Date**: 2026-04-04T06:59:29.464Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: c4721f8a-9287-4e91-b3a7-17151c71b861
7
+ **Session**: 0bf68b75-f778-4c55-9c9d-a011d10130be
8
+
9
+ ---
10
+
11
+ Now I have a thorough picture of the codebase. Let me produce the final report.
12
+
13
+ ## Summary
14
+
15
+ **Language**: TypeScript (strict mode) | **Files**: 43 source files (~14,000 LoC) | **Modules**: core, storage, events, scheduler, planning, utils
16
+
17
+ Overall comment coverage is **high** — approximately **90%** of public API surface is documented, and ~70% of complex logic has inline explanation. The codebase demonstrates professional-grade JSDoc discipline: every exported type, interface, config object, and error class carries a doc comment. File-level module comments are present on nearly every file. The main gaps are a duplicated/misplaced JSDoc block in `postgres.ts`, thin coverage on a handful of `StorageAdapter` implementation methods, and the absence of `@param`/`@returns` tags on several private helpers. Test files are undocumented, which is standard practice but leaves intent implicit for complex scenarios.
18
+
19
+ **Overall rating: 8.5 / 10**
20
+
21
+ ---
22
+
23
+ ## Well-Documented Areas
24
+
25
+ ### `src/core/orchestrator.ts`
26
+ Exemplary documentation. The two private helpers `executeWithTimeout` and `raceWithAbort` carry unusually thorough comments explaining *why* the `finally` cleanup blocks exist (memory-leak prevention, listener accumulation). The module-level JSDoc accurately describes the file's role. Inline comments mark every logical phase of `executeWorkflow` and `executeStep` (checkpoint resume, cancellation checks, error strategy branching).
27
+
28
+ ```typescript
29
+ /**
30
+ * Execute a function with a timeout.
31
+ *
32
+ * Uses Promise.race to resolve as soon as either the function completes, the
33
+ * timeout fires, or the abort signal triggers. The `finally` block is critical:
34
+ * without it, the losing promise's timer/listener would remain active, leaking
35
+ * memory — especially problematic in long-running engines processing many steps.
36
+ */
37
+ ```
38
+
39
+ ### `src/utils/retry.ts`
40
+ Every exported symbol has full JSDoc with `@param`, `@returns`, and `@throws` tags. The `sleep` function documents its cancellation contract. `DEFAULT_RETRY_OPTIONS` is explicitly exported and described. A closing comment on the unreachable branch (`// This should never be reached, but TypeScript needs it`) is an honest and useful annotation.
41
+
42
+ ### `src/core/types.ts`
43
+ All foundational types are documented. `StepErrorStrategy` includes inline prose describing the effect of each union member:
44
+ ```typescript
45
+ /**
46
+ * - 'fail': Stop the workflow immediately
47
+ * - 'retry': Retry the step up to maxRetries times
48
+ * - 'skip': Mark as skipped and continue to next step
49
+ */
50
+ ```
51
+
52
+ ### `src/utils/errors.ts`
53
+ Each of the eight custom error classes has a one-line JSDoc that clearly states the triggering condition. `WorkflowEngineError.fromError` documents its fallback behavior.
54
+
55
+ ### `src/storage/postgres.ts` — configuration and schema sections
56
+ `PostgresStorageConfig` has `@example` blocks for both the basic and shared-pool cases. Kysely row types (`WorkflowRunsTable`, `WorkflowRunStepsTable`, etc.) document every column inline, including semantic meaning, units, and null semantics. The `qb` getter explains the schema-scoping invariant.
57
+
58
+ ### `src/utils/postgres-deps.ts`
59
+ The lazy-loader pattern is precisely explained in the file-level comment, and the `@throws` tags on `loadPostgresDeps` enumerate both failure modes. The `// any is intentional here` comment pre-empts reviewer questions about the `PostgresDeps` interface.
60
+
61
+ ### `src/events/socketio.ts`
62
+ `setupClientHandlers` includes a full `@example` block showing correct usage including the authorization callback pattern. The `close()` method clarifies that the underlying Socket.IO socket lifecycle is the caller's responsibility.
63
+
64
+ ### `src/planning/planner.ts` — `scoreConditions`
65
+ The scoring formula comment is unusually transparent:
66
+ ```typescript
67
+ // Base score 50 ensures condition-matched recipes beat unconditional defaults (score 10).
68
+ // Each additional condition adds 10 points, capped at 100 so forced recipes (100) still win.
69
+ ```
70
+
71
+ ### `src/utils/id.ts`
72
+ Despite being a single exported function, it documents the ID format, design rationale (time-ordering, collision resistance, URL safety), and the approximate output length.
73
+
74
+ ---
75
+
76
+ ## Underdocumented Areas
77
+
78
+ | File | Issue | Severity |
79
+ |---|---|---|
80
+ | `src/storage/postgres.ts:563–565` | Two JSDoc blocks stacked immediately on top of each other (`listRuns` doc + `applyRunsFilters` doc) with no actual method between them; the `listRuns` method itself gets the private helper's comment, and `listRuns` is effectively undocumented | High |
81
+ | `src/storage/postgres.ts:516–524` | `getRun`, `createStep`, `getStep`, `updateStep`, `getSteps`, `createEvent`, `listEvents` — none have JSDoc; only `createRun`, `updateRun`, and `listRuns` are documented | High |
82
+ | `src/core/engine.ts:109–119` | `QueuedRun` interface is documented but the `WorkflowEngine` class declaration at line 121 has no JSDoc block (the preceding `@example` block belongs to a dangling doc comment from the closing `*/` of the example at line 107, making it visually orphaned) | High |
83
+ | `src/storage/types.ts:80–200` | `WorkflowEventRecord`, `StepResult`, `StepRecord`, `CreateRunInput`, `UpdateRunInput`, `StorageAdapter` interface and all its method signatures lack individual JSDoc; only the top-level record types are documented | Medium |
84
+ | `src/scheduler/cron.ts` (internal methods) | Private methods `checkAndTriggerSchedules`, `handleWorkflowCompletionTriggers`, `calculateNextRun` have no JSDoc; the polling and event-subscription architecture is not explained | Medium |
85
+ | `src/core/engine.ts:324–332` | `executeRun` is documented as "Execute a run (internal method)" with no detail; it is a thin wrapper that calls `launchRun` — the indirection is unexplained | Medium |
86
+ | `src/storage/sqlite.ts` | `createStep`, `updateStep`, `getSteps`, `createEvent`, `listEvents` methods lack JSDoc; only configuration types are documented | Medium |
87
+ | `src/planning/registry.ts` | `MemoryStepHandlerRegistry` and `MemoryRecipeRegistry` class-level JSDoc is absent; method comments are single-line but several complex query methods (e.g., `getByKind`, `getVariant`) have no parameter documentation | Medium |
88
+ | `src/events/webhook.ts` | `WebhookEventTransport` class body is not visible in sampling; retry/backoff logic for failed webhook deliveries is expected to lack inline explanation based on surrounding pattern | Medium |
89
+ | `src/storage/memory.ts:67–200` | `listRuns` filter/sort/pagination logic has inline `// Filter by X` headers but no explanation of sort stability guarantees or edge-case behaviour (e.g., null `startedAt`) | Low |
90
+ | `src/scheduler/sqlite-persistence.ts` | Prepared-statement initialization pattern (using `stmts` nullable object) is not commented; the reason for lazy initialization vs. constructor setup is implicit | Low |
91
+ | `src/utils/logger.ts` | `sanitizeErrorForStorage` is exported but has no JSDoc — its role (stripping sensitive fields before DB writes) is non-obvious from the name alone | Low |
92
+ | `src/core/engine.ts` (test files) | `src/core/engine.test.ts` has no describe-level comments explaining what scenario each test group targets; complex concurrency and resume tests lack rationale comments | Low |
93
+ | `src/storage/types.ts` | `ExtendedRunStatus` and `ExtendedStepStatus` comment says "New" but those are likely legacy designations now; no explanation of when to use extended vs. core status types | Low |
94
+ | `src/planning/types.ts` | `PlanModificationType` union values (`'add_step'`, `'remove_step'`, `'modify_step'`, `'set_default'`) are not individually annotated with their intended semantics | Low |
95
+
96
+ ---
97
+
98
+ ## Comment Quality Issues
99
+
100
+ **1. Stacked / orphaned JSDoc block — `src/storage/postgres.ts:563–568`**
101
+
102
+ ```typescript
103
+ /**
104
+ * List workflow runs with filtering and pagination.
105
+ */
106
+ /**
107
+ * Apply common run filters to a Kysely query builder.
108
+ * Used by both the data query and count query in listRuns to avoid duplication.
109
+ */
110
+ private applyRunsFilters<T ...>(
111
+ ```
112
+
113
+ Two `/** */` blocks appear consecutively. TypeScript/JSDoc tooling attaches the *last* block to the declaration, meaning `listRuns` (a public method) loses its documentation entirely and only the private helper `applyRunsFilters` gets a comment. The public method's JSDoc is silently discarded. This is a bug in the comment structure.
114
+
115
+ **2. `WorkflowEngine` class body starts without a JSDoc — `src/core/engine.ts:109–121`**
116
+
117
+ The `@example` closing marker at line 107 (`*/`) is followed by the undocumented `QueuedRun` interface, then the `export class WorkflowEngine` at line 121 with no doc comment of its own. The intended class-level JSDoc (lines 83–108) is rendered as a freestanding comment, not as attached JSDoc for `WorkflowEngine`. IDEs will not show hover documentation for the class constructor.
118
+
119
+ **3. `StorageAdapter.listRuns` — `src/storage/types.ts` (approximately line 130–160)**
120
+
121
+ The `StorageAdapter` interface lists all method signatures but provides no per-method JSDoc. A consumer implementing the interface receives no guidance on whether `listRuns` should be transactional, whether filters are AND- or OR-combined, or what an empty result looks like.
122
+
123
+ **4. `cancelRun` comment inaccuracy — `src/core/engine.ts:444`**
124
+
125
+ ```typescript
126
+ // Update status in storage (the orchestrator will also update on completion)
127
+ await this.storage.updateRun(runId, { status: 'canceled', finishedAt: new Date() });
128
+ ```
129
+
130
+ The comment says "the orchestrator will also update on completion" but when `cancelRun` is called, the orchestrator may not be running (if the run was still queued). The comment implies a double-write that is only sometimes true, which could mislead a reader investigating status inconsistencies.
131
+
132
+ **5. `ensureInitialized` — `src/storage/postgres.ts:246`**
133
+
134
+ This private guard method has no JSDoc. Since it throws on un-initialized state, documenting the throw condition would aid callers implementing `StorageAdapter`.
135
+
136
+ **6. `ExtendedRunStatus` comment — `src/storage/types.ts:12–23`**
137
+
138
+ ```typescript
139
+ /**
140
+ * Extended status of a workflow run.
141
+ * Adds 'pending' and 'timeout' to the core statuses.
142
+ */
143
+ ```
144
+
145
+ `RunStatus` (core) already includes `'queued'` and `'running'`; `ExtendedRunStatus` adds `'pending'` and `'timeout'`. The comment correctly notes the additions, but gives no guidance on *when* a consumer should use `ExtendedRunStatus` vs. `RunStatus` — the distinction between the two parallel type hierarchies is undocumented.
146
+
147
+ ---
148
+
149
+ ## Recommendations
150
+
151
+ **1. Fix the stacked JSDoc block in `src/storage/postgres.ts:563–568`**
152
+ Remove or merge the orphaned `listRuns` JSDoc into a single block placed directly above the `async listRuns(...)` method declaration. The `applyRunsFilters` private helper should retain its own comment, but it should immediately precede that private method, not `listRuns`. This is a correctness issue: as written, `listRuns` is invisible to IDE hover and documentation generators.
153
+
154
+ **2. Attach the `WorkflowEngine` class-level JSDoc in `src/core/engine.ts`**
155
+ The `@example` block (lines 83–107) and the `QueuedRun` interface (lines 109–119) currently sit between the intended class doc and the class declaration. Move `QueuedRun` above the class-level JSDoc, or place the class JSDoc immediately before `export class WorkflowEngine` at line 121. Without this fix, TypeScript language servers do not display hover documentation for the class or its constructor.
156
+
157
+ **3. Add JSDoc to `StorageAdapter` method signatures in `src/storage/types.ts`**
158
+ The `StorageAdapter` interface is the primary extension point for library consumers implementing custom backends. Document at minimum: `createRun`, `updateRun`, `listRuns` (note AND semantics for filters), `createStep`, `updateStep`, and `createEvent`. Include `@param` descriptions and `@returns` contracts. This directly impacts third-party adapter authors.
159
+
160
+ **4. Document `sanitizeErrorForStorage` in `src/utils/logger.ts`**
161
+ This exported utility performs security-sensitive work (scrubbing error objects before persistence), but it has no JSDoc. A doc comment should state what fields are removed (e.g., `stack`), why (storage size / privacy), and what the return type contract is. Its current name gives no indication it performs sanitization beyond what the type system shows.
162
+
163
+ **5. Document the `ExtendedRunStatus` / `RunStatus` dual hierarchy in `src/storage/types.ts`**
164
+ Add a comment block before `ExtendedRunStatus` explaining when each type should be used (e.g., "Use `RunStatus` for the core engine; use `ExtendedRunStatus` when working with the extended storage schema which includes `pending` and `timeout`"). This prevents consumers from accidentally mixing the two.
165
+
166
+ **6. Add inline comments to `CronScheduler`'s internal scheduling loop in `src/scheduler/cron.ts`**
167
+ The private methods responsible for polling (`checkAndTriggerSchedules`), event-based triggering (`handleWorkflowCompletionTriggers`), and next-run calculation (`calculateNextRun`) contain the most nuanced logic in the scheduler module (timezone handling, cron expression parsing, status matching). Each should have at minimum a one-paragraph JSDoc explaining its role and any non-obvious invariants (e.g., why the poll interval is 1 second by default, how `lastRunAt` prevents double-firing).
168
+
169
+ **7. Fix the `cancelRun` comment inaccuracy in `src/core/engine.ts:444`**
170
+ Replace `// Update status in storage (the orchestrator will also update on completion)` with a comment that accurately reflects the dual-write scenario: the orchestrator updates status when it catches `WorkflowCanceledError`, but this pre-emptive write ensures the status is visible immediately and handles the case where the run was queued (never started). This removes a misleading implication about guaranteed orchestrator involvement.
171
+
172
+ **8. Add JSDoc to `getRun`, `createStep`, `updateStep`, and `listEvents` in `src/storage/postgres.ts`**
173
+ These `StorageAdapter` implementation methods (~lines 516–750) have no doc comments, unlike `createRun`, `updateRun`, and `listRuns`. At minimum, note any PostgreSQL-specific behaviour that differs from the `MemoryStorageAdapter` (e.g., JSON serialization round-trips, `null` vs. `undefined` handling for optional fields).
174
+
175
+ **9. Annotate `PlanModificationType` union values in `src/planning/types.ts`**
176
+ The four modification types (`'add_step'`, `'remove_step'`, `'modify_step'`, `'set_default'`) are used in the planner output to describe what was changed, but their intended semantics are not documented. Add inline comments (or expand the type into a documented discriminated union) so that consumers of `Plan.modifications` know how to interpret and render each modification type.
177
+
178
+ **10. Add a brief rationale comment in `src/core/engine.ts:568–574` (`resumeRun` checkpoint logic)**
179
+ The line `const completedStepKeys = new Set(Object.keys(run.context))` silently encodes a convention: that the `context` object's keys are exactly the completed step keys. This is a non-obvious coupling between the orchestrator's checkpoint-save strategy (`storage.updateRun(runId, { context: { ...context.results } })`) and the resume logic. A one-line comment (`// context keys == completed step keys by orchestrator checkpoint convention`) would make this invariant explicit and prevent it from being accidentally broken.
@@ -0,0 +1,161 @@
1
+ # Comment Assessment: stepflow
2
+
3
+ **Date**: 2026-04-11T02:48:00.194Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: 98e67949-d200-491e-8d2e-333880bba263
7
+ **Session**: 1f4952f0-14a9-4677-a164-d0cd05cc6d35
8
+
9
+ ---
10
+
11
+ ## Summary
12
+
13
+ **Stepflow** is a TypeScript workflow orchestration library (~43 source files, ~30 non-test). Overall documentation quality is **Good to Excellent**, with approximately **67% of source files** carrying JSDoc/TSDoc comments. Public APIs are thoroughly documented; the core engine and utility modules set a high bar. Test files carry no comments, which is appropriate given self-descriptive `describe`/`it` naming. The most notable gaps are confined to a handful of interface implementation methods in the scheduler persistence layer, and two non-obvious semantic behaviors in the planning module that lack any explanatory comment.
14
+
15
+ **Rating: 8/10** — above industry average; minor targeted additions would close all meaningful gaps.
16
+
17
+ ---
18
+
19
+ ## Well-Documented Areas
20
+
21
+ ### `src/index.ts`
22
+ A 56-line module-level JSDoc block enumerates every major feature (workflow registration, async execution, state management, event system, pluggable storage, retry/error handling) followed by a complete 45-line working code example. Serves as the library's living documentation entry point.
23
+
24
+ ### `src/core/engine.ts` (693 lines)
25
+ Every public method includes `@param`, `@returns`, and `@throws` tags. Logical sections are separated by banner dividers (`// ========…`). Example:
26
+ ```ts
27
+ /**
28
+ * Register a workflow definition with the engine.
29
+ * @param definition - The workflow definition to register.
30
+ * @throws {WorkflowEngineError} If a workflow with the same kind is already registered.
31
+ */
32
+ registerWorkflow(definition: WorkflowDefinition): void
33
+ ```
34
+
35
+ ### `src/core/orchestrator.ts` — timeout / memory-leak rationale (lines 482–518)
36
+ The `executeWithTimeout` helper explains *why* the `finally` block exists, not just what it does:
37
+ ```ts
38
+ /**
39
+ * …The `finally` block is critical: without it, the losing promise's timer/listener
40
+ * would remain active, leaking memory — especially problematic in long-running engines
41
+ * processing many steps.
42
+ */
43
+ ```
44
+ This is exemplary "why" commenting.
45
+
46
+ ### `src/utils/errors.ts`
47
+ Every custom error class carries a one-sentence JSDoc stating its purpose and the specific condition that triggers it.
48
+
49
+ ### `src/utils/retry.ts`
50
+ `RetryOptions` fields are individually documented. The exponential backoff formula includes an example showing calculated delays at each attempt.
51
+
52
+ ### `src/core/types.ts`
53
+ Every interface field has a one-line `/** … */` doc comment. `WorkflowContext`, `WorkflowDefinition`, `WorkflowHooks`, `RunStatus`, and `StepStatus` are all self-contained.
54
+
55
+ ### `src/storage/postgres.ts` — schema type docs (lines 46–128)
56
+ Kysely row-type interfaces (`WorkflowRunsTable`, `WorkflowRunStepsTable`, etc.) document every column with field-level comments explaining nullability, units, and JSON encoding:
57
+ ```ts
58
+ /** JSON-serialized output payload, null until run completes. */
59
+ output_json: string | null;
60
+ /** Execution priority (lower number = higher precedence). */
61
+ priority: number;
62
+ ```
63
+
64
+ ### `src/scheduler/postgres-persistence.ts` — class-level JSDoc with two `@example` blocks
65
+ Includes a basic usage example and a "Sharing connection pool" example, covering the two main integration patterns.
66
+
67
+ ---
68
+
69
+ ## Underdocumented Areas
70
+
71
+ | File | Issue | Severity |
72
+ |------|-------|----------|
73
+ | `src/planning/planner.ts` | `evaluateCondition()` (line 44): uses `RE2` instead of native `RegExp` for ReDoS safety — security-relevant choice is entirely uncommented | **High** |
74
+ | `src/planning/planner.ts` | `exists`/`notExists` operators (lines 95–99): treat empty string `''` as non-existent — opinionated semantic behaviour with no comment | **High** |
75
+ | `src/scheduler/postgres-persistence.ts` | `loadSchedules()` (line 306), `saveSchedule()` (line 316), `updateSchedule()` (line 340), `deleteSchedule()` (line 402): public interface methods with no JSDoc | **Medium** |
76
+ | `src/planning/planner.ts` | `applyConstraints()` (line 466): `perStepTimeout = maxDuration / steps.length` — evenly distributes budget across steps without noting the equal-duration assumption | **Medium** |
77
+ | `src/planning/planner.ts` | `RuleBasedPlanner` class (line 170): single-line class JSDoc; scoring algorithm overview (condition-score vs priority-score vs forced/preferred-variant paths) not explained | **Medium** |
78
+ | `src/scheduler/postgres-persistence.ts` | `rowToSchedule()` (line 480): dual JSON.parse path (`typeof … === 'string'` guard) is non-obvious with no comment explaining why it handles both string and pre-parsed JSONB | **Medium** |
79
+ | `src/storage/postgres.ts` | `StepflowDatabase` interface (line 130): no doc comment explaining that this is the Kysely schema map tying table names to row types | **Low** |
80
+ | `src/scheduler/postgres-persistence.ts` | `ensureInitialized()` (line 166): private guard method with no doc comment | **Low** |
81
+ | `src/planning/planner.ts` | `scoreConditions()` scoring formula (lines 127–134) has inline comments but no JSDoc — callers of the public `RuleBasedPlanner` API cannot understand selection scoring from public docs alone | **Low** |
82
+ | `src/storage/postgres.ts` | `stripStack()` helper (line 37): has a one-liner but does not explain *why* `stack` is stripped (prevent large stack strings from bloating persisted JSON) | **Low** |
83
+ | `src/utils/id.ts` | `generateId()` explains ULID advantages but omits the practical consequence: IDs are lexicographically sortable by creation time | **Low** |
84
+ | `src/events/memory.ts` | Channel naming convention explained in code (lines 34–41) but not in any exported type or JSDoc for consumers building custom transports | **Low** |
85
+ | `src/scheduler/sqlite-persistence.ts` | `createTables()` has schema comments; `loadSchedules()`, `saveSchedule()`, `deleteSchedule()` interface methods have no JSDoc (same gap as postgres counterpart) | **Low** |
86
+ | `src/planning/registry.ts` | `MemoryRecipeRegistry` class has no class-level JSDoc; only individual methods are commented | **Low** |
87
+ | `src/core/orchestrator.ts` | `WorkflowCheckpoint` interface is documented, but no comment explains when/why checkpoints are created (only on step completion, not at arbitrary points) | **Low** |
88
+
89
+ ---
90
+
91
+ ## Comment Quality Issues
92
+
93
+ No clearly **inaccurate** or **misleading** comments were found. The following are cases where comments exist but create a gap between stated and actual behaviour:
94
+
95
+ ### 1. `src/planning/planner.ts:95–99` — `exists`/`notExists` undocumented empty-string treatment
96
+ ```ts
97
+ case 'exists':
98
+ return fieldValue !== undefined && fieldValue !== null && fieldValue !== '';
99
+
100
+ case 'notExists':
101
+ return fieldValue === undefined || fieldValue === null || fieldValue === '';
102
+ ```
103
+ The corresponding type definition in `src/planning/types.ts` describes `exists` as "field exists and is non-null." It does not mention that `''` is also treated as absent. A consumer who stores an empty string legitimately would get a surprising false from `exists`. No comment at either the operator definition or the implementation site flags this.
104
+
105
+ ### 2. `src/planning/planner.ts:85–93` — RE2 usage is silent
106
+ ```ts
107
+ case 'matches':
108
+ if (typeof fieldValue === 'string' && typeof conditionValue === 'string') {
109
+ try {
110
+ return new RE2(conditionValue).test(fieldValue);
111
+ } catch {
112
+ return false;
113
+ }
114
+ }
115
+ ```
116
+ `RE2` is a linear-time regex engine that prevents ReDoS attacks — a meaningful security property. The comment on `evaluateCondition` (line 42) says only "Evaluate a single condition against a value." Callers writing user-supplied patterns need to know RE2 syntax differs from ECMA regex (e.g., no lookaheads), and security-conscious reviewers need to know the DoS protection is intentional.
117
+
118
+ ### 3. `src/scheduler/postgres-persistence.ts:387` — condition always true for `domainKey`
119
+ ```ts
120
+ if (updates[domainKey] !== undefined || merged[domainKey] !== undefined) {
121
+ ```
122
+ Because `merged` is `{ ...existing, ...updates }`, `merged[domainKey]` is always defined for fields present in the existing row. The inline comment above (lines 342–345) correctly describes intent ("only included if explicitly provided"), but the condition as written effectively includes every field on every update. The comment describes the goal, not the actual behaviour — a subtle mislead.
123
+
124
+ ---
125
+
126
+ ## Recommendations
127
+
128
+ 1. **`src/planning/planner.ts:85–93` — Document RE2's ReDoS safety in `evaluateCondition`**
129
+ Add to the `matches` case a brief comment: `// RE2 provides linear-time matching, preventing ReDoS attacks from user-supplied patterns.` Also note in the `evaluateCondition` JSDoc that RE2 syntax is used (no lookaheads, no backreferences), so callers know pattern compatibility constraints.
130
+
131
+ 2. **`src/planning/planner.ts:95–99` and `src/planning/types.ts` — Align `exists` docs with empty-string behaviour**
132
+ Either update the `ConditionOperator` type definition to read "field is non-null and non-empty-string," or add a `// NOTE:` inline explaining the empty-string-as-absent convention. Doing both ensures consumers reading either the type or the implementation get consistent expectations.
133
+
134
+ 3. **`src/scheduler/postgres-persistence.ts:306–408` — Add JSDoc to all four public `SchedulePersistence` interface methods**
135
+ `loadSchedules`, `saveSchedule`, `updateSchedule`, and `deleteSchedule` are part of the persistence contract but have no documentation. At minimum add `@returns` and `@throws` tags to match the documented `getSchedule`, `getDueSchedules`, and `getCompletionTriggers` below them. Apply the same fix to `src/scheduler/sqlite-persistence.ts` for consistency.
136
+
137
+ 4. **`src/planning/planner.ts:170` — Expand `RuleBasedPlanner` class JSDoc**
138
+ The current one-line class comment ("Rule-based planner that selects recipes based on condition matching") does not explain the selection algorithm. Add a paragraph describing the scoring hierarchy: forced recipe → preferred variant → condition scoring (50 + 10×N conditions, capped at 100) → default recipe → first available. This allows users to understand and predict selection without reading the source.
139
+
140
+ 5. **`src/planning/planner.ts:462–468` — Document the per-step timeout distribution assumption**
141
+ ```ts
142
+ const perStepTimeout = constraints.maxDuration
143
+ ? Math.floor(constraints.maxDuration / steps.length)
144
+ : undefined;
145
+ ```
146
+ Add a comment: `// Budget is divided equally across steps; steps with inherently longer durations may time out even if the overall run would complete within maxDuration.` This sets expectations and helps users tune `maxDuration` correctly.
147
+
148
+ 6. **`src/scheduler/postgres-persistence.ts:480` — Document the dual JSON.parse guard in `rowToSchedule`**
149
+ The pattern `typeof row.X === 'string' ? JSON.parse(row.X) : row.X` appears six times without explanation. Add a single comment near the first occurrence: `// Kysely returns JSONB columns pre-parsed in some drivers; guard ensures correct deserialization regardless of driver behaviour.`
150
+
151
+ 7. **`src/planning/types.ts` — Document each `ConditionOperator` value in the enum/union**
152
+ The `ConditionOperator` type has a comment block, but individual values like `'matches'`, `'contains'`, `'exists'` have no per-value docs. Add a TSDoc `@remarks` or switch to a documented string union listing what each operator accepts (e.g., `'matches'` works on strings using RE2 syntax; `'contains'` works on both strings and arrays).
153
+
154
+ 8. **`src/core/orchestrator.ts` — Document when checkpoints are written**
155
+ `WorkflowCheckpoint` is documented, but `executeWorkflow` does not explain that checkpoints capture state only after a step fully completes. Add a note in the `ExecuteOptions.checkpoint` field doc: `// Checkpoint is built incrementally as steps complete; a partially executed step is NOT checkpointed and will re-run on resume.`
156
+
157
+ 9. **`src/storage/postgres.ts:130` — Add JSDoc to `StepflowDatabase`**
158
+ This interface is the Kysely schema map. A one-line comment — `/** Kysely database schema mapping table names to their row types. */` — makes the architectural role immediately clear to contributors adding new tables.
159
+
160
+ 10. **`src/utils/id.ts` — Note sort-by-creation-time property in `generateId` JSDoc**
161
+ The existing comment explains ULID advantages but omits the most practically useful one: generated IDs are lexicographically sortable by creation time, making them suitable as primary keys with implicit time-ordering. Adding this helps consumers decide whether to add a separate `created_at` sort column.
@@ -0,0 +1,34 @@
1
+ # Comment Assessment: stepflow
2
+
3
+ **Date**: 2026-04-14T01:18:27.171Z
4
+ **Repository**: /srv/repos/stepflow
5
+ **Branch**: main
6
+ **Workflow Run**: af53bfdc-9706-4e22-bafa-e5954c9c9875
7
+ **Session**: 27222af5-7c69-4c33-af75-7ad0318122a4
8
+
9
+ ---
10
+
11
+ ## Summary
12
+
13
+ **Overall rating: A+ (Excellent)**
14
+
15
+ The Stepflow codebase has outstanding documentation practices. The report has been saved to `.codekin/reports/comments/2026-04-14_comment-assessment.md`.
16
+
17
+ Key findings:
18
+
19
+ **Strengths (~94% coverage):**
20
+ - Every exported function, interface, class, and type carries JSDoc
21
+ - File-level module comments in 19/20 sampled files
22
+ - Critical complex logic (abort handling, memory leak prevention, retry backoff) is thoroughly explained
23
+ - Consistent use of `@param`, `@returns`, `@throws`, `@example` throughout
24
+ - Zero stale, outdated, or misleading comments detected
25
+ - External docs (`README.md`, `docs/stepflow-api-reference.md`) are comprehensive
26
+
27
+ **Minor gaps (all Low/Medium severity):**
28
+ - `QueuedRun` internal interface in `engine.ts` lacks JSDoc
29
+ - `MemoryStorageAdapter` methods have one-liner summaries but no `@param`/`@returns`
30
+ - Test files have no file-level comments
31
+ - `postgres.ts` and persistence files not fully verified for comment parity
32
+ - ID generation strategy in `utils/id.ts` undocumented
33
+
34
+ Top recommendations: add JSDoc to `QueuedRun`, document the SQLite statement cache strategy, audit Postgres implementation for parity with SQLite docs, and add `@param`/`@returns` to storage implementation methods.