Files
fusion/packages/core/src/task-store/task-commit-associations.ts
gsxdsm cb57093d03 refactor: domain folder layout (types, API, core, engine) (#2398)
## Summary

Wave 17 organizes Fusion into **domain folders** (stacks on #2397).

### Layout
- **core/types/** — board, task, agents, settings, merge, workflow,
mesh, …
- **core/src/** — agents, ai, async-stores, workflows, tasks, config,
db, …
- **dashboard/app/api/** — client, tasks, agents, git, missions,
planning, …
- **engine/src/** — agents, auth, execution, merge, missions, overseer,
worktree, …

Root keepers retained for large entrypoints (`store.ts`, `executor.ts`,
`merger.ts`, …).

Public barrels (`@fusion/core`, `@fusion/engine`, `app/api.ts` → legacy)
stay stable.

## Test plan
- [x] `@fusion/core` typecheck
- [x] `@fusion/engine` typecheck (pre-existing playwright-core noise
only)
- [ ] CI merge gate

**Stack:** #2394 → #2397 → **this PR**
2026-08-03 00:20:53 -07:00

79 lines
3.1 KiB
TypeScript

/**
* Task commit-association persistence (domain module).
*
* FNXC:StoreModularization 2026-06-25-00:00:
* Extracted from the monolithic packages/core/src/store.ts as a pure
* behavior-preserving refactor. Each function receives the TaskStore
* instance as its first parameter and performs byte-identical work.
*
* FNXC:CodeOrganization 2026-07-15-00:00:
* Renamed from remaining-ops-9 to a domain name; no behavior change.
*/
import { TaskStore } from "../store.js";
import { normalizeTaskCommitAssociation } from "../tasks/task-lineage.js";
import { TaskCommitAssociationRow } from "./row-types.js";
import { TaskCommitAssociation } from "../types.js";
import { and, desc, eq, inArray } from "drizzle-orm";
import * as schema from "../postgres/schema/index.js";
export async function getTaskCommitAssociationsByLineageIdImpl(store: TaskStore, lineageId: string): Promise<TaskCommitAssociation[]> {
/*
FNXC:PostgresCutover 2026-07-04:
Backend-mode read of task_commit_associations by lineage id via async
Drizzle. Mirrors the SQLite ORDER BY authoredAt DESC, createdAt DESC. The
Drizzle select returns camelCase columns (schema-mapped), cast to the
shared TaskCommitAssociationRow shape used by normalizeTaskCommitAssociation.
*/
const layer = store.asyncLayer!;
const rows = await layer.db
.select()
.from(schema.project.taskCommitAssociations)
.where(eq(schema.project.taskCommitAssociations.taskLineageId, lineageId))
.orderBy(
desc(schema.project.taskCommitAssociations.authoredAt),
desc(schema.project.taskCommitAssociations.createdAt),
);
return (rows as TaskCommitAssociationRow[]).map((row) =>
normalizeTaskCommitAssociation({
...row,
note: row.note ?? undefined,
additions: row.additions ?? undefined,
deletions: row.deletions ?? undefined,
}),
);
}
export async function replaceLegacyTaskCommitAssociationsImpl(store: TaskStore,
lineageId: string,
associations: Array<Omit<TaskCommitAssociation, "id" | "createdAt" | "updatedAt" | "taskLineageId">>,
): Promise<void> {
/*
FNXC:PostgresCutover 2026-07-04:
Backend-mode replacement of legacy-matched task_commit_associations: delete
the legacy-/manual-matched rows for the lineage via async Drizzle, then
re-insert through store.upsertTaskCommitAssociation (which itself dispatches
to the async upsert in backend mode). Canonical-lineage-trailer rows are
preserved (matched only on the three legacy sources), matching the SQLite
path's IN-filter exactly.
*/
const layer = store.asyncLayer!;
await layer.db
.delete(schema.project.taskCommitAssociations)
.where(
and(
eq(schema.project.taskCommitAssociations.taskLineageId, lineageId),
inArray(schema.project.taskCommitAssociations.matchedBy, [
"legacy-task-id-trailer",
"legacy-subject",
"manual-reconciliation",
]),
),
);
for (const association of associations) {
await store.upsertTaskCommitAssociation({ ...association, taskLineageId: lineageId });
}
return;
}