pi-workflow-engine 0.2.0 → 0.4.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 (37) hide show
  1. package/.pi/extensions/pi-workflow-engine/index.ts +256 -0
  2. package/.pi/extensions/pi-workflow-engine/src/advisory-schema.ts +64 -0
  3. package/.pi/extensions/pi-workflow-engine/src/agent-runner.ts +238 -0
  4. package/.pi/extensions/pi-workflow-engine/src/cancellation.ts +28 -0
  5. package/.pi/extensions/pi-workflow-engine/src/concurrency.ts +171 -0
  6. package/.pi/extensions/pi-workflow-engine/src/diff-capture.ts +152 -0
  7. package/.pi/extensions/pi-workflow-engine/src/discovery.ts +114 -0
  8. package/{src → .pi/extensions/pi-workflow-engine/src}/engine.ts +36 -8
  9. package/.pi/extensions/pi-workflow-engine/src/options.ts +46 -0
  10. package/.pi/extensions/pi-workflow-engine/src/perf.ts +139 -0
  11. package/{src → .pi/extensions/pi-workflow-engine/src}/progress.ts +82 -35
  12. package/{src → .pi/extensions/pi-workflow-engine/src}/types.ts +12 -4
  13. package/{src → .pi/extensions/pi-workflow-engine/src}/ui/workflow-format.ts +59 -6
  14. package/{src → .pi/extensions/pi-workflow-engine/src}/ui/workflow-inspector.ts +8 -6
  15. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-result-renderer.ts +198 -0
  16. package/.pi/extensions/pi-workflow-engine/src/ui/workflow-widget.ts +182 -0
  17. package/.pi/extensions/pi-workflow-engine/src/workflow-advisory-utils.ts +263 -0
  18. package/.pi/extensions/pi-workflow-engine/src/workflows.ts +24 -0
  19. package/{workflows → .pi/extensions/pi-workflow-engine/workflows}/code-review.ts +89 -145
  20. package/.pi/extensions/pi-workflow-engine/workflows/diagnose.ts +244 -0
  21. package/.pi/extensions/pi-workflow-engine/workflows/perf-review.ts +188 -0
  22. package/{workflows → .pi/extensions/pi-workflow-engine/workflows}/ping.ts +1 -0
  23. package/.pi/extensions/pi-workflow-engine/workflows/refactor-scout.ts +181 -0
  24. package/.pi/settings.json +3 -0
  25. package/README.md +163 -63
  26. package/assets/preview-candidate-core.png +0 -0
  27. package/assets/preview-candidate-relic-emblem.png +0 -0
  28. package/assets/preview-candidate-robot-squad.png +0 -0
  29. package/assets/preview.png +0 -0
  30. package/package.json +32 -6
  31. package/src/agent-runner.ts +0 -122
  32. package/src/concurrency.ts +0 -55
  33. package/src/discovery.ts +0 -54
  34. package/src/index.ts +0 -141
  35. package/src/ui/workflow-result-renderer.ts +0 -149
  36. package/src/ui/workflow-widget.ts +0 -143
  37. package/src/workflows.ts +0 -14
@@ -0,0 +1,188 @@
1
+ import { Type } from "typebox";
2
+ import {
3
+ AdvisoryReportSchema,
4
+ type AdvisoryCandidate,
5
+ type AdvisoryReport,
6
+ type AdvisoryVerdict,
7
+ } from "../src/advisory-schema.ts";
8
+ import {
9
+ backfillAdvisoryFindings,
10
+ formatEvidence,
11
+ formatLocation,
12
+ runLensVerificationPipeline,
13
+ } from "../src/workflow-advisory-utils.ts";
14
+ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.ts";
15
+
16
+ export const meta: WorkflowMeta = {
17
+ name: "perf-review",
18
+ description: "Advisory-only performance review: scope slow path → per-lens bottleneck hypotheses → verify evidence → synthesize measurements and safe optimizations.",
19
+ phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
20
+ };
21
+
22
+ const ScopeSchema = Type.Object({
23
+ target: Type.String({ description: "Verbatim slow path, workload, command, or performance concern." }),
24
+ files: Type.Array(Type.String(), { description: "Repository-relative files likely involved in the performance path." }),
25
+ commands: Type.Array(Type.String(), { description: "Existing benchmark, smoke, or measurement commands relevant to this path." }),
26
+ summary: Type.String({ description: "One-paragraph summary of the performance-relevant path." }),
27
+ knownMeasurements: Type.Optional(Type.String({ description: "Existing measurements, timings, or explicit lack of measurements." })),
28
+ });
29
+
30
+ interface PerfLens {
31
+ label: string;
32
+ category: string;
33
+ text: string;
34
+ }
35
+
36
+ type Candidate = AdvisoryCandidate;
37
+
38
+ interface Verified extends Candidate {
39
+ verdict: AdvisoryVerdict["verdict"];
40
+ evidence: string[];
41
+ confidence?: AdvisoryVerdict["confidence"];
42
+ lens: PerfLens;
43
+ }
44
+
45
+ const PERF_LENSES: PerfLens[] = [
46
+ { label: "algorithmic", category: "algorithmic", text: "Complexity, repeated scans, avoidable nested loops, or data-structure choices that grow poorly with input size." },
47
+ { label: "io", category: "io", text: "Filesystem, subprocess, network, or other I/O costs on hot paths or startup paths." },
48
+ { label: "concurrency", category: "concurrency", text: "Unnecessary serialization, missing batching, excessive fan-out, contention, or concurrency limits." },
49
+ { label: "startup", category: "startup", text: "Import/module loading, initialization, discovery, or cold-start overhead." },
50
+ { label: "allocation", category: "allocation", text: "Memory churn, large intermediate strings/objects, repeated serialization, or retained state growth." },
51
+ { label: "measurement", category: "measurement", text: "Missing, misleading, noisy, or insufficient benchmark/measurement design." },
52
+ ];
53
+
54
+ const TOOLS = ["read", "bash"];
55
+ const PER_LENS = 4;
56
+
57
+ export default async function run(api: WorkflowApi): Promise<unknown> {
58
+ const { agent, parallel, pipeline, phase, log, progress, args } = api;
59
+ const target = args.trim() || "repository performance";
60
+ let fileCount = 0;
61
+ let rawCandidateCount = 0;
62
+ let droppedCandidateCount = 0;
63
+ let refutedCandidateCount = 0;
64
+ const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
65
+ files: fileCount,
66
+ candidates: rawCandidateCount,
67
+ verified,
68
+ kept,
69
+ dropped: droppedCandidateCount,
70
+ refuted: refutedCandidateCount,
71
+ });
72
+
73
+ phase("Scope");
74
+ const scope = await agent(
75
+ "Establish the scope for an advisory-only performance review. Do not edit files.\n" +
76
+ `Performance target / concern (verbatim): ${target}\n\n` +
77
+ "Inspect repository structure, scripts, likely hot-path files, and any existing benchmark or measurement commands. " +
78
+ "Prefer identifying what to measure before claiming bottlenecks. Return files, commands, summary, and known measurements or the lack of them. " +
79
+ `This workflow will fan out across ${PERF_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
80
+ { phase: "Scope", label: "scope", tools: TOOLS, thinkingLevel: "medium", schema: ScopeSchema },
81
+ );
82
+
83
+ if (!scope || scope.files.length === 0) {
84
+ return emptyReport(
85
+ "No performance-relevant files were identified.",
86
+ ["Provide a slow command, workload, file path, or user-visible latency concern to review."],
87
+ makeStats(0, 0),
88
+ );
89
+ }
90
+
91
+ fileCount = scope.files.length;
92
+ progress({ type: "counter", key: "files", label: "files", value: fileCount });
93
+ progress({ type: "summary", key: "target", value: scope.target });
94
+ progress({ type: "summary", key: "files", value: scope.files.join(", ") });
95
+ log(`${scope.files.length} files scoped for performance review`);
96
+
97
+ const scopeBlock =
98
+ `## Target\n${scope.target}\n\n## Files\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
99
+ `## Measurement commands\n${scope.commands.map((command) => `- ${command}`).join("\n") || "(none identified)"}\n\n` +
100
+ `## Summary\n${scope.summary}\n\n## Known measurements\n${scope.knownMeasurements ?? "(none known)"}\n` +
101
+ (args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
102
+
103
+ phase("Find");
104
+ const pipelineResult = await runLensVerificationPipeline({
105
+ api: { agent, parallel, pipeline, progress, log },
106
+ lenses: PERF_LENSES,
107
+ schedulingMode: "finder-barrier",
108
+ perLens: PER_LENS,
109
+ tools: TOOLS,
110
+ finderPrompt: (lens) =>
111
+ `## Perf-review finder — ${lens.label}\n\n${scopeBlock}\n` +
112
+ "This workflow is advisory-only: identify bottleneck hypotheses, measurement gaps, and safe optimization directions, but do not edit files.\n" +
113
+ `Investigate ONLY this lens:\n${lens.text}\n\n` +
114
+ `Surface up to ${PER_LENS} candidates. Use category exactly "${lens.category}". ` +
115
+ "Each candidate must include a one-line summary, locations, impact stating the suspected performance consequence and workload where it matters, " +
116
+ "and an optional recommendation. Prefer measurement recommendations when evidence is weak. Structured output only.",
117
+ verifierPrompt: (candidate) =>
118
+ `## Perf-review verifier\n\n${scopeBlock}\n## Candidate\n` +
119
+ `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
120
+ `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
121
+ "Read relevant files and package/scripts. Run only safe read-only measurement or inspection commands when useful. " +
122
+ "Return CONFIRMED, PLAUSIBLE, or REFUTED with evidence from code, scripts, config, or measurement output. " +
123
+ "Default toward PLAUSIBLE or REFUTED when no measurement exists; do not overstate a bottleneck. Structured output only.",
124
+ makeVerified: (candidate, lens, judged): Verified => ({
125
+ ...candidate,
126
+ verdict: judged.verdict,
127
+ evidence: judged.evidence,
128
+ confidence: judged.confidence,
129
+ lens,
130
+ }),
131
+ });
132
+ rawCandidateCount += pipelineResult.rawCandidates;
133
+ droppedCandidateCount += pipelineResult.dropped;
134
+ refutedCandidateCount += pipelineResult.refuted;
135
+ const verified = pipelineResult.verified;
136
+ const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
137
+ const stats = makeStats(verified.length, surviving.length);
138
+ progress({ type: "counter", key: "verified", label: "verified", value: verified.length });
139
+ progress({ type: "counter", key: "kept", label: "kept", value: surviving.length });
140
+ progress({ type: "summary", key: "verified", value: verified.length });
141
+ progress({ type: "summary", key: "kept", value: surviving.length });
142
+ log(`${verified.length} verified → ${surviving.length} kept`);
143
+
144
+ if (surviving.length === 0) {
145
+ return emptyReport(
146
+ "No performance finding survived verification.",
147
+ ["Add or run a focused measurement for the target workload before optimizing.", "Rerun perf-review with benchmark output or a narrower slow path."],
148
+ stats,
149
+ );
150
+ }
151
+
152
+ phase("Synthesize");
153
+ const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
154
+ const block = ranked
155
+ .map(
156
+ (finding, index) =>
157
+ `### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
158
+ `${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nRecommendation: ${finding.recommendation ?? "(none supplied)"}`,
159
+ )
160
+ .join("\n\n");
161
+
162
+ const report = await agent(
163
+ `## Synthesis: final perf-review report\n\n${ranked.length} candidates survived independent verification.\n\n${block}\n\n` +
164
+ "Produce the shared advisory report shape. Categories should be algorithmic, io, concurrency, startup, allocation, or measurement when applicable. " +
165
+ "Severity is expected performance impact for the target workload. Prefer measurement recommendations before optimization recommendations when evidence is weak. " +
166
+ "Recommendations must be safe advisory next actions, not patches. Include risky optimizations to avoid in recommendations or nextSteps when relevant. Structured output only.",
167
+ { phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
168
+ );
169
+
170
+ if (!report) return emptyReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun perf-review with a narrower target."], stats);
171
+
172
+ const findings = backfillAdvisoryFindings(report.findings, ranked, {
173
+ impact: "Performance impact not restated by synthesis.",
174
+ recommendation: "Measure the target workload before changing code.",
175
+ });
176
+ return { ...report, findings, stats };
177
+ }
178
+
179
+ function emptyReport(summary: string, nextSteps: string[], stats: WorkflowRunStats): AdvisoryReport & { stats: WorkflowRunStats } {
180
+ return { summary, findings: [], nextSteps, stats };
181
+ }
182
+
183
+ function rank(finding: Verified): number {
184
+ const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
185
+ const measurementPenalty = finding.category === "measurement" ? 1 : 0;
186
+ return verdictRank + measurementPenalty;
187
+ }
188
+
@@ -18,6 +18,7 @@ export default async function run(api: WorkflowApi): Promise<unknown> {
18
18
  phase: "Ping",
19
19
  label: "ping",
20
20
  tools: [],
21
+ thinkingLevel: "low",
21
22
  schema: Type.Object({
22
23
  headline: Type.String({ description: "One word" }),
23
24
  summary: Type.String({ description: "One sentence" }),
@@ -0,0 +1,181 @@
1
+ import { Type } from "typebox";
2
+ import {
3
+ AdvisoryReportSchema,
4
+ type AdvisoryCandidate,
5
+ type AdvisoryReport,
6
+ type AdvisoryVerdict,
7
+ } from "../src/advisory-schema.ts";
8
+ import {
9
+ backfillAdvisoryFindings,
10
+ formatEvidence,
11
+ formatLocation,
12
+ runLensVerificationPipeline,
13
+ } from "../src/workflow-advisory-utils.ts";
14
+ import type { WorkflowApi, WorkflowMeta, WorkflowRunStats } from "../src/types.ts";
15
+
16
+ export const meta: WorkflowMeta = {
17
+ name: "refactor-scout",
18
+ description: "Advisory-only refactor scout: scope → per-lens find → independent verify → synthesize safe refactor opportunities.",
19
+ phases: [{ title: "Scope" }, { title: "Find" }, { title: "Verify" }, { title: "Synthesize" }],
20
+ };
21
+
22
+ const ScopeSchema = Type.Object({
23
+ target: Type.String({ description: "Verbatim target path, module, or focus area being scouted." }),
24
+ files: Type.Array(Type.String(), { description: "Repository-relative files in scope." }),
25
+ summary: Type.String({ description: "One-paragraph summary of the scoped code." }),
26
+ conventions: Type.Optional(Type.String({ description: "Relevant project conventions from AGENTS.md / docs." })),
27
+ });
28
+
29
+ interface RefactorLens {
30
+ label: string;
31
+ category: string;
32
+ text: string;
33
+ }
34
+
35
+ type Candidate = AdvisoryCandidate;
36
+
37
+ interface Verified extends Candidate {
38
+ verdict: AdvisoryVerdict["verdict"];
39
+ evidence: string[];
40
+ confidence?: AdvisoryVerdict["confidence"];
41
+ lens: RefactorLens;
42
+ }
43
+
44
+ const REFACTOR_LENSES: RefactorLens[] = [
45
+ { label: "duplication", category: "duplication", text: "Repeated logic, copy-pasted structures, or near-duplicate flows that could share one clearer implementation." },
46
+ { label: "complexity", category: "complexity", text: "Oversized functions, tangled control flow, or abstractions that make local reasoning harder than necessary." },
47
+ { label: "type-safety", category: "type-safety", text: "Weak typing, avoidable casts, unchecked shapes, or places stronger types would prevent mistakes." },
48
+ { label: "boundaries", category: "boundary", text: "Leaky module boundaries, misplaced responsibilities, or imports that couple unrelated layers." },
49
+ { label: "dead-code", category: "dead-code", text: "Unused, obsolete, or redundant code paths that can likely be removed safely." },
50
+ { label: "conventions", category: "conventions", text: "Departures from project conventions, naming, dependency rules, or local idioms." },
51
+ ];
52
+
53
+ const TOOLS = ["read", "bash"];
54
+ const PER_LENS = 5;
55
+
56
+ export default async function run(api: WorkflowApi): Promise<unknown> {
57
+ const { agent, parallel, pipeline, phase, log, progress, args } = api;
58
+ const target = args.trim() || ".";
59
+ let fileCount = 0;
60
+ let rawCandidateCount = 0;
61
+ let droppedCandidateCount = 0;
62
+ let refutedCandidateCount = 0;
63
+ const makeStats = (verified: number, kept: number): WorkflowRunStats => ({
64
+ files: fileCount,
65
+ candidates: rawCandidateCount,
66
+ verified,
67
+ kept,
68
+ dropped: droppedCandidateCount,
69
+ refuted: refutedCandidateCount,
70
+ });
71
+
72
+ phase("Scope");
73
+ const scope = await agent(
74
+ "Establish the scope for an advisory-only refactor scout. Do not edit files.\n" +
75
+ `Target / focus (verbatim): ${target}\n\n` +
76
+ "Inspect repository structure, the target path or module, and relevant AGENTS.md / project docs conventions. " +
77
+ "Return the concrete files that should be considered, a short summary, and any conventions that affect refactor advice. " +
78
+ `This workflow will fan out across ${REFACTOR_LENSES.length} lenses with up to ${PER_LENS} candidates per lens. Structured output only.`,
79
+ { phase: "Scope", label: "scope", tools: TOOLS, thinkingLevel: "medium", schema: ScopeSchema },
80
+ );
81
+
82
+ if (!scope || scope.files.length === 0) {
83
+ return emptyReport(
84
+ "No files were identified for refactor scouting.",
85
+ ["Provide a target path, module, or subsystem to scout for refactor opportunities."],
86
+ makeStats(0, 0),
87
+ );
88
+ }
89
+
90
+ fileCount = scope.files.length;
91
+ progress({ type: "counter", key: "files", label: "files", value: fileCount });
92
+ progress({ type: "summary", key: "files", value: scope.files.join(", ") });
93
+ log(`${scope.files.length} files scoped for refactor scouting`);
94
+
95
+ const scopeBlock =
96
+ `## Target\n${scope.target}\n\n## Files in scope\n${scope.files.map((file) => `- ${file}`).join("\n")}\n\n` +
97
+ `## Summary\n${scope.summary}\n\n## Conventions\n${scope.conventions ?? "(none noted)"}\n` +
98
+ (args.trim() ? `\n## User instructions (verbatim)\n${args.trim()}\n` : "");
99
+
100
+ phase("Find");
101
+ const pipelineResult = await runLensVerificationPipeline({
102
+ api: { agent, parallel, pipeline, progress, log },
103
+ lenses: REFACTOR_LENSES,
104
+ schedulingMode: "finder-barrier",
105
+ perLens: PER_LENS,
106
+ tools: TOOLS,
107
+ finderPrompt: (lens) =>
108
+ `## Refactor-scout finder — ${lens.label}\n\n${scopeBlock}\n` +
109
+ "This workflow is advisory-only: do not edit files and do not propose broad rewrites.\n" +
110
+ `Scout through ONLY this lens:\n${lens.text}\n\n` +
111
+ `Surface up to ${PER_LENS} candidates. Use category exactly "${lens.category}". ` +
112
+ "Each candidate must include a one-line summary, locations, impact on maintainability or future correctness, and an optional safe first recommendation. " +
113
+ "Only include opportunities where a small, reviewable first step is plausible. Structured output only.",
114
+ verifierPrompt: (candidate) =>
115
+ `## Refactor-scout verifier\n\n${scopeBlock}\n## Candidate\n` +
116
+ `Location: ${formatLocation(candidate)}\nCategory: ${candidate.category}\nSummary: ${candidate.summary}\nImpact: ${candidate.impact}\n` +
117
+ `Recommendation: ${candidate.recommendation ?? "(none supplied)"}\n\n` +
118
+ "Read the relevant files and return CONFIRMED, PLAUSIBLE, or REFUTED. " +
119
+ "Default toward REFUTED if the opportunity is generic, too broad, not evidenced by code, or lacks a safe first step. " +
120
+ "Evidence must quote or cite code. Structured output only.",
121
+ makeVerified: (candidate, lens, judged): Verified => ({
122
+ ...candidate,
123
+ verdict: judged.verdict,
124
+ evidence: judged.evidence,
125
+ confidence: judged.confidence,
126
+ lens,
127
+ }),
128
+ });
129
+ rawCandidateCount += pipelineResult.rawCandidates;
130
+ droppedCandidateCount += pipelineResult.dropped;
131
+ refutedCandidateCount += pipelineResult.refuted;
132
+ const verified = pipelineResult.verified;
133
+ const surviving = verified.filter((finding) => finding.verdict !== "REFUTED");
134
+ const stats = makeStats(verified.length, surviving.length);
135
+ progress({ type: "counter", key: "verified", label: "verified", value: verified.length });
136
+ progress({ type: "counter", key: "kept", label: "kept", value: surviving.length });
137
+ progress({ type: "summary", key: "verified", value: verified.length });
138
+ progress({ type: "summary", key: "kept", value: surviving.length });
139
+ log(`${verified.length} verified → ${surviving.length} kept`);
140
+
141
+ if (surviving.length === 0) {
142
+ return emptyReport("No refactor opportunities survived verification.", ["Leave the scoped code unchanged unless a human reviewer has additional context."], stats);
143
+ }
144
+
145
+ phase("Synthesize");
146
+ const ranked = [...surviving].sort((a, b) => rank(a) - rank(b));
147
+ const block = ranked
148
+ .map(
149
+ (finding, index) =>
150
+ `### [${index}] ${formatLocation(finding)} (${finding.verdict}, ${finding.category})\n` +
151
+ `${finding.summary}\nImpact: ${finding.impact}\nEvidence: ${formatEvidence(finding.evidence)}\nSafe first step: ${finding.recommendation ?? "(none supplied)"}`,
152
+ )
153
+ .join("\n\n");
154
+
155
+ const report = await agent(
156
+ `## Synthesis: final refactor-scout report\n\n${ranked.length} opportunities survived independent verification.\n\n${block}\n\n` +
157
+ "Merge findings with the same root cause and rank highest leverage / lowest risk first. " +
158
+ "Return the shared advisory report shape. Categories should come from the verified candidates. " +
159
+ "Severity is maintenance or future-correctness impact. Confidence is high for CONFIRMED, medium for PLAUSIBLE unless verifier confidence says otherwise. " +
160
+ "Recommendations must be safe first refactor steps, not rewrites. Include concrete nextSteps for the host developer. Structured output only.",
161
+ { phase: "Synthesize", label: "synthesize", thinkingLevel: "medium", schema: AdvisoryReportSchema },
162
+ );
163
+
164
+ if (!report) return emptyReport("Synthesis produced no output.", ["Inspect verifier evidence manually or rerun the workflow with a narrower target."], stats);
165
+
166
+ const findings = backfillAdvisoryFindings(report.findings, ranked, {
167
+ impact: "Impact not restated by synthesis.",
168
+ recommendation: "Choose a small, behavior-preserving refactor first step.",
169
+ });
170
+ return { ...report, findings, stats };
171
+ }
172
+
173
+ function emptyReport(summary: string, nextSteps: string[], stats: WorkflowRunStats): AdvisoryReport & { stats: WorkflowRunStats } {
174
+ return { summary, findings: [], nextSteps, stats };
175
+ }
176
+
177
+ function rank(finding: Verified): number {
178
+ const verdictRank = finding.verdict === "CONFIRMED" ? 0 : 1;
179
+ const categoryRank = finding.category === "dead-code" || finding.category === "conventions" ? 2 : 0;
180
+ return verdictRank + categoryRank;
181
+ }
@@ -0,0 +1,3 @@
1
+ {
2
+ "extensions": ["extensions/pi-workflow-engine/index.ts"]
3
+ }