Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

## 0.2.1 - Unreleased

- Added deslopify review mode and ranked maintainability/performance report clusters for repeated cleanup patterns, thanks @mbelinky.
- Added a `pi` provider for routing review, fix, revalidate, and agent map through the [pi coding agent](https://pi.dev) in non-interactive print mode, thanks @danielmarbach.
- Added explicit Codex reasoning effort selection via `--reasoning-effort`, `CLAWPATCH_REASONING_EFFORT`, and provider config, with `doctor` reporting the active setting.
- Added deterministic Express, Fastify, and Hono route mapping for Node projects, thanks @rohitjavvadi.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ pnpm link --global
clawpatch init
clawpatch map
clawpatch review --limit 3 --jobs 3
clawpatch review --mode deslopify --limit 3
clawpatch report
clawpatch next
clawpatch show --finding <id>
Expand Down Expand Up @@ -109,6 +110,7 @@ Supported provider names today:
- `clawpatch map`: write feature records
- `clawpatch status`: show project, dirty state, feature/finding counts
- `clawpatch review`: review pending or selected features
- `clawpatch review --mode deslopify`: review only for locally provable slop cleanup
- `clawpatch report`: print or write a Markdown findings report
- `clawpatch next`: print the next actionable finding
- `clawpatch show --finding <id>`: inspect one finding with evidence and suggested validation
Expand Down
32 changes: 32 additions & 0 deletions docs/code-review.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ clawpatch review --limit 3
clawpatch review --limit 12 --jobs 4
clawpatch review --feature <featureId>
clawpatch review --since origin/main
clawpatch review --mode deslopify --limit 3
clawpatch review --provider codex --model <model>
```

Expand Down Expand Up @@ -56,6 +57,37 @@ count as `lockFiles`.

There is no multi-provider panel yet.

### --mode deslopify

Use deslopify mode when you want one narrow lane for simplifying code and
improving performance by removing code slop. It restricts findings to
maintainability or performance issues caused by accidental complexity,
inefficient indirection, semantic duplication, needless wrappers, dead code, or
avoidable repeated work. It should not report unrelated correctness, security,
API contract, data-loss, or build-release issues; provider findings outside
maintainability and performance are discarded in this mode.

The deslopify rubric is intentionally narrow. It asks the provider to prioritize
locally provable slop patterns where the likely fix is deletion, consolidation,
or reuse of an existing local pattern:

- semantic duplication across files, tests, CLIs, SQL queries, adapters, wrappers,
or generated-looking utilities
- shadow modules and thin pass-through wrappers
- concrete code bloat: generated-looking mass, production-included test/debug/demo
artifacts, wrapper swarms, duplicated boilerplate, or manual registries that
duplicate a source of truth
- dead legacy paths kept alive by tests
- cargo-cult defensive code that does not match a real trust boundary
- tautological or coupled tests that preserve implementation internals instead of
behavior
- type/build silencing and band-aid hacks such as broad disables, `any`,
`type-ignore`, sleeps/timeouts, path mutation, fake success returns, or removed
checks, when simplification is the fix

It should not report file size, explicit generated files, normal framework
boilerplate, or domain modules that merely look large.

Categories requested from the provider:

- `bug`
Expand Down
8 changes: 8 additions & 0 deletions docs/reporting.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ clawpatch report --feature <featureId>

Markdown output includes:

- ranked action clusters when related maintainability/performance findings share
a slop pattern and evidence area
- finding ID
- severity, category, confidence, triage, and status
- feature ID and title when available
Expand All @@ -27,6 +29,12 @@ Markdown output includes:
- recommendation and reproduction text when available
- next inspection command for status-filtered queues

Action clusters are report-only. They do not change finding IDs, status, triage,
or fix commands. They are intended to make deslopify-style reports easier to
scan by grouping repeated root causes such as duplication, dead code, wrapper
bloat, test coupling, defensive bloat, band-aid fixes, and concrete code bloat.
The full finding details remain in the report beneath the cluster summary.

`review` also writes a Markdown report for each run under:

```text
Expand Down
37 changes: 35 additions & 2 deletions src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ import { mapFeatures } from "./mapper.js";
import { emitProgress } from "./progress.js";
import { providerByName } from "./provider.js";
import { buildFixPrompt, buildReviewPrompt, buildRevalidatePrompt } from "./prompt.js";
import type { ReviewMode } from "./prompt.js";
import {
evidenceLabel,
findingSummaries,
Expand Down Expand Up @@ -66,6 +67,7 @@ import {
FixPlanOutput,
FindingRecord,
PatchAttempt,
ReviewOutput,
RunRecord,
reasoningEffortSchema,
reasoningEfforts,
Expand Down Expand Up @@ -231,6 +233,7 @@ export async function reviewCommand(
const loaded = await loadProjectState(context);
const config = applyProviderFlags(loaded.config, flags);
const provider = providerByName(config.provider.name);
const mode = reviewMode(flags);
const features = await selectReviewFeatures(loaded, flags);
if (features.length === 0 && typeof flags["since"] === "string") {
return { next: "no features touched by diff" };
Expand All @@ -239,6 +242,7 @@ export async function reviewCommand(
return {
dryRun: true,
wouldReview: features.length,
mode,
jobs: reviewJobs(flags),
featureIds: features.map((feature) => feature.featureId),
};
Expand Down Expand Up @@ -276,6 +280,7 @@ export async function reviewCommand(
currentRunId,
index,
total: features.length,
mode,
allowNonPendingFeatureReview: stringFlag(flags, "feature") !== undefined,
});
findingIds.push(...reviewed.findingIds);
Expand Down Expand Up @@ -483,6 +488,7 @@ type ReviewFeatureOptions = {
currentRunId: string;
index: number;
total: number;
mode: ReviewMode;
allowNonPendingFeatureReview: boolean;
};

Expand All @@ -496,6 +502,7 @@ async function reviewFeature(options: ReviewFeatureOptions): Promise<{ findingId
currentRunId,
index,
total,
mode,
allowNonPendingFeatureReview,
} = options;
const started = Date.now();
Expand All @@ -516,9 +523,16 @@ async function reviewFeature(options: ReviewFeatureOptions): Promise<{ findingId
},
);
locked = lockedFeature;
const prompt = await buildReviewPrompt(loaded.root, loaded.project, lockedFeature, config);
const prompt = await buildReviewPrompt(
loaded.root,
loaded.project,
lockedFeature,
config,
mode,
);
const output = await provider.review(loaded.root, prompt, providerOptions(config));
const records = output.findings
const modeFindings = reviewFindingsForMode(output.findings, mode);
const records = modeFindings
.slice(0, config.review.maxFindingsPerFeature)
.map((finding) => findingFromOutput(finding, lockedFeature.featureId, currentRunId));
const findingIds: string[] = [];
Expand Down Expand Up @@ -1054,6 +1068,25 @@ function reviewJobs(flags: Record<string, string | boolean>): number {
return Math.min(Math.floor(parsed), 32);
}

function reviewMode(flags: Record<string, string | boolean>): ReviewMode {
const mode = stringFlag(flags, "mode") ?? "default";
if (mode === "default" || mode === "deslopify") {
return mode;
}
throw new ClawpatchError("invalid --mode; expected default or deslopify", 2, "invalid-usage");
}

function reviewFindingsForMode(
findings: ReviewOutput["findings"],
mode: ReviewMode,
): ReviewOutput["findings"] {
if (mode !== "deslopify") {
return findings;
}
return findings.filter(
(finding) => finding.category === "maintainability" || finding.category === "performance",
);
}
function featureLock(currentRunId: string): NonNullable<FeatureRecord["lock"]> {
return {
lockedByRunId: currentRunId,
Expand Down
11 changes: 11 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,7 @@ const commandFlags = {
"limit",
"since",
"jobs",
"mode",
"provider",
"model",
"reasoningEffort",
Expand Down Expand Up @@ -197,6 +198,7 @@ const valueFlagNames = new Set([
"limit",
"since",
"jobs",
"mode",
"source",
"provider",
"model",
Expand Down Expand Up @@ -267,6 +269,14 @@ function validateCommandRequirements(
) {
throw new ClawpatchError("missing --finding or --all", 2, "invalid-usage");
}
if (
command === "review" &&
typeof flags["mode"] === "string" &&
flags["mode"] !== "default" &&
flags["mode"] !== "deslopify"
) {
throw new ClawpatchError("invalid --mode; expected default or deslopify", 2, "invalid-usage");
}
}

function isKnownCommand(command: string): command is keyof typeof commandFlags {
Expand Down Expand Up @@ -370,6 +380,7 @@ Flags:
--limit <n>
--since <ref>
--jobs <n> default: 10
--mode <default|deslopify>
--provider <name>
--model <name>
--reasoning-effort <none|minimal|low|medium|high|xhigh>
Expand Down
30 changes: 30 additions & 0 deletions src/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { readFile, realpath } from "node:fs/promises";
import { isAbsolute, relative, resolve } from "node:path";
import { ClawpatchConfig, FeatureRecord, FindingRecord, ProjectRecord } from "./types.js";

export type ReviewMode = "default" | "deslopify";

export function buildAgentMapPrompt(project: ProjectRecord, inventory: unknown): string {
return `You are mapping a repository into semantic clawpatch review slices.

Expand Down Expand Up @@ -57,6 +59,7 @@ export async function buildReviewPrompt(
project: ProjectRecord,
feature: FeatureRecord,
config: ClawpatchConfig,
mode: ReviewMode = "default",
): Promise<string> {
const owned = feature.ownedFiles.slice(0, config.review.maxOwnedFiles);
const context = feature.contextFiles.slice(0, config.review.maxContextFiles);
Expand Down Expand Up @@ -87,6 +90,8 @@ Review categories:
- release/build hazards
- maintainability risks with concrete impact

${reviewModeInstructions(mode)}

Inspect owned files, context files, and linked tests. Treat included tests as first-class
evidence of intended behavior. If tests contradict a suspected bug, either skip it or
downgrade confidence and explain the uncertainty. Avoid reporting behavior as a bug
Expand Down Expand Up @@ -120,6 +125,31 @@ Files:
${fileBlocks.join("\n\n")}`;
}

function reviewModeInstructions(mode: ReviewMode): string {
if (mode === "default") {
return "";
}
if (mode === "deslopify") {
return `Deslopify mode:
- report only simplification findings in category "maintainability" or "performance"
- stay separate from normal review: do not look for general bugs, security issues, API contract problems, or missing edge-case handling
- focus on locally provable AI-slop patterns whose likely fix is deletion, consolidation, or reuse of an existing local pattern
- prioritize semantic duplication: repeated behavior across files, tests, CLIs, SQL queries, adapters, wrappers, or generated-looking utilities
- prioritize shadow modules and useless wrappers: thin layers that pass through to another path without hiding real complexity
- prioritize concrete code bloat: generated-looking mass, production-included test/debug/demo artifacts, wrapper swarms, duplicated boilerplate, or manual registries that duplicate a source of truth
- prioritize dead legacy paths kept alive by tests: obsolete validators, schemas, adapters, compatibility branches, feature flags, or helpers
- prioritize cargo-cult defensive code: broad try/catch, fallback, logging, null guard, or "safe" wrapper code that does not match a real trust boundary
- prioritize tautological or coupled tests: tests that mirror implementation internals, repeat giant fake harnesses, or preserve accidental private structure instead of behavior
- prioritize type/build silencing and band-aid hacks: broad disables, any/type-ignore casts, sleeps/timeouts, path mutation, fake success returns, or removed checks when simplification is the fix
- every finding must have a concrete maintenance or runtime cost in the included files
- prefer deletion, consolidation, or existing local patterns over new abstractions
- do not report file size, explicit generated files, normal framework boilerplate, or domain modules that merely look large
- do not report style taste, naming preference, broad architecture opinions, or speculative cleanup
- do not report correctness, security, API contract, data-loss, or build-release issues unless the root cause is accidental complexity and the minimum fix is simplification`;
}
throw new Error(`Unsupported review mode: ${mode}`);
}

export async function buildRevalidatePrompt(root: string, findingJson: string): Promise<string> {
return `Revalidate this clawpatch finding against the current repository at ${root}.

Expand Down
2 changes: 1 addition & 1 deletion src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,7 +204,7 @@ const piProvider: Provider = {
if (result.exitCode !== 0) {
throw new ClawpatchError("pi CLI not available", 4, "provider-auth");
}
return (result.stdout.trim() || result.stderr.trim());
return result.stdout.trim() || result.stderr.trim();
},
async map(root: string, prompt: string, options: ProviderOptions): Promise<AgentMapOutput> {
const output = await runPiJson(root, prompt, options, agentMapJsonSchema, true);
Expand Down
Loading