fix(ci): clean member work sync validation

This commit is contained in:
777genius 2026-04-30 00:05:58 +03:00
parent e634895a08
commit 7d08205a23
40 changed files with 194 additions and 169 deletions

View file

@ -1,2 +1,2 @@
export * from './ipc';
export * from './types';
export type * from './types';

View file

@ -221,11 +221,11 @@ export interface MemberWorkSyncNudgePayload {
source: 'member-work-sync';
actionMode: 'do';
text: string;
taskRefs: Array<{
taskRefs: {
taskId: string;
displayId: string;
teamName: string;
}>;
}[];
}
export interface MemberWorkSyncOutboxItem {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts';
import { MemberWorkSyncReconciler } from './MemberWorkSyncReconciler';
import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports';
export class MemberWorkSyncDiagnosticsReader {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncMetricsRequest, MemberWorkSyncTeamMetrics } from '../../contracts';
import { assessMemberWorkSyncPhase2Readiness } from '../domain';
import type { MemberWorkSyncMetricsRequest, MemberWorkSyncTeamMetrics } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports';
function emptyMetrics(teamName: string, generatedAt: string): MemberWorkSyncTeamMetrics {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncStatus } from '../../contracts';
import { buildMemberWorkSyncOutboxEnsureInput } from '../domain';
import type { MemberWorkSyncStatus } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports';
export interface MemberWorkSyncNudgeOutboxPlanResult {

View file

@ -1,5 +1,6 @@
import type { MemberWorkSyncReportIntentStatus } from '../../contracts';
import { MemberWorkSyncReporter } from './MemberWorkSyncReporter';
import type { MemberWorkSyncReportIntentStatus } from '../../contracts';
import type { MemberWorkSyncUseCaseDeps } from './ports';
export interface MemberWorkSyncPendingReportReplaySummary {

View file

@ -4,9 +4,11 @@ import {
decideMemberWorkSyncStatus,
formatAgendaFingerprint,
} from '../domain';
import { MemberWorkSyncNudgeOutboxPlanner } from './MemberWorkSyncNudgeOutboxPlanner';
import type { MemberWorkSyncStatus, MemberWorkSyncStatusRequest } from '../../contracts';
import type { MemberWorkSyncAgendaSourceResult, MemberWorkSyncUseCaseDeps } from './ports';
import { MemberWorkSyncNudgeOutboxPlanner } from './MemberWorkSyncNudgeOutboxPlanner';
export interface MemberWorkSyncReconcileContext {
reconciledBy?: 'request' | 'queue';

View file

@ -1,15 +1,17 @@
import { validateMemberWorkSyncReport } from '../domain';
import {
attachMemberWorkSyncReportToken,
finalizeMemberWorkSyncAgenda,
MemberWorkSyncReconciler,
} from './MemberWorkSyncReconciler';
import type {
MemberWorkSyncReport,
MemberWorkSyncReportRequest,
MemberWorkSyncReportResult,
MemberWorkSyncStatus,
} from '../../contracts';
import { validateMemberWorkSyncReport } from '../domain';
import {
attachMemberWorkSyncReportToken,
finalizeMemberWorkSyncAgenda,
MemberWorkSyncReconciler,
} from './MemberWorkSyncReconciler';
import type { MemberWorkSyncUseCaseDeps } from './ports';
export class MemberWorkSyncReporter {
@ -28,7 +30,11 @@ export class MemberWorkSyncReporter {
: true;
if (!teamActive) {
const status = await this.reconciler.execute(request);
const rejectedStatus = await this.recordRejectedReport(status, request, 'team_runtime_inactive');
const rejectedStatus = await this.recordRejectedReport(
status,
request,
'team_runtime_inactive'
);
return {
accepted: false,
code: 'team_runtime_inactive',

View file

@ -5,6 +5,6 @@ export * from './MemberWorkSyncNudgeOutboxPlanner';
export * from './MemberWorkSyncPendingReportIntentReplayer';
export * from './MemberWorkSyncReconciler';
export * from './MemberWorkSyncReporter';
export * from './ports';
export type * from './ports';
export * from './RuntimeTurnSettledIngestor';
export * from './RuntimeTurnSettledPorts';
export type * from './RuntimeTurnSettledPorts';

View file

@ -1,7 +1,5 @@
import type {
MemberWorkSyncAgenda,
MemberWorkSyncTeamMetrics,
MemberWorkSyncProviderId,
MemberWorkSyncOutboxClaimInput,
MemberWorkSyncOutboxCountRecentDeliveredInput,
MemberWorkSyncOutboxEnsureInput,
@ -10,11 +8,13 @@ import type {
MemberWorkSyncOutboxMarkDeliveredInput,
MemberWorkSyncOutboxMarkFailedInput,
MemberWorkSyncOutboxMarkSupersededInput,
MemberWorkSyncProviderId,
MemberWorkSyncReport,
MemberWorkSyncReportIntent,
MemberWorkSyncReportIntentStatus,
MemberWorkSyncReportRequest,
MemberWorkSyncStatus,
MemberWorkSyncTeamMetrics,
} from '../../contracts';
export interface MemberWorkSyncClockPort {

View file

@ -1,8 +1,3 @@
import type {
MemberWorkSyncActionableWorkItem,
MemberWorkSyncAgenda,
MemberWorkSyncProviderId,
} from '../../contracts';
import {
buildAgendaFingerprintPayload,
canonicalizeAgendaFingerprintPayload,
@ -11,6 +6,12 @@ import {
import { resolveCurrentReviewOwner, type ReviewHistoryEventLike } from './currentReviewCycle';
import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName';
import type {
MemberWorkSyncActionableWorkItem,
MemberWorkSyncAgenda,
MemberWorkSyncProviderId,
} from '../../contracts';
export interface MemberWorkSyncTaskLike {
id: string;
displayId?: string;

View file

@ -19,7 +19,7 @@ export interface AgendaFingerprintPayload {
version: 1;
teamName: string;
memberName: string;
items: Array<{
items: {
taskId: string;
displayId?: string;
subject: string;
@ -27,7 +27,7 @@ export interface AgendaFingerprintPayload {
assignee: string;
priority: string;
evidence: MemberWorkSyncActionableWorkItem['evidence'];
}>;
}[];
sourceRevision?: string;
}

View file

@ -1,9 +1,10 @@
import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName';
import type {
MemberWorkSyncAgenda,
MemberWorkSyncReportRequest,
MemberWorkSyncReportState,
} from '../../contracts';
import { isReservedMemberName, normalizeMemberName, sameMemberName } from './memberName';
export interface MemberWorkSyncReportValidation {
ok: boolean;

View file

@ -2,8 +2,8 @@ export * from './ActionableWorkAgenda';
export * from './AgendaFingerprint';
export * from './currentReviewCycle';
export * from './memberName';
export * from './MemberWorkSyncPhase2Readiness';
export * from './MemberWorkSyncNudge';
export * from './MemberWorkSyncPhase2Readiness';
export * from './MemberWorkSyncReportValidator';
export * from './RuntimeTurnSettledEvent';
export * from './RuntimeTurnSettledProvider';

View file

@ -1,3 +1,5 @@
import { createLogger } from '@shared/utils/logger';
import {
MEMBER_WORK_SYNC_GET_METRICS,
MEMBER_WORK_SYNC_GET_STATUS,
@ -9,7 +11,6 @@ import {
type MemberWorkSyncStatusRequest,
type MemberWorkSyncTeamMetrics,
} from '../../../contracts';
import { createLogger } from '@shared/utils/logger';
import type { MemberWorkSyncFeatureFacade } from '../../composition/createMemberWorkSyncFeature';
import type { IpcMain } from 'electron';

View file

@ -1,5 +1,6 @@
import { TeamInboxReader } from '@main/services/team/TeamInboxReader';
import { TeamInboxWriter } from '@main/services/team/TeamInboxWriter';
import type { MemberWorkSyncInboxNudgePort } from '../../../core/application';
export class TeamInboxMemberWorkSyncNudgeSink implements MemberWorkSyncInboxNudgePort {

View file

@ -1,16 +1,16 @@
import path from 'path';
import { isReservedMemberName, normalizeMemberName } from '../../../core/domain';
import type {
RuntimeTurnSettledTargetResolution,
RuntimeTurnSettledTargetResolverPort,
} from '../../../core/application';
import { TeamMemberLogsFinder } from '@main/services/team/TeamMemberLogsFinder';
import {
inferTeamProviderIdFromModel,
normalizeOptionalTeamProviderId,
} from '@shared/utils/teamProvider';
import path from 'path';
import { isReservedMemberName, normalizeMemberName } from '../../../core/domain';
import type {
RuntimeTurnSettledTargetResolution,
RuntimeTurnSettledTargetResolverPort,
} from '../../../core/application';
import type { RuntimeTurnSettledEvent } from '../../../core/domain';
import type { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore';
@ -69,9 +69,7 @@ function normalizePath(value: string | undefined): string | null {
return path.resolve(value.trim());
}
export class TeamRuntimeTurnSettledTargetResolver
implements RuntimeTurnSettledTargetResolverPort
{
export class TeamRuntimeTurnSettledTargetResolver implements RuntimeTurnSettledTargetResolverPort {
private readonly memberLogsFinder: AttributedMemberFileSource;
private readonly maxTeamsToScan: number;
@ -99,12 +97,12 @@ export class TeamRuntimeTurnSettledTargetResolver
.filter((team) => !team.deletedAt)
.slice(0, this.maxTeamsToScan);
const candidates: Array<{
const candidates: {
teamName: string;
memberName: string;
exactPath: boolean;
mtimeMs: number;
}> = [];
}[] = [];
for (const team of teams) {
const attributedFiles = await this.memberLogsFinder

View file

@ -1,19 +1,20 @@
import {
buildActionableWorkAgenda,
isReservedMemberName,
normalizeMemberName,
type MemberWorkSyncMemberLike,
} from '../../../core/domain';
import type {
MemberWorkSyncAgendaSourcePort,
MemberWorkSyncAgendaSourceResult,
MemberWorkSyncHashPort,
} from '../../../core/application';
import {
inferTeamProviderIdFromModel,
normalizeOptionalTeamProviderId,
} from '@shared/utils/teamProvider';
import {
buildActionableWorkAgenda,
isReservedMemberName,
type MemberWorkSyncMemberLike,
normalizeMemberName,
} from '../../../core/domain';
import type {
MemberWorkSyncAgendaSourcePort,
MemberWorkSyncAgendaSourceResult,
MemberWorkSyncHashPort,
} from '../../../core/application';
import type { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import type { TeamKanbanManager } from '@main/services/team/TeamKanbanManager';
import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore';

View file

@ -1,5 +1,6 @@
import { readFile } from 'fs/promises';
import { join } from 'path';
import type { MemberWorkSyncWatchdogCooldownPort } from '../../../core/application';
const DEFAULT_WATCHDOG_COOLDOWN_MS = 10 * 60_000;
@ -54,10 +55,7 @@ export class TeamTaskStallJournalWorkSyncCooldown implements MemberWorkSyncWatch
return alertedAt != null && now - alertedAt <= this.cooldownMs;
});
} catch (error) {
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
return false;
}
return true;
return (error as NodeJS.ErrnoException).code !== 'ENOENT';
}
}
}

View file

@ -1,3 +1,43 @@
import {
MemberWorkSyncDiagnosticsReader,
MemberWorkSyncMetricsReader,
MemberWorkSyncNudgeDispatcher,
type MemberWorkSyncNudgeDispatchSummary,
MemberWorkSyncPendingReportIntentReplayer,
type MemberWorkSyncPendingReportReplaySummary,
type MemberWorkSyncReconcileContext,
MemberWorkSyncReconciler,
MemberWorkSyncReporter,
type RuntimeTurnSettledDrainSummary,
RuntimeTurnSettledIngestor,
type RuntimeTurnSettledTargetResolverPort,
} from '../../core/application';
import { MemberWorkSyncTeamChangeRouter } from '../adapters/input/MemberWorkSyncTeamChangeRouter';
import { TeamInboxMemberWorkSyncNudgeSink } from '../adapters/output/TeamInboxMemberWorkSyncNudgeSink';
import { TeamRuntimeTurnSettledTargetResolver } from '../adapters/output/TeamRuntimeTurnSettledTargetResolver';
import { TeamTaskAgendaSource } from '../adapters/output/TeamTaskAgendaSource';
import { TeamTaskStallJournalWorkSyncCooldown } from '../adapters/output/TeamTaskStallJournalWorkSyncCooldown';
import { ClaudeStopHookPayloadNormalizer } from '../infrastructure/ClaudeStopHookPayloadNormalizer';
import { CodexNativeTurnSettledPayloadNormalizer } from '../infrastructure/CodexNativeTurnSettledPayloadNormalizer';
import { CompositeRuntimeTurnSettledPayloadNormalizer } from '../infrastructure/CompositeRuntimeTurnSettledPayloadNormalizer';
import { FileRuntimeTurnSettledEventStore } from '../infrastructure/FileRuntimeTurnSettledEventStore';
import { HmacMemberWorkSyncReportTokenAdapter } from '../infrastructure/HmacMemberWorkSyncReportTokenAdapter';
import { JsonMemberWorkSyncStore } from '../infrastructure/JsonMemberWorkSyncStore';
import {
MemberWorkSyncEventQueue,
type MemberWorkSyncQueueDiagnostics,
} from '../infrastructure/MemberWorkSyncEventQueue';
import { MemberWorkSyncNudgeDispatchScheduler } from '../infrastructure/MemberWorkSyncNudgeDispatchScheduler';
import { MemberWorkSyncStorePaths } from '../infrastructure/MemberWorkSyncStorePaths';
import { MemberWorkSyncToolActivityBusySignal } from '../infrastructure/MemberWorkSyncToolActivityBusySignal';
import { NodeHashAdapter } from '../infrastructure/NodeHashAdapter';
import { RuntimeTurnSettledDrainScheduler } from '../infrastructure/RuntimeTurnSettledDrainScheduler';
import { buildRuntimeTurnSettledEnvironment } from '../infrastructure/runtimeTurnSettledEnvironment';
import { buildRuntimeTurnSettledHookSettings } from '../infrastructure/runtimeTurnSettledHookSettings';
import { RuntimeTurnSettledSpoolPaths } from '../infrastructure/RuntimeTurnSettledSpoolPaths';
import { ShellRuntimeTurnSettledHookScriptInstaller } from '../infrastructure/ShellRuntimeTurnSettledHookScriptInstaller';
import { SystemClockAdapter } from '../infrastructure/SystemClockAdapter';
import type {
MemberWorkSyncMetricsRequest,
MemberWorkSyncReportRequest,
@ -6,53 +46,13 @@ import type {
MemberWorkSyncStatusRequest,
MemberWorkSyncTeamMetrics,
} from '../../contracts';
import {
MemberWorkSyncDiagnosticsReader,
MemberWorkSyncMetricsReader,
MemberWorkSyncNudgeDispatcher,
type MemberWorkSyncNudgeDispatchSummary,
MemberWorkSyncPendingReportIntentReplayer,
type MemberWorkSyncPendingReportReplaySummary,
MemberWorkSyncReconciler,
MemberWorkSyncReporter,
type MemberWorkSyncReconcileContext,
RuntimeTurnSettledIngestor,
type RuntimeTurnSettledDrainSummary,
type RuntimeTurnSettledTargetResolverPort,
} from '../../core/application';
import { MemberWorkSyncTeamChangeRouter } from '../adapters/input/MemberWorkSyncTeamChangeRouter';
import { TeamInboxMemberWorkSyncNudgeSink } from '../adapters/output/TeamInboxMemberWorkSyncNudgeSink';
import { TeamTaskStallJournalWorkSyncCooldown } from '../adapters/output/TeamTaskStallJournalWorkSyncCooldown';
import { TeamTaskAgendaSource } from '../adapters/output/TeamTaskAgendaSource';
import { TeamRuntimeTurnSettledTargetResolver } from '../adapters/output/TeamRuntimeTurnSettledTargetResolver';
import { ClaudeStopHookPayloadNormalizer } from '../infrastructure/ClaudeStopHookPayloadNormalizer';
import { CodexNativeTurnSettledPayloadNormalizer } from '../infrastructure/CodexNativeTurnSettledPayloadNormalizer';
import { CompositeRuntimeTurnSettledPayloadNormalizer } from '../infrastructure/CompositeRuntimeTurnSettledPayloadNormalizer';
import { FileRuntimeTurnSettledEventStore } from '../infrastructure/FileRuntimeTurnSettledEventStore';
import { HmacMemberWorkSyncReportTokenAdapter } from '../infrastructure/HmacMemberWorkSyncReportTokenAdapter';
import {
MemberWorkSyncEventQueue,
type MemberWorkSyncQueueDiagnostics,
} from '../infrastructure/MemberWorkSyncEventQueue';
import { JsonMemberWorkSyncStore } from '../infrastructure/JsonMemberWorkSyncStore';
import { MemberWorkSyncStorePaths } from '../infrastructure/MemberWorkSyncStorePaths';
import { MemberWorkSyncNudgeDispatchScheduler } from '../infrastructure/MemberWorkSyncNudgeDispatchScheduler';
import { MemberWorkSyncToolActivityBusySignal } from '../infrastructure/MemberWorkSyncToolActivityBusySignal';
import { NodeHashAdapter } from '../infrastructure/NodeHashAdapter';
import { RuntimeTurnSettledDrainScheduler } from '../infrastructure/RuntimeTurnSettledDrainScheduler';
import { RuntimeTurnSettledSpoolPaths } from '../infrastructure/RuntimeTurnSettledSpoolPaths';
import { ShellRuntimeTurnSettledHookScriptInstaller } from '../infrastructure/ShellRuntimeTurnSettledHookScriptInstaller';
import { buildRuntimeTurnSettledEnvironment } from '../infrastructure/runtimeTurnSettledEnvironment';
import { buildRuntimeTurnSettledHookSettings } from '../infrastructure/runtimeTurnSettledHookSettings';
import { SystemClockAdapter } from '../infrastructure/SystemClockAdapter';
import type { MemberWorkSyncLoggerPort } from '../../core/application';
import type { RuntimeTurnSettledProvider } from '../../core/domain';
import type { TeamConfigReader } from '@main/services/team/TeamConfigReader';
import type { TeamKanbanManager } from '@main/services/team/TeamKanbanManager';
import type { TeamMembersMetaStore } from '@main/services/team/TeamMembersMetaStore';
import type { TeamTaskReader } from '@main/services/team/TeamTaskReader';
import type { TeamChangeEvent } from '@shared/types';
import type { MemberWorkSyncLoggerPort } from '../../core/application';
import type { RuntimeTurnSettledProvider } from '../../core/domain';
export const MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV =
'CLAUDE_TEAM_MEMBER_WORK_SYNC_NUDGES_ENABLED';
@ -208,17 +208,18 @@ export function createMemberWorkSyncFeature(deps: {
drain: () => runtimeTurnSettledIngestor.drainPending(),
logger: deps.logger,
});
const nudgeDispatchScheduler = nudgeSideEffectsEnabled && deps.listLifecycleActiveTeamNames
? new MemberWorkSyncNudgeDispatchScheduler({
listLifecycleActiveTeamNames: deps.listLifecycleActiveTeamNames,
dispatchDue: (teamNames) =>
nudgeDispatcher.dispatchDue({
teamNames,
claimedBy: `member-work-sync:${process.pid}:scheduled`,
}),
logger: deps.logger,
})
: null;
const nudgeDispatchScheduler =
nudgeSideEffectsEnabled && deps.listLifecycleActiveTeamNames
? new MemberWorkSyncNudgeDispatchScheduler({
listLifecycleActiveTeamNames: deps.listLifecycleActiveTeamNames,
dispatchDue: (teamNames) =>
nudgeDispatcher.dispatchDue({
teamNames,
claimedBy: `member-work-sync:${process.pid}:scheduled`,
}),
logger: deps.logger,
})
: null;
runtimeTurnSettledDrainScheduler.start();
nudgeDispatchScheduler?.start();

View file

@ -1,10 +1,11 @@
export type { RuntimeTurnSettledProvider } from '../core/domain';
export {
registerMemberWorkSyncIpc,
removeMemberWorkSyncIpc,
} from './adapters/input/registerMemberWorkSyncIpc';
export type { MemberWorkSyncFeatureFacade } from './composition/createMemberWorkSyncFeature';
export {
MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV,
createMemberWorkSyncFeature,
MEMBER_WORK_SYNC_NUDGE_SIDE_EFFECTS_ENV,
resolveMemberWorkSyncNudgeSideEffectsEnabled,
} from './composition/createMemberWorkSyncFeature';
export type { MemberWorkSyncFeatureFacade } from './composition/createMemberWorkSyncFeature';

View file

@ -2,6 +2,7 @@ import {
buildRuntimeTurnSettledSourceId,
type RuntimeTurnSettledProvider,
} from '../../core/domain';
import type {
MemberWorkSyncHashPort,
RuntimeTurnSettledPayloadNormalization,

View file

@ -2,6 +2,7 @@ import {
buildRuntimeTurnSettledSourceId,
type RuntimeTurnSettledProvider,
} from '../../core/domain';
import type {
MemberWorkSyncHashPort,
RuntimeTurnSettledPayloadNormalization,
@ -28,9 +29,7 @@ function getString(record: Record<string, unknown>, ...keys: string[]): string |
return undefined;
}
export class CodexNativeTurnSettledPayloadNormalizer
implements RuntimeTurnSettledPayloadNormalizerPort
{
export class CodexNativeTurnSettledPayloadNormalizer implements RuntimeTurnSettledPayloadNormalizerPort {
constructor(private readonly hash: MemberWorkSyncHashPort) {}
normalize(input: {

View file

@ -2,6 +2,7 @@ import { mkdir, readdir, readFile, rename, rm, stat, writeFile } from 'fs/promis
import path from 'path';
import { isRuntimeTurnSettledProvider } from '../../core/domain';
import type {
RuntimeTurnSettledClaimedPayload,
RuntimeTurnSettledEventStorePort,
@ -131,7 +132,11 @@ export class FileRuntimeTurnSettledEventStore implements RuntimeTurnSettledEvent
): Promise<void> {
const processedPath = path.join(this.deps.paths.getProcessedDir(), payload.fileName);
await moveFileBestEffort(payload.filePath, processedPath);
await writeFile(buildMetaFilePath(processedPath), `${JSON.stringify(result, null, 2)}\n`, 'utf8');
await writeFile(
buildMetaFilePath(processedPath),
`${JSON.stringify(result, null, 2)}\n`,
'utf8'
);
await this.cleanupDirectory(this.deps.paths.getProcessedDir());
}
@ -164,9 +169,7 @@ export class FileRuntimeTurnSettledEventStore implements RuntimeTurnSettledEvent
entries
.filter(
(entry) =>
entry.isFile() &&
!entry.name.startsWith('.') &&
!entry.name.endsWith('.meta.json')
entry.isFile() && !entry.name.startsWith('.') && !entry.name.endsWith('.meta.json')
)
.map(async (entry) => {
const processingPath = path.join(this.deps.paths.getProcessingDir(), entry.name);

View file

@ -6,8 +6,8 @@ import { atomicWriteAsync } from '@main/utils/atomicWrite';
import type {
MemberWorkSyncReportTokenCreateInput,
MemberWorkSyncReportTokenPort,
MemberWorkSyncReportTokenVerifyInput,
MemberWorkSyncReportTokenVerification,
MemberWorkSyncReportTokenVerifyInput,
} from '../../core/application';
import type { MemberWorkSyncStorePaths } from './MemberWorkSyncStorePaths';

View file

@ -1,8 +1,10 @@
import { withFileLock } from '@main/services/team/fileLock';
import { atomicWriteAsync } from '@main/utils/atomicWrite';
import { createHash } from 'crypto';
import { mkdir, readFile, rename } from 'fs/promises';
import { withFileLock } from '@main/services/team/fileLock';
import { assessMemberWorkSyncPhase2Readiness } from '../../core/domain';
import type {
MemberWorkSyncMetricEvent,
MemberWorkSyncOutboxClaimInput,
@ -19,7 +21,6 @@ import type {
MemberWorkSyncStatusState,
MemberWorkSyncTeamMetrics,
} from '../../contracts';
import { assessMemberWorkSyncPhase2Readiness } from '../../core/domain';
import type {
MemberWorkSyncOutboxStorePort,
MemberWorkSyncReportStorePort,
@ -288,13 +289,10 @@ export class JsonMemberWorkSyncStore
stateCounts,
actionableItemCount,
wouldNudgeCount: recentEvents.filter((event) => event.kind === 'would_nudge').length,
fingerprintChangeCount: recentEvents.filter(
(event) => event.kind === 'fingerprint_changed'
).length,
reportAcceptedCount: recentEvents.filter((event) => event.kind === 'report_accepted')
.length,
reportRejectedCount: recentEvents.filter((event) => event.kind === 'report_rejected')
fingerprintChangeCount: recentEvents.filter((event) => event.kind === 'fingerprint_changed')
.length,
reportAcceptedCount: recentEvents.filter((event) => event.kind === 'report_accepted').length,
reportRejectedCount: recentEvents.filter((event) => event.kind === 'report_rejected').length,
recentEvents,
};
return {
@ -349,7 +347,7 @@ export class JsonMemberWorkSyncStore
await withFileLock(this.paths.getPendingReportsPath(teamName), async () => {
const existing = await this.readPendingFile(teamName);
const current = existing.intents[id];
if (!current || current.status !== 'pending') {
if (current?.status !== 'pending') {
return;
}
existing.intents[id] = {
@ -471,7 +469,7 @@ export class JsonMemberWorkSyncStore
async markDelivered(input: MemberWorkSyncOutboxMarkDeliveredInput): Promise<void> {
await this.updateOutboxItem(input.teamName, input.id, (current) => {
if (!current || current.attemptGeneration !== input.attemptGeneration) {
if (current?.attemptGeneration !== input.attemptGeneration) {
return current;
}
const next: MemberWorkSyncOutboxItem = {
@ -501,7 +499,7 @@ export class JsonMemberWorkSyncStore
async markFailed(input: MemberWorkSyncOutboxMarkFailedInput): Promise<void> {
await this.updateOutboxItem(input.teamName, input.id, (current) => {
if (!current || current.attemptGeneration !== input.attemptGeneration) {
if (current?.attemptGeneration !== input.attemptGeneration) {
return current;
}
const next: MemberWorkSyncOutboxItem = {
@ -589,9 +587,7 @@ export class JsonMemberWorkSyncStore
private async updateOutboxItem(
teamName: string,
id: string,
updater: (
current: MemberWorkSyncOutboxItem | undefined
) => MemberWorkSyncOutboxItem | undefined
updater: (current: MemberWorkSyncOutboxItem | undefined) => MemberWorkSyncOutboxItem | undefined
): Promise<void> {
await this.enqueue(teamName, async () => {
await withFileLock(this.paths.getOutboxPath(teamName), async () => {

View file

@ -1,5 +1,5 @@
import type { MemberWorkSyncReconcileContext } from '../../core/application/MemberWorkSyncReconciler';
import type { MemberWorkSyncLoggerPort } from '../../core/application';
import type { MemberWorkSyncReconcileContext } from '../../core/application/MemberWorkSyncReconciler';
export type MemberWorkSyncTriggerReason =
| 'startup_scan'

View file

@ -1,5 +1,4 @@
import type { MemberWorkSyncBusySignalPort } from '../../core/application';
import type { TeamChangeEvent, ToolActivityEventPayload } from '@shared/types';
const DEFAULT_TOOL_ACTIVITY_BUSY_GRACE_MS = 90_000;

View file

@ -1,6 +1,6 @@
import fs from 'fs/promises';
import { RuntimeTurnSettledSpoolPaths } from './RuntimeTurnSettledSpoolPaths';
import type { RuntimeTurnSettledSpoolPaths } from './RuntimeTurnSettledSpoolPaths';
const HOOK_SCRIPT_CONTENT = `#!/bin/sh
set +e
@ -58,6 +58,7 @@ export class ShellRuntimeTurnSettledHookScriptInstaller {
const scriptPath = this.paths.getHookScriptPath();
await fs.writeFile(scriptPath, HOOK_SCRIPT_CONTENT, 'utf8');
// eslint-disable-next-line sonarjs/file-permissions -- the hook script must be executable on POSIX hosts.
await fs.chmod(scriptPath, 0o755);
return {

View file

@ -2,12 +2,13 @@ import { useEffect, useState } from 'react';
import { api } from '@renderer/api';
import type { MemberWorkSyncStatus } from '../../contracts';
import {
toMemberWorkSyncStatusViewModel,
type MemberWorkSyncStatusViewModel,
toMemberWorkSyncStatusViewModel,
} from '../adapters/memberWorkSyncStatusViewModel';
import type { MemberWorkSyncStatus } from '../../contracts';
export interface UseMemberWorkSyncStatusOptions {
teamName?: string | null;
memberName?: string | null;

View file

@ -1,18 +1,19 @@
import { Badge } from '@renderer/components/ui/badge';
import { cn } from '@renderer/lib/utils';
import type React from 'react';
import type { MemberWorkSyncStatus } from '../../contracts';
import {
toMemberWorkSyncStatusViewModel,
type MemberWorkSyncStatusViewModel,
toMemberWorkSyncStatusViewModel,
} from '../adapters/memberWorkSyncStatusViewModel';
interface MemberWorkSyncBadgeProps {
import type { MemberWorkSyncStatus } from '../../contracts';
import type React from 'react';
type MemberWorkSyncBadgeProps = Readonly<{
status?: MemberWorkSyncStatus | null;
viewModel?: MemberWorkSyncStatusViewModel;
className?: string;
}
}>;
const toneClassName: Record<MemberWorkSyncStatusViewModel['tone'], string> = {
neutral: 'border-[var(--color-border)] text-[var(--color-text-muted)]',
@ -32,7 +33,11 @@ export function MemberWorkSyncBadge({
return (
<Badge
variant="outline"
className={cn('cursor-default whitespace-nowrap font-medium', toneClassName[resolved.tone], className)}
className={cn(
'cursor-default whitespace-nowrap font-medium',
toneClassName[resolved.tone],
className
)}
title={resolved.tooltip}
>
{resolved.label}

View file

@ -1,12 +1,14 @@
import { MemberWorkSyncBadge } from './MemberWorkSyncBadge';
import type { MemberWorkSyncStatus } from '../../contracts';
import { toMemberWorkSyncStatusViewModel } from '../adapters/memberWorkSyncStatusViewModel';
import { MemberWorkSyncBadge } from './MemberWorkSyncBadge';
import type { MemberWorkSyncStatus } from '../../contracts';
import type React from 'react';
interface MemberWorkSyncDetailsProps {
type MemberWorkSyncDetailsProps = Readonly<{
status: MemberWorkSyncStatus | null;
showDiagnostics?: boolean;
}
}>;
function shortFingerprint(fingerprint?: string): string {
if (!fingerprint) {

View file

@ -1,15 +1,16 @@
import type React from 'react';
import { useMemberWorkSyncStatus } from '../hooks/useMemberWorkSyncStatus';
import { MemberWorkSyncBadge } from './MemberWorkSyncBadge';
import { MemberWorkSyncDetails } from './MemberWorkSyncDetails';
import { useMemberWorkSyncStatus } from '../hooks/useMemberWorkSyncStatus';
interface MemberWorkSyncStatusPanelProps {
import type React from 'react';
type MemberWorkSyncStatusPanelProps = Readonly<{
teamName: string;
memberName: string;
enabled?: boolean;
showDiagnostics?: boolean;
}
}>;
export function MemberWorkSyncStatusPanel({
teamName,

View file

@ -9,7 +9,6 @@ import {
type RecentProjectsFeatureFacade,
registerRecentProjectsHttp,
} from '@features/recent-projects/main';
import type { MemberWorkSyncFeatureFacade } from '@features/member-work-sync/main';
import { createLogger } from '@shared/utils/logger';
import { registerConfigRoutes } from './config';
@ -36,6 +35,7 @@ import type {
import type { SshConnectionManager } from '../services/infrastructure/SshConnectionManager';
import type { TeamDataService } from '../services/team/TeamDataService';
import type { TeamProvisioningService } from '../services/team/TeamProvisioningService';
import type { MemberWorkSyncFeatureFacade } from '@features/member-work-sync/main';
import type { FastifyInstance } from 'fastify';
const logger = createLogger('HTTP:routes');

View file

@ -30,18 +30,18 @@ import {
type CodexModelCatalogFeatureFacade,
createCodexModelCatalogFeature,
} from '@features/codex-model-catalog/main';
import {
createRecentProjectsFeature,
type RecentProjectsFeatureFacade,
registerRecentProjectsIpc,
removeRecentProjectsIpc,
} from '@features/recent-projects/main';
import {
createMemberWorkSyncFeature,
type MemberWorkSyncFeatureFacade,
registerMemberWorkSyncIpc,
removeMemberWorkSyncIpc,
} from '@features/member-work-sync/main';
import {
createRecentProjectsFeature,
type RecentProjectsFeatureFacade,
registerRecentProjectsIpc,
removeRecentProjectsIpc,
} from '@features/recent-projects/main';
import {
createRuntimeProviderManagementFeature,
registerRuntimeProviderManagementIpc,
@ -179,19 +179,19 @@ import {
SshConnectionManager,
TaskBoundaryParser,
TeamDataService,
TeamKanbanManager,
TeamLogSourceTracker,
TeammateToolTracker,
TeamMemberLogsFinder,
TeamMembersMetaStore,
TeamProvisioningService,
TeamRuntimeAdapterRegistry,
TeamKanbanManager,
TeamMembersMetaStore,
TeamTaskReader,
TeamTaskStallJournal,
TeamTaskStallMonitor,
TeamTaskStallNotifier,
TeamTaskStallPolicy,
TeamTaskStallSnapshotSource,
TeamTaskReader,
UpdaterService,
} from './services';

View file

@ -1152,7 +1152,7 @@ export class TaskChangeLedgerReader {
string,
{ event: LedgerEvent; index: number; rank: number }
>();
const passthrough: Array<{ event: LedgerEvent; index: number }> = [];
const passthrough: { event: LedgerEvent; index: number }[] = [];
events.forEach((event, index) => {
const sourceImportKey = this.sourceImportKeyForEvent(event);

View file

@ -2,7 +2,6 @@ import {
resolveAnthropicFastMode,
resolveAnthropicRuntimeSelection,
} from '@features/anthropic-runtime-profile/main';
import type { RuntimeTurnSettledProvider } from '@features/member-work-sync/core/domain';
import {
buildCodexFastModeArgs,
resolveCodexFastMode,
@ -239,6 +238,7 @@ import type {
TeamRuntimePrepareResult,
TeamRuntimeStopInput,
} from './runtime';
import type { RuntimeTurnSettledProvider } from '@features/member-work-sync/main';
/**
* Kill a team CLI process using SIGKILL (uncatchable).

View file

@ -1,12 +1,12 @@
import { useEffect, useMemo, useState } from 'react';
import { MemberWorkSyncStatusPanel } from '@features/member-work-sync/renderer';
import { Button } from '@renderer/components/ui/button';
import { Dialog, DialogContent, DialogFooter, DialogHeader } from '@renderer/components/ui/dialog';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@renderer/components/ui/tabs';
import { useMemberStats } from '@renderer/hooks/useMemberStats';
import { useStore } from '@renderer/store';
import { selectMemberMessagesForTeamMember } from '@renderer/store/slices/teamSlice';
import { MemberWorkSyncStatusPanel } from '@features/member-work-sync/renderer';
import {
buildMemberLaunchDiagnosticsPayload,
getMemberLaunchDiagnosticsErrorMessage,

View file

@ -95,8 +95,6 @@ import type { TerminalAPI } from './terminal';
import type { TmuxAPI } from './tmux';
import type { WaterfallData } from './visualization';
import type { CodexAccountElectronApi } from '@features/codex-account/contracts';
import type { RecentProjectsElectronApi } from '@features/recent-projects/contracts';
import type { RuntimeProviderManagementApi } from '@features/runtime-provider-management/contracts';
import type {
MemberWorkSyncMetricsRequest,
MemberWorkSyncReportRequest,
@ -105,6 +103,8 @@ import type {
MemberWorkSyncStatusRequest,
MemberWorkSyncTeamMetrics,
} from '@features/member-work-sync/contracts';
import type { RecentProjectsElectronApi } from '@features/recent-projects/contracts';
import type { RuntimeProviderManagementApi } from '@features/runtime-provider-management/contracts';
import type {
ConversationGroup,
FileChangeEvent,

View file

@ -79,8 +79,10 @@ describe('runtime turn-settled hook settings', () => {
expect(fs.existsSync(paths.getProcessedDir())).toBe(true);
expect(fs.existsSync(paths.getInvalidDir())).toBe(true);
const stat = fs.statSync(result.scriptPath);
expect(stat.mode & 0o111).toBeGreaterThan(0);
if (process.platform !== 'win32') {
const stat = fs.statSync(result.scriptPath);
expect(stat.mode & 0o111).toBeGreaterThan(0);
}
expect(fs.readFileSync(result.scriptPath, 'utf8')).toContain('dd bs="$max_bytes"');
});
});