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.
- package/README.md +12 -1
- package/package.json +11 -16
- package/src/api.ts +310 -0
- package/src/cli.ts +54 -0
- package/src/commands/add.ts +61 -0
- package/src/commands/compact.ts +924 -0
- package/src/commands/delete.ts +103 -0
- package/src/commands/diff.ts +209 -0
- package/src/commands/doctor.ts +586 -0
- package/src/commands/edit.ts +253 -0
- package/src/commands/init.ts +33 -0
- package/src/commands/learn.ts +170 -0
- package/src/commands/onboard.ts +362 -0
- package/src/commands/prime.ts +327 -0
- package/src/commands/prune.ts +128 -0
- package/src/commands/query.ts +177 -0
- package/src/commands/ready.ts +194 -0
- package/src/commands/record.ts +959 -0
- package/src/commands/search.ts +234 -0
- package/src/commands/setup.ts +823 -0
- package/src/commands/status.ts +83 -0
- package/src/commands/sync.ts +224 -0
- package/src/commands/update.ts +112 -0
- package/src/commands/validate.ts +107 -0
- package/src/index.ts +50 -0
- package/src/schemas/config.ts +31 -0
- package/src/schemas/index.ts +18 -0
- package/src/schemas/record-schema.ts +177 -0
- package/src/schemas/record.ts +83 -0
- package/src/utils/bm25.ts +243 -0
- package/src/utils/budget.ts +157 -0
- package/src/utils/config.ts +117 -0
- package/src/utils/expertise.ts +379 -0
- package/src/utils/format.ts +767 -0
- package/src/utils/git.ts +89 -0
- package/src/utils/index.ts +54 -0
- package/src/utils/json-output.ts +13 -0
- package/src/utils/lock.ts +82 -0
- package/src/utils/markers.ts +51 -0
- package/{dist/utils/scoring.d.ts → src/utils/scoring.ts} +53 -9
- package/src/utils/version.ts +46 -0
- package/dist/api.d.ts +0 -65
- package/dist/api.d.ts.map +0 -1
- package/dist/api.js +0 -196
- package/dist/api.js.map +0 -1
- package/dist/cli.d.ts +0 -3
- package/dist/cli.d.ts.map +0 -1
- package/dist/cli.js +0 -50
- package/dist/cli.js.map +0 -1
- package/dist/commands/add.d.ts +0 -3
- package/dist/commands/add.d.ts.map +0 -1
- package/dist/commands/add.js +0 -47
- package/dist/commands/add.js.map +0 -1
- package/dist/commands/compact.d.ts +0 -5
- package/dist/commands/compact.d.ts.map +0 -1
- package/dist/commands/compact.js +0 -709
- package/dist/commands/compact.js.map +0 -1
- package/dist/commands/delete.d.ts +0 -3
- package/dist/commands/delete.d.ts.map +0 -1
- package/dist/commands/delete.js +0 -82
- package/dist/commands/delete.js.map +0 -1
- package/dist/commands/diff.d.ts +0 -11
- package/dist/commands/diff.d.ts.map +0 -1
- package/dist/commands/diff.js +0 -170
- package/dist/commands/diff.js.map +0 -1
- package/dist/commands/doctor.d.ts +0 -3
- package/dist/commands/doctor.d.ts.map +0 -1
- package/dist/commands/doctor.js +0 -391
- package/dist/commands/doctor.js.map +0 -1
- package/dist/commands/edit.d.ts +0 -3
- package/dist/commands/edit.d.ts.map +0 -1
- package/dist/commands/edit.js +0 -198
- package/dist/commands/edit.js.map +0 -1
- package/dist/commands/init.d.ts +0 -3
- package/dist/commands/init.d.ts.map +0 -1
- package/dist/commands/init.js +0 -30
- package/dist/commands/init.js.map +0 -1
- package/dist/commands/learn.d.ts +0 -12
- package/dist/commands/learn.d.ts.map +0 -1
- package/dist/commands/learn.js +0 -130
- package/dist/commands/learn.js.map +0 -1
- package/dist/commands/onboard.d.ts +0 -10
- package/dist/commands/onboard.d.ts.map +0 -1
- package/dist/commands/onboard.js +0 -286
- package/dist/commands/onboard.js.map +0 -1
- package/dist/commands/prime.d.ts +0 -3
- package/dist/commands/prime.d.ts.map +0 -1
- package/dist/commands/prime.js +0 -242
- package/dist/commands/prime.js.map +0 -1
- package/dist/commands/prune.d.ts +0 -8
- package/dist/commands/prune.d.ts.map +0 -1
- package/dist/commands/prune.js +0 -90
- package/dist/commands/prune.js.map +0 -1
- package/dist/commands/query.d.ts +0 -3
- package/dist/commands/query.d.ts.map +0 -1
- package/dist/commands/query.js +0 -133
- package/dist/commands/query.js.map +0 -1
- package/dist/commands/ready.d.ts +0 -3
- package/dist/commands/ready.d.ts.map +0 -1
- package/dist/commands/ready.js +0 -160
- package/dist/commands/ready.js.map +0 -1
- package/dist/commands/record.d.ts +0 -13
- package/dist/commands/record.d.ts.map +0 -1
- package/dist/commands/record.js +0 -689
- package/dist/commands/record.js.map +0 -1
- package/dist/commands/search.d.ts +0 -3
- package/dist/commands/search.d.ts.map +0 -1
- package/dist/commands/search.js +0 -163
- package/dist/commands/search.js.map +0 -1
- package/dist/commands/setup.d.ts +0 -29
- package/dist/commands/setup.d.ts.map +0 -1
- package/dist/commands/setup.js +0 -548
- package/dist/commands/setup.js.map +0 -1
- package/dist/commands/status.d.ts +0 -3
- package/dist/commands/status.d.ts.map +0 -1
- package/dist/commands/status.js +0 -61
- package/dist/commands/status.js.map +0 -1
- package/dist/commands/sync.d.ts +0 -3
- package/dist/commands/sync.d.ts.map +0 -1
- package/dist/commands/sync.js +0 -176
- package/dist/commands/sync.js.map +0 -1
- package/dist/commands/update.d.ts +0 -3
- package/dist/commands/update.d.ts.map +0 -1
- package/dist/commands/update.js +0 -72
- package/dist/commands/update.js.map +0 -1
- package/dist/commands/validate.d.ts +0 -3
- package/dist/commands/validate.d.ts.map +0 -1
- package/dist/commands/validate.js +0 -86
- package/dist/commands/validate.js.map +0 -1
- package/dist/index.d.ts +0 -9
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -10
- package/dist/index.js.map +0 -1
- package/dist/schemas/config.d.ts +0 -17
- package/dist/schemas/config.d.ts.map +0 -1
- package/dist/schemas/config.js +0 -16
- package/dist/schemas/config.js.map +0 -1
- package/dist/schemas/index.d.ts +0 -5
- package/dist/schemas/index.d.ts.map +0 -1
- package/dist/schemas/index.js +0 -3
- package/dist/schemas/index.js.map +0 -1
- package/dist/schemas/record-schema.d.ts +0 -403
- package/dist/schemas/record-schema.d.ts.map +0 -1
- package/dist/schemas/record-schema.js +0 -150
- package/dist/schemas/record-schema.js.map +0 -1
- package/dist/schemas/record.d.ts +0 -62
- package/dist/schemas/record.d.ts.map +0 -1
- package/dist/schemas/record.js +0 -2
- package/dist/schemas/record.js.map +0 -1
- package/dist/utils/bm25.d.ts +0 -39
- package/dist/utils/bm25.d.ts.map +0 -1
- package/dist/utils/bm25.js +0 -171
- package/dist/utils/bm25.js.map +0 -1
- package/dist/utils/budget.d.ts +0 -35
- package/dist/utils/budget.d.ts.map +0 -1
- package/dist/utils/budget.js +0 -114
- package/dist/utils/budget.js.map +0 -1
- package/dist/utils/config.d.ts +0 -12
- package/dist/utils/config.d.ts.map +0 -1
- package/dist/utils/config.js +0 -89
- package/dist/utils/config.js.map +0 -1
- package/dist/utils/expertise.d.ts +0 -57
- package/dist/utils/expertise.d.ts.map +0 -1
- package/dist/utils/expertise.js +0 -276
- package/dist/utils/expertise.js.map +0 -1
- package/dist/utils/format.d.ts +0 -31
- package/dist/utils/format.d.ts.map +0 -1
- package/dist/utils/format.js +0 -562
- package/dist/utils/format.js.map +0 -1
- package/dist/utils/git.d.ts +0 -6
- package/dist/utils/git.d.ts.map +0 -1
- package/dist/utils/git.js +0 -81
- package/dist/utils/git.js.map +0 -1
- package/dist/utils/index.d.ts +0 -8
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -8
- package/dist/utils/index.js.map +0 -1
- package/dist/utils/json-output.d.ts +0 -8
- package/dist/utils/json-output.d.ts.map +0 -1
- package/dist/utils/json-output.js +0 -7
- package/dist/utils/json-output.js.map +0 -1
- package/dist/utils/lock.d.ts +0 -6
- package/dist/utils/lock.d.ts.map +0 -1
- package/dist/utils/lock.js +0 -70
- package/dist/utils/lock.js.map +0 -1
- package/dist/utils/markers.d.ts +0 -22
- package/dist/utils/markers.d.ts.map +0 -1
- package/dist/utils/markers.js +0 -42
- package/dist/utils/markers.js.map +0 -1
- package/dist/utils/scoring.d.ts.map +0 -1
- package/dist/utils/scoring.js +0 -80
- package/dist/utils/scoring.js.map +0 -1
- package/dist/utils/version.d.ts +0 -15
- package/dist/utils/version.d.ts.map +0 -1
- package/dist/utils/version.js +0 -48
- package/dist/utils/version.js.map +0 -1
package/src/utils/git.ts
ADDED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
57
|
-
|
|
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"}
|