@mrpatronz/nexusflow 0.2.19 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -7
- package/dist/analyzers/index.d.ts +23 -0
- package/dist/analyzers/index.d.ts.map +1 -1
- package/dist/analyzers/index.js +62 -0
- package/dist/analyzers/index.js.map +1 -1
- package/dist/commands/add-repo.d.ts +9 -0
- package/dist/commands/add-repo.d.ts.map +1 -1
- package/dist/commands/add-repo.js +12 -1
- package/dist/commands/add-repo.js.map +1 -1
- package/dist/commands/add-repo.test.d.ts +2 -0
- package/dist/commands/add-repo.test.d.ts.map +1 -0
- package/dist/commands/add-repo.test.js +30 -0
- package/dist/commands/add-repo.test.js.map +1 -0
- package/dist/commands/commands.test.js +26 -6
- package/dist/commands/commands.test.js.map +1 -1
- package/dist/commands/commit.d.ts +7 -1
- package/dist/commands/commit.d.ts.map +1 -1
- package/dist/commands/commit.js +31 -8
- package/dist/commands/commit.js.map +1 -1
- package/dist/commands/commit.test.d.ts +2 -0
- package/dist/commands/commit.test.d.ts.map +1 -0
- package/dist/commands/commit.test.js +54 -0
- package/dist/commands/commit.test.js.map +1 -0
- package/dist/commands/diff.d.ts +9 -2
- package/dist/commands/diff.d.ts.map +1 -1
- package/dist/commands/diff.js +36 -12
- package/dist/commands/diff.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +16 -20
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/handoff.d.ts.map +1 -1
- package/dist/commands/handoff.js +11 -15
- package/dist/commands/handoff.js.map +1 -1
- package/dist/commands/refresh.d.ts +4 -2
- package/dist/commands/refresh.d.ts.map +1 -1
- package/dist/commands/refresh.js +20 -36
- package/dist/commands/refresh.js.map +1 -1
- package/dist/commands/schedule.d.ts +35 -0
- package/dist/commands/schedule.d.ts.map +1 -0
- package/dist/commands/schedule.js +151 -0
- package/dist/commands/schedule.js.map +1 -0
- package/dist/commands/sync.d.ts.map +1 -1
- package/dist/commands/sync.js +28 -7
- package/dist/commands/sync.js.map +1 -1
- package/dist/commands/tui.js +1 -1
- package/dist/commands/tui.js.map +1 -1
- package/dist/commands/ui.d.ts +1 -0
- package/dist/commands/ui.d.ts.map +1 -1
- package/dist/commands/ui.js +5 -2
- package/dist/commands/ui.js.map +1 -1
- package/dist/core/adapters/local-storage.d.ts +7 -0
- package/dist/core/adapters/local-storage.d.ts.map +1 -1
- package/dist/core/adapters/local-storage.js +13 -4
- package/dist/core/adapters/local-storage.js.map +1 -1
- package/dist/core/adapters/storage.test.js +18 -0
- package/dist/core/adapters/storage.test.js.map +1 -1
- package/dist/core/analysis-cache.d.ts +58 -0
- package/dist/core/analysis-cache.d.ts.map +1 -0
- package/dist/core/analysis-cache.js +116 -0
- package/dist/core/analysis-cache.js.map +1 -0
- package/dist/core/analysis-cache.test.d.ts +2 -0
- package/dist/core/analysis-cache.test.d.ts.map +1 -0
- package/dist/core/analysis-cache.test.js +108 -0
- package/dist/core/analysis-cache.test.js.map +1 -0
- package/dist/core/refresh.d.ts +41 -0
- package/dist/core/refresh.d.ts.map +1 -0
- package/dist/core/refresh.js +56 -0
- package/dist/core/refresh.js.map +1 -0
- package/dist/core/scheduler.d.ts +117 -0
- package/dist/core/scheduler.d.ts.map +1 -0
- package/dist/core/scheduler.js +361 -0
- package/dist/core/scheduler.js.map +1 -0
- package/dist/core/scheduler.test.d.ts +2 -0
- package/dist/core/scheduler.test.d.ts.map +1 -0
- package/dist/core/scheduler.test.js +163 -0
- package/dist/core/scheduler.test.js.map +1 -0
- package/dist/core/sync.d.ts +6 -0
- package/dist/core/sync.d.ts.map +1 -1
- package/dist/core/sync.js +17 -12
- package/dist/core/sync.js.map +1 -1
- package/dist/core/workspace.d.ts +6 -0
- package/dist/core/workspace.d.ts.map +1 -1
- package/dist/core/workspace.js +32 -15
- package/dist/core/workspace.js.map +1 -1
- package/dist/core/workspace.test.d.ts +2 -0
- package/dist/core/workspace.test.d.ts.map +1 -0
- package/dist/core/workspace.test.js +42 -0
- package/dist/core/workspace.test.js.map +1 -0
- package/dist/generators/index.d.ts +7 -1
- package/dist/generators/index.d.ts.map +1 -1
- package/dist/generators/index.js +14 -1
- package/dist/generators/index.js.map +1 -1
- package/dist/generators/map-generator.d.ts.map +1 -1
- package/dist/generators/map-generator.js +4 -3
- package/dist/generators/map-generator.js.map +1 -1
- package/dist/generators/map-generator.test.js +4 -2
- package/dist/generators/map-generator.test.js.map +1 -1
- package/dist/gui/assets/index-ElroPpSP.js +26 -0
- package/dist/gui/index.html +1 -1
- package/dist/index.js +108 -4
- package/dist/index.js.map +1 -1
- package/dist/orchestration/index.d.ts +1 -1
- package/dist/orchestration/index.d.ts.map +1 -1
- package/dist/orchestration/index.js +1 -1
- package/dist/orchestration/index.js.map +1 -1
- package/dist/orchestration/runner.d.ts +16 -1
- package/dist/orchestration/runner.d.ts.map +1 -1
- package/dist/orchestration/runner.js +41 -5
- package/dist/orchestration/runner.js.map +1 -1
- package/dist/orchestration/runner.test.d.ts +2 -0
- package/dist/orchestration/runner.test.d.ts.map +1 -0
- package/dist/orchestration/runner.test.js +71 -0
- package/dist/orchestration/runner.test.js.map +1 -0
- package/dist/server.d.ts +17 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +275 -114
- package/dist/server.js.map +1 -1
- package/dist/server.test.js +158 -1
- package/dist/server.test.js.map +1 -1
- package/dist/utils/multi-git.d.ts +21 -0
- package/dist/utils/multi-git.d.ts.map +1 -1
- package/dist/utils/multi-git.js +40 -4
- package/dist/utils/multi-git.js.map +1 -1
- package/dist/utils/prompts.d.ts.map +1 -1
- package/dist/utils/prompts.js +8 -2
- package/dist/utils/prompts.js.map +1 -1
- package/package.json +13 -1
- package/.agents/skills/ccc/SKILL.md +0 -71
- package/.agents/skills/ccc/references/management.md +0 -110
- package/.agents/skills/ccc/references/settings.md +0 -126
- package/.github/dependabot.yml +0 -52
- package/.github/workflows/ci.yml +0 -44
- package/.github/workflows/dependabot-merge.yml +0 -53
- package/.github/workflows/release-desktop.yml +0 -67
- package/.github/workflows/release-npm.yml +0 -65
- package/.github/workflows/release-vscode.yml +0 -61
- package/.vscode/launch.json +0 -17
- package/.vscode/tasks.json +0 -17
- package/GETTING_STARTED.md +0 -114
- package/desktop/build-installer.js +0 -228
- package/desktop/build.js +0 -118
- package/desktop/installer/nexusflow.iss +0 -35
- package/desktop/neutralino.config.json +0 -39
- package/desktop/package-lock.json +0 -1060
- package/desktop/package.json +0 -14
- package/dist/gui/assets/index-D8iJPvnk.js +0 -26
- package/extension/media/icon.svg +0 -1
- package/extension/package-lock.json +0 -6044
- package/extension/package.json +0 -117
- package/extension/src/extension.ts +0 -653
- package/extension/tsconfig.json +0 -21
- package/gui/README.md +0 -73
- package/gui/e2e/dashboard.spec.ts +0 -61
- package/gui/e2e/wizard.spec.ts +0 -346
- package/gui/eslint.config.js +0 -26
- package/gui/index.html +0 -17
- package/gui/package-lock.json +0 -3137
- package/gui/package.json +0 -37
- package/gui/playwright.config.ts +0 -41
- package/gui/public/app_logo.png +0 -0
- package/gui/public/favicon.svg +0 -1
- package/gui/public/icons.svg +0 -24
- package/gui/src/App.css +0 -1
- package/gui/src/App.tsx +0 -3573
- package/gui/src/app/AppSidebar.tsx +0 -69
- package/gui/src/assets/hero.png +0 -0
- package/gui/src/assets/react.svg +0 -1
- package/gui/src/assets/vite.svg +0 -1
- package/gui/src/components/AddRepoPicker.tsx +0 -81
- package/gui/src/components/ui/Button.tsx +0 -40
- package/gui/src/components/ui/Card.tsx +0 -6
- package/gui/src/components/ui/EmptyState.tsx +0 -22
- package/gui/src/components/ui/Input.tsx +0 -21
- package/gui/src/components/ui/Menu.tsx +0 -71
- package/gui/src/components/ui/Modal.tsx +0 -39
- package/gui/src/components/ui/PageHeader.tsx +0 -21
- package/gui/src/components/ui/RepoStatusStrip.tsx +0 -33
- package/gui/src/components/ui/Skeleton.tsx +0 -5
- package/gui/src/components/ui/StatusPill.tsx +0 -36
- package/gui/src/components/ui/Tabs.tsx +0 -40
- package/gui/src/components/ui/cn.ts +0 -4
- package/gui/src/components/ui/index.ts +0 -16
- package/gui/src/features/changes/ChangesViewer.tsx +0 -388
- package/gui/src/features/knowledge/KnowledgeBase.tsx +0 -84
- package/gui/src/features/onboarding/OnboardingWizard.tsx +0 -230
- package/gui/src/features/plan/ImplementationPlan.tsx +0 -32
- package/gui/src/features/services/ServiceConsole.tsx +0 -215
- package/gui/src/features/sessions/SessionHistory.tsx +0 -195
- package/gui/src/index.css +0 -167
- package/gui/src/lib/status.ts +0 -24
- package/gui/src/main.tsx +0 -94
- package/gui/src/pages/DashboardPage.tsx +0 -129
- package/gui/src/pages/WorkspacesPage.tsx +0 -352
- package/gui/src/types.ts +0 -81
- package/gui/tsconfig.app.json +0 -25
- package/gui/tsconfig.json +0 -7
- package/gui/tsconfig.node.json +0 -24
- package/gui/vite.config.ts +0 -12
- package/resources/workflows/plan-implement-review.md +0 -8
- package/resources/workflows/research-verify.md +0 -6
- package/resources/workflows/solo-developer.md +0 -3
- package/scripts/simulate-workspaces.ts +0 -55
- package/skills-lock.json +0 -11
- package/src/analyzers/detect-apis.ts +0 -290
- package/src/analyzers/detect-deps.ts +0 -315
- package/src/analyzers/detect-existing.ts +0 -74
- package/src/analyzers/detect-ports.ts +0 -110
- package/src/analyzers/index.ts +0 -97
- package/src/analyzers/messaging-analyzer.ts +0 -254
- package/src/analyzers/readme-summarizer.ts +0 -102
- package/src/analyzers/run-analyzer.ts +0 -269
- package/src/analyzers/tech-stack.ts +0 -283
- package/src/commands/adapter.ts +0 -348
- package/src/commands/add-repo.ts +0 -156
- package/src/commands/commands.test.ts +0 -156
- package/src/commands/commit.ts +0 -131
- package/src/commands/config.ts +0 -52
- package/src/commands/create.ts +0 -204
- package/src/commands/desktop.ts +0 -128
- package/src/commands/diff.ts +0 -125
- package/src/commands/doctor.ts +0 -370
- package/src/commands/handoff.ts +0 -266
- package/src/commands/init.ts +0 -134
- package/src/commands/list.ts +0 -46
- package/src/commands/logs.ts +0 -63
- package/src/commands/mcp.ts +0 -94
- package/src/commands/open.ts +0 -129
- package/src/commands/refresh.ts +0 -137
- package/src/commands/remove.ts +0 -98
- package/src/commands/start.ts +0 -117
- package/src/commands/status.ts +0 -54
- package/src/commands/stop.ts +0 -55
- package/src/commands/sync.ts +0 -125
- package/src/commands/tui.ts +0 -484
- package/src/commands/ui.ts +0 -111
- package/src/core/adapters/local-storage.ts +0 -66
- package/src/core/adapters/obsidian-storage.ts +0 -151
- package/src/core/adapters/registry.ts +0 -44
- package/src/core/adapters/storage.test.ts +0 -174
- package/src/core/adapters/vault-storage.ts +0 -86
- package/src/core/config.test.ts +0 -96
- package/src/core/config.ts +0 -140
- package/src/core/graph.ts +0 -344
- package/src/core/plugins/index.ts +0 -17
- package/src/core/plugins/loader.ts +0 -36
- package/src/core/ports/storage.ts +0 -71
- package/src/core/scanner.test.ts +0 -61
- package/src/core/scanner.ts +0 -91
- package/src/core/storage.ts +0 -37
- package/src/core/sync.ts +0 -121
- package/src/core/workspace-state.test.ts +0 -108
- package/src/core/workspace-state.ts +0 -130
- package/src/core/workspace.ts +0 -487
- package/src/core/worktree.ts +0 -120
- package/src/generators/antigravity.ts +0 -32
- package/src/generators/base.ts +0 -243
- package/src/generators/claude.ts +0 -34
- package/src/generators/codex.ts +0 -48
- package/src/generators/copilot.ts +0 -56
- package/src/generators/cursor.ts +0 -44
- package/src/generators/diff-context.ts +0 -75
- package/src/generators/index.ts +0 -322
- package/src/generators/map-generator.test.ts +0 -159
- package/src/generators/map-generator.ts +0 -539
- package/src/generators/plan-generator.ts +0 -483
- package/src/generators/skills-generator.ts +0 -259
- package/src/index.ts +0 -539
- package/src/mcp/server.ts +0 -338
- package/src/orchestration/detect.ts +0 -313
- package/src/orchestration/index.ts +0 -7
- package/src/orchestration/runner.ts +0 -283
- package/src/server.test.ts +0 -618
- package/src/server.ts +0 -1402
- package/src/types.ts +0 -451
- package/src/utils/detect-ai.test.ts +0 -44
- package/src/utils/detect-ai.ts +0 -84
- package/src/utils/detect-editors.test.ts +0 -48
- package/src/utils/detect-editors.ts +0 -57
- package/src/utils/git.test.ts +0 -74
- package/src/utils/git.ts +0 -117
- package/src/utils/local-ai.test.ts +0 -130
- package/src/utils/local-ai.ts +0 -116
- package/src/utils/multi-git.test.ts +0 -158
- package/src/utils/multi-git.ts +0 -405
- package/src/utils/prompts.ts +0 -209
- package/src/utils/session-finder.ts +0 -483
- package/src/utils/system-scanner.test.ts +0 -89
- package/src/utils/system-scanner.ts +0 -96
- package/src/utils/update-check.ts +0 -286
- package/src/utils/workflow-advisor.ts +0 -118
- package/src/utils/workflows.test.ts +0 -114
- package/src/utils/workflows.ts +0 -178
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -14
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module core/scheduler
|
|
3
|
+
* Recurring workspace jobs — periodic `sync` and `refresh` runs per workspace.
|
|
4
|
+
*
|
|
5
|
+
* Job definitions are persisted at `~/.nexusflow/schedules.json` and executed
|
|
6
|
+
* by any long-running NexusFlow process (the dashboard server started with
|
|
7
|
+
* `nexusflow ui` runs the scheduler automatically). Jobs use the headless
|
|
8
|
+
* cores (`syncWorkspace`, `refreshWorkspace`), which are cache-aware: a
|
|
9
|
+
* scheduled run only re-analyzes repos whose content changed, so unattended
|
|
10
|
+
* schedules stay token-efficient and never churn unchanged context files.
|
|
11
|
+
*/
|
|
12
|
+
import * as fs from 'node:fs/promises';
|
|
13
|
+
import * as path from 'node:path';
|
|
14
|
+
import { getConfigDir, ensureConfigDir } from './config.js';
|
|
15
|
+
import { syncWorkspace } from './sync.js';
|
|
16
|
+
import { refreshWorkspace } from './refresh.js';
|
|
17
|
+
/** Name of the schedules file inside ~/.nexusflow. */
|
|
18
|
+
const SCHEDULES_FILE = 'schedules.json';
|
|
19
|
+
const SCHEDULES_LOCK_FILE = `${SCHEDULES_FILE}.lock`;
|
|
20
|
+
const RUN_LOCK_DIR = 'schedule-runs';
|
|
21
|
+
const STORE_LOCK_TIMEOUT_MS = 10_000;
|
|
22
|
+
const STORE_LOCK_STALE_MS = 2 * 60_000;
|
|
23
|
+
const RUN_LOCK_STALE_MS = 24 * 60 * 60_000;
|
|
24
|
+
const LOCK_RETRY_MS = 50;
|
|
25
|
+
let scheduleStoreMutationQueue = Promise.resolve();
|
|
26
|
+
/**
|
|
27
|
+
* Returns the path to the schedules file (~/.nexusflow/schedules.json).
|
|
28
|
+
*/
|
|
29
|
+
export function getSchedulesPath() {
|
|
30
|
+
return path.join(getConfigDir(), SCHEDULES_FILE);
|
|
31
|
+
}
|
|
32
|
+
function getSchedulesLockPath() {
|
|
33
|
+
return path.join(getConfigDir(), SCHEDULES_LOCK_FILE);
|
|
34
|
+
}
|
|
35
|
+
function getRunLockPath(jobId) {
|
|
36
|
+
const safeId = jobId.replace(/[^a-zA-Z0-9._-]/g, '_');
|
|
37
|
+
return path.join(getConfigDir(), RUN_LOCK_DIR, `${safeId}.lock`);
|
|
38
|
+
}
|
|
39
|
+
function getErrorCode(error) {
|
|
40
|
+
return typeof error === 'object' && error !== null && 'code' in error
|
|
41
|
+
? String(error.code)
|
|
42
|
+
: undefined;
|
|
43
|
+
}
|
|
44
|
+
function delay(ms) {
|
|
45
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
46
|
+
}
|
|
47
|
+
async function clearStaleLock(lockPath, staleMs) {
|
|
48
|
+
try {
|
|
49
|
+
const stat = await fs.stat(lockPath);
|
|
50
|
+
if (Date.now() - stat.mtimeMs <= staleMs)
|
|
51
|
+
return false;
|
|
52
|
+
await fs.unlink(lockPath);
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
catch (error) {
|
|
56
|
+
return getErrorCode(error) === 'ENOENT';
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
async function acquireLock(lockPath, options) {
|
|
60
|
+
await fs.mkdir(path.dirname(lockPath), { recursive: true });
|
|
61
|
+
const startedAt = Date.now();
|
|
62
|
+
for (;;) {
|
|
63
|
+
try {
|
|
64
|
+
const handle = await fs.open(lockPath, 'wx');
|
|
65
|
+
try {
|
|
66
|
+
await handle.writeFile(JSON.stringify({
|
|
67
|
+
pid: process.pid,
|
|
68
|
+
createdAt: new Date().toISOString(),
|
|
69
|
+
}) + '\n', 'utf-8');
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
await handle.close().catch(() => { });
|
|
73
|
+
await fs.unlink(lockPath).catch(() => { });
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
let released = false;
|
|
77
|
+
return async () => {
|
|
78
|
+
if (released)
|
|
79
|
+
return;
|
|
80
|
+
released = true;
|
|
81
|
+
await handle.close().catch(() => { });
|
|
82
|
+
await fs.unlink(lockPath).catch(() => { });
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
if (getErrorCode(error) !== 'EEXIST')
|
|
87
|
+
throw error;
|
|
88
|
+
if (await clearStaleLock(lockPath, options.staleMs))
|
|
89
|
+
continue;
|
|
90
|
+
if (Date.now() - startedAt >= options.timeoutMs) {
|
|
91
|
+
throw new Error(options.timeoutMessage);
|
|
92
|
+
}
|
|
93
|
+
await delay(LOCK_RETRY_MS);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function enqueueScheduleStoreMutation(operation) {
|
|
98
|
+
const run = scheduleStoreMutationQueue.then(operation, operation);
|
|
99
|
+
scheduleStoreMutationQueue = run.then(() => undefined, () => undefined);
|
|
100
|
+
return run;
|
|
101
|
+
}
|
|
102
|
+
async function mutateSchedules(mutator) {
|
|
103
|
+
return enqueueScheduleStoreMutation(async () => {
|
|
104
|
+
const release = await acquireLock(getSchedulesLockPath(), {
|
|
105
|
+
staleMs: STORE_LOCK_STALE_MS,
|
|
106
|
+
timeoutMs: STORE_LOCK_TIMEOUT_MS,
|
|
107
|
+
timeoutMessage: 'Timed out waiting for schedules.json to become writable.',
|
|
108
|
+
});
|
|
109
|
+
try {
|
|
110
|
+
const store = await loadSchedules();
|
|
111
|
+
const result = await mutator(store);
|
|
112
|
+
if (result.save) {
|
|
113
|
+
await saveSchedules(store);
|
|
114
|
+
}
|
|
115
|
+
return result.value;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
await release();
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Loads all schedules, returning an empty store when the file does not exist
|
|
124
|
+
* or cannot be parsed.
|
|
125
|
+
*/
|
|
126
|
+
export async function loadSchedules() {
|
|
127
|
+
try {
|
|
128
|
+
const raw = await fs.readFile(getSchedulesPath(), 'utf-8');
|
|
129
|
+
const store = JSON.parse(raw);
|
|
130
|
+
if (!Array.isArray(store.jobs)) {
|
|
131
|
+
store.jobs = [];
|
|
132
|
+
}
|
|
133
|
+
return store;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return { version: 1, jobs: [] };
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Persists the schedule store to ~/.nexusflow/schedules.json.
|
|
141
|
+
*/
|
|
142
|
+
export async function saveSchedules(store) {
|
|
143
|
+
await ensureConfigDir();
|
|
144
|
+
const data = JSON.stringify(store, null, 2) + '\n';
|
|
145
|
+
await fs.writeFile(getSchedulesPath(), data, 'utf-8');
|
|
146
|
+
}
|
|
147
|
+
/**
|
|
148
|
+
* Parses a human interval like '30m', '2h', '1d', or a bare minute count
|
|
149
|
+
* ('45') into minutes. Returns null for unparseable or non-positive input.
|
|
150
|
+
*/
|
|
151
|
+
export function parseInterval(text) {
|
|
152
|
+
const match = String(text).trim().match(/^(\d+)\s*([mhd]?)$/i);
|
|
153
|
+
if (!match)
|
|
154
|
+
return null;
|
|
155
|
+
const value = parseInt(match[1], 10);
|
|
156
|
+
if (value <= 0)
|
|
157
|
+
return null;
|
|
158
|
+
switch ((match[2] || 'm').toLowerCase()) {
|
|
159
|
+
case 'h':
|
|
160
|
+
return value * 60;
|
|
161
|
+
case 'd':
|
|
162
|
+
return value * 60 * 24;
|
|
163
|
+
default:
|
|
164
|
+
return value;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Formats an interval in minutes back to a compact human string.
|
|
169
|
+
*/
|
|
170
|
+
export function formatInterval(minutes) {
|
|
171
|
+
if (minutes % (60 * 24) === 0)
|
|
172
|
+
return `${minutes / (60 * 24)}d`;
|
|
173
|
+
if (minutes % 60 === 0)
|
|
174
|
+
return `${minutes / 60}h`;
|
|
175
|
+
return `${minutes}m`;
|
|
176
|
+
}
|
|
177
|
+
/**
|
|
178
|
+
* Whether a job is due to run: it is enabled and either never ran or its
|
|
179
|
+
* interval has elapsed since the last run. A job whose interval elapsed while
|
|
180
|
+
* no scheduler was running simply runs on the next tick.
|
|
181
|
+
*/
|
|
182
|
+
export function isDue(job, now = new Date()) {
|
|
183
|
+
if (!job.enabled)
|
|
184
|
+
return false;
|
|
185
|
+
if (!job.lastRunAt)
|
|
186
|
+
return true;
|
|
187
|
+
const last = Date.parse(job.lastRunAt);
|
|
188
|
+
if (Number.isNaN(last))
|
|
189
|
+
return true;
|
|
190
|
+
return now.getTime() - last >= job.intervalMinutes * 60_000;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Computes when a job will next be due, for display purposes.
|
|
194
|
+
*/
|
|
195
|
+
export function nextDueAt(job) {
|
|
196
|
+
if (!job.enabled)
|
|
197
|
+
return null;
|
|
198
|
+
if (!job.lastRunAt)
|
|
199
|
+
return new Date();
|
|
200
|
+
const last = Date.parse(job.lastRunAt);
|
|
201
|
+
if (Number.isNaN(last))
|
|
202
|
+
return new Date();
|
|
203
|
+
return new Date(last + job.intervalMinutes * 60_000);
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Adds a new schedule and persists it.
|
|
207
|
+
*
|
|
208
|
+
* @param input - Workspace, task, and interval for the new job.
|
|
209
|
+
* @returns The created job.
|
|
210
|
+
*/
|
|
211
|
+
export async function addSchedule(input) {
|
|
212
|
+
return mutateSchedules((store) => {
|
|
213
|
+
const workspaceName = path.basename(input.workspacePath);
|
|
214
|
+
const job = {
|
|
215
|
+
id: `${input.task}-${workspaceName}-${Date.now().toString(36)}`,
|
|
216
|
+
workspacePath: input.workspacePath,
|
|
217
|
+
task: input.task,
|
|
218
|
+
intervalMinutes: input.intervalMinutes,
|
|
219
|
+
enabled: true,
|
|
220
|
+
createdAt: new Date().toISOString(),
|
|
221
|
+
};
|
|
222
|
+
store.jobs.push(job);
|
|
223
|
+
return { value: job, save: true };
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
/**
|
|
227
|
+
* Removes a schedule by id. Returns false when no job matched.
|
|
228
|
+
*/
|
|
229
|
+
export async function removeSchedule(id) {
|
|
230
|
+
return mutateSchedules((store) => {
|
|
231
|
+
const before = store.jobs.length;
|
|
232
|
+
store.jobs = store.jobs.filter((j) => j.id !== id);
|
|
233
|
+
const removed = store.jobs.length !== before;
|
|
234
|
+
return { value: removed, save: removed };
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Enables or disables a schedule by id. Returns the updated job, or null
|
|
239
|
+
* when no job matched.
|
|
240
|
+
*/
|
|
241
|
+
export async function setScheduleEnabled(id, enabled) {
|
|
242
|
+
return mutateSchedules((store) => {
|
|
243
|
+
const job = store.jobs.find((j) => j.id === id);
|
|
244
|
+
if (!job)
|
|
245
|
+
return { value: null, save: false };
|
|
246
|
+
job.enabled = enabled;
|
|
247
|
+
return { value: job, save: true };
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
/**
|
|
251
|
+
* Runs a single job immediately and records the outcome in the store.
|
|
252
|
+
* Never throws — failures are captured in the returned result.
|
|
253
|
+
*
|
|
254
|
+
* @param job - The job to run.
|
|
255
|
+
* @returns The classified outcome with a summary message.
|
|
256
|
+
*/
|
|
257
|
+
export async function runJob(job) {
|
|
258
|
+
let releaseRunLock = null;
|
|
259
|
+
try {
|
|
260
|
+
releaseRunLock = await acquireLock(getRunLockPath(job.id), {
|
|
261
|
+
staleMs: RUN_LOCK_STALE_MS,
|
|
262
|
+
timeoutMs: 0,
|
|
263
|
+
timeoutMessage: `Schedule ${job.id} is already running.`,
|
|
264
|
+
});
|
|
265
|
+
}
|
|
266
|
+
catch (error) {
|
|
267
|
+
return {
|
|
268
|
+
status: 'error',
|
|
269
|
+
message: error instanceof Error ? error.message : String(error),
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
let result;
|
|
273
|
+
try {
|
|
274
|
+
if (job.task === 'sync') {
|
|
275
|
+
const report = await syncWorkspace(job.workspacePath);
|
|
276
|
+
const parts = [`${report.syncedCount} synced`];
|
|
277
|
+
if (report.conflictCount > 0)
|
|
278
|
+
parts.push(`${report.conflictCount} conflict(s)`);
|
|
279
|
+
if (report.errorCount > 0)
|
|
280
|
+
parts.push(`${report.errorCount} error(s)`);
|
|
281
|
+
parts.push(report.contextRefreshed ? 'context refreshed' : 'context unchanged');
|
|
282
|
+
result = {
|
|
283
|
+
status: report.conflictCount > 0 || report.errorCount > 0 ? 'error' : 'success',
|
|
284
|
+
message: parts.join(', '),
|
|
285
|
+
};
|
|
286
|
+
}
|
|
287
|
+
else {
|
|
288
|
+
const report = await refreshWorkspace(job.workspacePath);
|
|
289
|
+
result = {
|
|
290
|
+
status: 'success',
|
|
291
|
+
message: `${report.analyzedRepos.length} repo(s) re-analyzed, ${report.reusedRepos.length} reused from cache`,
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
catch (error) {
|
|
296
|
+
result = {
|
|
297
|
+
status: 'error',
|
|
298
|
+
message: error instanceof Error ? error.message : String(error),
|
|
299
|
+
};
|
|
300
|
+
}
|
|
301
|
+
// Record the run. Re-load the store so concurrent CLI edits are not lost.
|
|
302
|
+
try {
|
|
303
|
+
await mutateSchedules((store) => {
|
|
304
|
+
const stored = store.jobs.find((j) => j.id === job.id);
|
|
305
|
+
if (!stored)
|
|
306
|
+
return { value: undefined, save: false };
|
|
307
|
+
stored.lastRunAt = new Date().toISOString();
|
|
308
|
+
stored.lastStatus = result.status;
|
|
309
|
+
stored.lastMessage = result.message;
|
|
310
|
+
return { value: undefined, save: true };
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
catch {
|
|
314
|
+
// Best-effort bookkeeping; the job itself already ran.
|
|
315
|
+
}
|
|
316
|
+
finally {
|
|
317
|
+
await releaseRunLock();
|
|
318
|
+
}
|
|
319
|
+
return result;
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Starts the scheduler loop inside the current process. Every tick it
|
|
323
|
+
* re-loads the schedules file (so jobs added/removed via CLI are picked up
|
|
324
|
+
* without a restart) and runs all due jobs sequentially. Ticks never overlap.
|
|
325
|
+
*
|
|
326
|
+
* @param options - Tick interval (default 60s) and an optional log sink.
|
|
327
|
+
* @returns A stop function that clears the interval.
|
|
328
|
+
*/
|
|
329
|
+
export function startScheduler(options = {}) {
|
|
330
|
+
const tickMs = options.tickMs ?? 60_000;
|
|
331
|
+
const log = options.log ?? (() => { });
|
|
332
|
+
let running = false;
|
|
333
|
+
const tick = async () => {
|
|
334
|
+
if (running)
|
|
335
|
+
return;
|
|
336
|
+
running = true;
|
|
337
|
+
try {
|
|
338
|
+
const store = await loadSchedules();
|
|
339
|
+
for (const job of store.jobs) {
|
|
340
|
+
if (!isDue(job))
|
|
341
|
+
continue;
|
|
342
|
+
log(`Running scheduled ${job.task} for ${path.basename(job.workspacePath)} (${job.id})...`);
|
|
343
|
+
const result = await runJob(job);
|
|
344
|
+
log(`Scheduled ${job.task} ${result.status === 'success' ? 'completed' : 'failed'}: ${result.message}`);
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch {
|
|
348
|
+
// A broken schedules file must not kill the host process's timer.
|
|
349
|
+
}
|
|
350
|
+
finally {
|
|
351
|
+
running = false;
|
|
352
|
+
}
|
|
353
|
+
};
|
|
354
|
+
const timer = setInterval(tick, tickMs);
|
|
355
|
+
// Don't let the scheduler keep a short-lived CLI process alive.
|
|
356
|
+
timer.unref?.();
|
|
357
|
+
// First tick runs immediately so overdue jobs don't wait a full interval.
|
|
358
|
+
void tick();
|
|
359
|
+
return () => clearInterval(timer);
|
|
360
|
+
}
|
|
361
|
+
//# sourceMappingURL=scheduler.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.js","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.test.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import { addSchedule, formatInterval, isDue, loadSchedules, nextDueAt, parseInterval, removeSchedule, runJob, setScheduleEnabled, } from './scheduler.js';
|
|
4
|
+
import { refreshWorkspace } from './refresh.js';
|
|
5
|
+
import { syncWorkspace } from './sync.js';
|
|
6
|
+
vi.mock('node:fs/promises');
|
|
7
|
+
vi.mock('./refresh.js');
|
|
8
|
+
vi.mock('./sync.js');
|
|
9
|
+
/** Parses the JSON written by the most recent writeFile call. */
|
|
10
|
+
function lastWritten() {
|
|
11
|
+
const calls = vi.mocked(fs.writeFile).mock.calls;
|
|
12
|
+
const data = calls[calls.length - 1][1];
|
|
13
|
+
return JSON.parse(data);
|
|
14
|
+
}
|
|
15
|
+
function mockScheduleStore(initial) {
|
|
16
|
+
let current = JSON.parse(JSON.stringify(initial));
|
|
17
|
+
vi.mocked(fs.readFile).mockImplementation(async () => JSON.stringify(current));
|
|
18
|
+
vi.mocked(fs.writeFile).mockImplementation(async (_path, data) => {
|
|
19
|
+
current = JSON.parse(String(data));
|
|
20
|
+
});
|
|
21
|
+
return { current: () => current };
|
|
22
|
+
}
|
|
23
|
+
function makeJob(overrides = {}) {
|
|
24
|
+
return {
|
|
25
|
+
id: 'sync-ws-abc',
|
|
26
|
+
workspacePath: '/ws',
|
|
27
|
+
task: 'sync',
|
|
28
|
+
intervalMinutes: 60,
|
|
29
|
+
enabled: true,
|
|
30
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
31
|
+
...overrides,
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
describe('scheduler', () => {
|
|
35
|
+
beforeEach(() => {
|
|
36
|
+
vi.clearAllMocks();
|
|
37
|
+
vi.mocked(fs.writeFile).mockResolvedValue(undefined);
|
|
38
|
+
vi.mocked(fs.mkdir).mockResolvedValue(undefined);
|
|
39
|
+
vi.mocked(fs.unlink).mockResolvedValue(undefined);
|
|
40
|
+
vi.mocked(fs.stat).mockResolvedValue({ mtimeMs: Date.now() });
|
|
41
|
+
vi.mocked(fs.open).mockResolvedValue({
|
|
42
|
+
writeFile: vi.fn().mockResolvedValue(undefined),
|
|
43
|
+
close: vi.fn().mockResolvedValue(undefined),
|
|
44
|
+
});
|
|
45
|
+
vi.mocked(syncWorkspace).mockResolvedValue({
|
|
46
|
+
syncedCount: 1,
|
|
47
|
+
conflictCount: 0,
|
|
48
|
+
errorCount: 0,
|
|
49
|
+
contextRefreshed: false,
|
|
50
|
+
});
|
|
51
|
+
vi.mocked(refreshWorkspace).mockResolvedValue({
|
|
52
|
+
analyzedRepos: ['/repo'],
|
|
53
|
+
reusedRepos: [],
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
describe('parseInterval', () => {
|
|
57
|
+
it('parses minutes, hours, days, and bare numbers', () => {
|
|
58
|
+
expect(parseInterval('30m')).toBe(30);
|
|
59
|
+
expect(parseInterval('2h')).toBe(120);
|
|
60
|
+
expect(parseInterval('1d')).toBe(1440);
|
|
61
|
+
expect(parseInterval('45')).toBe(45);
|
|
62
|
+
});
|
|
63
|
+
it('rejects invalid or non-positive input', () => {
|
|
64
|
+
expect(parseInterval('')).toBeNull();
|
|
65
|
+
expect(parseInterval('abc')).toBeNull();
|
|
66
|
+
expect(parseInterval('0m')).toBeNull();
|
|
67
|
+
expect(parseInterval('-5m')).toBeNull();
|
|
68
|
+
expect(parseInterval('2w')).toBeNull();
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
describe('formatInterval', () => {
|
|
72
|
+
it('formats back to compact strings', () => {
|
|
73
|
+
expect(formatInterval(30)).toBe('30m');
|
|
74
|
+
expect(formatInterval(120)).toBe('2h');
|
|
75
|
+
expect(formatInterval(1440)).toBe('1d');
|
|
76
|
+
expect(formatInterval(90)).toBe('90m');
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
describe('isDue', () => {
|
|
80
|
+
it('is due when it has never run', () => {
|
|
81
|
+
expect(isDue(makeJob())).toBe(true);
|
|
82
|
+
});
|
|
83
|
+
it('is not due when disabled, even if never run', () => {
|
|
84
|
+
expect(isDue(makeJob({ enabled: false }))).toBe(false);
|
|
85
|
+
});
|
|
86
|
+
it('respects the interval since the last run', () => {
|
|
87
|
+
const now = new Date('2026-01-02T12:00:00.000Z');
|
|
88
|
+
const recent = makeJob({ lastRunAt: '2026-01-02T11:30:00.000Z' }); // 30m ago, every 60m
|
|
89
|
+
const stale = makeJob({ lastRunAt: '2026-01-02T10:00:00.000Z' }); // 2h ago, every 60m
|
|
90
|
+
expect(isDue(recent, now)).toBe(false);
|
|
91
|
+
expect(isDue(stale, now)).toBe(true);
|
|
92
|
+
});
|
|
93
|
+
it('treats an unparseable lastRunAt as due', () => {
|
|
94
|
+
expect(isDue(makeJob({ lastRunAt: 'garbage' }))).toBe(true);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
describe('nextDueAt', () => {
|
|
98
|
+
it('is null for disabled jobs', () => {
|
|
99
|
+
expect(nextDueAt(makeJob({ enabled: false }))).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
it('is lastRunAt + interval for jobs that ran', () => {
|
|
102
|
+
const job = makeJob({ lastRunAt: '2026-01-02T10:00:00.000Z' });
|
|
103
|
+
expect(nextDueAt(job).toISOString()).toBe('2026-01-02T11:00:00.000Z');
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
describe('store operations', () => {
|
|
107
|
+
it('loadSchedules returns an empty store when the file is absent', async () => {
|
|
108
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
|
|
109
|
+
const store = await loadSchedules();
|
|
110
|
+
expect(store.jobs).toEqual([]);
|
|
111
|
+
});
|
|
112
|
+
it('addSchedule persists a new enabled job', async () => {
|
|
113
|
+
vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
|
|
114
|
+
const job = await addSchedule({ workspacePath: '/ws/feature-x', task: 'refresh', intervalMinutes: 120 });
|
|
115
|
+
expect(job.enabled).toBe(true);
|
|
116
|
+
expect(job.task).toBe('refresh');
|
|
117
|
+
expect(job.id).toContain('refresh-feature-x');
|
|
118
|
+
const written = lastWritten();
|
|
119
|
+
expect(written.jobs).toHaveLength(1);
|
|
120
|
+
expect(written.jobs[0].intervalMinutes).toBe(120);
|
|
121
|
+
});
|
|
122
|
+
it('removeSchedule deletes by id and reports misses', async () => {
|
|
123
|
+
mockScheduleStore({ version: 1, jobs: [makeJob()] });
|
|
124
|
+
expect(await removeSchedule('sync-ws-abc')).toBe(true);
|
|
125
|
+
expect(lastWritten().jobs).toHaveLength(0);
|
|
126
|
+
expect(await removeSchedule('nope')).toBe(false);
|
|
127
|
+
});
|
|
128
|
+
it('setScheduleEnabled toggles and persists', async () => {
|
|
129
|
+
mockScheduleStore({ version: 1, jobs: [makeJob()] });
|
|
130
|
+
const job = await setScheduleEnabled('sync-ws-abc', false);
|
|
131
|
+
expect(job.enabled).toBe(false);
|
|
132
|
+
expect(lastWritten().jobs[0].enabled).toBe(false);
|
|
133
|
+
expect(await setScheduleEnabled('nope', true)).toBeNull();
|
|
134
|
+
});
|
|
135
|
+
it('serializes concurrent schedule mutations so updates are not lost', async () => {
|
|
136
|
+
const store = mockScheduleStore({ version: 1, jobs: [] });
|
|
137
|
+
const [first, second] = await Promise.all([
|
|
138
|
+
addSchedule({ workspacePath: '/ws/feature-a', task: 'sync', intervalMinutes: 30 }),
|
|
139
|
+
addSchedule({ workspacePath: '/ws/feature-b', task: 'refresh', intervalMinutes: 60 }),
|
|
140
|
+
]);
|
|
141
|
+
expect(store.current().jobs.map((job) => job.id)).toEqual([first.id, second.id]);
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
describe('runJob', () => {
|
|
145
|
+
it('does not run a job that already has an active run lock', async () => {
|
|
146
|
+
vi.mocked(fs.open).mockRejectedValueOnce(Object.assign(new Error('exists'), { code: 'EEXIST' }));
|
|
147
|
+
const result = await runJob(makeJob());
|
|
148
|
+
expect(result.status).toBe('error');
|
|
149
|
+
expect(result.message).toContain('already running');
|
|
150
|
+
expect(syncWorkspace).not.toHaveBeenCalled();
|
|
151
|
+
expect(refreshWorkspace).not.toHaveBeenCalled();
|
|
152
|
+
});
|
|
153
|
+
it('records the run result through the serialized schedule mutation path', async () => {
|
|
154
|
+
const store = mockScheduleStore({ version: 1, jobs: [makeJob()] });
|
|
155
|
+
const result = await runJob(makeJob());
|
|
156
|
+
expect(result.status).toBe('success');
|
|
157
|
+
expect(syncWorkspace).toHaveBeenCalledWith('/ws');
|
|
158
|
+
expect(store.current().jobs[0].lastStatus).toBe('success');
|
|
159
|
+
expect(store.current().jobs[0].lastMessage).toBe('1 synced, context unchanged');
|
|
160
|
+
});
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
//# sourceMappingURL=scheduler.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"scheduler.test.js","sourceRoot":"","sources":["../../src/core/scheduler.test.ts"],"names":[],"mappings":"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"}
|
package/dist/core/sync.d.ts
CHANGED
|
@@ -10,12 +10,16 @@ import type { SyncStatus } from '../types.js';
|
|
|
10
10
|
export interface RepoSyncReport {
|
|
11
11
|
/** Directory name of the repo. */
|
|
12
12
|
name: string;
|
|
13
|
+
/** Base branch the repo rebases onto (e.g. 'main'). */
|
|
14
|
+
baseBranch: string;
|
|
13
15
|
/** Classified outcome. */
|
|
14
16
|
status: SyncStatus;
|
|
15
17
|
/** Human-readable message. */
|
|
16
18
|
message: string;
|
|
17
19
|
/** Conflict stderr — populated only for `status === 'conflict'`. */
|
|
18
20
|
conflict?: string;
|
|
21
|
+
/** Paths that had merge conflicts — populated only for `status === 'conflict'`. */
|
|
22
|
+
conflictFiles?: string[];
|
|
19
23
|
}
|
|
20
24
|
/** Aggregated result of syncing an entire workspace. */
|
|
21
25
|
export interface SyncReport {
|
|
@@ -31,6 +35,8 @@ export interface SyncReport {
|
|
|
31
35
|
conflictCount: number;
|
|
32
36
|
/** Count of repos that failed for infrastructure reasons (fetch/auth, etc.). */
|
|
33
37
|
errorCount: number;
|
|
38
|
+
/** Whether context files/maps were regenerated (only when content changed). */
|
|
39
|
+
contextRefreshed: boolean;
|
|
34
40
|
}
|
|
35
41
|
/**
|
|
36
42
|
* Syncs all repos in a workspace. Throws if the workspace configuration cannot
|
package/dist/core/sync.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/core/sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,aAAa,CAAC;AAEhE,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,0BAA0B;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"sync.d.ts","sourceRoot":"","sources":["../../src/core/sync.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AASH,OAAO,KAAK,EAAE,UAAU,EAAoB,MAAM,aAAa,CAAC;AAEhE,sCAAsC;AACtC,MAAM,WAAW,cAAc;IAC7B,kCAAkC;IAClC,IAAI,EAAE,MAAM,CAAC;IACb,uDAAuD;IACvD,UAAU,EAAE,MAAM,CAAC;IACnB,0BAA0B;IAC1B,MAAM,EAAE,UAAU,CAAC;IACnB,8BAA8B;IAC9B,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,mFAAmF;IACnF,aAAa,CAAC,EAAE,MAAM,EAAE,CAAC;CAC1B;AAED,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,UAAU,EAAE,MAAM,CAAC;IACnB,6CAA6C;IAC7C,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,mFAAmF;IACnF,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,aAAa,EAAE,MAAM,CAAC;IACtB,gFAAgF;IAChF,UAAU,EAAE,MAAM,CAAC;IACnB,+EAA+E;IAC/E,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAOD;;;;;;;GAOG;AACH,wBAAsB,aAAa,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,CAAC,CAsE9E"}
|
package/dist/core/sync.js
CHANGED
|
@@ -5,11 +5,10 @@
|
|
|
5
5
|
* context files. Produces no console output, so it can back the CLI, the HTTP
|
|
6
6
|
* API, and the MCP tool alike.
|
|
7
7
|
*/
|
|
8
|
-
import
|
|
9
|
-
import { loadFeatureConfig } from './workspace.js';
|
|
8
|
+
import { loadFeatureConfig, resolveRepoInfos } from './workspace.js';
|
|
10
9
|
import { recordRepoSync } from './workspace-state.js';
|
|
11
10
|
import { getWorkspaceRepos, rebaseRepo } from '../utils/multi-git.js';
|
|
12
|
-
import {
|
|
11
|
+
import { analyzeAllReposCached } from '../analyzers/index.js';
|
|
13
12
|
import { generateContextFiles } from '../generators/index.js';
|
|
14
13
|
/** A sync status counts as "synced" when the rebase itself landed. */
|
|
15
14
|
function isSynced(status) {
|
|
@@ -42,9 +41,11 @@ export async function syncWorkspace(workspacePath) {
|
|
|
42
41
|
});
|
|
43
42
|
repoReports.push({
|
|
44
43
|
name: repo.name,
|
|
44
|
+
baseBranch: defaultBranch,
|
|
45
45
|
status: result.status,
|
|
46
46
|
message: result.message,
|
|
47
47
|
conflict: result.conflict,
|
|
48
|
+
conflictFiles: result.conflictFiles,
|
|
48
49
|
});
|
|
49
50
|
if (isSynced(result.status))
|
|
50
51
|
syncedCount++;
|
|
@@ -53,18 +54,21 @@ export async function syncWorkspace(workspacePath) {
|
|
|
53
54
|
else
|
|
54
55
|
errorCount++;
|
|
55
56
|
}
|
|
56
|
-
// Regenerate maps/context when
|
|
57
|
+
// Regenerate maps/context only when a rebase actually changed repo content
|
|
58
|
+
// ('rebased' or 'stash-conflict'). When every repo was already up to date,
|
|
59
|
+
// skipping regeneration keeps context files byte-identical — no wasted
|
|
60
|
+
// analysis and no invalidated AI prompt caches. The cached analyzer then
|
|
61
|
+
// limits the work to the repos that changed. A regen failure must never
|
|
57
62
|
// fail the sync itself — the rebases already happened.
|
|
58
|
-
|
|
63
|
+
const contentChanged = repoReports.some((r) => r.status === 'rebased' || r.status === 'stash-conflict');
|
|
64
|
+
let contextRefreshed = false;
|
|
65
|
+
if (contentChanged) {
|
|
59
66
|
try {
|
|
60
|
-
const allRepos = feature.repos
|
|
61
|
-
|
|
62
|
-
path: r,
|
|
63
|
-
defaultBranch: 'main',
|
|
64
|
-
}));
|
|
65
|
-
const analysis = await analyzeAllRepos(allRepos);
|
|
67
|
+
const allRepos = await resolveRepoInfos(feature.repos);
|
|
68
|
+
const { analysis, analyzed } = await analyzeAllReposCached(allRepos, workspacePath);
|
|
66
69
|
const ctx = { feature, repos: allRepos, analysis };
|
|
67
|
-
await generateContextFiles(ctx, feature.assistants, workspacePath);
|
|
70
|
+
await generateContextFiles(ctx, feature.assistants, workspacePath, undefined, undefined, analyzed);
|
|
71
|
+
contextRefreshed = true;
|
|
68
72
|
}
|
|
69
73
|
catch {
|
|
70
74
|
// Best-effort regeneration; ignore failures.
|
|
@@ -77,6 +81,7 @@ export async function syncWorkspace(workspacePath) {
|
|
|
77
81
|
syncedCount,
|
|
78
82
|
conflictCount,
|
|
79
83
|
errorCount,
|
|
84
|
+
contextRefreshed,
|
|
80
85
|
};
|
|
81
86
|
}
|
|
82
87
|
//# sourceMappingURL=sync.js.map
|