@mrpatronz/nexusflow 1.3.2 → 1.5.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 (143) hide show
  1. package/README.md +50 -0
  2. package/dist/commands/create.d.ts.map +1 -1
  3. package/dist/commands/create.js +54 -37
  4. package/dist/commands/create.js.map +1 -1
  5. package/dist/commands/diff.d.ts.map +1 -1
  6. package/dist/commands/diff.js +16 -73
  7. package/dist/commands/diff.js.map +1 -1
  8. package/dist/commands/doctor.d.ts +4 -0
  9. package/dist/commands/doctor.d.ts.map +1 -1
  10. package/dist/commands/doctor.js +58 -318
  11. package/dist/commands/doctor.js.map +1 -1
  12. package/dist/commands/finish.d.ts +17 -0
  13. package/dist/commands/finish.d.ts.map +1 -0
  14. package/dist/commands/finish.js +172 -0
  15. package/dist/commands/finish.js.map +1 -0
  16. package/dist/commands/handoff.d.ts.map +1 -1
  17. package/dist/commands/handoff.js +11 -6
  18. package/dist/commands/handoff.js.map +1 -1
  19. package/dist/commands/knowledge.d.ts +36 -0
  20. package/dist/commands/knowledge.d.ts.map +1 -0
  21. package/dist/commands/knowledge.js +184 -0
  22. package/dist/commands/knowledge.js.map +1 -0
  23. package/dist/commands/open.d.ts.map +1 -1
  24. package/dist/commands/open.js +2 -4
  25. package/dist/commands/open.js.map +1 -1
  26. package/dist/core/commit.d.ts +41 -0
  27. package/dist/core/commit.d.ts.map +1 -0
  28. package/dist/core/commit.js +53 -0
  29. package/dist/core/commit.js.map +1 -0
  30. package/dist/core/config.d.ts +3 -1
  31. package/dist/core/config.d.ts.map +1 -1
  32. package/dist/core/config.js +60 -32
  33. package/dist/core/config.js.map +1 -1
  34. package/dist/core/config.test.js +24 -0
  35. package/dist/core/config.test.js.map +1 -1
  36. package/dist/core/diff.d.ts +27 -0
  37. package/dist/core/diff.d.ts.map +1 -0
  38. package/dist/core/diff.js +50 -0
  39. package/dist/core/diff.js.map +1 -0
  40. package/dist/core/doctor.d.ts +34 -0
  41. package/dist/core/doctor.d.ts.map +1 -0
  42. package/dist/core/doctor.js +276 -0
  43. package/dist/core/doctor.js.map +1 -0
  44. package/dist/core/finish.d.ts +56 -0
  45. package/dist/core/finish.d.ts.map +1 -0
  46. package/dist/core/finish.js +121 -0
  47. package/dist/core/finish.js.map +1 -0
  48. package/dist/core/finish.test.d.ts +2 -0
  49. package/dist/core/finish.test.d.ts.map +1 -0
  50. package/dist/core/finish.test.js +110 -0
  51. package/dist/core/finish.test.js.map +1 -0
  52. package/dist/core/knowledge.d.ts +96 -0
  53. package/dist/core/knowledge.d.ts.map +1 -0
  54. package/dist/core/knowledge.js +299 -0
  55. package/dist/core/knowledge.js.map +1 -0
  56. package/dist/core/knowledge.test.d.ts +2 -0
  57. package/dist/core/knowledge.test.d.ts.map +1 -0
  58. package/dist/core/knowledge.test.js +157 -0
  59. package/dist/core/knowledge.test.js.map +1 -0
  60. package/dist/core/refresh.test.d.ts +2 -0
  61. package/dist/core/refresh.test.d.ts.map +1 -0
  62. package/dist/core/refresh.test.js +67 -0
  63. package/dist/core/refresh.test.js.map +1 -0
  64. package/dist/core/status.d.ts +45 -0
  65. package/dist/core/status.d.ts.map +1 -0
  66. package/dist/core/status.js +45 -0
  67. package/dist/core/status.js.map +1 -0
  68. package/dist/core/sync.test.d.ts +2 -0
  69. package/dist/core/sync.test.d.ts.map +1 -0
  70. package/dist/core/sync.test.js +86 -0
  71. package/dist/core/sync.test.js.map +1 -0
  72. package/dist/core/workspace.create.test.d.ts +2 -0
  73. package/dist/core/workspace.create.test.d.ts.map +1 -0
  74. package/dist/core/workspace.create.test.js +95 -0
  75. package/dist/core/workspace.create.test.js.map +1 -0
  76. package/dist/core/workspace.d.ts +9 -3
  77. package/dist/core/workspace.d.ts.map +1 -1
  78. package/dist/core/workspace.js +91 -13
  79. package/dist/core/workspace.js.map +1 -1
  80. package/dist/core/worktree.d.ts +11 -1
  81. package/dist/core/worktree.d.ts.map +1 -1
  82. package/dist/core/worktree.js +2 -0
  83. package/dist/core/worktree.js.map +1 -1
  84. package/dist/core/worktree.test.d.ts +2 -0
  85. package/dist/core/worktree.test.d.ts.map +1 -0
  86. package/dist/core/worktree.test.js +111 -0
  87. package/dist/core/worktree.test.js.map +1 -0
  88. package/dist/generators/base.js +1 -1
  89. package/dist/generators/index.d.ts +7 -0
  90. package/dist/generators/index.d.ts.map +1 -1
  91. package/dist/generators/index.js +4 -1
  92. package/dist/generators/index.js.map +1 -1
  93. package/dist/index.js +110 -3
  94. package/dist/index.js.map +1 -1
  95. package/dist/mcp/server.d.ts.map +1 -1
  96. package/dist/mcp/server.js +51 -282
  97. package/dist/mcp/server.js.map +1 -1
  98. package/dist/mcp/tools.d.ts +45 -0
  99. package/dist/mcp/tools.d.ts.map +1 -0
  100. package/dist/mcp/tools.js +413 -0
  101. package/dist/mcp/tools.js.map +1 -0
  102. package/dist/server.d.ts.map +1 -1
  103. package/dist/server.js +43 -26
  104. package/dist/server.js.map +1 -1
  105. package/dist/utils/debug.d.ts +17 -0
  106. package/dist/utils/debug.d.ts.map +1 -0
  107. package/dist/utils/debug.js +25 -0
  108. package/dist/utils/debug.js.map +1 -0
  109. package/dist/utils/detect-ai.d.ts +14 -6
  110. package/dist/utils/detect-ai.d.ts.map +1 -1
  111. package/dist/utils/detect-ai.js +22 -9
  112. package/dist/utils/detect-ai.js.map +1 -1
  113. package/dist/utils/detect-ai.test.js +33 -0
  114. package/dist/utils/detect-ai.test.js.map +1 -1
  115. package/dist/utils/git.d.ts +13 -2
  116. package/dist/utils/git.d.ts.map +1 -1
  117. package/dist/utils/git.js +64 -5
  118. package/dist/utils/git.js.map +1 -1
  119. package/dist/utils/git.test.js +46 -8
  120. package/dist/utils/git.test.js.map +1 -1
  121. package/dist/utils/multi-git.d.ts +40 -0
  122. package/dist/utils/multi-git.d.ts.map +1 -1
  123. package/dist/utils/multi-git.js +80 -0
  124. package/dist/utils/multi-git.js.map +1 -1
  125. package/dist/utils/multi-git.test.js +54 -1
  126. package/dist/utils/multi-git.test.js.map +1 -1
  127. package/dist/utils/open-editor.d.ts +20 -0
  128. package/dist/utils/open-editor.d.ts.map +1 -0
  129. package/dist/utils/open-editor.js +44 -0
  130. package/dist/utils/open-editor.js.map +1 -0
  131. package/dist/utils/pr.d.ts +50 -0
  132. package/dist/utils/pr.d.ts.map +1 -0
  133. package/dist/utils/pr.js +144 -0
  134. package/dist/utils/pr.js.map +1 -0
  135. package/dist/utils/pr.test.d.ts +2 -0
  136. package/dist/utils/pr.test.d.ts.map +1 -0
  137. package/dist/utils/pr.test.js +47 -0
  138. package/dist/utils/pr.test.js.map +1 -0
  139. package/dist/utils/resolve-workspace.d.ts +30 -0
  140. package/dist/utils/resolve-workspace.d.ts.map +1 -0
  141. package/dist/utils/resolve-workspace.js +77 -0
  142. package/dist/utils/resolve-workspace.js.map +1 -0
  143. package/package.json +11 -11
@@ -0,0 +1,56 @@
1
+ /**
2
+ * @module core/finish
3
+ * Headless "finish a feature" engine shared by the CLI `finish` command and the
4
+ * MCP `finish_workspace` tool. Commits remaining changes, pushes branches, and
5
+ * surfaces a way to open a PR for each repo (via `gh` when available, otherwise
6
+ * a compare URL). It never promotes knowledge or deletes anything — those are
7
+ * interactive concerns owned by the command.
8
+ */
9
+ import { type WorkspaceStatusReport } from './status.js';
10
+ /** Options for {@link finishWorkspace}. */
11
+ export interface FinishOptions {
12
+ /** Commit message for any remaining dirty changes. */
13
+ message?: string;
14
+ /** Do not push after committing. */
15
+ skipPush?: boolean;
16
+ /** Attempt `gh pr create` when the GitHub CLI is installed and authenticated. */
17
+ createPrs?: boolean;
18
+ prTitle?: string;
19
+ prBody?: string;
20
+ }
21
+ /** Per-repo finish outcome. */
22
+ export interface RepoFinishReport {
23
+ name: string;
24
+ committed: boolean;
25
+ commitHash?: string;
26
+ pushed: boolean;
27
+ /** PR URL created via `gh`, when available. */
28
+ prUrl?: string;
29
+ /** Compare/create-PR URL to open in a browser (fallback and default path). */
30
+ compareUrl?: string;
31
+ /** Set when the repo was intentionally skipped (wrong branch, no remote, …). */
32
+ skipped?: string;
33
+ /** Set when an operation failed for this repo. */
34
+ error?: string;
35
+ }
36
+ /** Aggregate finish report. */
37
+ export interface FinishReport {
38
+ workspacePath: string;
39
+ branchName: string;
40
+ preflight: WorkspaceStatusReport;
41
+ repos: RepoFinishReport[];
42
+ /** True when `gh` was used to create PRs. */
43
+ ghUsed: boolean;
44
+ /** True when, after finishing, every repo is clean and pushed. */
45
+ safeToCleanup: boolean;
46
+ }
47
+ /**
48
+ * Runs the finish sequence: preflight → commit dirty repos → push → PR links →
49
+ * recheck. Repos on the wrong branch or in a detached-HEAD state are skipped
50
+ * (never committed) so a stray checkout can't push to the wrong branch.
51
+ *
52
+ * @param workspacePath - Absolute path to the workspace directory.
53
+ * @param options - Finish options.
54
+ */
55
+ export declare function finishWorkspace(workspacePath: string, options?: FinishOptions): Promise<FinishReport>;
56
+ //# sourceMappingURL=finish.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish.d.ts","sourceRoot":"","sources":["../../src/core/finish.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAA4B,KAAK,qBAAqB,EAAE,MAAM,aAAa,CAAC;AAKnF,2CAA2C;AAC3C,MAAM,WAAW,aAAa;IAC5B,sDAAsD;IACtD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,iFAAiF;IACjF,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,+BAA+B;AAC/B,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,OAAO,CAAC;IAChB,+CAA+C;IAC/C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,8EAA8E;IAC9E,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,kDAAkD;IAClD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+BAA+B;AAC/B,MAAM,WAAW,YAAY;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,qBAAqB,CAAC;IACjC,KAAK,EAAE,gBAAgB,EAAE,CAAC;IAC1B,6CAA6C;IAC7C,MAAM,EAAE,OAAO,CAAC;IAChB,kEAAkE;IAClE,aAAa,EAAE,OAAO,CAAC;CACxB;AAED;;;;;;;GAOG;AACH,wBAAsB,eAAe,CACnC,aAAa,EAAE,MAAM,EACrB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAmGvB"}
@@ -0,0 +1,121 @@
1
+ /**
2
+ * @module core/finish
3
+ * Headless "finish a feature" engine shared by the CLI `finish` command and the
4
+ * MCP `finish_workspace` tool. Commits remaining changes, pushes branches, and
5
+ * surfaces a way to open a PR for each repo (via `gh` when available, otherwise
6
+ * a compare URL). It never promotes knowledge or deletes anything — those are
7
+ * interactive concerns owned by the command.
8
+ */
9
+ import { getWorkspaceStatusReport } from './status.js';
10
+ import { commitWorkspace } from './commit.js';
11
+ import { pushRepo } from '../utils/multi-git.js';
12
+ import { detectGh, createPrWithGh, parseRemoteUrl, buildCompareUrl } from '../utils/pr.js';
13
+ /**
14
+ * Runs the finish sequence: preflight → commit dirty repos → push → PR links →
15
+ * recheck. Repos on the wrong branch or in a detached-HEAD state are skipped
16
+ * (never committed) so a stray checkout can't push to the wrong branch.
17
+ *
18
+ * @param workspacePath - Absolute path to the workspace directory.
19
+ * @param options - Finish options.
20
+ */
21
+ export async function finishWorkspace(workspacePath, options = {}) {
22
+ const preflight = await getWorkspaceStatusReport(workspacePath);
23
+ const reports = new Map();
24
+ for (const repo of preflight.repos) {
25
+ reports.set(repo.name, { name: repo.name, committed: false, pushed: false });
26
+ }
27
+ // Repos we are allowed to act on: on the expected branch, not detached.
28
+ const actionable = preflight.repos.filter((r) => r.onExpectedBranch && r.branch);
29
+ for (const repo of preflight.repos) {
30
+ if (!r_isActionable(repo.name, actionable)) {
31
+ const rep = reports.get(repo.name);
32
+ rep.skipped = repo.branch
33
+ ? `on branch "${repo.branch}", not the feature branch "${repo.expectedBranch}"`
34
+ : 'detached HEAD';
35
+ }
36
+ }
37
+ // ── Commit dirty, actionable repos ──────────────────────────────────────
38
+ const dirtyActionable = actionable.filter((r) => r.dirty);
39
+ if (dirtyActionable.length > 0) {
40
+ if (!options.message || !options.message.trim()) {
41
+ for (const r of dirtyActionable) {
42
+ reports.get(r.name).error = 'uncommitted changes and no commit message provided';
43
+ }
44
+ }
45
+ else {
46
+ const commitReport = await commitWorkspace(workspacePath, options.message, {
47
+ noPush: true, // push is handled uniformly below
48
+ repos: dirtyActionable.map((r) => r.name),
49
+ });
50
+ for (const c of commitReport.repos) {
51
+ const rep = reports.get(c.name);
52
+ rep.committed = c.success;
53
+ rep.commitHash = c.commitHash || undefined;
54
+ if (!c.success)
55
+ rep.error = c.message;
56
+ }
57
+ }
58
+ }
59
+ // ── Push actionable repos with a remote ─────────────────────────────────
60
+ for (const repo of actionable) {
61
+ const rep = reports.get(repo.name);
62
+ if (rep.error)
63
+ continue;
64
+ if (!repo.remoteUrl) {
65
+ rep.skipped = rep.skipped ?? 'no remote configured';
66
+ continue;
67
+ }
68
+ if (options.skipPush)
69
+ continue;
70
+ // Push when we committed, when the branch is ahead, or when it was never pushed.
71
+ const needsPush = rep.committed || repo.ahead === null || (repo.ahead ?? 0) > 0;
72
+ if (needsPush) {
73
+ const result = await pushRepo(repo.path, repo.expectedBranch);
74
+ rep.pushed = result.success;
75
+ if (!result.success)
76
+ rep.error = result.message;
77
+ }
78
+ }
79
+ // ── PR links ────────────────────────────────────────────────────────────
80
+ const gh = options.createPrs ? await detectGh() : { installed: false, authenticated: false };
81
+ const ghUsed = Boolean(options.createPrs && gh.installed && gh.authenticated);
82
+ for (const repo of actionable) {
83
+ const rep = reports.get(repo.name);
84
+ if (!repo.remoteUrl || rep.error)
85
+ continue;
86
+ const remote = parseRemoteUrl(repo.remoteUrl);
87
+ if (remote) {
88
+ rep.compareUrl = buildCompareUrl(remote, repo.defaultBranch, repo.expectedBranch) ?? undefined;
89
+ }
90
+ if (ghUsed && rep.pushed) {
91
+ const title = options.prTitle?.trim() || repo.expectedBranch;
92
+ const body = options.prBody ?? '';
93
+ const pr = await createPrWithGh(repo.path, {
94
+ base: repo.defaultBranch,
95
+ head: repo.expectedBranch,
96
+ title,
97
+ body,
98
+ });
99
+ if (pr.url) {
100
+ rep.prUrl = pr.url;
101
+ }
102
+ // On gh failure we silently keep compareUrl (already populated above).
103
+ }
104
+ }
105
+ // ── Recheck to decide whether cleanup is safe ───────────────────────────
106
+ const after = await getWorkspaceStatusReport(workspacePath);
107
+ const safeToCleanup = after.allClean && after.allPushed;
108
+ return {
109
+ workspacePath,
110
+ branchName: preflight.branchName,
111
+ preflight,
112
+ repos: preflight.repos.map((r) => reports.get(r.name)),
113
+ ghUsed,
114
+ safeToCleanup,
115
+ };
116
+ }
117
+ /** Whether a repo name is in the actionable set. */
118
+ function r_isActionable(name, actionable) {
119
+ return actionable.some((r) => r.name === name);
120
+ }
121
+ //# sourceMappingURL=finish.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish.js","sourceRoot":"","sources":["../../src/core/finish.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=finish.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish.test.d.ts","sourceRoot":"","sources":["../../src/core/finish.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,110 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest';
2
+ import { finishWorkspace } from './finish.js';
3
+ import * as status from './status.js';
4
+ import * as commit from './commit.js';
5
+ import * as multiGit from '../utils/multi-git.js';
6
+ import * as pr from '../utils/pr.js';
7
+ vi.mock('./status.js');
8
+ vi.mock('./commit.js');
9
+ vi.mock('../utils/multi-git.js');
10
+ vi.mock('../utils/pr.js');
11
+ function repo(overrides) {
12
+ return {
13
+ name: 'api',
14
+ path: '/ws/api',
15
+ branch: 'feat',
16
+ expectedBranch: 'feat',
17
+ onExpectedBranch: true,
18
+ dirty: false,
19
+ changedFiles: [],
20
+ ahead: 0,
21
+ behind: 0,
22
+ remoteUrl: 'https://github.com/o/api.git',
23
+ defaultBranch: 'main',
24
+ ...overrides,
25
+ };
26
+ }
27
+ function report(repos) {
28
+ return {
29
+ workspacePath: '/ws',
30
+ branchName: 'feat',
31
+ repos,
32
+ allClean: repos.every((r) => !r.dirty),
33
+ allPushed: repos.every((r) => r.ahead === 0),
34
+ };
35
+ }
36
+ describe('finishWorkspace', () => {
37
+ beforeEach(() => {
38
+ vi.clearAllMocks();
39
+ vi.mocked(pr.parseRemoteUrl).mockReturnValue({ host: 'github.com', owner: 'o', repo: 'api', kind: 'github' });
40
+ vi.mocked(pr.buildCompareUrl).mockReturnValue('https://github.com/o/api/compare/main...feat?expand=1');
41
+ vi.mocked(pr.detectGh).mockResolvedValue({ installed: false, authenticated: false });
42
+ vi.mocked(multiGit.pushRepo).mockResolvedValue({ success: true, message: 'pushed' });
43
+ });
44
+ it('does nothing to a clean, pushed repo and reports safe to cleanup', async () => {
45
+ const clean = report([repo({ dirty: false, ahead: 0 })]);
46
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValue(clean);
47
+ const result = await finishWorkspace('/ws', {});
48
+ expect(commit.commitWorkspace).not.toHaveBeenCalled();
49
+ expect(multiGit.pushRepo).not.toHaveBeenCalled();
50
+ expect(result.safeToCleanup).toBe(true);
51
+ expect(result.repos[0].compareUrl).toContain('/compare/main...feat');
52
+ });
53
+ it('commits a dirty repo when given a message, then pushes', async () => {
54
+ const dirty = report([repo({ dirty: true, ahead: 0 })]);
55
+ const after = report([repo({ dirty: false, ahead: 0 })]);
56
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValueOnce(dirty).mockResolvedValueOnce(after);
57
+ vi.mocked(commit.commitWorkspace).mockResolvedValue({
58
+ repos: [{ name: 'api', success: true, commitHash: 'abc1234', filesChanged: 2, message: 'ok' }],
59
+ committedCount: 1,
60
+ failedCount: 0,
61
+ });
62
+ const result = await finishWorkspace('/ws', { message: 'wip' });
63
+ expect(commit.commitWorkspace).toHaveBeenCalledWith('/ws', 'wip', { noPush: true, repos: ['api'] });
64
+ expect(multiGit.pushRepo).toHaveBeenCalledWith('/ws/api', 'feat');
65
+ expect(result.repos[0]).toMatchObject({ committed: true, commitHash: 'abc1234', pushed: true });
66
+ expect(result.safeToCleanup).toBe(true);
67
+ });
68
+ it('errors on a dirty repo when no message is provided', async () => {
69
+ const dirty = report([repo({ dirty: true })]);
70
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValue(dirty);
71
+ const result = await finishWorkspace('/ws', {});
72
+ expect(commit.commitWorkspace).not.toHaveBeenCalled();
73
+ expect(result.repos[0].error).toMatch(/no commit message/i);
74
+ expect(result.safeToCleanup).toBe(false);
75
+ });
76
+ it('skips a repo checked out on the wrong branch', async () => {
77
+ const wrong = report([repo({ branch: 'main', onExpectedBranch: false, dirty: true })]);
78
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValue(wrong);
79
+ const result = await finishWorkspace('/ws', { message: 'wip' });
80
+ expect(commit.commitWorkspace).not.toHaveBeenCalled();
81
+ expect(multiGit.pushRepo).not.toHaveBeenCalled();
82
+ expect(result.repos[0].skipped).toMatch(/not the feature branch/i);
83
+ });
84
+ it('pushes a never-pushed branch (ahead === null)', async () => {
85
+ const unpushed = report([repo({ dirty: false, ahead: null })]);
86
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValue(unpushed);
87
+ const result = await finishWorkspace('/ws', {});
88
+ expect(multiGit.pushRepo).toHaveBeenCalledWith('/ws/api', 'feat');
89
+ expect(result.repos[0].pushed).toBe(true);
90
+ });
91
+ it('falls back to a compare URL when gh is unavailable and does not create a PR', async () => {
92
+ const unpushed = report([repo({ dirty: false, ahead: 2 })]);
93
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValue(unpushed);
94
+ const result = await finishWorkspace('/ws', { createPrs: true });
95
+ expect(result.ghUsed).toBe(false);
96
+ expect(pr.createPrWithGh).not.toHaveBeenCalled();
97
+ expect(result.repos[0].compareUrl).toContain('/compare/main...feat');
98
+ expect(result.repos[0].prUrl).toBeUndefined();
99
+ });
100
+ it('creates a PR via gh when authenticated', async () => {
101
+ const unpushed = report([repo({ dirty: false, ahead: 2 })]);
102
+ vi.mocked(status.getWorkspaceStatusReport).mockResolvedValue(unpushed);
103
+ vi.mocked(pr.detectGh).mockResolvedValue({ installed: true, authenticated: true });
104
+ vi.mocked(pr.createPrWithGh).mockResolvedValue({ url: 'https://github.com/o/api/pull/7' });
105
+ const result = await finishWorkspace('/ws', { createPrs: true });
106
+ expect(result.ghUsed).toBe(true);
107
+ expect(result.repos[0].prUrl).toBe('https://github.com/o/api/pull/7');
108
+ });
109
+ });
110
+ //# sourceMappingURL=finish.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"finish.test.js","sourceRoot":"","sources":["../../src/core/finish.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * @module core/knowledge
3
+ * Single engine for capturing workspace/base knowledge, shared by the CLI
4
+ * `knowledge` command, the dashboard server, and the MCP `add_knowledge` /
5
+ * `promote_knowledge` tools.
6
+ *
7
+ * All file I/O routes through the active storage adapter (`core/storage.ts`)
8
+ * so the local, central-vault, and Obsidian backends keep working — the
9
+ * previous direct-`fs` knowledge routes silently wrote to a file the
10
+ * generators never read under the Obsidian adapter.
11
+ *
12
+ * The markdown helpers (`insertUnderHeading`, `formatEntry`,
13
+ * `parseKnowledgeEntries`) are pure and section-aware: they insert under the
14
+ * correct heading, tolerate leading YAML frontmatter (Obsidian) and CRLF, and
15
+ * never destroy existing content.
16
+ */
17
+ /** Categories of learning that can be captured. */
18
+ export type KnowledgeEntryType = 'decision' | 'gotcha' | 'progress' | 'assumption' | 'question';
19
+ /** A learning to record. */
20
+ export interface KnowledgeEntry {
21
+ type: KnowledgeEntryType;
22
+ message: string;
23
+ /** Short title, used for the heading of a `decision` entry. */
24
+ title?: string;
25
+ /** ISO timestamp; defaults to now. */
26
+ timestamp?: string;
27
+ }
28
+ /** Result of writing a knowledge entry. */
29
+ export interface KnowledgeWriteResult {
30
+ /** Storage-resolved location (adapter URL) of the file written. */
31
+ location: string;
32
+ /** The heading the entry landed under. */
33
+ section: string;
34
+ /** True when the file did not exist and was created. */
35
+ createdFile: boolean;
36
+ }
37
+ /** A parsed existing entry, used when promoting learnings. */
38
+ export interface ParsedKnowledgeEntry {
39
+ /** The `## ` section heading the entry was found under. */
40
+ section: string;
41
+ /** The category, or `null` when the section is not a recognized knowledge section. */
42
+ type: KnowledgeEntryType | null;
43
+ /** The raw markdown of the entry (a bullet line or a `### ` decision block). */
44
+ text: string;
45
+ }
46
+ /** Options for {@link promoteKnowledge}. */
47
+ export interface PromoteOptions {
48
+ repoName: string;
49
+ entries: ParsedKnowledgeEntry[];
50
+ /** `copy` (default) keeps the workspace entry; `move` replaces it with a note. */
51
+ mode?: 'copy' | 'move';
52
+ }
53
+ /** Result of {@link promoteKnowledge}. */
54
+ export interface PromoteResult {
55
+ promotedCount: number;
56
+ baseLocation: string;
57
+ }
58
+ /**
59
+ * Formats a {@link KnowledgeEntry} as the markdown snippet to insert.
60
+ *
61
+ * @param entry - The entry to format.
62
+ * @param _target - Reserved for target-specific formatting (currently identical).
63
+ */
64
+ export declare function formatEntry(entry: KnowledgeEntry, _target: 'workspace' | 'base'): string;
65
+ /**
66
+ * Inserts `entryMarkdown` under the first `## ` heading matching any of
67
+ * `headingAliases` (case-insensitive), stripping placeholder lines. When no
68
+ * matching heading exists, a new section is appended at end-of-file using the
69
+ * first alias. Existing content is never removed. CRLF line endings and a
70
+ * leading YAML frontmatter block are preserved.
71
+ *
72
+ * @returns The updated markdown.
73
+ */
74
+ export declare function insertUnderHeading(markdown: string, headingAliases: string[], entryMarkdown: string): string;
75
+ /**
76
+ * Parses a knowledge markdown document into individual entries: bullet lines
77
+ * and `### ` decision blocks, each tagged with its section and inferred type.
78
+ * Placeholder and comment lines are ignored.
79
+ */
80
+ export declare function parseKnowledgeEntries(markdown: string): ParsedKnowledgeEntry[];
81
+ /** Reads the workspace knowledge file, or `null` when it does not exist. */
82
+ export declare function readWorkspaceKnowledge(workspacePath: string): Promise<string | null>;
83
+ /** Reads a repo's base knowledge file, or `null` when it does not exist. */
84
+ export declare function readBaseKnowledge(workspacePath: string, repoName: string): Promise<string | null>;
85
+ /** Appends a formatted entry to the workspace knowledge file (creating it if missing). */
86
+ export declare function addWorkspaceKnowledge(workspacePath: string, entry: KnowledgeEntry): Promise<KnowledgeWriteResult>;
87
+ /** Appends a formatted entry to a repo's base knowledge file (creating it if missing). */
88
+ export declare function addBaseKnowledge(workspacePath: string, repoName: string, entry: KnowledgeEntry): Promise<KnowledgeWriteResult>;
89
+ /**
90
+ * Promotes selected workspace learnings into a repo's persistent base
91
+ * knowledge so they survive across features. Entries whose type is not
92
+ * promotable (progress, question) are skipped. In `move` mode the source entry
93
+ * in the workspace file is replaced with an auditable "promoted" note.
94
+ */
95
+ export declare function promoteKnowledge(workspacePath: string, options: PromoteOptions): Promise<PromoteResult>;
96
+ //# sourceMappingURL=knowledge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"knowledge.d.ts","sourceRoot":"","sources":["../../src/core/knowledge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAoBH,mDAAmD;AACnD,MAAM,MAAM,kBAAkB,GAC1B,UAAU,GACV,QAAQ,GACR,UAAU,GACV,YAAY,GACZ,UAAU,CAAC;AAEf,4BAA4B;AAC5B,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,kBAAkB,CAAC;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sCAAsC;IACtC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,2CAA2C;AAC3C,MAAM,WAAW,oBAAoB;IACnC,mEAAmE;IACnE,QAAQ,EAAE,MAAM,CAAC;IACjB,0CAA0C;IAC1C,OAAO,EAAE,MAAM,CAAC;IAChB,wDAAwD;IACxD,WAAW,EAAE,OAAO,CAAC;CACtB;AAED,8DAA8D;AAC9D,MAAM,WAAW,oBAAoB;IACnC,2DAA2D;IAC3D,OAAO,EAAE,MAAM,CAAC;IAChB,sFAAsF;IACtF,IAAI,EAAE,kBAAkB,GAAG,IAAI,CAAC;IAChC,gFAAgF;IAChF,IAAI,EAAE,MAAM,CAAC;CACd;AAED,4CAA4C;AAC5C,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,oBAAoB,EAAE,CAAC;IAChC,kFAAkF;IAClF,IAAI,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;CACxB;AAED,0CAA0C;AAC1C,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,CAAC;CACtB;AAqDD;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,WAAW,GAAG,MAAM,GAAG,MAAM,CAiBxF;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAChC,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,EAAE,EACxB,aAAa,EAAE,MAAM,GACpB,MAAM,CAgDR;AAiBD;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,QAAQ,EAAE,MAAM,GAAG,oBAAoB,EAAE,CAiD9E;AAeD,4EAA4E;AAC5E,wBAAsB,sBAAsB,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAM1F;AAED,4EAA4E;AAC5E,wBAAsB,iBAAiB,CACrC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAKxB;AAED,0FAA0F;AAC1F,wBAAsB,qBAAqB,CACzC,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,oBAAoB,CAAC,CAiB/B;AA+BD,0FAA0F;AAC1F,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,cAAc,GACpB,OAAO,CAAC,oBAAoB,CAAC,CAG/B;AAED;;;;;GAKG;AACH,wBAAsB,gBAAgB,CACpC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,cAAc,GACtB,OAAO,CAAC,aAAa,CAAC,CA2BxB"}