Capture merge-time diff stats so Command Center Productivity can report real Lines changed when available. - Add nullable additions/deletions columns to task commit associations with migration and storage normalization. - Persist git shortstat counts from merge association paths without blocking merges when stats are unavailable. - Aggregate Productivity LOC from recorded commit stats while preserving the unavailable sentinel for historical unknowns. - Cover migration, store upsert, analytics, and merger association stats behavior with tests and documentation. Files changed: .changeset/fn-6704-command-center-loc.md | 5 ++ docs/architecture.md | 4 +- docs/storage.md | 3 + packages/core/src/__tests__/db-migrate.test.ts | 32 ++++---- packages/core/src/__tests__/db.test.ts | 90 ++++++++++++++++------ .../src/__tests__/productivity-analytics.test.ts | 47 +++++++++-- packages/core/src/__tests__/store-upsert.test.ts | 40 ++++++++++ packages/core/src/db.ts | 14 +++- packages/core/src/productivity-analytics.ts | 56 +++++++++----- packages/core/src/store.ts | 17 +++- packages/core/src/task-lineage.ts | 2 + packages/core/src/types.ts | 2 + .../merger-commit-association-stats.test.ts | 90 ++++++++++++++++++++++ packages/engine/src/merger-ai.ts | 2 + packages/engine/src/merger.ts | 17 +++- 15 files changed, 349 insertions(+), 72 deletions(-) Fusion-Task-Id: FN-6704 Fusion-Task-Lineage: f079fecb-eade-4318-bc55-23aa48097b4a
50 lines
1.5 KiB
TypeScript
50 lines
1.5 KiB
TypeScript
import { randomUUID } from "node:crypto";
|
|
import type {
|
|
TaskCommitAssociation,
|
|
TaskCommitAssociationConfidence,
|
|
TaskCommitAssociationMatchSource,
|
|
} from "./types.js";
|
|
|
|
export const FUSION_TASK_LINEAGE_TRAILER_KEY = "Fusion-Task-Lineage";
|
|
|
|
export function generateTaskLineageId(): string {
|
|
return randomUUID();
|
|
}
|
|
|
|
export function buildTaskLineageTrailer(lineageId: string): string {
|
|
return `${FUSION_TASK_LINEAGE_TRAILER_KEY}: ${lineageId}`;
|
|
}
|
|
|
|
export function parseTaskLineageTrailer(message: string): string | undefined {
|
|
const lines = message.split(/\r?\n/);
|
|
const prefix = `${FUSION_TASK_LINEAGE_TRAILER_KEY}:`;
|
|
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
|
const line = lines[i]?.trim();
|
|
if (line?.startsWith(prefix)) {
|
|
const value = line.slice(prefix.length).trim();
|
|
if (value) return value;
|
|
}
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
export function classifyTaskCommitAssociationConfidence(
|
|
matchedBy: TaskCommitAssociationMatchSource,
|
|
): TaskCommitAssociationConfidence {
|
|
if (matchedBy === "canonical-lineage-trailer") return "canonical";
|
|
if (matchedBy === "manual-reconciliation") return "ambiguous";
|
|
return "legacy";
|
|
}
|
|
|
|
export function normalizeTaskCommitAssociation(
|
|
row: TaskCommitAssociation,
|
|
): TaskCommitAssociation {
|
|
return {
|
|
...row,
|
|
note: row.note?.trim() || undefined,
|
|
additions: row.additions ?? undefined,
|
|
deletions: row.deletions ?? undefined,
|
|
confidence: row.confidence ?? classifyTaskCommitAssociationConfidence(row.matchedBy),
|
|
};
|
|
}
|