diff --git a/src/cli.ts b/src/cli.ts index e6f858c..877f874 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -168,6 +168,7 @@ const commandFlags = { "dryRun", "promptFile", "exportTribunalLedger", + "includeDirty", ]), ci: new Set([ "limit", @@ -178,6 +179,7 @@ const commandFlags = { "reasoningEffort", "skipGitRepoCheck", "output", + "includeDirty", ]), report: new Set(["status", "severity", "feature", "project", "category", "triage", "output"]), show: new Set(["finding"]), @@ -199,6 +201,7 @@ const commandFlags = { "model", "reasoningEffort", "skipGitRepoCheck", + "includeDirty", ]), doctor: new Set(["provider", "model", "reasoningEffort"]), "clean-locks": new Set(), @@ -253,6 +256,7 @@ const booleanFlagNames = new Set([ "force", "all", "draft", + "include-dirty", ]); const shortFlagNames = new Set(["-h", "-q", "-v", "-o"]); @@ -409,6 +413,7 @@ Flags: --project --limit --since + --include-dirty --jobs default: 10 --mode --provider @@ -454,6 +459,7 @@ Usage: Flags: --since + --include-dirty --limit --jobs default: 10 --provider @@ -585,6 +591,7 @@ Flags: --triage --limit --since + --include-dirty --provider --model --reasoning-effort diff --git a/src/git.ts b/src/git.ts index c54d87c..dcc0c6d 100644 --- a/src/git.ts +++ b/src/git.ts @@ -87,6 +87,41 @@ export async function changedFilesSince(root: string, ref: string): Promise> { + 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(); + 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 { const result = await runCommand(command, cwd); if (result.exitCode !== 0) { diff --git a/src/selection.ts b/src/selection.ts index 6c2af7b..f05bb8f 100644 --- a/src/selection.ts +++ b/src/selection.ts @@ -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); @@ -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; }