@mrpatronz/nexusflow 0.2.19 → 1.3.0

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.
Files changed (236) hide show
  1. package/README.md +27 -3
  2. package/dist/analyzers/index.d.ts +23 -0
  3. package/dist/analyzers/index.d.ts.map +1 -1
  4. package/dist/analyzers/index.js +62 -0
  5. package/dist/analyzers/index.js.map +1 -1
  6. package/dist/commands/commands.test.js +20 -6
  7. package/dist/commands/commands.test.js.map +1 -1
  8. package/dist/commands/commit.d.ts +2 -0
  9. package/dist/commands/commit.d.ts.map +1 -1
  10. package/dist/commands/commit.js +27 -5
  11. package/dist/commands/commit.js.map +1 -1
  12. package/dist/commands/diff.d.ts +9 -2
  13. package/dist/commands/diff.d.ts.map +1 -1
  14. package/dist/commands/diff.js +36 -12
  15. package/dist/commands/diff.js.map +1 -1
  16. package/dist/commands/doctor.js +2 -2
  17. package/dist/commands/doctor.js.map +1 -1
  18. package/dist/commands/handoff.js +2 -2
  19. package/dist/commands/handoff.js.map +1 -1
  20. package/dist/commands/refresh.d.ts +4 -2
  21. package/dist/commands/refresh.d.ts.map +1 -1
  22. package/dist/commands/refresh.js +20 -36
  23. package/dist/commands/refresh.js.map +1 -1
  24. package/dist/commands/schedule.d.ts +35 -0
  25. package/dist/commands/schedule.d.ts.map +1 -0
  26. package/dist/commands/schedule.js +151 -0
  27. package/dist/commands/schedule.js.map +1 -0
  28. package/dist/commands/sync.d.ts.map +1 -1
  29. package/dist/commands/sync.js +28 -7
  30. package/dist/commands/sync.js.map +1 -1
  31. package/dist/core/analysis-cache.d.ts +58 -0
  32. package/dist/core/analysis-cache.d.ts.map +1 -0
  33. package/dist/core/analysis-cache.js +116 -0
  34. package/dist/core/analysis-cache.js.map +1 -0
  35. package/dist/core/analysis-cache.test.d.ts +2 -0
  36. package/dist/core/analysis-cache.test.d.ts.map +1 -0
  37. package/dist/core/analysis-cache.test.js +108 -0
  38. package/dist/core/analysis-cache.test.js.map +1 -0
  39. package/dist/core/refresh.d.ts +41 -0
  40. package/dist/core/refresh.d.ts.map +1 -0
  41. package/dist/core/refresh.js +60 -0
  42. package/dist/core/refresh.js.map +1 -0
  43. package/dist/core/scheduler.d.ts +117 -0
  44. package/dist/core/scheduler.d.ts.map +1 -0
  45. package/dist/core/scheduler.js +247 -0
  46. package/dist/core/scheduler.js.map +1 -0
  47. package/dist/core/scheduler.test.d.ts +2 -0
  48. package/dist/core/scheduler.test.d.ts.map +1 -0
  49. package/dist/core/scheduler.test.js +111 -0
  50. package/dist/core/scheduler.test.js.map +1 -0
  51. package/dist/core/sync.d.ts +6 -0
  52. package/dist/core/sync.d.ts.map +1 -1
  53. package/dist/core/sync.js +15 -5
  54. package/dist/core/sync.js.map +1 -1
  55. package/dist/generators/index.d.ts +7 -1
  56. package/dist/generators/index.d.ts.map +1 -1
  57. package/dist/generators/index.js +14 -1
  58. package/dist/generators/index.js.map +1 -1
  59. package/dist/index.js +107 -4
  60. package/dist/index.js.map +1 -1
  61. package/dist/server.d.ts.map +1 -1
  62. package/dist/server.js +81 -0
  63. package/dist/server.js.map +1 -1
  64. package/dist/server.test.js +66 -0
  65. package/dist/server.test.js.map +1 -1
  66. package/dist/utils/multi-git.d.ts +21 -0
  67. package/dist/utils/multi-git.d.ts.map +1 -1
  68. package/dist/utils/multi-git.js +40 -4
  69. package/dist/utils/multi-git.js.map +1 -1
  70. package/package.json +13 -1
  71. package/.agents/skills/ccc/SKILL.md +0 -71
  72. package/.agents/skills/ccc/references/management.md +0 -110
  73. package/.agents/skills/ccc/references/settings.md +0 -126
  74. package/.github/dependabot.yml +0 -52
  75. package/.github/workflows/ci.yml +0 -44
  76. package/.github/workflows/dependabot-merge.yml +0 -53
  77. package/.github/workflows/release-desktop.yml +0 -67
  78. package/.github/workflows/release-npm.yml +0 -65
  79. package/.github/workflows/release-vscode.yml +0 -61
  80. package/.vscode/launch.json +0 -17
  81. package/.vscode/tasks.json +0 -17
  82. package/GETTING_STARTED.md +0 -114
  83. package/desktop/build-installer.js +0 -228
  84. package/desktop/build.js +0 -118
  85. package/desktop/installer/nexusflow.iss +0 -35
  86. package/desktop/neutralino.config.json +0 -39
  87. package/desktop/package-lock.json +0 -1060
  88. package/desktop/package.json +0 -14
  89. package/extension/media/icon.svg +0 -1
  90. package/extension/package-lock.json +0 -6044
  91. package/extension/package.json +0 -117
  92. package/extension/src/extension.ts +0 -653
  93. package/extension/tsconfig.json +0 -21
  94. package/gui/README.md +0 -73
  95. package/gui/e2e/dashboard.spec.ts +0 -61
  96. package/gui/e2e/wizard.spec.ts +0 -346
  97. package/gui/eslint.config.js +0 -26
  98. package/gui/index.html +0 -17
  99. package/gui/package-lock.json +0 -3137
  100. package/gui/package.json +0 -37
  101. package/gui/playwright.config.ts +0 -41
  102. package/gui/public/app_logo.png +0 -0
  103. package/gui/public/favicon.svg +0 -1
  104. package/gui/public/icons.svg +0 -24
  105. package/gui/src/App.css +0 -1
  106. package/gui/src/App.tsx +0 -3573
  107. package/gui/src/app/AppSidebar.tsx +0 -69
  108. package/gui/src/assets/hero.png +0 -0
  109. package/gui/src/assets/react.svg +0 -1
  110. package/gui/src/assets/vite.svg +0 -1
  111. package/gui/src/components/AddRepoPicker.tsx +0 -81
  112. package/gui/src/components/ui/Button.tsx +0 -40
  113. package/gui/src/components/ui/Card.tsx +0 -6
  114. package/gui/src/components/ui/EmptyState.tsx +0 -22
  115. package/gui/src/components/ui/Input.tsx +0 -21
  116. package/gui/src/components/ui/Menu.tsx +0 -71
  117. package/gui/src/components/ui/Modal.tsx +0 -39
  118. package/gui/src/components/ui/PageHeader.tsx +0 -21
  119. package/gui/src/components/ui/RepoStatusStrip.tsx +0 -33
  120. package/gui/src/components/ui/Skeleton.tsx +0 -5
  121. package/gui/src/components/ui/StatusPill.tsx +0 -36
  122. package/gui/src/components/ui/Tabs.tsx +0 -40
  123. package/gui/src/components/ui/cn.ts +0 -4
  124. package/gui/src/components/ui/index.ts +0 -16
  125. package/gui/src/features/changes/ChangesViewer.tsx +0 -388
  126. package/gui/src/features/knowledge/KnowledgeBase.tsx +0 -84
  127. package/gui/src/features/onboarding/OnboardingWizard.tsx +0 -230
  128. package/gui/src/features/plan/ImplementationPlan.tsx +0 -32
  129. package/gui/src/features/services/ServiceConsole.tsx +0 -215
  130. package/gui/src/features/sessions/SessionHistory.tsx +0 -195
  131. package/gui/src/index.css +0 -167
  132. package/gui/src/lib/status.ts +0 -24
  133. package/gui/src/main.tsx +0 -94
  134. package/gui/src/pages/DashboardPage.tsx +0 -129
  135. package/gui/src/pages/WorkspacesPage.tsx +0 -352
  136. package/gui/src/types.ts +0 -81
  137. package/gui/tsconfig.app.json +0 -25
  138. package/gui/tsconfig.json +0 -7
  139. package/gui/tsconfig.node.json +0 -24
  140. package/gui/vite.config.ts +0 -12
  141. package/resources/workflows/plan-implement-review.md +0 -8
  142. package/resources/workflows/research-verify.md +0 -6
  143. package/resources/workflows/solo-developer.md +0 -3
  144. package/scripts/simulate-workspaces.ts +0 -55
  145. package/skills-lock.json +0 -11
  146. package/src/analyzers/detect-apis.ts +0 -290
  147. package/src/analyzers/detect-deps.ts +0 -315
  148. package/src/analyzers/detect-existing.ts +0 -74
  149. package/src/analyzers/detect-ports.ts +0 -110
  150. package/src/analyzers/index.ts +0 -97
  151. package/src/analyzers/messaging-analyzer.ts +0 -254
  152. package/src/analyzers/readme-summarizer.ts +0 -102
  153. package/src/analyzers/run-analyzer.ts +0 -269
  154. package/src/analyzers/tech-stack.ts +0 -283
  155. package/src/commands/adapter.ts +0 -348
  156. package/src/commands/add-repo.ts +0 -156
  157. package/src/commands/commands.test.ts +0 -156
  158. package/src/commands/commit.ts +0 -131
  159. package/src/commands/config.ts +0 -52
  160. package/src/commands/create.ts +0 -204
  161. package/src/commands/desktop.ts +0 -128
  162. package/src/commands/diff.ts +0 -125
  163. package/src/commands/doctor.ts +0 -370
  164. package/src/commands/handoff.ts +0 -266
  165. package/src/commands/init.ts +0 -134
  166. package/src/commands/list.ts +0 -46
  167. package/src/commands/logs.ts +0 -63
  168. package/src/commands/mcp.ts +0 -94
  169. package/src/commands/open.ts +0 -129
  170. package/src/commands/refresh.ts +0 -137
  171. package/src/commands/remove.ts +0 -98
  172. package/src/commands/start.ts +0 -117
  173. package/src/commands/status.ts +0 -54
  174. package/src/commands/stop.ts +0 -55
  175. package/src/commands/sync.ts +0 -125
  176. package/src/commands/tui.ts +0 -484
  177. package/src/commands/ui.ts +0 -111
  178. package/src/core/adapters/local-storage.ts +0 -66
  179. package/src/core/adapters/obsidian-storage.ts +0 -151
  180. package/src/core/adapters/registry.ts +0 -44
  181. package/src/core/adapters/storage.test.ts +0 -174
  182. package/src/core/adapters/vault-storage.ts +0 -86
  183. package/src/core/config.test.ts +0 -96
  184. package/src/core/config.ts +0 -140
  185. package/src/core/graph.ts +0 -344
  186. package/src/core/plugins/index.ts +0 -17
  187. package/src/core/plugins/loader.ts +0 -36
  188. package/src/core/ports/storage.ts +0 -71
  189. package/src/core/scanner.test.ts +0 -61
  190. package/src/core/scanner.ts +0 -91
  191. package/src/core/storage.ts +0 -37
  192. package/src/core/sync.ts +0 -121
  193. package/src/core/workspace-state.test.ts +0 -108
  194. package/src/core/workspace-state.ts +0 -130
  195. package/src/core/workspace.ts +0 -487
  196. package/src/core/worktree.ts +0 -120
  197. package/src/generators/antigravity.ts +0 -32
  198. package/src/generators/base.ts +0 -243
  199. package/src/generators/claude.ts +0 -34
  200. package/src/generators/codex.ts +0 -48
  201. package/src/generators/copilot.ts +0 -56
  202. package/src/generators/cursor.ts +0 -44
  203. package/src/generators/diff-context.ts +0 -75
  204. package/src/generators/index.ts +0 -322
  205. package/src/generators/map-generator.test.ts +0 -159
  206. package/src/generators/map-generator.ts +0 -539
  207. package/src/generators/plan-generator.ts +0 -483
  208. package/src/generators/skills-generator.ts +0 -259
  209. package/src/index.ts +0 -539
  210. package/src/mcp/server.ts +0 -338
  211. package/src/orchestration/detect.ts +0 -313
  212. package/src/orchestration/index.ts +0 -7
  213. package/src/orchestration/runner.ts +0 -283
  214. package/src/server.test.ts +0 -618
  215. package/src/server.ts +0 -1402
  216. package/src/types.ts +0 -451
  217. package/src/utils/detect-ai.test.ts +0 -44
  218. package/src/utils/detect-ai.ts +0 -84
  219. package/src/utils/detect-editors.test.ts +0 -48
  220. package/src/utils/detect-editors.ts +0 -57
  221. package/src/utils/git.test.ts +0 -74
  222. package/src/utils/git.ts +0 -117
  223. package/src/utils/local-ai.test.ts +0 -130
  224. package/src/utils/local-ai.ts +0 -116
  225. package/src/utils/multi-git.test.ts +0 -158
  226. package/src/utils/multi-git.ts +0 -405
  227. package/src/utils/prompts.ts +0 -209
  228. package/src/utils/session-finder.ts +0 -483
  229. package/src/utils/system-scanner.test.ts +0 -89
  230. package/src/utils/system-scanner.ts +0 -96
  231. package/src/utils/update-check.ts +0 -286
  232. package/src/utils/workflow-advisor.ts +0 -118
  233. package/src/utils/workflows.test.ts +0 -114
  234. package/src/utils/workflows.ts +0 -178
  235. package/tsconfig.json +0 -19
  236. package/vitest.config.ts +0 -14
@@ -0,0 +1,116 @@
1
+ /**
2
+ * @module core/analysis-cache
3
+ * Persists per-repo analysis results in `.nexusflow-analysis-cache.json` at
4
+ * the workspace root, keyed by a git content fingerprint (HEAD SHA plus a
5
+ * signature of dirty files). Lets refresh/sync re-analyze only repos whose
6
+ * content actually changed, keeping unchanged maps byte-identical — which
7
+ * avoids file churn and preserves AI assistants' prompt caches (token saving).
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import * as fs from 'node:fs/promises';
11
+ import * as path from 'node:path';
12
+ import { execa } from 'execa';
13
+ /** Name of the analysis cache file, written at the workspace root. */
14
+ const CACHE_FILE = '.nexusflow-analysis-cache.json';
15
+ /**
16
+ * Returns the path to the analysis cache file for a workspace.
17
+ */
18
+ export function getAnalysisCachePath(workspacePath) {
19
+ return path.join(workspacePath, CACHE_FILE);
20
+ }
21
+ /**
22
+ * Loads the analysis cache, returning an empty skeleton when the file does
23
+ * not exist or cannot be parsed.
24
+ *
25
+ * @param workspacePath - Absolute path to the workspace root.
26
+ */
27
+ export async function loadAnalysisCache(workspacePath) {
28
+ try {
29
+ const raw = await fs.readFile(getAnalysisCachePath(workspacePath), 'utf-8');
30
+ const cache = JSON.parse(raw);
31
+ if (!cache.repos || typeof cache.repos !== 'object') {
32
+ cache.repos = {};
33
+ }
34
+ return cache;
35
+ }
36
+ catch {
37
+ return { version: 1, repos: {} };
38
+ }
39
+ }
40
+ /**
41
+ * Saves the analysis cache, pruning entries for repos that are no longer in
42
+ * the given list of current repo names.
43
+ *
44
+ * @param workspacePath - Absolute path to the workspace root.
45
+ * @param cache - The cache to persist.
46
+ * @param currentRepoNames - Names of repos currently in the workspace; when
47
+ * provided, entries for other repos are dropped.
48
+ */
49
+ export async function saveAnalysisCache(workspacePath, cache, currentRepoNames) {
50
+ if (currentRepoNames) {
51
+ for (const name of Object.keys(cache.repos)) {
52
+ if (!currentRepoNames.includes(name)) {
53
+ delete cache.repos[name];
54
+ }
55
+ }
56
+ }
57
+ const data = JSON.stringify(cache, null, 2) + '\n';
58
+ await fs.writeFile(getAnalysisCachePath(workspacePath), data, 'utf-8');
59
+ }
60
+ /**
61
+ * Strips the surrounding quotes git adds to porcelain paths containing
62
+ * special characters.
63
+ */
64
+ function unquotePorcelainPath(p) {
65
+ if (p.startsWith('"') && p.endsWith('"')) {
66
+ return p.slice(1, -1);
67
+ }
68
+ return p;
69
+ }
70
+ /**
71
+ * Computes a content fingerprint for a repo: the HEAD commit SHA, extended
72
+ * with a hash of the dirty working-tree files (status line + size + mtime per
73
+ * file) when the tree is not clean. Editing, adding, or deleting an
74
+ * uncommitted file therefore changes the fingerprint too.
75
+ *
76
+ * @param repoPath - Absolute path to the repo root.
77
+ * @returns The fingerprint, or null when git fails (caller should re-analyze).
78
+ */
79
+ export async function getRepoFingerprint(repoPath) {
80
+ try {
81
+ const { stdout: shaOut } = await execa('git', ['rev-parse', 'HEAD'], {
82
+ cwd: repoPath,
83
+ });
84
+ const sha = shaOut.trim();
85
+ const { stdout: statusOut } = await execa('git', ['status', '--porcelain'], {
86
+ cwd: repoPath,
87
+ });
88
+ const lines = statusOut.split('\n').filter(Boolean);
89
+ if (lines.length === 0) {
90
+ return sha;
91
+ }
92
+ let dirtySignature = '';
93
+ for (const line of lines) {
94
+ const rel = line.slice(3).trim();
95
+ // Rename lines look like "R old -> new"; stat the new path.
96
+ const target = rel.includes(' -> ') ? rel.split(' -> ').pop() : rel;
97
+ const filePath = path.join(repoPath, unquotePorcelainPath(target));
98
+ try {
99
+ const st = await fs.stat(filePath);
100
+ dirtySignature += `${line}|${st.size}|${Math.floor(st.mtimeMs)}\n`;
101
+ }
102
+ catch {
103
+ dirtySignature += `${line}|missing\n`;
104
+ }
105
+ }
106
+ const dirtyHash = createHash('sha1')
107
+ .update(dirtySignature)
108
+ .digest('hex')
109
+ .slice(0, 12);
110
+ return `${sha}+${dirtyHash}`;
111
+ }
112
+ catch {
113
+ return null;
114
+ }
115
+ }
116
+ //# sourceMappingURL=analysis-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis-cache.js","sourceRoot":"","sources":["../../src/core/analysis-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=analysis-cache.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis-cache.test.d.ts","sourceRoot":"","sources":["../../src/core/analysis-cache.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,108 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import * as fs from 'node:fs/promises';
3
+ import { execa } from 'execa';
4
+ import { loadAnalysisCache, saveAnalysisCache, getRepoFingerprint, } from './analysis-cache.js';
5
+ vi.mock('node:fs/promises');
6
+ vi.mock('execa');
7
+ const mockAnalysis = {
8
+ name: 'repo-1',
9
+ path: '/ws/repo-1',
10
+ techStack: { languages: ['typescript'], frameworks: [], buildTools: [], projectType: 'backend' },
11
+ endpoints: [],
12
+ dependencies: [],
13
+ ports: [],
14
+ readmeSummary: null,
15
+ existingAIConfigs: [],
16
+ };
17
+ /** Parses the JSON written by the most recent writeFile call. */
18
+ function lastWritten() {
19
+ const calls = vi.mocked(fs.writeFile).mock.calls;
20
+ const data = calls[calls.length - 1][1];
21
+ return JSON.parse(data);
22
+ }
23
+ describe('analysis-cache', () => {
24
+ beforeEach(() => {
25
+ vi.clearAllMocks();
26
+ vi.mocked(fs.writeFile).mockResolvedValue(undefined);
27
+ });
28
+ describe('loadAnalysisCache', () => {
29
+ it('returns an empty skeleton when the file is absent', async () => {
30
+ vi.mocked(fs.readFile).mockRejectedValue(new Error('ENOENT'));
31
+ const cache = await loadAnalysisCache('/ws');
32
+ expect(cache.version).toBe(1);
33
+ expect(cache.repos).toEqual({});
34
+ });
35
+ it('round-trips an existing cache file', async () => {
36
+ const existing = {
37
+ version: 1,
38
+ repos: {
39
+ 'repo-1': {
40
+ repoName: 'repo-1',
41
+ fingerprint: 'abc123',
42
+ analyzedAt: '2026-01-01T00:00:00.000Z',
43
+ analysis: mockAnalysis,
44
+ },
45
+ },
46
+ };
47
+ vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify(existing));
48
+ const cache = await loadAnalysisCache('/ws');
49
+ expect(cache.repos['repo-1'].fingerprint).toBe('abc123');
50
+ expect(cache.repos['repo-1'].analysis.name).toBe('repo-1');
51
+ });
52
+ });
53
+ describe('saveAnalysisCache', () => {
54
+ it('prunes entries for repos no longer in the workspace', async () => {
55
+ const cache = {
56
+ version: 1,
57
+ repos: {
58
+ 'repo-1': { repoName: 'repo-1', fingerprint: 'a', analyzedAt: '', analysis: mockAnalysis },
59
+ 'removed-repo': { repoName: 'removed-repo', fingerprint: 'b', analyzedAt: '', analysis: mockAnalysis },
60
+ },
61
+ };
62
+ await saveAnalysisCache('/ws', cache, ['repo-1']);
63
+ const written = lastWritten();
64
+ expect(written.repos['repo-1']).toBeDefined();
65
+ expect(written.repos['removed-repo']).toBeUndefined();
66
+ });
67
+ });
68
+ describe('getRepoFingerprint', () => {
69
+ it('returns the bare HEAD SHA for a clean tree', async () => {
70
+ vi.mocked(execa).mockImplementation((async (_cmd, args) => {
71
+ if (args[0] === 'rev-parse')
72
+ return { stdout: 'abc123\n' };
73
+ return { stdout: '' }; // clean status
74
+ }));
75
+ const fp = await getRepoFingerprint('/ws/repo-1');
76
+ expect(fp).toBe('abc123');
77
+ });
78
+ it('extends the SHA with a dirty-files hash when the tree is dirty', async () => {
79
+ vi.mocked(execa).mockImplementation((async (_cmd, args) => {
80
+ if (args[0] === 'rev-parse')
81
+ return { stdout: 'abc123\n' };
82
+ return { stdout: ' M src/file1.ts\n' };
83
+ }));
84
+ vi.mocked(fs.stat).mockResolvedValue({ size: 100, mtimeMs: 1000 });
85
+ const fp = await getRepoFingerprint('/ws/repo-1');
86
+ expect(fp).toMatch(/^abc123\+[0-9a-f]{12}$/);
87
+ });
88
+ it('changes the fingerprint when a dirty file is edited', async () => {
89
+ vi.mocked(execa).mockImplementation((async (_cmd, args) => {
90
+ if (args[0] === 'rev-parse')
91
+ return { stdout: 'abc123\n' };
92
+ return { stdout: ' M src/file1.ts\n' };
93
+ }));
94
+ vi.mocked(fs.stat).mockResolvedValue({ size: 100, mtimeMs: 1000 });
95
+ const before = await getRepoFingerprint('/ws/repo-1');
96
+ vi.mocked(fs.stat).mockResolvedValue({ size: 150, mtimeMs: 2000 });
97
+ const after = await getRepoFingerprint('/ws/repo-1');
98
+ expect(before).not.toBe(after);
99
+ expect(before).not.toBe('abc123');
100
+ });
101
+ it('returns null when git fails', async () => {
102
+ vi.mocked(execa).mockRejectedValue(new Error('not a git repository'));
103
+ const fp = await getRepoFingerprint('/not-a-repo');
104
+ expect(fp).toBeNull();
105
+ });
106
+ });
107
+ });
108
+ //# sourceMappingURL=analysis-cache.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analysis-cache.test.js","sourceRoot":"","sources":["../../src/core/analysis-cache.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,41 @@
1
+ /**
2
+ * @module core/refresh
3
+ * Headless workspace refresh — regenerates context files, maps, and plans for
4
+ * a workspace using the analysis cache, so only repos whose content changed
5
+ * are re-analyzed and only their maps are rewritten. Backs the CLI command,
6
+ * the HTTP API, and the scheduler alike.
7
+ */
8
+ /** Options for a headless refresh run. */
9
+ export interface RefreshOptions {
10
+ /** Restrict regeneration to a single repo (by directory name). */
11
+ onlyRepo?: string;
12
+ /** Only refresh base-layer maps and codebase knowledge. */
13
+ baseOnly?: boolean;
14
+ /** Ignore the analysis cache and re-analyze every repo. */
15
+ force?: boolean;
16
+ }
17
+ /** Outcome of a headless refresh run. */
18
+ export interface RefreshReport {
19
+ /** Absolute path to the workspace. */
20
+ workspacePath: string;
21
+ /** Names of repos that were re-analyzed because their content changed. */
22
+ analyzedRepos: string[];
23
+ /** Names of repos whose cached analysis was reused. */
24
+ reusedRepos: string[];
25
+ /** Whether the handoff bundle was refreshed too. */
26
+ refreshedHandoff: boolean;
27
+ }
28
+ /**
29
+ * Refreshes a workspace's context files, maps, and plans. Throws if the
30
+ * workspace configuration cannot be loaded.
31
+ *
32
+ * Token-saving behavior: repos with an unchanged git fingerprint reuse their
33
+ * cached analysis and keep their existing map files byte-identical, so AI
34
+ * assistants' prompt caches stay valid across refreshes.
35
+ *
36
+ * @param workspacePath - Absolute path to the workspace root.
37
+ * @param options - Refresh options.
38
+ * @returns A report of what was analyzed vs reused.
39
+ */
40
+ export declare function refreshWorkspace(workspacePath: string, options?: RefreshOptions): Promise<RefreshReport>;
41
+ //# sourceMappingURL=refresh.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.d.ts","sourceRoot":"","sources":["../../src/core/refresh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAUH,0CAA0C;AAC1C,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,2DAA2D;IAC3D,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB;AAED,yCAAyC;AACzC,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,aAAa,EAAE,MAAM,CAAC;IACtB,0EAA0E;IAC1E,aAAa,EAAE,MAAM,EAAE,CAAC;IACxB,uDAAuD;IACvD,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,oDAAoD;IACpD,gBAAgB,EAAE,OAAO,CAAC;CAC3B;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,aAAa,CAAC,CAoDxB"}
@@ -0,0 +1,60 @@
1
+ /**
2
+ * @module core/refresh
3
+ * Headless workspace refresh — regenerates context files, maps, and plans for
4
+ * a workspace using the analysis cache, so only repos whose content changed
5
+ * are re-analyzed and only their maps are rewritten. Backs the CLI command,
6
+ * the HTTP API, and the scheduler alike.
7
+ */
8
+ import * as fs from 'node:fs/promises';
9
+ import * as path from 'node:path';
10
+ import { loadFeatureConfig } from './workspace.js';
11
+ import { analyzeAllReposCached } from '../analyzers/index.js';
12
+ import { generateContextFiles } from '../generators/index.js';
13
+ /**
14
+ * Refreshes a workspace's context files, maps, and plans. Throws if the
15
+ * workspace configuration cannot be loaded.
16
+ *
17
+ * Token-saving behavior: repos with an unchanged git fingerprint reuse their
18
+ * cached analysis and keep their existing map files byte-identical, so AI
19
+ * assistants' prompt caches stay valid across refreshes.
20
+ *
21
+ * @param workspacePath - Absolute path to the workspace root.
22
+ * @param options - Refresh options.
23
+ * @returns A report of what was analyzed vs reused.
24
+ */
25
+ export async function refreshWorkspace(workspacePath, options = {}) {
26
+ const feature = await loadFeatureConfig(workspacePath);
27
+ if (!feature) {
28
+ throw new Error(`Failed to load workspace configuration. Ensure nexusflow.json exists at ${workspacePath}.`);
29
+ }
30
+ const allRepos = feature.repos.map((r) => ({
31
+ name: path.basename(r),
32
+ path: r,
33
+ defaultBranch: 'main',
34
+ }));
35
+ const { analysis, analyzed, reused } = await analyzeAllReposCached(allRepos, workspacePath, { force: options.force });
36
+ const ctx = { feature, repos: allRepos, analysis };
37
+ await generateContextFiles(ctx, feature.assistants, workspacePath, options.onlyRepo, options.baseOnly, options.force ? undefined : analyzed);
38
+ // If a handoff bundle exists, refresh it too — it reuses the cached
39
+ // analysis, so this no longer triggers a second full analysis pass.
40
+ let refreshedHandoff = false;
41
+ const handoffPath = path.join(workspacePath, 'nexusflow-handoff.md');
42
+ if (!options.baseOnly) {
43
+ try {
44
+ await fs.access(handoffPath);
45
+ const { handoffCommand } = await import('../commands/handoff.js');
46
+ await handoffCommand(workspacePath);
47
+ refreshedHandoff = true;
48
+ }
49
+ catch {
50
+ // No handoff file, or refresh failed — non-fatal either way.
51
+ }
52
+ }
53
+ return {
54
+ workspacePath,
55
+ analyzedRepos: analyzed,
56
+ reusedRepos: reused,
57
+ refreshedHandoff,
58
+ };
59
+ }
60
+ //# sourceMappingURL=refresh.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"refresh.js","sourceRoot":"","sources":["../../src/core/refresh.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACvC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAElC,OAAO,EAAE,iBAAiB,EAAE,MAAM,gBAAgB,CAAC;AACnD,OAAO,EAAE,qBAAqB,EAAE,MAAM,uBAAuB,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAyB9D;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,aAAqB,EACrB,UAA0B,EAAE;IAE5B,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,aAAa,CAAC,CAAC;IACvD,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,MAAM,IAAI,KAAK,CACb,2EAA2E,aAAa,GAAG,CAC5F,CAAC;IACJ,CAAC;IAED,MAAM,QAAQ,GAAG,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QACzC,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QACtB,IAAI,EAAE,CAAC;QACP,aAAa,EAAE,MAAM;KACtB,CAAC,CAAC,CAAC;IAEJ,MAAM,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,EAAE,GAAG,MAAM,qBAAqB,CAChE,QAAQ,EACR,aAAa,EACb,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,CACzB,CAAC;IAEF,MAAM,GAAG,GAAqB,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,QAAQ,EAAE,CAAC;IAErE,MAAM,oBAAoB,CACxB,GAAG,EACH,OAAO,CAAC,UAAU,EAClB,aAAa,EACb,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,QAAQ,EAChB,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,QAAQ,CACrC,CAAC;IAEF,oEAAoE;IACpE,oEAAoE;IACpE,IAAI,gBAAgB,GAAG,KAAK,CAAC;IAC7B,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,aAAa,EAAE,sBAAsB,CAAC,CAAC;IACrE,IAAI,CAAC,OAAO,CAAC,QAAQ,EAAE,CAAC;QACtB,IAAI,CAAC;YACH,MAAM,EAAE,CAAC,MAAM,CAAC,WAAW,CAAC,CAAC;YAC7B,MAAM,EAAE,cAAc,EAAE,GAAG,MAAM,MAAM,CAAC,wBAAwB,CAAC,CAAC;YAClE,MAAM,cAAc,CAAC,aAAa,CAAC,CAAC;YACpC,gBAAgB,GAAG,IAAI,CAAC;QAC1B,CAAC;QAAC,MAAM,CAAC;YACP,6DAA6D;QAC/D,CAAC;IACH,CAAC;IAED,OAAO;QACL,aAAa;QACb,aAAa,EAAE,QAAQ;QACvB,WAAW,EAAE,MAAM;QACnB,gBAAgB;KACjB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,117 @@
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
+ /** Kinds of work a schedule can run. */
13
+ export type ScheduleTask = 'sync' | 'refresh';
14
+ /** A persisted recurring job definition. */
15
+ export interface ScheduledJob {
16
+ /** Unique job id, e.g. 'sync-my-feature-k3x9q2'. */
17
+ id: string;
18
+ /** Absolute path to the workspace the job operates on. */
19
+ workspacePath: string;
20
+ /** What the job runs. */
21
+ task: ScheduleTask;
22
+ /** How often the job runs, in minutes. */
23
+ intervalMinutes: number;
24
+ /** Disabled jobs are kept but never run. */
25
+ enabled: boolean;
26
+ /** ISO timestamp of when the job was created. */
27
+ createdAt: string;
28
+ /** ISO timestamp of the last run, if any. */
29
+ lastRunAt?: string;
30
+ /** Outcome of the last run. */
31
+ lastStatus?: 'success' | 'error';
32
+ /** Human-readable summary of the last run. */
33
+ lastMessage?: string;
34
+ }
35
+ /** On-disk shape of the schedules file. */
36
+ export interface ScheduleStore {
37
+ version: 1;
38
+ jobs: ScheduledJob[];
39
+ }
40
+ /** Result of running a single job. */
41
+ export interface JobRunResult {
42
+ status: 'success' | 'error';
43
+ message: string;
44
+ }
45
+ /**
46
+ * Returns the path to the schedules file (~/.nexusflow/schedules.json).
47
+ */
48
+ export declare function getSchedulesPath(): string;
49
+ /**
50
+ * Loads all schedules, returning an empty store when the file does not exist
51
+ * or cannot be parsed.
52
+ */
53
+ export declare function loadSchedules(): Promise<ScheduleStore>;
54
+ /**
55
+ * Persists the schedule store to ~/.nexusflow/schedules.json.
56
+ */
57
+ export declare function saveSchedules(store: ScheduleStore): Promise<void>;
58
+ /**
59
+ * Parses a human interval like '30m', '2h', '1d', or a bare minute count
60
+ * ('45') into minutes. Returns null for unparseable or non-positive input.
61
+ */
62
+ export declare function parseInterval(text: string): number | null;
63
+ /**
64
+ * Formats an interval in minutes back to a compact human string.
65
+ */
66
+ export declare function formatInterval(minutes: number): string;
67
+ /**
68
+ * Whether a job is due to run: it is enabled and either never ran or its
69
+ * interval has elapsed since the last run. A job whose interval elapsed while
70
+ * no scheduler was running simply runs on the next tick.
71
+ */
72
+ export declare function isDue(job: ScheduledJob, now?: Date): boolean;
73
+ /**
74
+ * Computes when a job will next be due, for display purposes.
75
+ */
76
+ export declare function nextDueAt(job: ScheduledJob): Date | null;
77
+ /**
78
+ * Adds a new schedule and persists it.
79
+ *
80
+ * @param input - Workspace, task, and interval for the new job.
81
+ * @returns The created job.
82
+ */
83
+ export declare function addSchedule(input: {
84
+ workspacePath: string;
85
+ task: ScheduleTask;
86
+ intervalMinutes: number;
87
+ }): Promise<ScheduledJob>;
88
+ /**
89
+ * Removes a schedule by id. Returns false when no job matched.
90
+ */
91
+ export declare function removeSchedule(id: string): Promise<boolean>;
92
+ /**
93
+ * Enables or disables a schedule by id. Returns the updated job, or null
94
+ * when no job matched.
95
+ */
96
+ export declare function setScheduleEnabled(id: string, enabled: boolean): Promise<ScheduledJob | null>;
97
+ /**
98
+ * Runs a single job immediately and records the outcome in the store.
99
+ * Never throws — failures are captured in the returned result.
100
+ *
101
+ * @param job - The job to run.
102
+ * @returns The classified outcome with a summary message.
103
+ */
104
+ export declare function runJob(job: ScheduledJob): Promise<JobRunResult>;
105
+ /**
106
+ * Starts the scheduler loop inside the current process. Every tick it
107
+ * re-loads the schedules file (so jobs added/removed via CLI are picked up
108
+ * without a restart) and runs all due jobs sequentially. Ticks never overlap.
109
+ *
110
+ * @param options - Tick interval (default 60s) and an optional log sink.
111
+ * @returns A stop function that clears the interval.
112
+ */
113
+ export declare function startScheduler(options?: {
114
+ tickMs?: number;
115
+ log?: (message: string) => void;
116
+ }): () => void;
117
+ //# sourceMappingURL=scheduler.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"scheduler.d.ts","sourceRoot":"","sources":["../../src/core/scheduler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAYH,wCAAwC;AACxC,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,SAAS,CAAC;AAE9C,4CAA4C;AAC5C,MAAM,WAAW,YAAY;IAC3B,oDAAoD;IACpD,EAAE,EAAE,MAAM,CAAC;IACX,0DAA0D;IAC1D,aAAa,EAAE,MAAM,CAAC;IACtB,yBAAyB;IACzB,IAAI,EAAE,YAAY,CAAC;IACnB,0CAA0C;IAC1C,eAAe,EAAE,MAAM,CAAC;IACxB,4CAA4C;IAC5C,OAAO,EAAE,OAAO,CAAC;IACjB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,6CAA6C;IAC7C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,UAAU,CAAC,EAAE,SAAS,GAAG,OAAO,CAAC;IACjC,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,CAAC,CAAC;IACX,IAAI,EAAE,YAAY,EAAE,CAAC;CACtB;AAED,sCAAsC;AACtC,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,GAAG,OAAO,CAAC;IAC5B,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;GAGG;AACH,wBAAsB,aAAa,IAAI,OAAO,CAAC,aAAa,CAAC,CAW5D;AAED;;GAEG;AACH,wBAAsB,aAAa,CAAC,KAAK,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAIvE;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAezD;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAItD;AAED;;;;GAIG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,YAAY,EAAE,GAAG,GAAE,IAAiB,GAAG,OAAO,CAQxE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,YAAY,GAAG,IAAI,GAAG,IAAI,CAMxD;AAED;;;;;GAKG;AACH,wBAAsB,WAAW,CAAC,KAAK,EAAE;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,YAAY,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;CACzB,GAAG,OAAO,CAAC,YAAY,CAAC,CAgBxB;AAED;;GAEG;AACH,wBAAsB,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAOjE;AAED;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAO9B;AAED;;;;;;GAMG;AACH,wBAAsB,MAAM,CAAC,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,YAAY,CAAC,CA2CrE;AAED;;;;;;;GAOG;AACH,wBAAgB,cAAc,CAAC,OAAO,GAAE;IACtC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;CAC5B,GAAG,MAAM,IAAI,CA8BlB"}