mirror of
https://github.com/morluto/rea.git
synced 2026-10-30 19:35:56 +00:00
Keep one typed accumulation of observed decode facts instead of constructing partial Evidence only after string projection. Record decoded client/target before the post-command cancellation checkpoint, parsed metadata before manifest reads, raw manifest before interpretation, and inventory prefix before rethrowing its exact original cause. Omit unexamined fields. Return inventory success or exact failure plus completed prefix through Result. Keep resource-cap stops distinct from physical failures. Attach truncation and inventory limitations at their producing stages. Validate complete results with the named domain schema; centralize partial Evidence assembly in the existing failure/cleanup boundary. Validation: six authored stage controls fail unchanged34a88d6c1; 131 focused checks across7files, check:fast, docs:check and discovery1556 pass. Covers manifest bytes/XML, interrupted inventory, late cleanup cancellation, denied removal/retry and existing table interruption. Two source reviewers clear after moving the command handoff checkpoint. No separate runtime qualification of that precise handoff race. Public CLI manifest failure retains metadata/raw manifest with unknown fields omitted and original failure framing unchanged. Twelve small normal CLI tasks retain exact replies. No speedup or memory-saving claim; failure reply estimate189 to610cl100k tokens preserves completed Evidence. No real Apktool, large-case memory or whole-agent qualification. Heavy tests remain stopped. Fixes #4882
84 lines
2.7 KiB
TypeScript
84 lines
2.7 KiB
TypeScript
import { ok, err, type Result } from "../domain/result.js";
|
|
import type {
|
|
ArtifactEntry,
|
|
ArtifactTraversalOmission,
|
|
} from "../artifacts/ArtifactReader.js";
|
|
import { DirectoryArtifactTraversalLimit } from "../artifacts/DirectoryArtifactReader.js";
|
|
import { assertArtifactReadActive } from "../artifacts/ArtifactFileOperation.js";
|
|
import { classifyAndroidStringsTable } from "../domain/apktool/apktoolResourceAnalysis.js";
|
|
import type { ApktoolWorkspace } from "./ApktoolWorkspace.js";
|
|
|
|
type TableEntryKind = ArtifactEntry["kind"] | ArtifactTraversalOmission["kind"];
|
|
interface StringsTableOmission {
|
|
readonly path: string;
|
|
readonly kind: Exclude<TableEntryKind, "file">;
|
|
}
|
|
|
|
interface DecodedInventory {
|
|
count: number;
|
|
capped: boolean;
|
|
locales: readonly string[];
|
|
otherStringsTables: readonly string[];
|
|
unreadableStringsTables: readonly StringsTableOmission[];
|
|
}
|
|
|
|
/** Count files over bounded examined coverage, including omitted special entries. */
|
|
export const countDecodedFiles = async (
|
|
workspace: ApktoolWorkspace,
|
|
root: string,
|
|
signal: AbortSignal | undefined,
|
|
maximumEntries: number,
|
|
): Promise<
|
|
Result<
|
|
DecodedInventory,
|
|
{ readonly cause: unknown; readonly partial: DecodedInventory }
|
|
>
|
|
> => {
|
|
let count = 0;
|
|
let capped = false;
|
|
const locales: string[] = [];
|
|
const otherStringsTables: string[] = [];
|
|
const unreadableStringsTables: StringsTableOmission[] = [];
|
|
const observeTable = (path: string, kind: TableEntryKind): void => {
|
|
const match = /^res\/(values(?:-([^/]*))?)\/strings\.xml$/u.exec(path);
|
|
if (match === null) return;
|
|
if (kind !== "file") {
|
|
unreadableStringsTables.push({ path, kind });
|
|
return;
|
|
}
|
|
const suffix = match[2];
|
|
if (suffix === undefined) return;
|
|
const table = classifyAndroidStringsTable(suffix);
|
|
if (table.kind === "locale") locales.push(table.locale);
|
|
else otherStringsTables.push(match[1]!);
|
|
};
|
|
const observation = (): DecodedInventory => {
|
|
return {
|
|
count,
|
|
capped,
|
|
locales: locales.sort(),
|
|
otherStringsTables: otherStringsTables.sort(),
|
|
unreadableStringsTables: unreadableStringsTables.sort((a, b) =>
|
|
a.path < b.path ? -1 : a.path > b.path ? 1 : 0,
|
|
),
|
|
};
|
|
};
|
|
try {
|
|
for await (const entry of workspace.entries(
|
|
root,
|
|
signal,
|
|
maximumEntries,
|
|
(omission) => observeTable(omission.path, omission.kind),
|
|
)) {
|
|
assertArtifactReadActive(signal);
|
|
observeTable(entry.path, entry.kind);
|
|
if (entry.kind === "file") count += 1;
|
|
}
|
|
} catch (cause) {
|
|
if (!(cause instanceof DirectoryArtifactTraversalLimit))
|
|
return err({ cause, partial: observation() });
|
|
capped = true;
|
|
}
|
|
return ok(observation());
|
|
};
|