Skip to content
Closed
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
7 changes: 7 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ const commandFlags = {
"dryRun",
"promptFile",
"exportTribunalLedger",
"includeDirty",
]),
ci: new Set([
"limit",
Expand All @@ -178,6 +179,7 @@ const commandFlags = {
"reasoningEffort",
"skipGitRepoCheck",
"output",
"includeDirty",
]),
report: new Set(["status", "severity", "feature", "project", "category", "triage", "output"]),
show: new Set(["finding"]),
Expand All @@ -199,6 +201,7 @@ const commandFlags = {
"model",
"reasoningEffort",
"skipGitRepoCheck",
"includeDirty",
]),
doctor: new Set(["provider", "model", "reasoningEffort"]),
"clean-locks": new Set<string>(),
Expand Down Expand Up @@ -253,6 +256,7 @@ const booleanFlagNames = new Set([
"force",
"all",
"draft",
"include-dirty",
]);

const shortFlagNames = new Set(["-h", "-q", "-v", "-o"]);
Expand Down Expand Up @@ -409,6 +413,7 @@ Flags:
--project <name-or-root>
--limit <n>
--since <ref>
--include-dirty
--jobs <n> default: 10
--mode <default|deslopify>
--provider <name>
Expand Down Expand Up @@ -454,6 +459,7 @@ Usage:

Flags:
--since <ref>
--include-dirty
--limit <n>
--jobs <n> default: 10
--provider <name>
Expand Down Expand Up @@ -585,6 +591,7 @@ Flags:
--triage <triage>
--limit <n>
--since <ref>
--include-dirty
--provider <name>
--model <name>
--reasoning-effort <none|minimal|low|medium|high|xhigh>
Expand Down
35 changes: 35 additions & 0 deletions src/git.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,41 @@ export async function changedFilesSince(root: string, ref: string): Promise<Set<
);
}

export async function dirtyFiles(root: string): Promise<Set<string>> {
const result = await runCommand(
"git status --porcelain=v1 -z --untracked-files=all",
root,
undefined,
{ trimOutput: false },
);
if (result.exitCode !== 0) {
throw new ClawpatchError(
`git status failed: ${result.stderr || result.stdout}`,
2,
"git-failure",
);
}
const fields = result.stdout.split("\0").filter((field) => field.length > 0);
const paths = new Set<string>();
for (let index = 0; index < fields.length; index += 1) {
const field = fields[index] ?? "";
if (field.length < 4) {
continue;
}
const status = field.slice(0, 2);
const primaryPath = field.slice(3).replace(/\\/gu, "/");
paths.add(primaryPath);
if (/[RC]/u.test(status)) {
const secondaryPath = (fields[index + 1] ?? "").replace(/\\/gu, "/");
if (secondaryPath.length > 0) {
paths.add(secondaryPath);
}
index += 1;
}
}
return paths;
}

async function gitLine(cwd: string, command: string): Promise<string | null> {
const result = await runCommand(command, cwd);
if (result.exitCode !== 0) {
Expand Down
5 changes: 3 additions & 2 deletions src/selection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ export function filterFindingsByChangedOwnedFiles(
export function limitFeatures(features: FeatureRecord[], flags: Flags): FeatureRecord[] {
const explicitLimit = stringFlag(flags, "limit");
if (explicitLimit === undefined) {
return features.slice(0, stringFlag(flags, "since") === undefined ? 1 : features.length);
const unlimited = stringFlag(flags, "since") !== undefined || flags["includeDirty"] === true;
return features.slice(0, unlimited ? features.length : 1);
}
const limit = Number(explicitLimit);
return features.slice(0, Number.isFinite(limit) && limit > 0 ? limit : 1);
Expand Down Expand Up @@ -133,7 +134,7 @@ function featurePaths(feature: FeatureRecord): string[] {
}

function normalizeProjectFilter(project: string): string {
const normalized = normalizeFeaturePath(project).replace(/^\.\//u, "");
const normalized = normalizeFeaturePath(project).replace(/^\.\/$/u, "");
return normalized.length === 0 ? "." : normalized;
}

Expand Down