pi-workflow-engine 0.12.0 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/.pi/extensions/pi-workflow-engine/src/advisory-challenge.ts +75 -0
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-evidence.ts +90 -0
  3. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +13 -1
  4. package/.pi/extensions/pi-workflow-engine/src/agent-workspace.ts +16 -2
  5. package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +12 -6
  6. package/.pi/extensions/pi-workflow-engine/src/journal.ts +1 -0
  7. package/.pi/extensions/pi-workflow-engine/src/resume-context.ts +53 -3
  8. package/.pi/extensions/pi-workflow-engine/src/review/code-review-orchestration.ts +0 -25
  9. package/.pi/extensions/pi-workflow-engine/src/review/patch-validation.ts +120 -0
  10. package/.pi/extensions/pi-workflow-engine/src/review/review-fix-workflow.ts +43 -7
  11. package/.pi/extensions/pi-workflow-engine/src/review/review-format.ts +2 -1
  12. package/.pi/extensions/pi-workflow-engine/src/review/review-issues.ts +1 -1
  13. package/.pi/extensions/pi-workflow-engine/src/types.ts +3 -0
  14. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +5 -2
  15. package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +131 -178
  16. package/.pi/extensions/pi-workflow-engine/src/worktree.ts +7 -0
  17. package/.pi/extensions/pi-workflow-engine/workflows/code-review.ts +55 -128
  18. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +45 -139
  19. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +25 -67
  20. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +25 -67
  21. package/USAGE.md +59 -0
  22. package/package.json +1 -1
@@ -1,11 +1,10 @@
1
+ import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
1
2
  import { Type } from "typebox";
2
3
  import {
3
- AdvisoryReportSchema,
4
- type AdvisoryCandidate,
5
- type AdvisoryVerdict,
6
- } from "../src/advisory-schema.ts";
7
- import {
8
- backfillAdvisoryFindings,
4
+ type AdvisoryVerified,
5
+ type AdvisoryLens,
6
+ synthesizeAdvisoryReport,
7
+ finishAdvisoryReport,
9
8
  emptyAdvisoryReport,
10
9
  formatEvidence,
11
10
  formatLocation,
@@ -19,7 +18,7 @@ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.t
19
18
  export const meta: WorkflowMeta = {
20
19
  name: "perf-review",
21
20
  description: "Advisory-only performance review: scope slow path → per-lens bottleneck hypotheses → verify evidence → synthesize measurements and safe optimizations.",
22
- phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
21
+ phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
23
22
  };
24
23
 
25
24
  const ScopeSchema = Type.Object({
@@ -30,22 +29,7 @@ const ScopeSchema = Type.Object({
30
29
  knownMeasurements: Type.Optional(Type.String({ description: "Existing measurements, timings, or explicit lack of measurements." })),
31
30
  });
32
31
 
33
- interface PerfLens {
34
- label: string;
35
- category: string;
36
- text: string;
37
- }
38
-
39
- type Candidate = AdvisoryCandidate;
40
-
41
- interface Verified extends Candidate {
42
- verdict: AdvisoryVerdict["verdict"];
43
- evidence: string[];
44
- confidence?: AdvisoryVerdict["confidence"];
45
- lens: PerfLens;
46
- }
47
-
48
- const PERF_LENSES: PerfLens[] = [
32
+ const PERF_LENSES: AdvisoryLens[] = [
49
33
  { label: "algorithmic", category: "algorithmic", text: "Complexity, repeated scans, avoidable nested loops, or data-structure choices that grow poorly with input size." },
50
34
  { label: "io", category: "io", text: "Filesystem, subprocess, network, or other I/O costs on hot paths or startup paths." },
51
35
  { label: "concurrency", category: "concurrency", text: "Unnecessary serialization, missing batching, excessive fan-out, contention, or concurrency limits." },
@@ -54,13 +38,12 @@ const PERF_LENSES: PerfLens[] = [
54
38
  { label: "measurement", category: "measurement", text: "Missing, misleading, noisy, or insufficient benchmark/measurement design." },
55
39
  ];
56
40
 
57
- const TOOLS = DEFAULT_ADVISORY_TOOLS;
58
- const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
59
41
  const PER_LENS = 4;
60
42
 
61
43
  export default async function run(api: WorkflowApi): Promise<unknown> {
62
- const { agent, parallel, pipeline, phase, log, progress, args } = api;
63
- const target = args.trim() || "repository performance";
44
+ const { agent, phase, log, progress, args } = api;
45
+ const challengeConfig = parseChallengeArgs(args);
46
+ const target = challengeConfig.args.trim() || "repository performance";
64
47
  let fileCount = 0;
65
48
  let rawCandidateCount = 0;
66
49
  let droppedCandidateCount = 0;
@@ -81,15 +64,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
81
64
  "Inspect repository structure, scripts, likely hot-path files, and any existing benchmark or measurement commands. " +
82
65
  "Prefer identifying what to measure before claiming bottlenecks. Return files, commands, summary, and known measurements or the lack of them. " +
83
66
  `This workflow will fan out across ${PERF_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
84
- { phase: "Scope", label: "scope", tools: TOOLS, toolHints: TOOL_HINTS, profile: "medium", schema: ScopeSchema },
67
+ { phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
85
68
  );
86
69
 
87
70
  if (!scope || scope.files.length === 0) {
88
- return emptyAdvisoryReport(
71
+ return finishAdvisoryReport(emptyAdvisoryReport(
89
72
  "No performance-relevant files were identified.",
90
73
  ["Provide a slow command, workload, file path, or user-visible latency concern to review."],
91
74
  makeStats(0, 0),
92
- );
75
+ ), []);
93
76
  }
94
77
 
95
78
  fileCount = scope.files.length;
@@ -104,14 +87,10 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
104
87
  `## Summary\n${scope.summary}\n\n## Known measurements\n${scope.knownMeasurements ?? "(none known)"}\n` +
105
88
  (args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
106
89
 
107
- phase("Find");
108
90
  const pipelineResult = await runLensVerificationPipeline({
109
- api: { agent, parallel, pipeline, progress, log },
91
+ api,
110
92
  lenses: PERF_LENSES,
111
- schedulingMode: "finder-barrier",
112
93
  perLens: PER_LENS,
113
- tools: TOOLS,
114
- toolHints: TOOL_HINTS,
115
94
  finderPrompt: (lens) =>
116
95
  `## Perf-review finder — ${lens.label}\n\n${scopeBlock}\n` +
117
96
  "This workflow is advisory-only: identify bottleneck hypotheses, measurement gaps, and safe optimization directions, but do not edit files.\n" +
@@ -124,67 +103,46 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
124
103
  `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
125
104
  `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
126
105
  "Read relevant files and package/scripts. Run only safe read-only measurement or inspection commands when useful. " +
127
- "Return CONFIRMED, PLAUSIBLE, or REFUTED with evidence from code, scripts, config, or measurement output. " +
106
+ "Return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED with evidence from code, scripts, config, or measurement output. " +
128
107
  "Default toward PLAUSIBLE or REFUTED when no measurement exists; do not overstate a bottleneck. Structured output only.",
129
- makeVerified: (candidate, lens, judged): Verified => ({
130
- ...candidate,
131
- verdict: judged.verdict,
132
- evidence: judged.evidence,
133
- confidence: judged.confidence,
134
- lens,
135
- }),
136
108
  });
137
109
  rawCandidateCount += pipelineResult.rawCandidates;
138
110
  droppedCandidateCount += pipelineResult.dropped;
139
111
  refutedCandidateCount += pipelineResult.refuted;
140
- const verified = pipelineResult.verified;
112
+ const { coverage } = pipelineResult;
113
+ const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
141
114
  const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
142
115
  const stats = makeStats(verified.length, surviving.length);
143
116
  publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
144
117
 
145
118
  if (surviving.length === 0) {
146
- return emptyAdvisoryReport(
119
+ return finishAdvisoryReport(emptyAdvisoryReport(
147
120
  "No performance finding survived verification.",
148
121
  ["Add or run a focused measurement for the target workload before optimizing.", "Rerun perf-review with benchmark output or a narrower slow path."],
149
122
  stats,
150
- );
123
+ ), coverage, verified);
151
124
  }
152
125
 
153
- phase("Synthesize");
154
126
  const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
155
127
  const block = ranked
156
128
  .map(
157
129
  (finding, index) =>
158
- `### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
130
+ `### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
159
131
  `${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nRecommendation: ${finding.recommendation ?? "(none supplied)"}`,
160
132
  )
161
133
  .join("\n\n");
162
134
 
163
- const report = await agent(
135
+ const resolved = await synthesizeAdvisoryReport(api,
164
136
  `## Synthesis: final perf-review report\n\n${ranked.length} candidates survived independent verification.\n\n${block}\n\n` +
165
- "Produce the shared advisory report shape. Categories should be algorithmic, io, concurrency, startup, allocation, or measurement when applicable. " +
137
+ "Select findings by ID. " +
166
138
  "Severity is expected performance impact for the target workload. Prefer measurement recommendations before optimization recommendations when evidence is weak. " +
167
139
  "Recommendations must be safe advisory next actions, not patches. Include risky optimizations to avoid in recommendations or nextSteps when relevant. Structured output only.",
168
- {
169
- phase: "Synthesize",
170
- label: "synthesize",
171
- tools: [],
172
- profile: "medium",
173
- resume: "read-only",
174
- schema: AdvisoryReportSchema,
175
- },
140
+ ranked, coverage,
176
141
  );
177
-
178
- if (!report) return emptyAdvisoryReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun perf-review with a narrower target."], stats);
179
-
180
- const findings = backfillAdvisoryFindings(report.findings, ranked, {
181
- impact: "Performance impact not restated by synthesis.",
182
- recommendation: "Measure the target workload before changing code.",
183
- });
184
- return { ...report, findings, stats };
142
+ return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
185
143
  }
186
144
 
187
- function rank(finding: Verified): number {
145
+ function rank(finding: AdvisoryVerified): number {
188
146
  const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
189
147
  const measurementPenalty = finding.category === "measurement" ? 1 : 0;
190
148
  return verdictRank + measurementPenalty;
@@ -1,11 +1,10 @@
1
+ import { challengeFindings, parseChallengeArgs } from "../src/advisory-challenge.ts";
1
2
  import { Type } from "typebox";
2
3
  import {
3
- AdvisoryReportSchema,
4
- type AdvisoryCandidate,
5
- type AdvisoryVerdict,
6
- } from "../src/advisory-schema.ts";
7
- import {
8
- backfillAdvisoryFindings,
4
+ type AdvisoryVerified,
5
+ type AdvisoryLens,
6
+ synthesizeAdvisoryReport,
7
+ finishAdvisoryReport,
9
8
  emptyAdvisoryReport,
10
9
  formatEvidence,
11
10
  formatLocation,
@@ -19,7 +18,7 @@ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.t
19
18
  export const meta: WorkflowMeta = {
20
19
  name: "refactor-scout",
21
20
  description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
22
- phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
21
+ phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Challenge" }, { title: "Synthesize" }],
23
22
  };
24
23
 
25
24
  const ScopeSchema = Type.Object({
@@ -29,22 +28,7 @@ const ScopeSchema = Type.Object({
29
28
  conventions: Type.Optional(Type.String({ description: "Relevant project conventions from AGENTS.md / docs." })),
30
29
  });
31
30
 
32
- interface RefactorLens {
33
- label: string;
34
- category: string;
35
- text: string;
36
- }
37
-
38
- type Candidate = AdvisoryCandidate;
39
-
40
- interface Verified extends Candidate {
41
- verdict: AdvisoryVerdict["verdict"];
42
- evidence: string[];
43
- confidence?: AdvisoryVerdict["confidence"];
44
- lens: RefactorLens;
45
- }
46
-
47
- const REFACTOR_LENSES: RefactorLens[] = [
31
+ const REFACTOR_LENSES: AdvisoryLens[] = [
48
32
  { label: "duplication", category: "duplication", text: "Repeated logic, copy-pasted structures, or near-duplicate flows that could share one clearer implementation." },
49
33
  { label: "complexity", category: "complexity", text: "Oversized functions, tangled control flow, or abstractions that make local reasoning harder than necessary." },
50
34
  { label: "type-safety", category: "type-safety", text: "Weak typing, avoidable casts, unchecked shapes, or places stronger types would prevent mistakes." },
@@ -53,13 +37,12 @@ const REFACTOR_LENSES: RefactorLens[] = [
53
37
  { label: "conventions", category: "conventions", text: "Departures from project conventions, naming, dependency rules, or local idioms." },
54
38
  ];
55
39
 
56
- const TOOLS = DEFAULT_ADVISORY_TOOLS;
57
- const TOOL_HINTS = DEFAULT_ADVISORY_TOOL_HINTS;
58
40
  const PER_LENS = 5;
59
41
 
60
42
  export default async function run(api: WorkflowApi): Promise<unknown> {
61
- const { agent, parallel, pipeline, phase, log, progress, args } = api;
62
- const target = args.trim() || ".";
43
+ const { agent, phase, log, progress, args } = api;
44
+ const challengeConfig = parseChallengeArgs(args);
45
+ const target = challengeConfig.args.trim() || ".";
63
46
  let fileCount = 0;
64
47
  let rawCandidateCount = 0;
65
48
  let droppedCandidateCount = 0;
@@ -80,15 +63,15 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
80
63
  "Inspect repository structure, the target path or module, and relevant AGENTS.md / project docs conventions. " +
81
64
  "Return the concrete files that should be considered, a short summary, and any conventions that affect refactor advice. " +
82
65
  `This workflow will fan out across ${REFACTOR_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
83
- { phase: "Scope", label: "scope", tools: TOOLS, toolHints: TOOL_HINTS, profile: "medium", schema: ScopeSchema },
66
+ { phase: "Scope", label: "scope", tools: DEFAULT_ADVISORY_TOOLS, toolHints: DEFAULT_ADVISORY_TOOL_HINTS, profile: "medium", schema: ScopeSchema },
84
67
  );
85
68
 
86
69
  if (!scope || scope.files.length === 0) {
87
- return emptyAdvisoryReport(
70
+ return finishAdvisoryReport(emptyAdvisoryReport(
88
71
  "No files were identified for refactor scouting.",
89
72
  ["Provide a target path, module, or subsystem to scout for refactor opportunities."],
90
73
  makeStats(0, 0),
91
- );
74
+ ), []);
92
75
  }
93
76
 
94
77
  fileCount = scope.files.length;
@@ -101,14 +84,10 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
101
84
  `## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
102
85
  (args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
103
86
 
104
- phase("Find");
105
87
  const pipelineResult = await runLensVerificationPipeline({
106
- api: { agent, parallel, pipeline, progress, log },
88
+ api,
107
89
  lenses: REFACTOR_LENSES,
108
- schedulingMode: "finder-barrier",
109
90
  perLens: PER_LENS,
110
- tools: TOOLS,
111
- toolHints: TOOL_HINTS,
112
91
  finderPrompt: (lens) =>
113
92
  `## Refactor-scout finder — ${lens.label}\n\n${scopeBlock}\n` +
114
93
  "This workflow is advisory-only: do not edit files and do not propose broad rewrites.\n" +
@@ -120,65 +99,44 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
120
99
  `## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
121
100
  `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
122
101
  `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
123
- "Read the relevant files and return CONFIRMED, PLAUSIBLE, or REFUTED. " +
102
+ "Read the relevant files and return CONFIRMED, PLAUSIBLE, NOT_SUBSTANTIATED, or REFUTED. " +
124
103
  "Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
125
104
  "Evidence must quote or cite code. Structured output only.",
126
- makeVerified: (candidate, lens, judged): Verified => ({
127
- ...candidate,
128
- verdict: judged.verdict,
129
- evidence: judged.evidence,
130
- confidence: judged.confidence,
131
- lens,
132
- }),
133
105
  });
134
106
  rawCandidateCount += pipelineResult.rawCandidates;
135
107
  droppedCandidateCount += pipelineResult.dropped;
136
108
  refutedCandidateCount += pipelineResult.refuted;
137
- const verified = pipelineResult.verified;
109
+ const { coverage } = pipelineResult;
110
+ const verified = await challengeFindings(api, pipelineResult.verified, scopeBlock, challengeConfig.options, coverage);
138
111
  const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
139
112
  const stats = makeStats(verified.length, surviving.length);
140
113
  publishVerifiedKeptProgress({ progress, log }, verified.length, surviving.length);
141
114
 
142
115
  if (surviving.length === 0) {
143
- return emptyAdvisoryReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats);
116
+ return finishAdvisoryReport(emptyAdvisoryReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats), coverage, verified);
144
117
  }
145
118
 
146
- phase("Synthesize");
147
119
  const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
148
120
  const block = ranked
149
121
  .map(
150
122
  (finding, index) =>
151
- `### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
123
+ `### [${index}] IDs: ${finding.sourceCandidateIds.join(", ")} ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
152
124
  `${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
153
125
  )
154
126
  .join("\n\n");
155
127
 
156
- const report = await agent(
128
+ const resolved = await synthesizeAdvisoryReport(api,
157
129
  `## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
158
130
  "Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
159
- "Return the shared advisory report shape. Categories should come from the verified candidates. " +
160
- "Severity is maintenance or future-correctness impact. Confidence is high for CONFIRMED, medium for PLAUSIBLE unless verifier confidence says otherwise. " +
131
+ "Select findings by ID. " +
132
+ "Severity is maintenance or future-correctness impact. " +
161
133
  "Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
162
- {
163
- phase: "Synthesize",
164
- label: "synthesize",
165
- tools: [],
166
- profile: "medium",
167
- resume: "read-only",
168
- schema: AdvisoryReportSchema,
169
- },
134
+ ranked, coverage,
170
135
  );
171
-
172
- if (!report) return emptyAdvisoryReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun the workflow with a narrower target."], stats);
173
-
174
- const findings = backfillAdvisoryFindings(report.findings, ranked, {
175
- impact: "Impact not restated by synthesis.",
176
- recommendation: "Choose a small, behavior-preserving refactor first step.",
177
- });
178
- return { ...report, findings, stats };
136
+ return finishAdvisoryReport({ ...resolved, stats: { ...stats, kept: resolved.findings.length } }, coverage, verified);
179
137
  }
180
138
 
181
- function rank(finding: Verified): number {
139
+ function rank(finding: AdvisoryVerified): number {
182
140
  const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
183
141
  const categoryRank = finding.category === "dead-code" || finding.category === "conventions" ? 2 : 0;
184
142
  return verdictRank + categoryRank;
package/USAGE.md CHANGED
@@ -543,3 +543,62 @@ failures, timeouts, and host aborts are never provider-retried.
543
543
  - **Slow run**: lower fan-out, set `thinkingLevel`, or reduce `--concurrency`.
544
544
  - **Budget exhausted**: narrow the target, raise `--budget`, reduce fan-out/concurrency, or guard custom loops with `api.budget.remaining()`.
545
545
  - **Duplicate command/tool warnings while developing**: avoid loading both the global package and the working copy.
546
+
547
+ ### Advisory evidence and coverage
548
+
549
+ `code-review`, `diagnose`, `refactor-scout`, and `perf-review` retain candidate IDs
550
+ from discovery through verification and synthesis. Findings contain
551
+ `sourceCandidateIds`; merged findings retain all contributing IDs, locations, and
552
+ evidence. Code review uses `reviewAnchor` for the changed line that caused the
553
+ finding. Other `locations` can identify unchanged callers or related files.
554
+
555
+ These results include `status` (`complete` or `incomplete`), `coverage` with
556
+ expected/completed/failed branch counts and failure reasons, `gaps`, and the
557
+ `verification` records. Failed verification is not a refutation. Verifiers use
558
+ `NOT_SUBSTANTIATED` when evidence is insufficient and `REFUTED` for concrete
559
+ disproof. A failed finder or verifier prevents a clean conclusion. If synthesis
560
+ fails, the result retains verified records and reports incomplete coverage.
561
+ Synthesis selects or merges IDs; the workflow reconstructs evidence-bearing
562
+ fields from those records.
563
+
564
+ ### Selective adversarial challenges
565
+
566
+ Add `--challenge` to an advisory workflow to challenge up to three uncertain or
567
+ high-risk findings. `--challenge=N` requires a non-negative integer and sets the
568
+ limit, capped at ten; zero disables it. Invalid values stop the workflow before
569
+ agents start. Ordinary confirmed, low-risk findings bypass this stage.
570
+
571
+ ```text
572
+ /workflow code-review --challenge=2
573
+ /workflow diagnose --challenge failing retry test
574
+ ```
575
+
576
+ The challenger tries to disprove the finding or identify an alternative cause.
577
+ A separate adjudicator receives both sides. Unresolved disagreement remains
578
+ `NOT_SUBSTANTIATED`; finding no counterexample does not upgrade confidence.
579
+ Custom workflows can use the `challengeFindings` recipe with `maxChallenges`
580
+ and a `shouldChallenge` predicate, including selection based on domain-specific
581
+ severity. It uses the existing `agent` and settled `parallel` calls.
582
+
583
+ ### Independently validated repair candidates
584
+
585
+ The results viewer's fix action produces candidate patches in disposable
586
+ worktrees. Each candidate retains the implementer's report separately from its
587
+ `validation` record: reviewed baseline fingerprint, baseline commit, patch
588
+ SHA-256, evaluator decision, executed commands, output, and process results.
589
+
590
+ A fresh evaluator receives the exact reviewed baseline plus captured patch.
591
+ The engine reconstructs another workspace and executes the evaluator's focused
592
+ checks. Where applicable, a test-only patch and expected failure text let it
593
+ check that a regression fails on the original baseline and passes after repair.
594
+ Outcomes are `verified`, `rejected`, `blocked`, or `no-patch`. Missing required
595
+ validation blocks verification. An empty patch does not prove that a finding
596
+ needs no repair. Rejected and blocked candidates retain their patch and failure
597
+ evidence for inspection. Cleanup failures also retain the validation evidence and
598
+ record a `cleanupError`; a passed candidate becomes `blocked` if cleanup fails,
599
+ while a rejected candidate remains `rejected`.
600
+
601
+ Isolated agent results now include `baselineOid`. For independent evaluation,
602
+ `agent()` accepts `candidatePatch: { baselineOid, patch }` with
603
+ `isolation: "worktree"`; the engine checks the fresh baseline before applying
604
+ it. Worktree isolation separates filesystem state; it is not a security sandbox.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-workflow-engine",
3
- "version": "0.12.0",
3
+ "version": "0.13.0",
4
4
  "type": "module",
5
5
  "description": "Zero-dependency Dynamax workflows for pi: live parallel subagents, typed handoffs, synthesized results.",
6
6
  "keywords": [