Files
fusion/packages/engine/src/merger-git-parse.ts
gsxdsm 9a34862586 refactor: package code organization waves 3–5 (#2148)
## Summary

Waves 3–5 of package code organization (plan:
`docs/plans/2026-07-14-001-refactor-package-code-organization-plan.md`).
Behavior-preserving peels after #2139 and #2143.

### Wave 3 — Merger + heartbeat recovery
- **`merger-errors.ts`** — verification/abort error classes
- **`merger-owned-landed.ts`** — ownership classification +
`Fusion-Task-Id` trailer
- **`merger-conflict-resolution.ts`** — conflict classify/auto-resolve
- **`agent-heartbeat-error-recovery.ts`** — durable error-recovery
budget helpers

### Wave 4 — Self-healing + dashboard API
- **`self-healing-constants.ts`** — public timing/budget constants
- **`self-healing-branch.ts`** — `isBranchAheadOfBase`
- **`app/api/client.ts`** — `api` / `ApiRequestError` / `buildApiUrl` /
`proxyApi`
- **`app/api/health.ts`** — health, engine status, updates +
`withProjectId`

### Wave 5 — Types tracking + merger parse + task CRUD
- **`types/task-tracking.ts`** — PR/issue/GitHub/GitLab tracking
contracts
- **`merger-git-parse.ts`** — `parseFailingFilesFromOutput`,
`parsePorcelainZ`, `parseShortstatSummary`
- **`app/api/tasks.ts`** — task list/detail/create/update/move client
surface
- Line-count baselines ratcheted down for `merger.ts`, `types.ts`,
`legacy.ts`

Public import paths stay on parent modules / `legacy.ts` / package
barrels via re-exports.

## Test plan
- [x] core/engine/dashboard typecheck (including `tsconfig.app.json`)
- [x] eslint on touched modules
- [x] `parse-porcelain-z` + merger parseFailing/getBranchChanged tests
- [x] dashboard `api-tasks` + legacy-prinfo/pr-types (69)
- [ ] CI merge gate

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

- **New Features**
- Added dashboard API support for task listing/detail, archiving,
creation, review updates, duplicate detection, bulk model updates,
moving tasks, and overlap repair.
- Added health/engine status and refresh/start controls, plus update
checking.

- **Bug Fixes**
- Improved dashboard API handling for non-JSON/HTML responses with
clearer errors, better URL routing for remote nodes, and project-scoped
queries.
- Strengthened automated recovery for heartbeat error/model-unavailable
scenarios and safer merge-conflict classification/auto-resolution.

- **Tests**
- Updated merge-conflict resolution and lifecycle test mocks to match
the updated git command behavior.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
2026-07-16 00:01:17 -07:00

69 lines
2.7 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* FNXC:CodeOrganization 2026-07-16-00:30:
* Pure git/test-output parse helpers peeled from merger.ts (string parsing only).
*/
export function parseFailingFilesFromOutput(output: string): string[] {
const paths = new Set<string>();
for (const line of output.split("\n")) {
// jest/vitest: "FAIL packages/engine/src/__tests__/foo.test.ts"
const failMatch = line.match(/^FAIL\s+(\S+)/);
if (failMatch && failMatch[1]) {
paths.add(failMatch[1]);
continue;
}
// vitest summary: " ❯ packages/engine/src/__tests__/foo.test.ts (2 tests | 1 failed)"
const vitestSummaryMatch = line.match(/^\s*[❯>]\s+(\S+\.(?:test|spec)\.[jt]sx?)\s/);
if (vitestSummaryMatch && vitestSummaryMatch[1]) {
paths.add(vitestSummaryMatch[1]);
continue;
}
// vitest: " × src/__tests__/foo.test.ts > some test name"
const crossMatch = line.match(/^\s*[×✕✗]\s+(\S+\.(?:test|spec)\.[jt]sx?)\s/);
if (crossMatch && crossMatch[1]) {
paths.add(crossMatch[1]);
}
}
return Array.from(paths);
}
/** Parse `git status -z --porcelain` into a Set of paths.
*
* Format per entry: `XY <space> <path>\0` where X = staged status, Y =
* unstaged status. Renames and copies are special: they emit TWO
* NUL-separated entries, `R <new>\0<old>\0` (or `C <new>\0<old>\0`).
* We must consume the trailing `<old>` entry without treating it as a
* separate path, otherwise observability code over-reports "cleared
* paths" with the historical names of renames. */
export function parsePorcelainZ(raw: string): Set<string> {
const paths = new Set<string>();
const entries = raw.split("\0");
for (let i = 0; i < entries.length; i++) {
const entry = entries[i];
if (!entry) continue;
if (entry.length < 4) continue;
const status = entry.slice(0, 2);
const path = entry.slice(3);
if (!path) continue;
paths.add(path);
// Rename/copy: the very next entry is the old path — skip it so it
// isn't mistaken for an independent dirty path.
if (status.charAt(0) === "R" || status.charAt(0) === "C") {
i++;
}
}
return paths;
}
export function parseShortstatSummary(statsOutput: string): { filesChanged: number; insertions: number; deletions: number } {
const normalized = statsOutput.trim().replace(/\n/g, " ");
const filesMatch = normalized.match(/(\d+) files? changed/);
const insertionsMatch = normalized.match(/(\d+) insertions?\(\+\)/);
const deletionsMatch = normalized.match(/(\d+) deletions?\(-\)/);
return {
filesChanged: filesMatch ? Number.parseInt(filesMatch[1], 10) : 0,
insertions: insertionsMatch ? Number.parseInt(insertionsMatch[1], 10) : 0,
deletions: deletionsMatch ? Number.parseInt(deletionsMatch[1], 10) : 0,
};
}