mulch-cli 0.4.3 → 0.6.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 (193) hide show
  1. package/README.md +24 -4
  2. package/package.json +11 -16
  3. package/src/api.ts +310 -0
  4. package/src/cli.ts +54 -0
  5. package/src/commands/add.ts +61 -0
  6. package/src/commands/compact.ts +924 -0
  7. package/src/commands/delete.ts +103 -0
  8. package/src/commands/diff.ts +209 -0
  9. package/src/commands/doctor.ts +586 -0
  10. package/src/commands/edit.ts +253 -0
  11. package/src/commands/init.ts +33 -0
  12. package/src/commands/learn.ts +170 -0
  13. package/src/commands/onboard.ts +362 -0
  14. package/src/commands/prime.ts +327 -0
  15. package/src/commands/prune.ts +128 -0
  16. package/src/commands/query.ts +177 -0
  17. package/src/commands/ready.ts +194 -0
  18. package/src/commands/record.ts +959 -0
  19. package/src/commands/search.ts +234 -0
  20. package/src/commands/setup.ts +823 -0
  21. package/src/commands/status.ts +83 -0
  22. package/src/commands/sync.ts +224 -0
  23. package/src/commands/update.ts +112 -0
  24. package/src/commands/validate.ts +107 -0
  25. package/src/index.ts +50 -0
  26. package/src/schemas/config.ts +31 -0
  27. package/src/schemas/index.ts +18 -0
  28. package/src/schemas/record-schema.ts +177 -0
  29. package/src/schemas/record.ts +83 -0
  30. package/src/utils/bm25.ts +243 -0
  31. package/src/utils/budget.ts +157 -0
  32. package/src/utils/config.ts +117 -0
  33. package/src/utils/expertise.ts +379 -0
  34. package/src/utils/format.ts +767 -0
  35. package/src/utils/git.ts +89 -0
  36. package/src/utils/index.ts +54 -0
  37. package/src/utils/json-output.ts +13 -0
  38. package/src/utils/lock.ts +82 -0
  39. package/src/utils/markers.ts +51 -0
  40. package/src/utils/scoring.ts +101 -0
  41. package/src/utils/version.ts +46 -0
  42. package/dist/cli.d.ts +0 -3
  43. package/dist/cli.d.ts.map +0 -1
  44. package/dist/cli.js +0 -50
  45. package/dist/cli.js.map +0 -1
  46. package/dist/commands/add.d.ts +0 -3
  47. package/dist/commands/add.d.ts.map +0 -1
  48. package/dist/commands/add.js +0 -47
  49. package/dist/commands/add.js.map +0 -1
  50. package/dist/commands/compact.d.ts +0 -5
  51. package/dist/commands/compact.d.ts.map +0 -1
  52. package/dist/commands/compact.js +0 -709
  53. package/dist/commands/compact.js.map +0 -1
  54. package/dist/commands/delete.d.ts +0 -3
  55. package/dist/commands/delete.d.ts.map +0 -1
  56. package/dist/commands/delete.js +0 -82
  57. package/dist/commands/delete.js.map +0 -1
  58. package/dist/commands/diff.d.ts +0 -11
  59. package/dist/commands/diff.d.ts.map +0 -1
  60. package/dist/commands/diff.js +0 -170
  61. package/dist/commands/diff.js.map +0 -1
  62. package/dist/commands/doctor.d.ts +0 -3
  63. package/dist/commands/doctor.d.ts.map +0 -1
  64. package/dist/commands/doctor.js +0 -391
  65. package/dist/commands/doctor.js.map +0 -1
  66. package/dist/commands/edit.d.ts +0 -3
  67. package/dist/commands/edit.d.ts.map +0 -1
  68. package/dist/commands/edit.js +0 -210
  69. package/dist/commands/edit.js.map +0 -1
  70. package/dist/commands/init.d.ts +0 -3
  71. package/dist/commands/init.d.ts.map +0 -1
  72. package/dist/commands/init.js +0 -30
  73. package/dist/commands/init.js.map +0 -1
  74. package/dist/commands/learn.d.ts +0 -12
  75. package/dist/commands/learn.d.ts.map +0 -1
  76. package/dist/commands/learn.js +0 -130
  77. package/dist/commands/learn.js.map +0 -1
  78. package/dist/commands/onboard.d.ts +0 -10
  79. package/dist/commands/onboard.d.ts.map +0 -1
  80. package/dist/commands/onboard.js +0 -286
  81. package/dist/commands/onboard.js.map +0 -1
  82. package/dist/commands/prime.d.ts +0 -3
  83. package/dist/commands/prime.d.ts.map +0 -1
  84. package/dist/commands/prime.js +0 -242
  85. package/dist/commands/prime.js.map +0 -1
  86. package/dist/commands/prune.d.ts +0 -8
  87. package/dist/commands/prune.d.ts.map +0 -1
  88. package/dist/commands/prune.js +0 -90
  89. package/dist/commands/prune.js.map +0 -1
  90. package/dist/commands/query.d.ts +0 -3
  91. package/dist/commands/query.d.ts.map +0 -1
  92. package/dist/commands/query.js +0 -118
  93. package/dist/commands/query.js.map +0 -1
  94. package/dist/commands/ready.d.ts +0 -3
  95. package/dist/commands/ready.d.ts.map +0 -1
  96. package/dist/commands/ready.js +0 -160
  97. package/dist/commands/ready.js.map +0 -1
  98. package/dist/commands/record.d.ts +0 -13
  99. package/dist/commands/record.d.ts.map +0 -1
  100. package/dist/commands/record.js +0 -688
  101. package/dist/commands/record.js.map +0 -1
  102. package/dist/commands/search.d.ts +0 -3
  103. package/dist/commands/search.d.ts.map +0 -1
  104. package/dist/commands/search.js +0 -163
  105. package/dist/commands/search.js.map +0 -1
  106. package/dist/commands/setup.d.ts +0 -29
  107. package/dist/commands/setup.d.ts.map +0 -1
  108. package/dist/commands/setup.js +0 -548
  109. package/dist/commands/setup.js.map +0 -1
  110. package/dist/commands/status.d.ts +0 -3
  111. package/dist/commands/status.d.ts.map +0 -1
  112. package/dist/commands/status.js +0 -61
  113. package/dist/commands/status.js.map +0 -1
  114. package/dist/commands/sync.d.ts +0 -3
  115. package/dist/commands/sync.d.ts.map +0 -1
  116. package/dist/commands/sync.js +0 -176
  117. package/dist/commands/sync.js.map +0 -1
  118. package/dist/commands/update.d.ts +0 -3
  119. package/dist/commands/update.d.ts.map +0 -1
  120. package/dist/commands/update.js +0 -72
  121. package/dist/commands/update.js.map +0 -1
  122. package/dist/commands/validate.d.ts +0 -3
  123. package/dist/commands/validate.d.ts.map +0 -1
  124. package/dist/commands/validate.js +0 -86
  125. package/dist/commands/validate.js.map +0 -1
  126. package/dist/index.d.ts +0 -7
  127. package/dist/index.d.ts.map +0 -1
  128. package/dist/index.js +0 -8
  129. package/dist/index.js.map +0 -1
  130. package/dist/schemas/config.d.ts +0 -17
  131. package/dist/schemas/config.d.ts.map +0 -1
  132. package/dist/schemas/config.js +0 -16
  133. package/dist/schemas/config.js.map +0 -1
  134. package/dist/schemas/index.d.ts +0 -5
  135. package/dist/schemas/index.d.ts.map +0 -1
  136. package/dist/schemas/index.js +0 -3
  137. package/dist/schemas/index.js.map +0 -1
  138. package/dist/schemas/record-schema.d.ts +0 -379
  139. package/dist/schemas/record-schema.d.ts.map +0 -1
  140. package/dist/schemas/record-schema.js +0 -148
  141. package/dist/schemas/record-schema.js.map +0 -1
  142. package/dist/schemas/record.d.ts +0 -60
  143. package/dist/schemas/record.d.ts.map +0 -1
  144. package/dist/schemas/record.js +0 -2
  145. package/dist/schemas/record.js.map +0 -1
  146. package/dist/utils/bm25.d.ts +0 -39
  147. package/dist/utils/bm25.d.ts.map +0 -1
  148. package/dist/utils/bm25.js +0 -171
  149. package/dist/utils/bm25.js.map +0 -1
  150. package/dist/utils/budget.d.ts +0 -35
  151. package/dist/utils/budget.d.ts.map +0 -1
  152. package/dist/utils/budget.js +0 -114
  153. package/dist/utils/budget.js.map +0 -1
  154. package/dist/utils/config.d.ts +0 -12
  155. package/dist/utils/config.d.ts.map +0 -1
  156. package/dist/utils/config.js +0 -89
  157. package/dist/utils/config.js.map +0 -1
  158. package/dist/utils/expertise.d.ts +0 -57
  159. package/dist/utils/expertise.d.ts.map +0 -1
  160. package/dist/utils/expertise.js +0 -264
  161. package/dist/utils/expertise.js.map +0 -1
  162. package/dist/utils/format.d.ts +0 -31
  163. package/dist/utils/format.d.ts.map +0 -1
  164. package/dist/utils/format.js +0 -556
  165. package/dist/utils/format.js.map +0 -1
  166. package/dist/utils/git.d.ts +0 -6
  167. package/dist/utils/git.d.ts.map +0 -1
  168. package/dist/utils/git.js +0 -81
  169. package/dist/utils/git.js.map +0 -1
  170. package/dist/utils/index.d.ts +0 -8
  171. package/dist/utils/index.d.ts.map +0 -1
  172. package/dist/utils/index.js +0 -8
  173. package/dist/utils/index.js.map +0 -1
  174. package/dist/utils/json-output.d.ts +0 -8
  175. package/dist/utils/json-output.d.ts.map +0 -1
  176. package/dist/utils/json-output.js +0 -7
  177. package/dist/utils/json-output.js.map +0 -1
  178. package/dist/utils/lock.d.ts +0 -6
  179. package/dist/utils/lock.d.ts.map +0 -1
  180. package/dist/utils/lock.js +0 -70
  181. package/dist/utils/lock.js.map +0 -1
  182. package/dist/utils/markers.d.ts +0 -22
  183. package/dist/utils/markers.d.ts.map +0 -1
  184. package/dist/utils/markers.js +0 -42
  185. package/dist/utils/markers.js.map +0 -1
  186. package/dist/utils/scoring.d.ts +0 -73
  187. package/dist/utils/scoring.d.ts.map +0 -1
  188. package/dist/utils/scoring.js +0 -80
  189. package/dist/utils/scoring.js.map +0 -1
  190. package/dist/utils/version.d.ts +0 -15
  191. package/dist/utils/version.d.ts.map +0 -1
  192. package/dist/utils/version.js +0 -48
  193. package/dist/utils/version.js.map +0 -1
@@ -0,0 +1,89 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import type { ExpertiseRecord } from "../schemas/record.ts";
3
+
4
+ export function isGitRepo(cwd: string): boolean {
5
+ try {
6
+ execFileSync("git", ["rev-parse", "--is-inside-work-tree"], {
7
+ cwd,
8
+ stdio: "pipe",
9
+ });
10
+ return true;
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ export function getChangedFiles(cwd: string, since: string): string[] {
17
+ const files = new Set<string>();
18
+
19
+ // Committed changes (since ref)
20
+ try {
21
+ const committed = execFileSync("git", ["diff", "--name-only", since], {
22
+ cwd,
23
+ encoding: "utf-8",
24
+ stdio: ["pipe", "pipe", "pipe"],
25
+ }).trim();
26
+ if (committed) {
27
+ for (const f of committed.split("\n")) {
28
+ if (f) files.add(f);
29
+ }
30
+ }
31
+ } catch {
32
+ // ref might not exist (e.g., first commit) — fall through
33
+ }
34
+
35
+ // Staged but uncommitted changes
36
+ try {
37
+ const staged = execFileSync("git", ["diff", "--name-only", "--cached"], {
38
+ cwd,
39
+ encoding: "utf-8",
40
+ stdio: ["pipe", "pipe", "pipe"],
41
+ }).trim();
42
+ if (staged) {
43
+ for (const f of staged.split("\n")) {
44
+ if (f) files.add(f);
45
+ }
46
+ }
47
+ } catch {
48
+ // ignore
49
+ }
50
+
51
+ // Unstaged working tree changes
52
+ try {
53
+ const unstaged = execFileSync("git", ["diff", "--name-only"], {
54
+ cwd,
55
+ encoding: "utf-8",
56
+ stdio: ["pipe", "pipe", "pipe"],
57
+ }).trim();
58
+ if (unstaged) {
59
+ for (const f of unstaged.split("\n")) {
60
+ if (f) files.add(f);
61
+ }
62
+ }
63
+ } catch {
64
+ // ignore
65
+ }
66
+
67
+ return [...files].sort();
68
+ }
69
+
70
+ export function fileMatchesAny(file: string, changedFiles: string[]): boolean {
71
+ return changedFiles.some(
72
+ (changed) =>
73
+ changed === file || changed.endsWith(file) || file.endsWith(changed),
74
+ );
75
+ }
76
+
77
+ export function filterByContext(
78
+ records: ExpertiseRecord[],
79
+ changedFiles: string[],
80
+ ): ExpertiseRecord[] {
81
+ return records.filter((r) => {
82
+ // Records without a files field are always relevant (conventions, failures, decisions, guides)
83
+ if (!("files" in r) || !r.files || r.files.length === 0) {
84
+ return true;
85
+ }
86
+ // Records with files: keep if any file matches a changed file
87
+ return r.files.some((f) => fileMatchesAny(f, changedFiles));
88
+ });
89
+ }
@@ -0,0 +1,54 @@
1
+ export {
2
+ getMulchDir,
3
+ getConfigPath,
4
+ getExpertiseDir,
5
+ getExpertisePath,
6
+ readConfig,
7
+ writeConfig,
8
+ initMulchDir,
9
+ } from "./config.ts";
10
+
11
+ export {
12
+ readExpertiseFile,
13
+ appendRecord,
14
+ createExpertiseFile,
15
+ getFileModTime,
16
+ countRecords,
17
+ filterByType,
18
+ generateRecordId,
19
+ } from "./expertise.ts";
20
+
21
+ export {
22
+ formatDomainExpertise,
23
+ formatPrimeOutput,
24
+ formatStatusOutput,
25
+ formatTimeAgo,
26
+ getRecordSummary,
27
+ } from "./format.ts";
28
+
29
+ export {
30
+ outputJson,
31
+ outputJsonError,
32
+ } from "./json-output.ts";
33
+
34
+ export {
35
+ isGitRepo,
36
+ getChangedFiles,
37
+ fileMatchesAny,
38
+ filterByContext,
39
+ } from "./git.ts";
40
+
41
+ export {
42
+ MARKER_START,
43
+ MARKER_END,
44
+ hasMarkerSection,
45
+ replaceMarkerSection,
46
+ removeMarkerSection,
47
+ wrapInMarkers,
48
+ } from "./markers.ts";
49
+
50
+ export {
51
+ getCurrentVersion,
52
+ getLatestVersion,
53
+ compareSemver,
54
+ } from "./version.ts";
@@ -0,0 +1,13 @@
1
+ export interface JsonResult {
2
+ success: boolean;
3
+ command: string;
4
+ [key: string]: unknown;
5
+ }
6
+
7
+ export function outputJson(result: JsonResult): void {
8
+ console.log(JSON.stringify(result, null, 2));
9
+ }
10
+
11
+ export function outputJsonError(command: string, error: string): void {
12
+ console.error(JSON.stringify({ success: false, command, error }, null, 2));
13
+ }
@@ -0,0 +1,82 @@
1
+ import { constants } from "node:fs";
2
+ import { lstat, open, unlink } from "node:fs/promises";
3
+
4
+ const LOCK_STALE_MS = 30_000; // 30 seconds
5
+ const LOCK_RETRY_INTERVAL_MS = 50;
6
+ const LOCK_TIMEOUT_MS = 5_000; // 5 seconds
7
+
8
+ /**
9
+ * Advisory file-level lock using O_CREAT | O_EXCL.
10
+ * Wraps an async function with lock acquisition and guaranteed cleanup.
11
+ */
12
+ export async function withFileLock<T>(
13
+ filePath: string,
14
+ fn: () => Promise<T>,
15
+ ): Promise<T> {
16
+ const lockPath = `${filePath}.lock`;
17
+ await acquireLock(lockPath);
18
+ try {
19
+ return await fn();
20
+ } finally {
21
+ await releaseLock(lockPath);
22
+ }
23
+ }
24
+
25
+ async function acquireLock(lockPath: string): Promise<void> {
26
+ const deadline = Date.now() + LOCK_TIMEOUT_MS;
27
+
28
+ while (true) {
29
+ try {
30
+ const fd = await open(
31
+ lockPath,
32
+ constants.O_CREAT | constants.O_EXCL | constants.O_WRONLY,
33
+ );
34
+ await fd.close();
35
+ return;
36
+ } catch (err) {
37
+ if ((err as NodeJS.ErrnoException).code !== "EEXIST") {
38
+ throw err;
39
+ }
40
+
41
+ // Lock file exists — check if it's stale
42
+ if (await isStaleLock(lockPath)) {
43
+ try {
44
+ await unlink(lockPath);
45
+ } catch {
46
+ // Another process may have already removed it
47
+ }
48
+ continue;
49
+ }
50
+
51
+ if (Date.now() >= deadline) {
52
+ throw new Error(
53
+ `Timed out waiting for lock on ${lockPath}. If no other mulch process is running, delete the lock file manually.`,
54
+ );
55
+ }
56
+
57
+ await sleep(LOCK_RETRY_INTERVAL_MS);
58
+ }
59
+ }
60
+ }
61
+
62
+ async function isStaleLock(lockPath: string): Promise<boolean> {
63
+ try {
64
+ const stats = await lstat(lockPath);
65
+ return Date.now() - stats.mtimeMs > LOCK_STALE_MS;
66
+ } catch {
67
+ // Lock file disappeared between check — not stale, just gone
68
+ return false;
69
+ }
70
+ }
71
+
72
+ async function releaseLock(lockPath: string): Promise<void> {
73
+ try {
74
+ await unlink(lockPath);
75
+ } catch {
76
+ // Lock file already gone — acceptable
77
+ }
78
+ }
79
+
80
+ function sleep(ms: number): Promise<void> {
81
+ return new Promise((resolve) => setTimeout(resolve, ms));
82
+ }
@@ -0,0 +1,51 @@
1
+ const MARKER_START = "<!-- mulch:start -->";
2
+ const MARKER_END = "<!-- mulch:end -->";
3
+
4
+ export { MARKER_START, MARKER_END };
5
+
6
+ /**
7
+ * Check whether content contains the mulch marker section.
8
+ */
9
+ export function hasMarkerSection(content: string): boolean {
10
+ return content.includes(MARKER_START);
11
+ }
12
+
13
+ /**
14
+ * Replace the marker-bounded section with new content.
15
+ * Returns null if no markers found.
16
+ */
17
+ export function replaceMarkerSection(
18
+ content: string,
19
+ newSection: string,
20
+ ): string | null {
21
+ const startIdx = content.indexOf(MARKER_START);
22
+ const endIdx = content.indexOf(MARKER_END);
23
+ if (startIdx === -1 || endIdx === -1) return null;
24
+
25
+ const before = content.substring(0, startIdx);
26
+ const after = content.substring(endIdx + MARKER_END.length);
27
+
28
+ return before + newSection + after;
29
+ }
30
+
31
+ /**
32
+ * Remove the marker-bounded section entirely.
33
+ * Cleans up extra newlines left behind.
34
+ */
35
+ export function removeMarkerSection(content: string): string {
36
+ const startIdx = content.indexOf(MARKER_START);
37
+ const endIdx = content.indexOf(MARKER_END);
38
+ if (startIdx === -1 || endIdx === -1) return content;
39
+
40
+ const before = content.substring(0, startIdx);
41
+ const after = content.substring(endIdx + MARKER_END.length);
42
+
43
+ return `${(before + after).replace(/\n{3,}/g, "\n\n").trim()}\n`;
44
+ }
45
+
46
+ /**
47
+ * Wrap a snippet in mulch markers.
48
+ */
49
+ export function wrapInMarkers(snippet: string): string {
50
+ return `${MARKER_START}\n${snippet}${MARKER_END}`;
51
+ }
@@ -0,0 +1,101 @@
1
+ import type { ExpertiseRecord, Outcome } from "../schemas/record.ts";
2
+
3
+ export type { Outcome };
4
+
5
+ /**
6
+ * An ExpertiseRecord with outcome history for confirmation-frequency scoring.
7
+ * Since ExpertiseRecord now includes outcomes?: Outcome[], this is an alias.
8
+ */
9
+ export type ScoredRecord = ExpertiseRecord;
10
+
11
+ /**
12
+ * Returns the number of successful outcomes for a record.
13
+ * Successful outcomes indicate confirmed, working applications of the record.
14
+ */
15
+ export function getSuccessCount(record: ScoredRecord): number {
16
+ if (!record.outcomes || record.outcomes.length === 0) return 0;
17
+ return record.outcomes.filter((o) => o.status === "success").length;
18
+ }
19
+
20
+ /**
21
+ * Returns the number of failed outcomes for a record.
22
+ */
23
+ export function getFailureCount(record: ScoredRecord): number {
24
+ if (!record.outcomes || record.outcomes.length === 0) return 0;
25
+ return record.outcomes.filter((o) => o.status === "failure").length;
26
+ }
27
+
28
+ /**
29
+ * Returns the total number of recorded outcomes (applications) for a record.
30
+ */
31
+ export function getTotalApplications(record: ScoredRecord): number {
32
+ return record.outcomes?.length ?? 0;
33
+ }
34
+
35
+ /**
36
+ * Returns the success rate (0-1) for a record.
37
+ * Partial outcomes are counted as 0.5 (half success).
38
+ * Returns 0 for records with no outcomes.
39
+ */
40
+ export function getSuccessRate(record: ScoredRecord): number {
41
+ const total = getTotalApplications(record);
42
+ if (total === 0) return 0;
43
+ const partialCount =
44
+ record.outcomes?.filter((o) => o.status === "partial").length ?? 0;
45
+ const successCount = getSuccessCount(record);
46
+ return (successCount + partialCount * 0.5) / total;
47
+ }
48
+
49
+ /**
50
+ * Computes the confirmation-frequency score for a record.
51
+ *
52
+ * The score is the count of successful confirmations (applications where
53
+ * the record's guidance was applied and the outcome was "success").
54
+ * Partial outcomes contribute 0.5 to the score.
55
+ *
56
+ * Records with no outcomes return 0.
57
+ * Records with only failures return 0.
58
+ */
59
+ export function computeConfirmationScore(record: ScoredRecord): number {
60
+ if (!record.outcomes || record.outcomes.length === 0) return 0;
61
+ const successCount = getSuccessCount(record);
62
+ const partialCount = record.outcomes.filter(
63
+ (o) => o.status === "partial",
64
+ ).length;
65
+ return successCount + partialCount * 0.5;
66
+ }
67
+
68
+ /**
69
+ * Applies a confirmation-frequency boost to a base score (e.g., a BM25 relevance score).
70
+ *
71
+ * Records with no outcomes (score = 0) are returned unchanged.
72
+ * Records with confirmed applications receive a multiplicative boost proportional
73
+ * to their confirmation score.
74
+ *
75
+ * @param baseScore - The base relevance score (e.g., from BM25)
76
+ * @param record - The record to score
77
+ * @param boostFactor - Multiplier controlling boost magnitude (default: 0.1)
78
+ * @returns The boosted score
79
+ */
80
+ export function applyConfirmationBoost(
81
+ baseScore: number,
82
+ record: ScoredRecord,
83
+ boostFactor = 0.1,
84
+ ): number {
85
+ const confirmationScore = computeConfirmationScore(record);
86
+ if (confirmationScore === 0) return baseScore;
87
+ return baseScore * (1 + boostFactor * confirmationScore);
88
+ }
89
+
90
+ /**
91
+ * Sorts records by confirmation-frequency score, highest first.
92
+ * Records with equal scores maintain their original relative order (stable sort).
93
+ * Records with no outcomes (score = 0) sort to the end.
94
+ */
95
+ export function sortByConfirmationScore<T extends ScoredRecord>(
96
+ records: T[],
97
+ ): T[] {
98
+ return [...records].sort(
99
+ (a, b) => computeConfirmationScore(b) - computeConfirmationScore(a),
100
+ );
101
+ }
@@ -0,0 +1,46 @@
1
+ import { execSync } from "node:child_process";
2
+ import { readFileSync } from "node:fs";
3
+ import { join } from "node:path";
4
+
5
+ /**
6
+ * Read the current CLI version from package.json.
7
+ */
8
+ export function getCurrentVersion(): string {
9
+ const pkgPath = join(import.meta.dir, "..", "..", "package.json");
10
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8")) as { version: string };
11
+ return pkg.version;
12
+ }
13
+
14
+ /**
15
+ * Fetch the latest published version of mulch-cli from npm.
16
+ * Returns null if the registry is unreachable.
17
+ */
18
+ export function getLatestVersion(): string | null {
19
+ try {
20
+ const result = execSync("npm view mulch-cli version", {
21
+ encoding: "utf-8",
22
+ timeout: 10000,
23
+ stdio: ["pipe", "pipe", "pipe"],
24
+ });
25
+ return result.trim();
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+
31
+ /**
32
+ * Compare two semver strings (major.minor.patch).
33
+ * Returns -1 if a < b, 0 if equal, 1 if a > b.
34
+ */
35
+ export function compareSemver(a: string, b: string): -1 | 0 | 1 {
36
+ const partsA = a.split(".").map(Number);
37
+ const partsB = b.split(".").map(Number);
38
+
39
+ for (let i = 0; i < 3; i++) {
40
+ const segA = partsA[i] ?? 0;
41
+ const segB = partsB[i] ?? 0;
42
+ if (segA < segB) return -1;
43
+ if (segA > segB) return 1;
44
+ }
45
+ return 0;
46
+ }
package/dist/cli.d.ts DELETED
@@ -1,3 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
3
- //# sourceMappingURL=cli.d.ts.map
package/dist/cli.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":""}
package/dist/cli.js DELETED
@@ -1,50 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Command } from "commander";
3
- import { registerInitCommand } from "./commands/init.js";
4
- import { registerAddCommand } from "./commands/add.js";
5
- import { registerRecordCommand } from "./commands/record.js";
6
- import { registerEditCommand } from "./commands/edit.js";
7
- import { registerQueryCommand } from "./commands/query.js";
8
- import { registerSetupCommand } from "./commands/setup.js";
9
- import { registerPrimeCommand } from "./commands/prime.js";
10
- import { registerOnboardCommand } from "./commands/onboard.js";
11
- import { registerStatusCommand } from "./commands/status.js";
12
- import { registerValidateCommand } from "./commands/validate.js";
13
- import { registerPruneCommand } from "./commands/prune.js";
14
- import { registerSearchCommand } from "./commands/search.js";
15
- import { registerDoctorCommand } from "./commands/doctor.js";
16
- import { registerReadyCommand } from "./commands/ready.js";
17
- import { registerSyncCommand } from "./commands/sync.js";
18
- import { registerDeleteCommand } from "./commands/delete.js";
19
- import { registerLearnCommand } from "./commands/learn.js";
20
- import { registerCompactCommand } from "./commands/compact.js";
21
- import { registerUpdateCommand } from "./commands/update.js";
22
- import { registerDiffCommand } from "./commands/diff.js";
23
- const program = new Command();
24
- program
25
- .name("mulch")
26
- .description("Let your agents grow 🌱")
27
- .version("0.4.3")
28
- .option("--json", "output as structured JSON");
29
- registerInitCommand(program);
30
- registerAddCommand(program);
31
- registerRecordCommand(program);
32
- registerEditCommand(program);
33
- registerQueryCommand(program);
34
- registerSetupCommand(program);
35
- registerPrimeCommand(program);
36
- registerOnboardCommand(program);
37
- registerStatusCommand(program);
38
- registerValidateCommand(program);
39
- registerPruneCommand(program);
40
- registerSearchCommand(program);
41
- registerDoctorCommand(program);
42
- registerReadyCommand(program);
43
- registerSyncCommand(program);
44
- registerDeleteCommand(program);
45
- registerLearnCommand(program);
46
- registerCompactCommand(program);
47
- registerDiffCommand(program);
48
- registerUpdateCommand(program);
49
- program.parse();
50
- //# sourceMappingURL=cli.js.map
package/dist/cli.js.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,uBAAuB,EAAE,MAAM,wBAAwB,CAAC;AACjE,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAC/D,OAAO,EAAE,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAC7D,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AAEzD,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,yBAAyB,CAAC;KACtC,OAAO,CAAC,OAAO,CAAC;KAChB,MAAM,CAAC,QAAQ,EAAE,2BAA2B,CAAC,CAAC;AAEjD,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,kBAAkB,CAAC,OAAO,CAAC,CAAC;AAC5B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,uBAAuB,CAAC,OAAO,CAAC,CAAC;AACjC,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;AAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;AAChC,mBAAmB,CAAC,OAAO,CAAC,CAAC;AAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;AAE/B,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -1,3 +0,0 @@
1
- import { Command } from "commander";
2
- export declare function registerAddCommand(program: Command): void;
3
- //# sourceMappingURL=add.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAMpC,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA+CzD"}
@@ -1,47 +0,0 @@
1
- import { existsSync } from "node:fs";
2
- import chalk from "chalk";
3
- import { getMulchDir, readConfig, writeConfig, getExpertisePath } from "../utils/config.js";
4
- import { createExpertiseFile } from "../utils/expertise.js";
5
- import { outputJson, outputJsonError } from "../utils/json-output.js";
6
- export function registerAddCommand(program) {
7
- program
8
- .command("add")
9
- .argument("<domain>", "expertise domain to add")
10
- .description("Add a new expertise domain")
11
- .action(async (domain) => {
12
- const jsonMode = program.opts().json === true;
13
- const mulchDir = getMulchDir();
14
- if (!existsSync(mulchDir)) {
15
- if (jsonMode) {
16
- outputJsonError("add", "No .mulch/ directory found. Run `mulch init` first.");
17
- }
18
- else {
19
- console.error(chalk.red("No .mulch/ directory found. Run `mulch init` first."));
20
- }
21
- process.exitCode = 1;
22
- return;
23
- }
24
- const config = await readConfig();
25
- if (config.domains.includes(domain)) {
26
- if (jsonMode) {
27
- outputJsonError("add", `Domain "${domain}" already exists.`);
28
- }
29
- else {
30
- console.error(chalk.red(`Domain "${domain}" already exists.`));
31
- }
32
- process.exitCode = 1;
33
- return;
34
- }
35
- const expertisePath = getExpertisePath(domain);
36
- await createExpertiseFile(expertisePath);
37
- config.domains.push(domain);
38
- await writeConfig(config);
39
- if (jsonMode) {
40
- outputJson({ success: true, command: "add", domain });
41
- }
42
- else {
43
- console.log(chalk.green(`Added domain "${domain}".`));
44
- }
45
- });
46
- }
47
- //# sourceMappingURL=add.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/commands/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AAErC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAC5F,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAEtE,MAAM,UAAU,kBAAkB,CAAC,OAAgB;IACjD,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,QAAQ,CAAC,UAAU,EAAE,yBAAyB,CAAC;SAC/C,WAAW,CAAC,4BAA4B,CAAC;SACzC,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,EAAE;QAC/B,MAAM,QAAQ,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,IAAI,KAAK,IAAI,CAAC;QAC9C,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;QAE/B,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;YAC1B,IAAI,QAAQ,EAAE,CAAC;gBACb,eAAe,CAAC,KAAK,EAAE,qDAAqD,CAAC,CAAC;YAChF,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,qDAAqD,CAAC,CACjE,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,MAAM,GAAG,MAAM,UAAU,EAAE,CAAC;QAElC,IAAI,MAAM,CAAC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC;YACpC,IAAI,QAAQ,EAAE,CAAC;gBACb,eAAe,CAAC,KAAK,EAAE,WAAW,MAAM,mBAAmB,CAAC,CAAC;YAC/D,CAAC;iBAAM,CAAC;gBACN,OAAO,CAAC,KAAK,CACX,KAAK,CAAC,GAAG,CAAC,WAAW,MAAM,mBAAmB,CAAC,CAChD,CAAC;YACJ,CAAC;YACD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;YACrB,OAAO;QACT,CAAC;QAED,MAAM,aAAa,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;QAC/C,MAAM,mBAAmB,CAAC,aAAa,CAAC,CAAC;QAEzC,MAAM,CAAC,OAAO,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;QAC5B,MAAM,WAAW,CAAC,MAAM,CAAC,CAAC;QAE1B,IAAI,QAAQ,EAAE,CAAC;YACb,UAAU,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QACxD,CAAC;aAAM,CAAC;YACN,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iBAAiB,MAAM,IAAI,CAAC,CAAC,CAAC;QACxD,CAAC;IACH,CAAC,CAAC,CAAC;AACP,CAAC"}
@@ -1,5 +0,0 @@
1
- import { Command } from "commander";
2
- import type { ExpertiseRecord } from "../schemas/record.js";
3
- export declare function registerCompactCommand(program: Command): void;
4
- export declare function mergeRecords(records: ExpertiseRecord[]): ExpertiseRecord;
5
- //# sourceMappingURL=compact.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"compact.d.ts","sourceRoot":"","sources":["../../src/commands/compact.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAYpC,OAAO,KAAK,EACV,eAAe,EAEhB,MAAM,sBAAsB,CAAC;AAwF9B,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAsD7D;AA+RD,wBAAgB,YAAY,CAAC,OAAO,EAAE,eAAe,EAAE,GAAG,eAAe,CAwIxE"}