ftown-bridge 0.19.39 → 0.19.40

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 (47) hide show
  1. package/dist/create-ftown-session.js +3 -8
  2. package/dist/create-ftown-session.js.map +1 -1
  3. package/dist/ftown-cli.d.ts +1 -0
  4. package/dist/ftown-cli.js +15 -7
  5. package/dist/ftown-cli.js.map +1 -1
  6. package/dist/ftown-env-cli.d.ts +1 -0
  7. package/dist/ftown-env-cli.js +15 -4
  8. package/dist/ftown-env-cli.js.map +1 -1
  9. package/dist/ftown-home.js +1 -1
  10. package/dist/ftown-sessions-cli.js +3 -3
  11. package/dist/harness-cli.js +3 -3
  12. package/dist/harness-installer.js +8 -75
  13. package/dist/harness-installer.js.map +1 -1
  14. package/dist/harness-registry.d.ts +2 -23
  15. package/dist/harness-registry.js +2 -16
  16. package/dist/harness-registry.js.map +1 -1
  17. package/dist/index.js +2 -8
  18. package/dist/index.js.map +1 -1
  19. package/dist/install-ftown-skill.d.ts +6 -0
  20. package/dist/install-ftown-skill.js +78 -3
  21. package/dist/install-ftown-skill.js.map +1 -1
  22. package/dist/mail-delivery.js +1 -1
  23. package/dist/mail-delivery.js.map +1 -1
  24. package/dist/provider-env-store.js +1 -1
  25. package/dist/provider-env-store.js.map +1 -1
  26. package/dist/retired-skill-assets.d.ts +3 -0
  27. package/dist/retired-skill-assets.js +11 -0
  28. package/dist/retired-skill-assets.js.map +1 -1
  29. package/package.json +2 -3
  30. package/skills/ftown/SKILL.md +54 -19
  31. package/skills/ftown/references/orchestrator.md +22 -99
  32. package/skills/ftown/references/sessions.md +32 -35
  33. package/dist/claude-trust.d.ts +0 -11
  34. package/dist/claude-trust.js +0 -47
  35. package/dist/claude-trust.js.map +0 -1
  36. package/dist/install-ftown-workflows-cli.d.ts +0 -3
  37. package/dist/install-ftown-workflows-cli.js +0 -33
  38. package/dist/install-ftown-workflows-cli.js.map +0 -1
  39. package/dist/workflow-runner-cli.d.ts +0 -2
  40. package/dist/workflow-runner-cli.js +0 -376
  41. package/dist/workflow-runner-cli.js.map +0 -1
  42. package/dist/workflow-runner.d.ts +0 -163
  43. package/dist/workflow-runner.js +0 -307
  44. package/dist/workflow-runner.js.map +0 -1
  45. package/skills/fticket/SKILL.md +0 -166
  46. package/skills/ftown/references/workflows.md +0 -470
  47. package/skills/ftown/scripts/example.flow.mjs +0 -170
@@ -1,470 +0,0 @@
1
- # ftown-workflows
2
-
3
- `ftown-workflows` is a **scripted orchestration engine** for real ftown sessions.
4
- You write a `.mjs` script using a small API (`agent`, `parallel`, `pipeline`,
5
- `phase`, `log`, `args`, `budget`) and the runner spawns real ftown sessions (claude /
6
- cursor / codex), waits for each to write a result file, cleans up, and returns the
7
- value — all deterministically and repeatably.
8
-
9
- This complements the by-hand orchestrator playbook in
10
- `references/orchestrator.md`. Use ftown-workflows when the work is scripted and
11
- repeatable; use orchestrator guidance when you need to improvise or keep a
12
- human in the loop.
13
-
14
- A workflow runs deterministic control flow inside one explicitly started run.
15
-
16
- ## Operating Contract
17
-
18
- Use ftown-workflows to encode deterministic multi-session control flow: fan-out,
19
- verification, synthesis, loops, retries, and resumable handoffs. The workflow
20
- script is where the structure lives: which workers run independently, which
21
- results are verified, where a barrier is necessary, and what gets returned.
22
-
23
- Do not infer workflow permission just because a task might benefit from
24
- parallelism. Run a workflow only when the user explicitly asks for one, asks for
25
- multi-agent orchestration, asks to fan out workers, names `ftown-workflows`, asks
26
- to run a specific workflow, or invokes a skill/command whose instructions require
27
- this skill. Otherwise, answer inline or describe the workflow you would run and
28
- ask before spending the user's tokens.
29
-
30
- If the user explicitly says this task **must use ftown-workflows**, a manual
31
- simulation is not enough. Create a `.flow.mjs` script and run it with
32
- `~/.ftown/ftown-workflows`.
33
-
34
- ## Scout First
35
-
36
- Start with a cheap inline scout before writing the workflow script: list relevant
37
- files, search call sites, scope the diff, read key modules, and identify whether
38
- the task is understanding, design, review, research, migration, or greenfield
39
- build. You do not need the final DAG before starting the task; you need the
40
- work-list and shape before orchestration.
41
-
42
- Common single-phase shapes:
43
-
44
- | Intent | Shape |
45
- | --- | --- |
46
- | Understand | readers over subsystems -> structured map |
47
- | Design | independent approaches -> judge panel -> scored synthesis |
48
- | Review or audit | dimensions -> find -> adversarial verify -> synthesis |
49
- | Research | search/read sweep -> deep read -> verify -> cited synthesis |
50
- | Migrate | discover sites -> transform isolated slices -> verify |
51
- | Greenfield build | scout stack -> contract-first prep -> modules -> compile/review |
52
-
53
- For large work, run several small workflows in sequence instead of one giant
54
- script. Read each result before deciding the next phase. A practical split is:
55
-
56
- ```text
57
- discovery-design.flow.mjs -> implementation-review.flow.mjs
58
- ```
59
-
60
- The discovery/design workflow discovers constraints, compares approaches, writes
61
- a durable handoff (`discovery-design.handoff.json`, `plan.json`, `specs/`, a
62
- rubric), and stops. The implementation/review workflow consumes that handoff,
63
- implements the work-list, integrates, verifies, and repairs against the rubric.
64
-
65
- ## Pipeline By Default
66
-
67
- Default to `ctx.pipeline(...)` for multi-stage work. Each item should advance as
68
- soon as its previous stage finishes; do not make fast items wait for the slowest
69
- item unless the next stage genuinely needs all prior results at once.
70
-
71
- A barrier with `ctx.parallel(...)` is correct when a later stage needs
72
- cross-item context:
73
-
74
- - deduping or merging all findings before expensive verification
75
- - early exit when the full result set is empty
76
- - comparing one finding against the other findings
77
-
78
- A barrier is not justified by ordinary mapping/filtering, by conceptual phase
79
- boundaries, or by code tidiness. Put per-item transforms inside a pipeline stage.
80
- When unsure, pipeline.
81
-
82
- Use explicit `phase` names in `ctx.agent(..., { phase })` inside pipelines and
83
- parallel stages so progress groups are stable even when stages interleave.
84
-
85
- ## Quality Patterns
86
-
87
- Pick and compose these patterns based on the user's request:
88
-
89
- - **Adversarial verify:** for each claim/finding, spawn independent skeptics
90
- asked to refute it. Keep a finding only if it survives the vote.
91
- - **Perspective-diverse verify:** use distinct verifier lenses such as
92
- correctness, security, performance, reproducibility, and UX instead of cloned
93
- prompts.
94
- - **Judge panel:** generate multiple independent solutions, have judges score
95
- them, then synthesize from the winner while preserving useful ideas from
96
- runners-up.
97
- - **Loop-until-dry:** for unknown-size discovery, keep launching finder rounds
98
- until a fixed number of consecutive rounds returns nothing new.
99
- - **Multi-modal sweep:** search by different axes (file path, call graph,
100
- content, timestamp, dependency, runtime behavior) and merge findings.
101
- - **Completeness critic:** end with a worker that asks what was missed: unread
102
- sources, unverified claims, uncovered modalities, or dropped work.
103
- - **No silent caps:** if you cap coverage, sampling, retries, or result counts,
104
- log what was skipped with `ctx.log()`.
105
-
106
- Scale to the wording. "Find any bugs" can be a small finder set and one verifier.
107
- "Thoroughly audit" or a large explicit budget should increase finder diversity,
108
- verification votes, and loop-until-dry depth.
109
-
110
- ## Dependent Phases
111
-
112
- `ctx.agent()` returns `null` instead of throwing when a worker times out, exits
113
- without a result, exhausts budget, or writes `{ "ok": false }`. That is useful
114
- for optional fan-out, but dangerous for dependent phases. Fail fast before
115
- implementation, verification, or synthesis depends on a missing result.
116
-
117
- Use this guard in workflow scripts:
118
-
119
- ```js
120
- function requireAgentResult(value, label) {
121
- if (value == null) {
122
- throw new Error(`${label} failed; aborting dependent workflow phases`);
123
- }
124
- return value;
125
- }
126
- ```
127
-
128
- Optional fan-out may filter failures with `.filter(Boolean)`. Required handoffs,
129
- module implementations, verifiers, and final synthesis should use the guard.
130
-
131
- ## Greenfield Builds
132
-
133
- When a workflow is building a whole app, game, service, library, or system from
134
- scratch, the discovery/design phase should include contract-first prep before
135
- implementation fan-out. Read and apply:
136
-
137
- ```text
138
- ~/.claude/skills/contract-first-prep/SKILL.md
139
- ~/.claude/skills/contract-first-prep/references/contract-guide.md
140
- ```
141
-
142
- The prep worker should produce the parallel-safe handoff:
143
-
144
- - minimal scaffold with a strict typecheck/build gate
145
- - immutable type/interface contract for cross-module boundaries
146
- - pure-data config plus shared low-level helpers
147
- - disjoint module decomposition (`plan.json`) and per-module specs
148
-
149
- The later implementation/review workflow treats that contract, config, and shared
150
- helpers as frozen. Workers adapt their modules to the contract; only the
151
- integrator performs broad wiring. If review finds a design flaw, launch a new
152
- discovery/design workflow instead of letting implementers redesign in parallel.
153
-
154
- Skip contract-first prep for small edits, single-file scripts, or established
155
- codebases that already have their own architecture.
156
-
157
- ## Running a workflow
158
-
159
- You must be **inside an ftown session** — `FTOWN_SESSION_ID` must be set.
160
-
161
- ```bash
162
- ~/.ftown/ftown-workflows run path/to/script.mjs
163
- ```
164
-
165
- > **Caveat:** run ftown-workflows from a **top-level orchestrator session**. If the
166
- > session running it is itself a child, the bridge flattens the tree so spawned
167
- > workers become **siblings** of the orchestrator rather than its children. Results
168
- > are file-based, so this does not affect correctness — only the dashboard topology.
169
-
170
- Child/subagent sessions **can run workflows**. Do not refuse just because
171
- `FTOWN_PARENT_SESSION_ID` is set or because the current session was spawned by
172
- another agent. The only hard requirement is `FTOWN_SESSION_ID` plus a reachable
173
- bridge. The parent/child caveat above is about dashboard topology, not
174
- capability: results still flow through files under `~/.ftown/workflows/<run-id>/`.
175
-
176
- Full options:
177
-
178
- ```bash
179
- ~/.ftown/ftown-workflows run <script.mjs> \
180
- [--args <json>] # parsed and available as ctx.args in the script
181
- [--workdir <path>] # default working dir for spawned child sessions
182
- [--shell claude|cursor|codex|pi|opencode|shell] # explicit override; default is current harness
183
- [--concurrency <n>] # max simultaneous live sessions (default 4)
184
- [--timeout <ms>] # per-agent timeout (default 1 800 000 = 30 min)
185
- [--max-agents <n>] # hard budget cap on total spawns
186
- [--run-id <id>] # resume a previous run (skip completed steps)
187
- [--json] # print final result as raw JSON
188
- ```
189
-
190
- The runner prints the **run directory** (`~/.ftown/workflows/<run-id>/`) at start.
191
- For long runs, launch in the background and tail the run dir:
192
-
193
- ```bash
194
- ~/.ftown/ftown-workflows run script.mjs --args '{"pr":42}' &
195
- tail -f ~/.ftown/workflows/<run-id>/*.json
196
- ```
197
-
198
- See the runnable template at `scripts/example.flow.mjs` in this skill directory.
199
-
200
- ## Script API
201
-
202
- A workflow script is an ES module. Its **default export** (or named `run` export)
203
- is an async function that receives a `WorkflowContext`:
204
-
205
- ```js
206
- // my-workflow.mjs
207
- export default async function (ctx) {
208
- ctx.phase('Gather');
209
- ctx.log(`args: ${JSON.stringify(ctx.args)}`);
210
-
211
- const summary = await ctx.agent('Summarise the repo README', {
212
- label: 'summarise',
213
- workdir: '/path/to/repo',
214
- });
215
-
216
- ctx.log(`summary: ${summary}`);
217
- return summary;
218
- }
219
- ```
220
-
221
- ### `ctx.agent(prompt, opts?)`
222
-
223
- Spawns one real ftown session. Blocks until the session writes its result file,
224
- then removes the session and returns the result.
225
-
226
- - Without `schema`: returns a **string** (or `null` on failure/timeout). A string
227
- `result` is returned as-is; a non-string `result` is returned as a JSON string.
228
- - With `schema`: returns the child's `result` JSON value **as-is** (parsed from the
229
- result file), or `null` on failure. The engine does **not** validate `result`
230
- against the schema — the schema is embedded in the child's prompt as guidance only.
231
- Treat conformance as best-effort and validate it yourself if you depend on it.
232
-
233
- Returns `null` — never throws — for: timeout, session exits without a result,
234
- `ok: false` in the result, budget exhausted.
235
-
236
- Key options:
237
-
238
- | option | default | meaning |
239
- |---|---|---|
240
- | `label` | `step-<n>` | step key used for the result file and resume |
241
- | `phase` | — | progress grouping shown in logs |
242
- | `schema` | — | JSON Schema embedded in the worker prompt; requests JSON result |
243
- | `shell` | run-level default (current harness when unset) | `claude` / `cursor` / `codex` / `pi` / `opencode` / `shell` |
244
- | `model` | — | model override passed to the session |
245
- | `workdir` | run-level default | working directory for the child session |
246
- | `timeoutMs` | 1 800 000 | wall-clock cap for this step |
247
- | `pollIntervalMs` | 2000 | how often to check for the result file |
248
-
249
- ### `ctx.parallel(thunks)`
250
-
251
- Run an array of thunks concurrently (barrier: waits for all). Respects the
252
- run-level `--concurrency` cap. A thunk that errors → `null` entry; the call
253
- never rejects.
254
-
255
- ```js
256
- const reviews = await ctx.parallel(
257
- files.map(f => () => ctx.agent(`Review ${f}`, { label: `review-${f}` }))
258
- );
259
- ```
260
-
261
- ### `ctx.pipeline(items, ...stages)`
262
-
263
- Thread each item through a sequence of stages independently (no barrier between
264
- stages). A stage that throws drops that item to `null` and skips its remaining
265
- stages.
266
-
267
- ```js
268
- const results = await ctx.pipeline(
269
- files,
270
- async (file) => ctx.agent(`lint ${file}`, { label: `lint-${file}` }),
271
- async (lintResult, file) => ctx.agent(`fix issues in ${file}: ${lintResult}`, { label: `fix-${file}` }),
272
- );
273
- ```
274
-
275
- ### `ctx.phase(title)` / `ctx.log(message)`
276
-
277
- Emit progress events to stderr. Use `phase` for major milestones, `log` for
278
- detail lines.
279
-
280
- ### `ctx.args`
281
-
282
- The value passed via `--args <json>` (parsed). `undefined` if not provided.
283
-
284
- ### `ctx.budget`
285
-
286
- ```js
287
- ctx.budget.maxAgents // null = unbounded
288
- ctx.budget.spent() // spawns so far (cached don't count)
289
- ctx.budget.remaining() // maxAgents - spent(), or Infinity
290
- ```
291
-
292
- ## Result-file contract
293
-
294
- Each child session receives a prompt that ends with a protocol block instructing
295
- it to write its final result as JSON to a specific file path and then stop:
296
-
297
- ```json
298
- { "ok": true, "result": "...anything..." }
299
- ```
300
-
301
- or on failure:
302
-
303
- ```json
304
- { "ok": false, "error": "reason" }
305
- ```
306
-
307
- The engine polls the file every `pollIntervalMs` ms. A partial write (incomplete
308
- JSON) is silently ignored until it is valid. The child session is removed (archived)
309
- once the result is read, or on timeout/exit.
310
-
311
- **You do not write this file yourself** — the child agent is instructed to do it.
312
- The prompt injected by the engine tells the child agent exactly what to write.
313
-
314
- ## Script Patterns
315
-
316
- Scripts are plain JavaScript modules. Avoid nondeterministic labels or control
317
- flow (`Date.now()`, `Math.random()`, timestamp-derived labels) when you care
318
- about resume, because cached results are matched by step order and label.
319
-
320
- ### Canonical Pipeline
321
-
322
- Each item moves through review and verification independently. One file can be
323
- verifying while another is still reviewing.
324
-
325
- ```js
326
- export default async function (ctx) {
327
- const results = await ctx.pipeline(
328
- ctx.args.files,
329
- (file) => ctx.agent(`Find bugs in ${file}`, {
330
- label: `find-${file}`,
331
- phase: 'Find',
332
- schema: FINDINGS_SCHEMA,
333
- }),
334
- (review, file) => ctx.parallel(
335
- (review?.findings ?? []).map((finding, i) => () => ctx.agent(
336
- `Try to refute this finding:\n${JSON.stringify(finding)}`,
337
- { label: `verify-${file}-${i}`, phase: 'Verify', schema: VERDICT_SCHEMA },
338
- ).then((verdict) => ({ file, finding, verdict })))
339
- ),
340
- );
341
-
342
- return results.flat().filter(Boolean).filter((r) => r.verdict?.isReal);
343
- }
344
- ```
345
-
346
- ### Correct Barrier
347
-
348
- Use a barrier when deduplication or comparison needs every prior result.
349
-
350
- ```js
351
- export default async function (ctx) {
352
- ctx.phase('Find');
353
- const reviews = await ctx.parallel(
354
- ctx.args.dimensions.map((d) => () => ctx.agent(d.prompt, {
355
- label: `find-${d.key}`,
356
- schema: FINDINGS_SCHEMA,
357
- })),
358
- );
359
-
360
- const allFindings = reviews.filter(Boolean).flatMap((r) => r.findings ?? []);
361
- const deduped = dedupeByFileAndTitle(allFindings);
362
- if (deduped.length === 0) return { confirmed: [] };
363
-
364
- ctx.phase('Verify');
365
- const verified = await ctx.parallel(
366
- deduped.map((finding, i) => () => ctx.agent(
367
- `Verify this deduped finding:\n${JSON.stringify(finding)}`,
368
- { label: `verify-${i}`, schema: VERDICT_SCHEMA },
369
- ).then((verdict) => ({ finding, verdict }))),
370
- );
371
-
372
- return { confirmed: verified.filter(Boolean).filter((r) => r.verdict?.isReal) };
373
- }
374
- ```
375
-
376
- ### Loop Until Dry
377
-
378
- Use this for unknown-size searches. Dedup against everything seen, including
379
- rejected findings, so the loop converges.
380
-
381
- ```js
382
- export default async function (ctx) {
383
- const seen = new Set();
384
- const confirmed = [];
385
- let dryRounds = 0;
386
-
387
- while (dryRounds < 2 && ctx.budget.remaining() > 0) {
388
- ctx.phase(`Find round ${dryRounds + 1}`);
389
- const found = await ctx.agent('Find more bugs not already covered.', {
390
- label: `find-round-${confirmed.length}-${dryRounds}`,
391
- schema: FINDINGS_SCHEMA,
392
- });
393
-
394
- const fresh = (found?.findings ?? []).filter((finding) => {
395
- const key = `${finding.file}:${finding.title}`;
396
- if (seen.has(key)) return false;
397
- seen.add(key);
398
- return true;
399
- });
400
-
401
- if (fresh.length === 0) {
402
- dryRounds += 1;
403
- ctx.log(`dry round ${dryRounds}/2`);
404
- continue;
405
- }
406
- dryRounds = 0;
407
-
408
- const judged = await ctx.parallel(
409
- fresh.map((finding, i) => () => ctx.agent(
410
- `Try to refute this finding:\n${JSON.stringify(finding)}`,
411
- { label: `judge-${seen.size}-${i}`, phase: 'Verify', schema: VERDICT_SCHEMA },
412
- ).then((verdict) => ({ finding, verdict }))),
413
- );
414
- confirmed.push(...judged.filter(Boolean).filter((r) => r.verdict?.isReal));
415
- }
416
-
417
- return { confirmed };
418
- }
419
- ```
420
-
421
- ### Budget-Bounded Depth
422
-
423
- Guard loops with a real cap. With no `--max-agents`, `ctx.budget.remaining()` is
424
- `Infinity`, so add an explicit round limit or require a max-agent budget.
425
-
426
- ```js
427
- export default async function (ctx) {
428
- const rounds = ctx.budget.maxAgents == null ? 3 : ctx.budget.maxAgents;
429
- const results = [];
430
-
431
- for (let i = 0; i < rounds && ctx.budget.remaining() > 0; i += 1) {
432
- const result = await ctx.agent(`Research angle ${i + 1}`, {
433
- label: `research-${i + 1}`,
434
- schema: RESEARCH_SCHEMA,
435
- });
436
- if (result) results.push(result);
437
- ctx.log(`${i + 1}/${rounds} research rounds complete`);
438
- }
439
-
440
- return results;
441
- }
442
- ```
443
-
444
- ## Resume
445
-
446
- Every step is keyed by its `label` (or `step-<n>`). If a result file already
447
- exists for a step, the engine returns the cached result without spawning a new
448
- session. To resume a partial run:
449
-
450
- ```bash
451
- ~/.ftown/ftown-workflows run script.mjs --run-id <the-previous-run-id>
452
- ```
453
-
454
- The run id and run directory are printed at startup.
455
-
456
- ## When to use this vs orchestrator guidance
457
-
458
- | | orchestrator guidance | ftown-workflows |
459
- |---|---|---|
460
- | **style** | ad-hoc, by hand | scripted, deterministic |
461
- | **human in loop** | yes — you direct workers via mail | no — script drives everything |
462
- | **repeatability** | each run is improvised | same script, same steps |
463
- | **resume** | manual | automatic via `--run-id` |
464
- | **best for** | exploratory tasks, escalations, debugging | batch jobs, CI-style pipelines, fan-out reviews |
465
-
466
- ## If the CLI is missing
467
-
468
- Start or restart **ftown-bridge** on this machine. It installs
469
- `~/.ftown/ftown-workflows` and updates the unified skill under
470
- `~/.ftown/skills/ftown/` (linked into ~/.agents/skills and ~/.claude/skills).
@@ -1,170 +0,0 @@
1
- /**
2
- * example.flow.mjs - template workflow: review files, verify each finding, synthesize.
3
- *
4
- * Run it inside an ftown session:
5
- *
6
- * ~/.ftown/ftown-workflows run example.flow.mjs \
7
- * --args '{"files":["src/auth.ts","src/api.ts","src/db.ts"]}' \
8
- * --workdir /path/to/your/repo
9
- *
10
- * Add --run-id <previous-id> to resume a partial run without re-running
11
- * steps whose result files already exist.
12
- */
13
-
14
- const FINDINGS_SCHEMA = {
15
- type: 'object',
16
- required: ['findings'],
17
- properties: {
18
- findings: {
19
- type: 'array',
20
- items: {
21
- type: 'object',
22
- required: ['title', 'file', 'severity', 'evidence'],
23
- properties: {
24
- title: { type: 'string' },
25
- file: { type: 'string' },
26
- severity: { type: 'string' },
27
- evidence: { type: 'string' },
28
- recommendation: { type: 'string' },
29
- },
30
- },
31
- },
32
- },
33
- };
34
-
35
- const VERDICT_SCHEMA = {
36
- type: 'object',
37
- required: ['isReal', 'reason'],
38
- properties: {
39
- isReal: { type: 'boolean' },
40
- reason: { type: 'string' },
41
- },
42
- };
43
-
44
- const REPORT_SCHEMA = {
45
- type: 'object',
46
- required: ['summary', 'confirmed', 'rejected'],
47
- properties: {
48
- summary: { type: 'string' },
49
- confirmed: { type: 'array', items: { type: 'string' } },
50
- rejected: { type: 'array', items: { type: 'string' } },
51
- },
52
- };
53
-
54
- function requireAgentResult(value, label) {
55
- if (value == null) {
56
- throw new Error(`${label} failed; aborting dependent workflow phases`);
57
- }
58
- return value;
59
- }
60
-
61
- function stepKey(value) {
62
- return String(value).replace(/[^a-z0-9._-]+/gi, '-').replace(/^-+|-+$/g, '') || 'item';
63
- }
64
-
65
- function asFindings(value) {
66
- return value && Array.isArray(value.findings) ? value.findings : [];
67
- }
68
-
69
- /**
70
- * @param {import('../../../src/workflow-runner.js').WorkflowContext} ctx
71
- */
72
- export default async function (ctx) {
73
- const files = Array.isArray(ctx.args?.files)
74
- ? ctx.args.files
75
- : ['src/auth.ts', 'src/api.ts', 'src/db.ts'];
76
-
77
- ctx.phase('Setup');
78
- ctx.log(`Reviewing ${files.length} file(s): ${files.join(', ')}`);
79
- ctx.log(`Budget: ${ctx.budget.maxAgents ?? 'unlimited'} agents`);
80
-
81
- const reviewed = await ctx.pipeline(
82
- files,
83
- async (file) => {
84
- const review = await ctx.agent(
85
- `Review ${file} for correctness, security, and maintainability bugs.
86
- Return only concrete findings with evidence. Do not include style preferences.`,
87
- {
88
- label: `review-${stepKey(file)}`,
89
- phase: 'Review',
90
- schema: FINDINGS_SCHEMA,
91
- },
92
- );
93
-
94
- return {
95
- file,
96
- findings: asFindings(requireAgentResult(review, `review ${file}`)),
97
- };
98
- },
99
- async (review) => {
100
- if (review.findings.length === 0) {
101
- ctx.log(`No findings reported for ${review.file}`);
102
- return { file: review.file, confirmed: [], rejected: [] };
103
- }
104
-
105
- const verified = await ctx.parallel(
106
- review.findings.map((finding, index) => async () => {
107
- const verdict = await ctx.agent(
108
- `Try to refute this finding. Default to isReal=false if the evidence is weak,
109
- not reproducible, or not actually caused by the code.
110
-
111
- Finding:
112
- ${JSON.stringify(finding, null, 2)}`,
113
- {
114
- label: `verify-${stepKey(review.file)}-${index}`,
115
- phase: 'Verify',
116
- schema: VERDICT_SCHEMA,
117
- },
118
- );
119
-
120
- return {
121
- finding,
122
- verdict: requireAgentResult(verdict, `verify ${review.file} #${index + 1}`),
123
- };
124
- }),
125
- );
126
-
127
- const kept = [];
128
- const rejected = [];
129
- for (const item of verified.filter(Boolean)) {
130
- if (item.verdict.isReal === true) kept.push(item.finding);
131
- else rejected.push({ finding: item.finding, reason: item.verdict.reason });
132
- }
133
-
134
- return { file: review.file, confirmed: kept, rejected };
135
- },
136
- );
137
-
138
- const completed = reviewed.filter(Boolean);
139
- const confirmed = completed.flatMap((entry) => entry.confirmed);
140
- const rejected = completed.flatMap((entry) => entry.rejected);
141
-
142
- ctx.phase('Synthesis');
143
- if (confirmed.length === 0) {
144
- ctx.log('No confirmed findings survived verification');
145
- return {
146
- summary: 'No confirmed findings survived adversarial verification.',
147
- confirmed: [],
148
- rejected: rejected.map((entry) => entry.finding.title),
149
- };
150
- }
151
-
152
- const report = await ctx.agent(
153
- `Write a concise final code-review report from these verified findings.
154
- Group by severity and include evidence. Mention rejected findings only if useful.
155
-
156
- Confirmed:
157
- ${JSON.stringify(confirmed, null, 2)}
158
-
159
- Rejected:
160
- ${JSON.stringify(rejected, null, 2)}`,
161
- {
162
- label: 'synthesis',
163
- phase: 'Synthesis',
164
- schema: REPORT_SCHEMA,
165
- },
166
- );
167
-
168
- ctx.log(`Done. Budget used: ${ctx.budget.spent()} agent spawn(s).`);
169
- return requireAgentResult(report, 'synthesis');
170
- }