mulch-cli 0.5.0 → 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 (196) hide show
  1. package/README.md +12 -1
  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/{dist/utils/scoring.d.ts → src/utils/scoring.ts} +53 -9
  41. package/src/utils/version.ts +46 -0
  42. package/dist/api.d.ts +0 -65
  43. package/dist/api.d.ts.map +0 -1
  44. package/dist/api.js +0 -196
  45. package/dist/api.js.map +0 -1
  46. package/dist/cli.d.ts +0 -3
  47. package/dist/cli.d.ts.map +0 -1
  48. package/dist/cli.js +0 -50
  49. package/dist/cli.js.map +0 -1
  50. package/dist/commands/add.d.ts +0 -3
  51. package/dist/commands/add.d.ts.map +0 -1
  52. package/dist/commands/add.js +0 -47
  53. package/dist/commands/add.js.map +0 -1
  54. package/dist/commands/compact.d.ts +0 -5
  55. package/dist/commands/compact.d.ts.map +0 -1
  56. package/dist/commands/compact.js +0 -709
  57. package/dist/commands/compact.js.map +0 -1
  58. package/dist/commands/delete.d.ts +0 -3
  59. package/dist/commands/delete.d.ts.map +0 -1
  60. package/dist/commands/delete.js +0 -82
  61. package/dist/commands/delete.js.map +0 -1
  62. package/dist/commands/diff.d.ts +0 -11
  63. package/dist/commands/diff.d.ts.map +0 -1
  64. package/dist/commands/diff.js +0 -170
  65. package/dist/commands/diff.js.map +0 -1
  66. package/dist/commands/doctor.d.ts +0 -3
  67. package/dist/commands/doctor.d.ts.map +0 -1
  68. package/dist/commands/doctor.js +0 -391
  69. package/dist/commands/doctor.js.map +0 -1
  70. package/dist/commands/edit.d.ts +0 -3
  71. package/dist/commands/edit.d.ts.map +0 -1
  72. package/dist/commands/edit.js +0 -198
  73. package/dist/commands/edit.js.map +0 -1
  74. package/dist/commands/init.d.ts +0 -3
  75. package/dist/commands/init.d.ts.map +0 -1
  76. package/dist/commands/init.js +0 -30
  77. package/dist/commands/init.js.map +0 -1
  78. package/dist/commands/learn.d.ts +0 -12
  79. package/dist/commands/learn.d.ts.map +0 -1
  80. package/dist/commands/learn.js +0 -130
  81. package/dist/commands/learn.js.map +0 -1
  82. package/dist/commands/onboard.d.ts +0 -10
  83. package/dist/commands/onboard.d.ts.map +0 -1
  84. package/dist/commands/onboard.js +0 -286
  85. package/dist/commands/onboard.js.map +0 -1
  86. package/dist/commands/prime.d.ts +0 -3
  87. package/dist/commands/prime.d.ts.map +0 -1
  88. package/dist/commands/prime.js +0 -242
  89. package/dist/commands/prime.js.map +0 -1
  90. package/dist/commands/prune.d.ts +0 -8
  91. package/dist/commands/prune.d.ts.map +0 -1
  92. package/dist/commands/prune.js +0 -90
  93. package/dist/commands/prune.js.map +0 -1
  94. package/dist/commands/query.d.ts +0 -3
  95. package/dist/commands/query.d.ts.map +0 -1
  96. package/dist/commands/query.js +0 -133
  97. package/dist/commands/query.js.map +0 -1
  98. package/dist/commands/ready.d.ts +0 -3
  99. package/dist/commands/ready.d.ts.map +0 -1
  100. package/dist/commands/ready.js +0 -160
  101. package/dist/commands/ready.js.map +0 -1
  102. package/dist/commands/record.d.ts +0 -13
  103. package/dist/commands/record.d.ts.map +0 -1
  104. package/dist/commands/record.js +0 -689
  105. package/dist/commands/record.js.map +0 -1
  106. package/dist/commands/search.d.ts +0 -3
  107. package/dist/commands/search.d.ts.map +0 -1
  108. package/dist/commands/search.js +0 -163
  109. package/dist/commands/search.js.map +0 -1
  110. package/dist/commands/setup.d.ts +0 -29
  111. package/dist/commands/setup.d.ts.map +0 -1
  112. package/dist/commands/setup.js +0 -548
  113. package/dist/commands/setup.js.map +0 -1
  114. package/dist/commands/status.d.ts +0 -3
  115. package/dist/commands/status.d.ts.map +0 -1
  116. package/dist/commands/status.js +0 -61
  117. package/dist/commands/status.js.map +0 -1
  118. package/dist/commands/sync.d.ts +0 -3
  119. package/dist/commands/sync.d.ts.map +0 -1
  120. package/dist/commands/sync.js +0 -176
  121. package/dist/commands/sync.js.map +0 -1
  122. package/dist/commands/update.d.ts +0 -3
  123. package/dist/commands/update.d.ts.map +0 -1
  124. package/dist/commands/update.js +0 -72
  125. package/dist/commands/update.js.map +0 -1
  126. package/dist/commands/validate.d.ts +0 -3
  127. package/dist/commands/validate.d.ts.map +0 -1
  128. package/dist/commands/validate.js +0 -86
  129. package/dist/commands/validate.js.map +0 -1
  130. package/dist/index.d.ts +0 -9
  131. package/dist/index.d.ts.map +0 -1
  132. package/dist/index.js +0 -10
  133. package/dist/index.js.map +0 -1
  134. package/dist/schemas/config.d.ts +0 -17
  135. package/dist/schemas/config.d.ts.map +0 -1
  136. package/dist/schemas/config.js +0 -16
  137. package/dist/schemas/config.js.map +0 -1
  138. package/dist/schemas/index.d.ts +0 -5
  139. package/dist/schemas/index.d.ts.map +0 -1
  140. package/dist/schemas/index.js +0 -3
  141. package/dist/schemas/index.js.map +0 -1
  142. package/dist/schemas/record-schema.d.ts +0 -403
  143. package/dist/schemas/record-schema.d.ts.map +0 -1
  144. package/dist/schemas/record-schema.js +0 -150
  145. package/dist/schemas/record-schema.js.map +0 -1
  146. package/dist/schemas/record.d.ts +0 -62
  147. package/dist/schemas/record.d.ts.map +0 -1
  148. package/dist/schemas/record.js +0 -2
  149. package/dist/schemas/record.js.map +0 -1
  150. package/dist/utils/bm25.d.ts +0 -39
  151. package/dist/utils/bm25.d.ts.map +0 -1
  152. package/dist/utils/bm25.js +0 -171
  153. package/dist/utils/bm25.js.map +0 -1
  154. package/dist/utils/budget.d.ts +0 -35
  155. package/dist/utils/budget.d.ts.map +0 -1
  156. package/dist/utils/budget.js +0 -114
  157. package/dist/utils/budget.js.map +0 -1
  158. package/dist/utils/config.d.ts +0 -12
  159. package/dist/utils/config.d.ts.map +0 -1
  160. package/dist/utils/config.js +0 -89
  161. package/dist/utils/config.js.map +0 -1
  162. package/dist/utils/expertise.d.ts +0 -57
  163. package/dist/utils/expertise.d.ts.map +0 -1
  164. package/dist/utils/expertise.js +0 -276
  165. package/dist/utils/expertise.js.map +0 -1
  166. package/dist/utils/format.d.ts +0 -31
  167. package/dist/utils/format.d.ts.map +0 -1
  168. package/dist/utils/format.js +0 -562
  169. package/dist/utils/format.js.map +0 -1
  170. package/dist/utils/git.d.ts +0 -6
  171. package/dist/utils/git.d.ts.map +0 -1
  172. package/dist/utils/git.js +0 -81
  173. package/dist/utils/git.js.map +0 -1
  174. package/dist/utils/index.d.ts +0 -8
  175. package/dist/utils/index.d.ts.map +0 -1
  176. package/dist/utils/index.js +0 -8
  177. package/dist/utils/index.js.map +0 -1
  178. package/dist/utils/json-output.d.ts +0 -8
  179. package/dist/utils/json-output.d.ts.map +0 -1
  180. package/dist/utils/json-output.js +0 -7
  181. package/dist/utils/json-output.js.map +0 -1
  182. package/dist/utils/lock.d.ts +0 -6
  183. package/dist/utils/lock.d.ts.map +0 -1
  184. package/dist/utils/lock.js +0 -70
  185. package/dist/utils/lock.js.map +0 -1
  186. package/dist/utils/markers.d.ts +0 -22
  187. package/dist/utils/markers.d.ts.map +0 -1
  188. package/dist/utils/markers.js +0 -42
  189. package/dist/utils/markers.js.map +0 -1
  190. package/dist/utils/scoring.d.ts.map +0 -1
  191. package/dist/utils/scoring.js +0 -80
  192. package/dist/utils/scoring.js.map +0 -1
  193. package/dist/utils/version.d.ts +0 -15
  194. package/dist/utils/version.d.ts.map +0 -1
  195. package/dist/utils/version.js +0 -48
  196. 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
+ }
@@ -1,29 +1,51 @@
1
- import type { ExpertiseRecord, Outcome } from "../schemas/record.js";
1
+ import type { ExpertiseRecord, Outcome } from "../schemas/record.ts";
2
+
2
3
  export type { Outcome };
4
+
3
5
  /**
4
6
  * An ExpertiseRecord with outcome history for confirmation-frequency scoring.
5
7
  * Since ExpertiseRecord now includes outcomes?: Outcome[], this is an alias.
6
8
  */
7
9
  export type ScoredRecord = ExpertiseRecord;
10
+
8
11
  /**
9
12
  * Returns the number of successful outcomes for a record.
10
13
  * Successful outcomes indicate confirmed, working applications of the record.
11
14
  */
12
- export declare function getSuccessCount(record: ScoredRecord): number;
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
+
13
20
  /**
14
21
  * Returns the number of failed outcomes for a record.
15
22
  */
16
- export declare function getFailureCount(record: ScoredRecord): number;
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
+
17
28
  /**
18
29
  * Returns the total number of recorded outcomes (applications) for a record.
19
30
  */
20
- export declare function getTotalApplications(record: ScoredRecord): number;
31
+ export function getTotalApplications(record: ScoredRecord): number {
32
+ return record.outcomes?.length ?? 0;
33
+ }
34
+
21
35
  /**
22
36
  * Returns the success rate (0-1) for a record.
23
37
  * Partial outcomes are counted as 0.5 (half success).
24
38
  * Returns 0 for records with no outcomes.
25
39
  */
26
- export declare function getSuccessRate(record: ScoredRecord): number;
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
+
27
49
  /**
28
50
  * Computes the confirmation-frequency score for a record.
29
51
  *
@@ -34,7 +56,15 @@ export declare function getSuccessRate(record: ScoredRecord): number;
34
56
  * Records with no outcomes return 0.
35
57
  * Records with only failures return 0.
36
58
  */
37
- export declare function computeConfirmationScore(record: ScoredRecord): number;
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
+
38
68
  /**
39
69
  * Applies a confirmation-frequency boost to a base score (e.g., a BM25 relevance score).
40
70
  *
@@ -47,11 +77,25 @@ export declare function computeConfirmationScore(record: ScoredRecord): number;
47
77
  * @param boostFactor - Multiplier controlling boost magnitude (default: 0.1)
48
78
  * @returns The boosted score
49
79
  */
50
- export declare function applyConfirmationBoost(baseScore: number, record: ScoredRecord, boostFactor?: number): number;
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
+
51
90
  /**
52
91
  * Sorts records by confirmation-frequency score, highest first.
53
92
  * Records with equal scores maintain their original relative order (stable sort).
54
93
  * Records with no outcomes (score = 0) sort to the end.
55
94
  */
56
- export declare function sortByConfirmationScore<T extends ScoredRecord>(records: T[]): T[];
57
- //# sourceMappingURL=scoring.d.ts.map
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/api.d.ts DELETED
@@ -1,65 +0,0 @@
1
- import type { ExpertiseRecord, Classification, Outcome } from "./schemas/record.js";
2
- export interface RecordOptions {
3
- force?: boolean;
4
- cwd?: string;
5
- }
6
- export interface RecordResult {
7
- action: "created" | "updated" | "skipped";
8
- record: ExpertiseRecord;
9
- }
10
- export interface SearchOptions {
11
- domain?: string;
12
- type?: string;
13
- tag?: string;
14
- classification?: string;
15
- file?: string;
16
- cwd?: string;
17
- }
18
- export interface SearchResult {
19
- domain: string;
20
- records: ExpertiseRecord[];
21
- }
22
- export interface QueryOptions {
23
- type?: string;
24
- classification?: string;
25
- file?: string;
26
- cwd?: string;
27
- }
28
- export interface EditOptions {
29
- cwd?: string;
30
- }
31
- export interface RecordUpdates {
32
- classification?: Classification;
33
- tags?: string[];
34
- relates_to?: string[];
35
- supersedes?: string[];
36
- outcomes?: Outcome[];
37
- content?: string;
38
- name?: string;
39
- description?: string;
40
- resolution?: string;
41
- title?: string;
42
- rationale?: string;
43
- files?: string[];
44
- }
45
- /**
46
- * Record an expertise record in the given domain.
47
- * Named record types (pattern, decision, reference, guide) are upserted on
48
- * duplicate key; convention and failure duplicates are skipped unless force=true.
49
- */
50
- export declare function recordExpertise(domain: string, record: ExpertiseRecord, options?: RecordOptions): Promise<RecordResult>;
51
- /**
52
- * Search expertise records across domains using BM25 ranking.
53
- * Returns domains with at least one matching record.
54
- */
55
- export declare function searchExpertise(query: string, options?: SearchOptions): Promise<SearchResult[]>;
56
- /**
57
- * Query all records in a domain with optional filtering.
58
- */
59
- export declare function queryDomain(domain: string, options?: QueryOptions): Promise<ExpertiseRecord[]>;
60
- /**
61
- * Edit an existing record by ID in the given domain.
62
- * Only provided fields in updates are modified; all other fields are preserved.
63
- */
64
- export declare function editRecord(domain: string, id: string, updates: RecordUpdates, options?: EditOptions): Promise<ExpertiseRecord>;
65
- //# sourceMappingURL=api.d.ts.map
package/dist/api.d.ts.map DELETED
@@ -1 +0,0 @@
1
- {"version":3,"file":"api.d.ts","sourceRoot":"","sources":["../src/api.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAE,OAAO,EAAE,MAAM,qBAAqB,CAAC;AAepF,MAAM,WAAW,aAAa;IAC5B,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,MAAM,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,eAAe,EAAE,CAAC;CAC5B;AAED,MAAM,WAAW,YAAY;IAC3B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,cAAc,CAAC,EAAE,cAAc,CAAC;IAChC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IAErB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;GAIG;AACH,wBAAsB,eAAe,CACnC,MAAM,EAAE,MAAM,EACd,MAAM,EAAE,eAAe,EACvB,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAmCvB;AAED;;;GAGG;AACH,wBAAsB,eAAe,CACnC,KAAK,EAAE,MAAM,EACb,OAAO,GAAE,aAAkB,GAC1B,OAAO,CAAC,YAAY,EAAE,CAAC,CA6CzB;AAED;;GAEG;AACH,wBAAsB,WAAW,CAC/B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,YAAiB,GACzB,OAAO,CAAC,eAAe,EAAE,CAAC,CAwB5B;AAED;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,MAAM,EAAE,MAAM,EACd,EAAE,EAAE,MAAM,EACV,OAAO,EAAE,aAAa,EACtB,OAAO,GAAE,WAAgB,GACxB,OAAO,CAAC,eAAe,CAAC,CAoG1B"}