grounder 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 +21 -20
- package/dist/agents/claude.d.ts +4 -4
- package/dist/agents/claude.d.ts.map +1 -1
- package/dist/agents/claude.js +158 -179
- package/dist/agents/claude.js.map +1 -1
- package/dist/agents/cursor.d.ts +3 -3
- package/dist/agents/cursor.d.ts.map +1 -1
- package/dist/agents/cursor.js +97 -144
- package/dist/agents/cursor.js.map +1 -1
- package/dist/agents/home-skills.d.ts +46 -0
- package/dist/agents/home-skills.d.ts.map +1 -0
- package/dist/agents/home-skills.js +83 -0
- package/dist/agents/home-skills.js.map +1 -0
- package/dist/agents/hook-fragment.d.ts +52 -0
- package/dist/agents/hook-fragment.d.ts.map +1 -0
- package/dist/agents/hook-fragment.js +76 -0
- package/dist/agents/hook-fragment.js.map +1 -0
- package/dist/agents/hook-install.d.ts +62 -0
- package/dist/agents/hook-install.d.ts.map +1 -0
- package/dist/agents/hook-install.js +74 -0
- package/dist/agents/hook-install.js.map +1 -0
- package/dist/agents/hook-runtime.d.ts +77 -10
- package/dist/agents/hook-runtime.d.ts.map +1 -1
- package/dist/agents/hook-runtime.js +274 -70
- package/dist/agents/hook-runtime.js.map +1 -1
- package/dist/agents/index.d.ts +8 -14
- package/dist/agents/index.d.ts.map +1 -1
- package/dist/agents/index.js +13 -19
- package/dist/agents/index.js.map +1 -1
- package/dist/agents/types.d.ts +56 -16
- package/dist/agents/types.d.ts.map +1 -1
- package/dist/cli.js +4 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/apply.d.ts +69 -0
- package/dist/commands/apply.d.ts.map +1 -0
- package/dist/commands/apply.js +238 -0
- package/dist/commands/apply.js.map +1 -0
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +190 -97
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/handoff/peek.d.ts +1 -1
- package/dist/commands/handoff/peek.d.ts.map +1 -1
- package/dist/commands/handoff/peek.js +28 -12
- package/dist/commands/handoff/peek.js.map +1 -1
- package/dist/commands/install-drift.d.ts +34 -0
- package/dist/commands/install-drift.d.ts.map +1 -0
- package/dist/commands/install-drift.js +72 -0
- package/dist/commands/install-drift.js.map +1 -0
- package/dist/commands/migrate.d.ts +11 -3
- package/dist/commands/migrate.d.ts.map +1 -1
- package/dist/commands/migrate.js +67 -33
- package/dist/commands/migrate.js.map +1 -1
- package/dist/commands/overview.d.ts +33 -0
- package/dist/commands/overview.d.ts.map +1 -0
- package/dist/commands/overview.js +150 -0
- package/dist/commands/overview.js.map +1 -0
- package/dist/commands/render-artifact-table.d.ts +85 -0
- package/dist/commands/render-artifact-table.d.ts.map +1 -0
- package/dist/commands/render-artifact-table.js +232 -0
- package/dist/commands/render-artifact-table.js.map +1 -0
- package/dist/commands/search.d.ts.map +1 -1
- package/dist/commands/search.js +15 -4
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +86 -13
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +269 -88
- package/dist/commands/status.js.map +1 -1
- package/dist/connector/ledger-migrations.d.ts +55 -0
- package/dist/connector/ledger-migrations.d.ts.map +1 -0
- package/dist/connector/ledger-migrations.js +94 -0
- package/dist/connector/ledger-migrations.js.map +1 -0
- package/dist/connector/state.d.ts +96 -59
- package/dist/connector/state.d.ts.map +1 -1
- package/dist/connector/state.js +165 -107
- package/dist/connector/state.js.map +1 -1
- package/dist/help.d.ts +1 -1
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +27 -5
- package/dist/help.js.map +1 -1
- package/dist/reconcile/apply.d.ts +31 -0
- package/dist/reconcile/apply.d.ts.map +1 -0
- package/dist/reconcile/apply.js +97 -0
- package/dist/reconcile/apply.js.map +1 -0
- package/dist/reconcile/core.d.ts +82 -0
- package/dist/reconcile/core.d.ts.map +1 -0
- package/dist/reconcile/core.js +163 -0
- package/dist/reconcile/core.js.map +1 -0
- package/dist/reconcile/disk.d.ts +13 -0
- package/dist/reconcile/disk.d.ts.map +1 -0
- package/dist/reconcile/disk.js +28 -0
- package/dist/reconcile/disk.js.map +1 -0
- package/dist/util/fs.d.ts +14 -0
- package/dist/util/fs.d.ts.map +1 -1
- package/dist/util/fs.js +27 -1
- package/dist/util/fs.js.map +1 -1
- package/dist/util/merge-json.d.ts +13 -2
- package/dist/util/merge-json.d.ts.map +1 -1
- package/dist/util/merge-json.js +24 -10
- package/dist/util/merge-json.js.map +1 -1
- package/dist/vault/list-handoffs.d.ts +13 -3
- package/dist/vault/list-handoffs.d.ts.map +1 -1
- package/dist/vault/list-handoffs.js +28 -13
- package/dist/vault/list-handoffs.js.map +1 -1
- package/dist/vault/list-notes.d.ts +12 -3
- package/dist/vault/list-notes.d.ts.map +1 -1
- package/dist/vault/list-notes.js +13 -7
- package/dist/vault/list-notes.js.map +1 -1
- package/dist/vault/list-plans.d.ts +12 -3
- package/dist/vault/list-plans.d.ts.map +1 -1
- package/dist/vault/list-plans.js +13 -7
- package/dist/vault/list-plans.js.map +1 -1
- package/dist/vault/search.d.ts.map +1 -1
- package/dist/vault/search.js +128 -24
- package/dist/vault/search.js.map +1 -1
- package/package.json +2 -2
- package/templates/agents/claude/skills/grounder-handoff/SKILL.md +73 -0
- package/templates/agents/claude/{commands/grounder-note.md → skills/grounder-note/SKILL.md} +6 -0
- package/templates/agents/claude/skills/grounder-overview/SKILL.md +21 -0
- package/templates/agents/claude/{commands/grounder-plan.md → skills/grounder-plan/SKILL.md} +6 -0
- package/templates/agents/claude/skills/grounder-recall/SKILL.md +34 -0
- package/templates/agents/claude/{commands/grounder-search.md → skills/grounder-search/SKILL.md} +16 -8
- package/templates/agents/cursor/skills/grounder-handoff/SKILL.md +73 -0
- package/templates/agents/cursor/{commands/grounder-note.md → skills/grounder-note/SKILL.md} +6 -0
- package/templates/agents/cursor/skills/grounder-overview/SKILL.md +21 -0
- package/templates/agents/cursor/{commands/grounder-plan.md → skills/grounder-plan/SKILL.md} +6 -0
- package/templates/agents/cursor/skills/grounder-recall/SKILL.md +34 -0
- package/templates/agents/cursor/{commands/grounder-search.md → skills/grounder-search/SKILL.md} +16 -8
- package/templates/vault/plan.md +1 -1
- package/templates/vault/session-handoff.md +2 -1
- package/dist/agents/install-command.d.ts +0 -35
- package/dist/agents/install-command.d.ts.map +0 -1
- package/dist/agents/install-command.js +0 -65
- package/dist/agents/install-command.js.map +0 -1
- package/dist/commands/apply-agent-installs.d.ts +0 -34
- package/dist/commands/apply-agent-installs.d.ts.map +0 -1
- package/dist/commands/apply-agent-installs.js +0 -137
- package/dist/commands/apply-agent-installs.js.map +0 -1
- package/templates/agents/claude/commands/grounder-task-handoff.md +0 -58
- package/templates/agents/claude/commands/grounder-task.md +0 -22
- package/templates/agents/cursor/commands/grounder-task-handoff.md +0 -58
- package/templates/agents/cursor/commands/grounder-task.md +0 -22
|
@@ -1,15 +1,26 @@
|
|
|
1
1
|
export type MergeJsonResult = {
|
|
2
2
|
ok: true;
|
|
3
3
|
created: boolean;
|
|
4
|
+
changed: boolean;
|
|
4
5
|
} | {
|
|
5
6
|
ok: false;
|
|
6
7
|
error: "unparseable";
|
|
7
8
|
message: string;
|
|
8
9
|
};
|
|
10
|
+
export interface MergeJsonFileOptions {
|
|
11
|
+
/** Compute the result without writing to disk. */
|
|
12
|
+
dryRun?: boolean;
|
|
13
|
+
}
|
|
9
14
|
/**
|
|
10
|
-
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed
|
|
15
|
+
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed —
|
|
16
|
+
* only when the merged content actually differs from what's on disk, so callers can trust
|
|
17
|
+
* `changed` to mean a real content change rather than "we ran the merge function."
|
|
18
|
+
* A `merge` that returns its `current` argument by reference is treated as a no-op and
|
|
19
|
+
* never writes, even if the file's on-disk formatting (indentation, line endings, key
|
|
20
|
+
* order) differs from `JSON.stringify(current, null, 2)` — callers rely on this to leave
|
|
21
|
+
* a file with nothing to change byte-for-byte untouched instead of reformatting it.
|
|
11
22
|
* On parse failure or non-object root: leaves the file untouched and returns an error
|
|
12
23
|
* so callers can warn without clobbering shared config.
|
|
13
24
|
*/
|
|
14
|
-
export declare function mergeJsonFile(filePath: string, merge: (current: Record<string, unknown>) => Record<string, unknown
|
|
25
|
+
export declare function mergeJsonFile(filePath: string, merge: (current: Record<string, unknown>) => Record<string, unknown>, options?: MergeJsonFileOptions): Promise<MergeJsonResult>;
|
|
15
26
|
//# sourceMappingURL=merge-json.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-json.d.ts","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"merge-json.d.ts","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"AAGA,MAAM,MAAM,eAAe,GACvB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,OAAO,EAAE,OAAO,CAAC;IAAC,OAAO,EAAE,OAAO,CAAA;CAAE,GAChD;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,aAAa,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD,MAAM,WAAW,oBAAoB;IACnC,kDAAkD;IAClD,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,aAAa,CACjC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACpE,OAAO,GAAE,oBAAyB,GACjC,OAAO,CAAC,eAAe,CAAC,CA2C1B"}
|
package/dist/util/merge-json.js
CHANGED
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import
|
|
3
|
-
import { fileExists } from "./fs.js";
|
|
1
|
+
import { readFile, stat } from "node:fs/promises";
|
|
2
|
+
import { fileExists, writeFileAtomic } from "./fs.js";
|
|
4
3
|
/**
|
|
5
|
-
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed
|
|
4
|
+
* Read a JSON object file (default `{}` if missing), apply `merge`, write pretty-printed —
|
|
5
|
+
* only when the merged content actually differs from what's on disk, so callers can trust
|
|
6
|
+
* `changed` to mean a real content change rather than "we ran the merge function."
|
|
7
|
+
* A `merge` that returns its `current` argument by reference is treated as a no-op and
|
|
8
|
+
* never writes, even if the file's on-disk formatting (indentation, line endings, key
|
|
9
|
+
* order) differs from `JSON.stringify(current, null, 2)` — callers rely on this to leave
|
|
10
|
+
* a file with nothing to change byte-for-byte untouched instead of reformatting it.
|
|
6
11
|
* On parse failure or non-object root: leaves the file untouched and returns an error
|
|
7
12
|
* so callers can warn without clobbering shared config.
|
|
8
13
|
*/
|
|
9
|
-
export async function mergeJsonFile(filePath, merge) {
|
|
14
|
+
export async function mergeJsonFile(filePath, merge, options = {}) {
|
|
10
15
|
const existed = await fileExists(filePath);
|
|
11
16
|
let current = {};
|
|
17
|
+
let originalRaw;
|
|
12
18
|
if (existed) {
|
|
13
|
-
|
|
19
|
+
originalRaw = await readFile(filePath, "utf8");
|
|
14
20
|
let parsed;
|
|
15
21
|
try {
|
|
16
|
-
parsed = JSON.parse(
|
|
22
|
+
parsed = JSON.parse(originalRaw);
|
|
17
23
|
}
|
|
18
24
|
catch (err) {
|
|
19
25
|
const detail = err instanceof Error ? err.message : String(err);
|
|
@@ -33,8 +39,16 @@ export async function mergeJsonFile(filePath, merge) {
|
|
|
33
39
|
current = parsed;
|
|
34
40
|
}
|
|
35
41
|
const next = merge(current);
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
42
|
+
const nextRaw = `${JSON.stringify(next, null, 2)}\n`;
|
|
43
|
+
const changed = next !== current && nextRaw !== originalRaw;
|
|
44
|
+
if (changed && !options.dryRun) {
|
|
45
|
+
// Preserve the existing file's permission bits across the tmp+rename
|
|
46
|
+
// swap — these are host-owned config files (Cursor/Claude Code), not
|
|
47
|
+
// Grounder's own state, so a rename shouldn't reset them to the tmp
|
|
48
|
+
// file's default umask.
|
|
49
|
+
const mode = existed ? (await stat(filePath)).mode & 0o777 : undefined;
|
|
50
|
+
await writeFileAtomic(filePath, nextRaw, mode !== undefined ? { mode } : {});
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, created: !existed, changed };
|
|
39
53
|
}
|
|
40
54
|
//# sourceMappingURL=merge-json.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"merge-json.js","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"merge-json.js","sourceRoot":"","sources":["../../src/util/merge-json.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAWtD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,QAAgB,EAChB,KAAoE,EACpE,UAAgC,EAAE;IAElC,MAAM,OAAO,GAAG,MAAM,UAAU,CAAC,QAAQ,CAAC,CAAC;IAC3C,IAAI,OAAO,GAA4B,EAAE,CAAC;IAC1C,IAAI,WAA+B,CAAC;IAEpC,IAAI,OAAO,EAAE,CAAC;QACZ,WAAW,GAAG,MAAM,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC,CAAC;QAC/C,IAAI,MAAe,CAAC;QACpB,IAAI,CAAC;YACH,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;QACnC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,MAAM,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YAChE,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,sBAAsB,QAAQ,mBAAmB,MAAM,GAAG;aACpE,CAAC;QACJ,CAAC;QAED,IAAI,MAAM,KAAK,IAAI,IAAI,OAAO,MAAM,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;YAC3E,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,KAAK,EAAE,aAAa;gBACpB,OAAO,EAAE,sBAAsB,QAAQ,oCAAoC;aAC5E,CAAC;QACJ,CAAC;QAED,OAAO,GAAG,MAAiC,CAAC;IAC9C,CAAC;IAED,MAAM,IAAI,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC;IAC5B,MAAM,OAAO,GAAG,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;IACrD,MAAM,OAAO,GAAG,IAAI,KAAK,OAAO,IAAI,OAAO,KAAK,WAAW,CAAC;IAE5D,IAAI,OAAO,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QAC/B,qEAAqE;QACrE,qEAAqE;QACrE,oEAAoE;QACpE,wBAAwB;QACxB,MAAM,IAAI,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,SAAS,CAAC;QACvE,MAAM,eAAe,CAAC,QAAQ,EAAE,OAAO,EAAE,IAAI,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IAC/E,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC;AAClD,CAAC"}
|
|
@@ -2,11 +2,21 @@ export interface ListHandoffsOptions {
|
|
|
2
2
|
/** Max paths to return (newest first). Omit to return all. */
|
|
3
3
|
limit?: number;
|
|
4
4
|
}
|
|
5
|
+
/** One handoff with its mtime (see {@link listHandoffsDetailed}). */
|
|
6
|
+
export interface HandoffEntry {
|
|
7
|
+
path: string;
|
|
8
|
+
mtimeMs: number;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
11
|
* Lists handoff markdown files under `logsDir` recursively, newest basename
|
|
7
|
-
* first (timestamp-prefixed names sort correctly)
|
|
8
|
-
* subfolders ties break by
|
|
9
|
-
*
|
|
12
|
+
* first (timestamp-prefixed names sort correctly), with each entry's mtime
|
|
13
|
+
* attached. Same basename in different subfolders ties break by
|
|
14
|
+
* vault-relative path descending. Missing or empty dirs yield `[]`.
|
|
15
|
+
*
|
|
16
|
+
* Ranking never reads mtime (filenames already sort correctly), so this
|
|
17
|
+
* stats only the paths a `limit` actually keeps, not the whole bucket.
|
|
10
18
|
*/
|
|
19
|
+
export declare function listHandoffsDetailed(logsDir: string, options?: ListHandoffsOptions): Promise<HandoffEntry[]>;
|
|
20
|
+
/** Same ranking as {@link listHandoffsDetailed}, paths only (no stat calls). */
|
|
11
21
|
export declare function listHandoffs(logsDir: string, options?: ListHandoffsOptions): Promise<string[]>;
|
|
12
22
|
//# sourceMappingURL=list-handoffs.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-handoffs.d.ts","sourceRoot":"","sources":["../../src/vault/list-handoffs.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"list-handoffs.d.ts","sourceRoot":"","sources":["../../src/vault/list-handoffs.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,mBAAmB;IAClC,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,qEAAqE;AACrE,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AA6BD;;;;;;;;GAQG;AACH,wBAAsB,oBAAoB,CACxC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,YAAY,EAAE,CAAC,CAMzB;AAED,gFAAgF;AAChF,wBAAsB,YAAY,CAChC,OAAO,EAAE,MAAM,EACf,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,MAAM,EAAE,CAAC,CAGnB"}
|
|
@@ -1,14 +1,8 @@
|
|
|
1
|
+
import { stat } from "node:fs/promises";
|
|
1
2
|
import path from "node:path";
|
|
2
3
|
import { vaultRelativePath } from "../util/path.js";
|
|
3
4
|
import { listMarkdownFiles } from "./list-markdown.js";
|
|
4
|
-
|
|
5
|
-
* Lists handoff markdown files under `logsDir` recursively, newest basename
|
|
6
|
-
* first (timestamp-prefixed names sort correctly). Same basename in different
|
|
7
|
-
* subfolders ties break by vault-relative path descending.
|
|
8
|
-
* Returns absolute paths. Missing or empty dirs yield `[]`.
|
|
9
|
-
*/
|
|
10
|
-
export async function listHandoffs(logsDir, options = {}) {
|
|
11
|
-
const mdPaths = await listMarkdownFiles(logsDir);
|
|
5
|
+
function rankHandoffPaths(mdPaths, logsDir) {
|
|
12
6
|
const ranked = mdPaths.map((filePath) => ({
|
|
13
7
|
filePath,
|
|
14
8
|
name: path.basename(filePath),
|
|
@@ -20,13 +14,34 @@ export async function listHandoffs(logsDir, options = {}) {
|
|
|
20
14
|
}
|
|
21
15
|
return a.rel < b.rel ? 1 : a.rel > b.rel ? -1 : 0;
|
|
22
16
|
});
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
17
|
+
return ranked.map((entry) => entry.filePath);
|
|
18
|
+
}
|
|
19
|
+
function applyLimit(paths, limit) {
|
|
20
|
+
if (limit === undefined) {
|
|
21
|
+
return [...paths];
|
|
26
22
|
}
|
|
27
|
-
if (
|
|
23
|
+
if (limit <= 0) {
|
|
28
24
|
return [];
|
|
29
25
|
}
|
|
30
|
-
return paths.slice(0,
|
|
26
|
+
return paths.slice(0, limit);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Lists handoff markdown files under `logsDir` recursively, newest basename
|
|
30
|
+
* first (timestamp-prefixed names sort correctly), with each entry's mtime
|
|
31
|
+
* attached. Same basename in different subfolders ties break by
|
|
32
|
+
* vault-relative path descending. Missing or empty dirs yield `[]`.
|
|
33
|
+
*
|
|
34
|
+
* Ranking never reads mtime (filenames already sort correctly), so this
|
|
35
|
+
* stats only the paths a `limit` actually keeps, not the whole bucket.
|
|
36
|
+
*/
|
|
37
|
+
export async function listHandoffsDetailed(logsDir, options = {}) {
|
|
38
|
+
const mdPaths = await listMarkdownFiles(logsDir);
|
|
39
|
+
const paths = applyLimit(rankHandoffPaths(mdPaths, logsDir), options.limit);
|
|
40
|
+
return Promise.all(paths.map(async (filePath) => ({ path: filePath, mtimeMs: (await stat(filePath)).mtimeMs })));
|
|
41
|
+
}
|
|
42
|
+
/** Same ranking as {@link listHandoffsDetailed}, paths only (no stat calls). */
|
|
43
|
+
export async function listHandoffs(logsDir, options = {}) {
|
|
44
|
+
const mdPaths = await listMarkdownFiles(logsDir);
|
|
45
|
+
return applyLimit(rankHandoffPaths(mdPaths, logsDir), options.limit);
|
|
31
46
|
}
|
|
32
47
|
//# sourceMappingURL=list-handoffs.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-handoffs.js","sourceRoot":"","sources":["../../src/vault/list-handoffs.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"list-handoffs.js","sourceRoot":"","sources":["../../src/vault/list-handoffs.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAavD,SAAS,gBAAgB,CAAC,OAA0B,EAAE,OAAe;IACnE,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE,EAAE,CAAC,CAAC;QACxC,QAAQ;QACR,IAAI,EAAE,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC;QAC7B,GAAG,EAAE,iBAAiB,CAAC,OAAO,EAAE,QAAQ,CAAC;KAC1C,CAAC,CAAC,CAAC;IAEJ,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACnB,IAAI,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,IAAI,EAAE,CAAC;YACtB,OAAO,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,CAAC;QACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,OAAO,MAAM,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC/C,CAAC;AAED,SAAS,UAAU,CAAC,KAAwB,EAAE,KAAyB;IACrE,IAAI,KAAK,KAAK,SAAS,EAAE,CAAC;QACxB,OAAO,CAAC,GAAG,KAAK,CAAC,CAAC;IACpB,CAAC;IACD,IAAI,KAAK,IAAI,CAAC,EAAE,CAAC;QACf,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC;AAC/B,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,oBAAoB,CACxC,OAAe,EACf,UAA+B,EAAE;IAEjC,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACjD,MAAM,KAAK,GAAG,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;IAC5E,OAAO,OAAO,CAAC,GAAG,CAChB,KAAK,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,OAAO,EAAE,CAAC,CAAC,CAC7F,CAAC;AACJ,CAAC;AAED,gFAAgF;AAChF,MAAM,CAAC,KAAK,UAAU,YAAY,CAChC,OAAe,EACf,UAA+B,EAAE;IAEjC,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,OAAO,CAAC,CAAC;IACjD,OAAO,UAAU,CAAC,gBAAgB,CAAC,OAAO,EAAE,OAAO,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AACvE,CAAC"}
|
|
@@ -2,14 +2,23 @@ export interface ListNotesOptions {
|
|
|
2
2
|
/** Max paths to return (newest first). Omit to return all. */
|
|
3
3
|
limit?: number;
|
|
4
4
|
}
|
|
5
|
+
/** One note with the mtime already used to rank it (see {@link listNotesDetailed}). */
|
|
6
|
+
export interface NoteEntry {
|
|
7
|
+
path: string;
|
|
8
|
+
mtimeMs: number;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
|
-
* Lists note markdown files under `notesDir` recursively, newest mtime first
|
|
7
|
-
*
|
|
8
|
-
*
|
|
11
|
+
* Lists note markdown files under `notesDir` recursively, newest mtime first,
|
|
12
|
+
* with each entry's mtime attached — the same stat this module already does
|
|
13
|
+
* internally to rank notes, just not thrown away after sorting. Missing or
|
|
14
|
+
* empty dirs yield `[]`. Ties break by vault-relative path descending for
|
|
15
|
+
* stable output.
|
|
9
16
|
*
|
|
10
17
|
* Matches {@link listPlans} sorting for consistency across list commands
|
|
11
18
|
* (filename-descending alone is viable for timestamp-prefixed notes, like
|
|
12
19
|
* {@link listHandoffs}).
|
|
13
20
|
*/
|
|
21
|
+
export declare function listNotesDetailed(notesDir: string, options?: ListNotesOptions): Promise<NoteEntry[]>;
|
|
22
|
+
/** Same ranking as {@link listNotesDetailed}, paths only. */
|
|
14
23
|
export declare function listNotes(notesDir: string, options?: ListNotesOptions): Promise<string[]>;
|
|
15
24
|
//# sourceMappingURL=list-notes.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-notes.d.ts","sourceRoot":"","sources":["../../src/vault/list-notes.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"list-notes.d.ts","sourceRoot":"","sources":["../../src/vault/list-notes.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,uFAAuF;AACvF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;;;;;GAUG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,SAAS,EAAE,CAAC,CAyBtB;AAED,6DAA6D;AAC7D,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,EAAE,CAAC,CAEnB"}
|
package/dist/vault/list-notes.js
CHANGED
|
@@ -2,15 +2,17 @@ import { stat } from "node:fs/promises";
|
|
|
2
2
|
import { vaultRelativePath } from "../util/path.js";
|
|
3
3
|
import { listMarkdownFiles } from "./list-markdown.js";
|
|
4
4
|
/**
|
|
5
|
-
* Lists note markdown files under `notesDir` recursively, newest mtime first
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Lists note markdown files under `notesDir` recursively, newest mtime first,
|
|
6
|
+
* with each entry's mtime attached — the same stat this module already does
|
|
7
|
+
* internally to rank notes, just not thrown away after sorting. Missing or
|
|
8
|
+
* empty dirs yield `[]`. Ties break by vault-relative path descending for
|
|
9
|
+
* stable output.
|
|
8
10
|
*
|
|
9
11
|
* Matches {@link listPlans} sorting for consistency across list commands
|
|
10
12
|
* (filename-descending alone is viable for timestamp-prefixed notes, like
|
|
11
13
|
* {@link listHandoffs}).
|
|
12
14
|
*/
|
|
13
|
-
export async function
|
|
15
|
+
export async function listNotesDetailed(notesDir, options = {}) {
|
|
14
16
|
const mdPaths = await listMarkdownFiles(notesDir);
|
|
15
17
|
const withMtime = await Promise.all(mdPaths.map(async (filePath) => {
|
|
16
18
|
const { mtimeMs } = await stat(filePath);
|
|
@@ -22,13 +24,17 @@ export async function listNotes(notesDir, options = {}) {
|
|
|
22
24
|
}
|
|
23
25
|
return a.rel < b.rel ? 1 : a.rel > b.rel ? -1 : 0;
|
|
24
26
|
});
|
|
25
|
-
const
|
|
27
|
+
const entries = withMtime.map((entry) => ({ path: entry.filePath, mtimeMs: entry.mtimeMs }));
|
|
26
28
|
if (options.limit === undefined) {
|
|
27
|
-
return
|
|
29
|
+
return entries;
|
|
28
30
|
}
|
|
29
31
|
if (options.limit <= 0) {
|
|
30
32
|
return [];
|
|
31
33
|
}
|
|
32
|
-
return
|
|
34
|
+
return entries.slice(0, options.limit);
|
|
35
|
+
}
|
|
36
|
+
/** Same ranking as {@link listNotesDetailed}, paths only. */
|
|
37
|
+
export async function listNotes(notesDir, options = {}) {
|
|
38
|
+
return (await listNotesDetailed(notesDir, options)).map((entry) => entry.path);
|
|
33
39
|
}
|
|
34
40
|
//# sourceMappingURL=list-notes.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-notes.js","sourceRoot":"","sources":["../../src/vault/list-notes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"list-notes.js","sourceRoot":"","sources":["../../src/vault/list-notes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAavD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAA4B,EAAE;IAE9B,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CACjC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC7B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3E,CAAC,CAAC,CACH,CAAC;IAEF,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;YAC5B,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;QAC/B,CAAC;QACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAE7F,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAgB,EAChB,UAA4B,EAAE;IAE9B,OAAO,CAAC,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -2,10 +2,19 @@ export interface ListPlansOptions {
|
|
|
2
2
|
/** Max paths to return (newest first). Omit to return all. */
|
|
3
3
|
limit?: number;
|
|
4
4
|
}
|
|
5
|
+
/** One plan with the mtime already used to rank it (see {@link listPlansDetailed}). */
|
|
6
|
+
export interface PlanEntry {
|
|
7
|
+
path: string;
|
|
8
|
+
mtimeMs: number;
|
|
9
|
+
}
|
|
5
10
|
/**
|
|
6
|
-
* Lists plan markdown files under `plansDir` recursively, newest mtime first
|
|
7
|
-
*
|
|
8
|
-
*
|
|
11
|
+
* Lists plan markdown files under `plansDir` recursively, newest mtime first,
|
|
12
|
+
* with each entry's mtime attached — the same stat this module already does
|
|
13
|
+
* internally to rank plans, just not thrown away after sorting. Missing or
|
|
14
|
+
* empty dirs yield `[]`. Ties break by vault-relative path descending for
|
|
15
|
+
* stable output.
|
|
9
16
|
*/
|
|
17
|
+
export declare function listPlansDetailed(plansDir: string, options?: ListPlansOptions): Promise<PlanEntry[]>;
|
|
18
|
+
/** Same ranking as {@link listPlansDetailed}, paths only. */
|
|
10
19
|
export declare function listPlans(plansDir: string, options?: ListPlansOptions): Promise<string[]>;
|
|
11
20
|
//# sourceMappingURL=list-plans.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-plans.d.ts","sourceRoot":"","sources":["../../src/vault/list-plans.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED
|
|
1
|
+
{"version":3,"file":"list-plans.d.ts","sourceRoot":"","sources":["../../src/vault/list-plans.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,gBAAgB;IAC/B,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,uFAAuF;AACvF,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;;;;GAMG;AACH,wBAAsB,iBAAiB,CACrC,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,SAAS,EAAE,CAAC,CAyBtB;AAED,6DAA6D;AAC7D,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,gBAAqB,GAC7B,OAAO,CAAC,MAAM,EAAE,CAAC,CAEnB"}
|
package/dist/vault/list-plans.js
CHANGED
|
@@ -2,11 +2,13 @@ import { stat } from "node:fs/promises";
|
|
|
2
2
|
import { vaultRelativePath } from "../util/path.js";
|
|
3
3
|
import { listMarkdownFiles } from "./list-markdown.js";
|
|
4
4
|
/**
|
|
5
|
-
* Lists plan markdown files under `plansDir` recursively, newest mtime first
|
|
6
|
-
*
|
|
7
|
-
*
|
|
5
|
+
* Lists plan markdown files under `plansDir` recursively, newest mtime first,
|
|
6
|
+
* with each entry's mtime attached — the same stat this module already does
|
|
7
|
+
* internally to rank plans, just not thrown away after sorting. Missing or
|
|
8
|
+
* empty dirs yield `[]`. Ties break by vault-relative path descending for
|
|
9
|
+
* stable output.
|
|
8
10
|
*/
|
|
9
|
-
export async function
|
|
11
|
+
export async function listPlansDetailed(plansDir, options = {}) {
|
|
10
12
|
const mdPaths = await listMarkdownFiles(plansDir);
|
|
11
13
|
const withMtime = await Promise.all(mdPaths.map(async (filePath) => {
|
|
12
14
|
const { mtimeMs } = await stat(filePath);
|
|
@@ -18,13 +20,17 @@ export async function listPlans(plansDir, options = {}) {
|
|
|
18
20
|
}
|
|
19
21
|
return a.rel < b.rel ? 1 : a.rel > b.rel ? -1 : 0;
|
|
20
22
|
});
|
|
21
|
-
const
|
|
23
|
+
const entries = withMtime.map((entry) => ({ path: entry.filePath, mtimeMs: entry.mtimeMs }));
|
|
22
24
|
if (options.limit === undefined) {
|
|
23
|
-
return
|
|
25
|
+
return entries;
|
|
24
26
|
}
|
|
25
27
|
if (options.limit <= 0) {
|
|
26
28
|
return [];
|
|
27
29
|
}
|
|
28
|
-
return
|
|
30
|
+
return entries.slice(0, options.limit);
|
|
31
|
+
}
|
|
32
|
+
/** Same ranking as {@link listPlansDetailed}, paths only. */
|
|
33
|
+
export async function listPlans(plansDir, options = {}) {
|
|
34
|
+
return (await listPlansDetailed(plansDir, options)).map((entry) => entry.path);
|
|
29
35
|
}
|
|
30
36
|
//# sourceMappingURL=list-plans.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"list-plans.js","sourceRoot":"","sources":["../../src/vault/list-plans.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"list-plans.js","sourceRoot":"","sources":["../../src/vault/list-plans.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAE,MAAM,kBAAkB,CAAC;AACxC,OAAO,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC;AACpD,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AAavD;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,QAAgB,EAChB,UAA4B,EAAE;IAE9B,MAAM,OAAO,GAAG,MAAM,iBAAiB,CAAC,QAAQ,CAAC,CAAC;IAClD,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC,GAAG,CACjC,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,QAAQ,EAAE,EAAE;QAC7B,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,CAAC;QACzC,OAAO,EAAE,QAAQ,EAAE,GAAG,EAAE,iBAAiB,CAAC,QAAQ,EAAE,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC;IAC3E,CAAC,CAAC,CACH,CAAC;IAEF,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACtB,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,OAAO,EAAE,CAAC;YAC5B,OAAO,CAAC,CAAC,OAAO,GAAG,CAAC,CAAC,OAAO,CAAC;QAC/B,CAAC;QACD,OAAO,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IAEH,MAAM,OAAO,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,CAAC,QAAQ,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IAE7F,IAAI,OAAO,CAAC,KAAK,KAAK,SAAS,EAAE,CAAC;QAChC,OAAO,OAAO,CAAC;IACjB,CAAC;IACD,IAAI,OAAO,CAAC,KAAK,IAAI,CAAC,EAAE,CAAC;QACvB,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,OAAO,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC;AACzC,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,QAAgB,EAChB,UAA4B,EAAE;IAE9B,OAAO,CAAC,MAAM,iBAAiB,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;AACjF,CAAC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/vault/search.ts"],"names":[],"mappings":"AAKA,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,kEAAkE;IAClE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,IAAI,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../../src/vault/search.ts"],"names":[],"mappings":"AAKA,mDAAmD;AACnD,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,kEAAkE;AAClE,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,EAAE,OAAO,CAAC;IACrB,IAAI,EAAE,aAAa,EAAE,CAAC;IACtB,kEAAkE;IAClE,YAAY,EAAE,MAAM,EAAE,CAAC;CACxB;AAED,MAAM,WAAW,aAAa;IAC5B,4DAA4D;IAC5D,OAAO,EAAE,MAAM,CAAC;IAChB,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,mDAAmD;IACnD,KAAK,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IAC1B,qDAAqD;IACrD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf;;;OAGG;IACH,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mDAAmD;IACnD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,8DAA8D;IAC9D,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,iDAAiD;IACjD,KAAK,CAAC,EAAE,IAAI,CAAC;CACd;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,EAAE,CAAC;IAChB,mEAAmE;IACnE,aAAa,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,EAAE,aAAa,EAAE,CAAC;IACvB,SAAS,EAAE,OAAO,CAAC;CACpB;AAsUD;;;;;;GAMG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC,CA8LhF"}
|
package/dist/vault/search.js
CHANGED
|
@@ -80,15 +80,27 @@ function searchMetaPenalty(rootDir, filePath, query) {
|
|
|
80
80
|
}
|
|
81
81
|
return 0;
|
|
82
82
|
}
|
|
83
|
-
|
|
84
|
-
|
|
83
|
+
// Deliberately separate from termMatchesHaystack's dot-aware boundary below —
|
|
84
|
+
// body content has no dotted-pseudo-extension convention to guard against, so
|
|
85
|
+
// plain `\b` is correct here. Do not "unify" the two matchers: a shared regex
|
|
86
|
+
// would either reintroduce `plan` matching `<id>.plan.md` in filenames, or
|
|
87
|
+
// stop `.plan` matching `plan` in body text, silently changing one side.
|
|
88
|
+
/** One compiled matcher per term, built once per search so the per-line scan never recompiles a RegExp. */
|
|
89
|
+
function buildTermMatchers(terms) {
|
|
90
|
+
const matchers = new Map();
|
|
91
|
+
for (const term of terms) {
|
|
92
|
+
matchers.set(term, term.includes(" ") ? null : new RegExp(`\\b${escapeRegex(term)}\\b`, "i"));
|
|
93
|
+
}
|
|
94
|
+
return matchers;
|
|
95
|
+
}
|
|
96
|
+
function termMatchesLine(line, term, matcher) {
|
|
97
|
+
if (matcher === null) {
|
|
85
98
|
return line.toLowerCase().includes(term.toLowerCase());
|
|
86
99
|
}
|
|
87
|
-
|
|
88
|
-
return re.test(line);
|
|
100
|
+
return matcher.test(line);
|
|
89
101
|
}
|
|
90
|
-
function matchingTermsOnLine(line, terms) {
|
|
91
|
-
return terms.filter((term) => termMatchesLine(line, term));
|
|
102
|
+
function matchingTermsOnLine(line, terms, matchers) {
|
|
103
|
+
return terms.filter((term) => termMatchesLine(line, term, matchers.get(term) ?? null));
|
|
92
104
|
}
|
|
93
105
|
function longestTerm(matches) {
|
|
94
106
|
return matches.reduce((best, term) => (term.length > best.length ? term : best));
|
|
@@ -120,24 +132,79 @@ function buildSnippet(lines, lineIndex, context) {
|
|
|
120
132
|
}
|
|
121
133
|
return parts.join("\n");
|
|
122
134
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
135
|
+
/**
|
|
136
|
+
* `.` only counts as a word character on the *leading* side of a match
|
|
137
|
+
* (unlike `\b`'s default), so a term can't start right after a dot — this
|
|
138
|
+
* stops a dotted filename convention like `<id>.plan.md` from letting a
|
|
139
|
+
* generic term match its dotted suffix as a standalone word (query "plan"
|
|
140
|
+
* must not match every `*.plan.md` file), while still letting the id itself
|
|
141
|
+
* match (`schema_versioning` must match `schema_versioning.plan.md`) since a
|
|
142
|
+
* term is still allowed to *end* right before a dot. `\w`-based boundaries
|
|
143
|
+
* are otherwise unchanged from before this fix: a hyphen still splits words
|
|
144
|
+
* (`search` matches `search-feature.md`), but `_` does not (`search` does
|
|
145
|
+
* *not* match `search_feature.md` — same as `\b`'s existing behavior).
|
|
146
|
+
*/
|
|
147
|
+
function termMatchesHaystack(haystack, lower, matcher) {
|
|
148
|
+
if (matcher === null) {
|
|
149
|
+
return haystack.includes(lower);
|
|
150
|
+
}
|
|
151
|
+
return matcher.test(haystack);
|
|
152
|
+
}
|
|
153
|
+
/** One compiled matcher per lowercased term, built once per search — same rationale as {@link buildTermMatchers}. */
|
|
154
|
+
function buildHaystackMatchers(terms) {
|
|
155
|
+
const matchers = new Map();
|
|
127
156
|
for (const term of terms) {
|
|
128
157
|
const lower = term.toLowerCase();
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
158
|
+
matchers.set(lower, lower.includes(" ") ? null : new RegExp(`(?<![\\w.])${escapeRegex(lower)}(?!\\w)`, "i"));
|
|
159
|
+
}
|
|
160
|
+
return matchers;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Lowercased `terms` that appear in `filePath`'s vault-relative path,
|
|
164
|
+
* including parent directory names (e.g. `plans/p1.md` matches both `plans`
|
|
165
|
+
* and `p1`). Ranking signal only (`filenameTermCount`) — never a matching
|
|
166
|
+
* gate, since a query for a common folder name (`plans`, `notes`, `archive`)
|
|
167
|
+
* would otherwise return every file in that folder regardless of content.
|
|
168
|
+
*/
|
|
169
|
+
/** Shared by {@link pathMatchedTerms} and {@link stemMatchedTerms} — only the haystack differs between the two. */
|
|
170
|
+
function matchTermsAgainstHaystack(haystack, terms, haystackMatchers) {
|
|
171
|
+
const matched = new Set();
|
|
172
|
+
for (const term of terms) {
|
|
173
|
+
const lower = term.toLowerCase();
|
|
174
|
+
if (termMatchesHaystack(haystack, lower, haystackMatchers.get(lower) ?? null)) {
|
|
175
|
+
matched.add(lower);
|
|
138
176
|
}
|
|
139
177
|
}
|
|
140
|
-
return
|
|
178
|
+
return matched;
|
|
179
|
+
}
|
|
180
|
+
function pathMatchedTerms(rootDir, filePath, terms, haystackMatchers) {
|
|
181
|
+
const rel = path.relative(rootDir, filePath);
|
|
182
|
+
const haystack = rel.replace(/[/\\]/g, " ").replace(/\.md$/i, "").toLowerCase();
|
|
183
|
+
return matchTermsAgainstHaystack(haystack, terms, haystackMatchers);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Lowercased `terms` that appear in `filePath`'s own filename stem only (no
|
|
187
|
+
* parent directories, no extension) — e.g. `plans/pluggable.md` matches
|
|
188
|
+
* `pluggable` but not `plans`. Narrow enough to gate inclusion for a file
|
|
189
|
+
* with no content match, unlike {@link pathMatchedTerms}.
|
|
190
|
+
*/
|
|
191
|
+
/**
|
|
192
|
+
* Strips a leading vault timestamp prefix (`YYYY-MM-DD-HHmm[ss]`, see
|
|
193
|
+
* `dateSecondPrefix`/`dateMinutePrefix` in `timestamp-slug.ts`) so a bare
|
|
194
|
+
* date/time query doesn't stem-match every handoff/log filename — hyphens
|
|
195
|
+
* alone don't stop it, since each numeric segment still matches standalone
|
|
196
|
+
* as a "word" via termMatchesHaystack's boundary check. Strips to empty for
|
|
197
|
+
* a slug-less date-only stem, same as any other narrow-to-nothing case.
|
|
198
|
+
*/
|
|
199
|
+
function stripTimestampPrefix(stem) {
|
|
200
|
+
// `_` terminates too, not just `-`/end — a slug-less collision-suffixed
|
|
201
|
+
// stem (`writeUniqueMarkdown`'s `_02`, `_03`, …) attaches directly to the
|
|
202
|
+
// date with no separating hyphen.
|
|
203
|
+
return stem.replace(/^\d{4}-\d{2}-\d{2}-\d{4,6}(-|_|$)/, "");
|
|
204
|
+
}
|
|
205
|
+
function stemMatchedTerms(filePath, terms, haystackMatchers) {
|
|
206
|
+
const stem = stripTimestampPrefix(path.basename(filePath, path.extname(filePath)).toLowerCase());
|
|
207
|
+
return matchTermsAgainstHaystack(stem, terms, haystackMatchers);
|
|
141
208
|
}
|
|
142
209
|
function contentHasPhrase(content, query) {
|
|
143
210
|
const trimmed = query.trim();
|
|
@@ -213,6 +280,8 @@ export async function searchVault(options) {
|
|
|
213
280
|
};
|
|
214
281
|
}
|
|
215
282
|
const filePaths = await listMarkdownFiles(options.rootDir);
|
|
283
|
+
const termMatchers = buildTermMatchers(terms);
|
|
284
|
+
const haystackMatchers = buildHaystackMatchers(terms);
|
|
216
285
|
const rawFiles = [];
|
|
217
286
|
let totalMatchCount = 0;
|
|
218
287
|
// Lowercased doc-freq for ranking; JSON output remaps to original `terms` spelling.
|
|
@@ -238,7 +307,7 @@ export async function searchVault(options) {
|
|
|
238
307
|
let totalHitCount = 0;
|
|
239
308
|
for (let i = 0; i < lines.length; i++) {
|
|
240
309
|
const line = lines[i] ?? "";
|
|
241
|
-
const lineMatches = matchingTermsOnLine(line, terms);
|
|
310
|
+
const lineMatches = matchingTermsOnLine(line, terms, termMatchers);
|
|
242
311
|
if (lineMatches.length === 0) {
|
|
243
312
|
continue;
|
|
244
313
|
}
|
|
@@ -257,7 +326,37 @@ export async function searchVault(options) {
|
|
|
257
326
|
});
|
|
258
327
|
}
|
|
259
328
|
}
|
|
260
|
-
|
|
329
|
+
// Path terms (dir segments + stem) stay a ranking-only signal — they must
|
|
330
|
+
// never gate inclusion, or a query for a common folder name (`plans`,
|
|
331
|
+
// `notes`, `archive`) would return every file in that folder regardless
|
|
332
|
+
// of content. The *matcher* underneath did change (shares
|
|
333
|
+
// termMatchesHaystack's dot-boundary fix with stem matching below), so a
|
|
334
|
+
// content-matching `<id>.plan.md` no longer earns filenameTermCount for
|
|
335
|
+
// its dotted suffix — only the gating behavior is unchanged, not the
|
|
336
|
+
// matcher itself.
|
|
337
|
+
const pathTerms = pathMatchedTerms(options.rootDir, filePath, terms, haystackMatchers);
|
|
338
|
+
// Stem terms are narrow enough (the file's own name, not its folder) to
|
|
339
|
+
// justify surfacing a file that has no content match at all — e.g.
|
|
340
|
+
// `pluggable.md`, whose body never repeats the word "pluggable".
|
|
341
|
+
const stemTerms = stemMatchedTerms(filePath, terms, haystackMatchers);
|
|
342
|
+
if (matchedTerms.size === 0 && stemTerms.size > 0) {
|
|
343
|
+
// Every other zero-hit check in the CLI and its consumers (summary
|
|
344
|
+
// text, JSON, the extension's QuickPick) keys off `totalMatchCount`,
|
|
345
|
+
// not file count — count the title match itself so a filename-only
|
|
346
|
+
// result doesn't get reported as "no matches" while still returning a hit.
|
|
347
|
+
totalMatchCount++;
|
|
348
|
+
}
|
|
349
|
+
if (matchedTerms.size > 0 || stemTerms.size > 0) {
|
|
350
|
+
for (const t of stemTerms) {
|
|
351
|
+
// Only stem terms feed doc-freq (not the wider path terms): a term
|
|
352
|
+
// that only ever appears in a title should still show a nonzero
|
|
353
|
+
// `termHitCounts`, so the skill's zero-hit "bad term" broaden check
|
|
354
|
+
// doesn't misfire on it — but a common folder name shouldn't inflate
|
|
355
|
+
// every other term's IDF weighting.
|
|
356
|
+
if (!matchedTerms.has(t)) {
|
|
357
|
+
termDocFreq[t] = (termDocFreq[t] ?? 0) + 1;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
261
360
|
for (const t of matchedTerms) {
|
|
262
361
|
termDocFreq[t] = (termDocFreq[t] ?? 0) + 1;
|
|
263
362
|
}
|
|
@@ -266,10 +365,15 @@ export async function searchVault(options) {
|
|
|
266
365
|
mtimeMs,
|
|
267
366
|
topicsMatch,
|
|
268
367
|
hits: fileHits,
|
|
368
|
+
// Content-only: a filename/stem match justifies inclusion (above)
|
|
369
|
+
// and still earns the flat `filenameTermCount` rank bonus below, but
|
|
370
|
+
// doesn't inflate distinct-term coverage — a file that only matches
|
|
371
|
+
// by title shouldn't outrank one that actually discusses the topic.
|
|
269
372
|
distinctTermCount: matchedTerms.size,
|
|
270
373
|
totalHitCount,
|
|
271
374
|
idfDensity: 0,
|
|
272
|
-
filenameTermCount:
|
|
375
|
+
filenameTermCount: pathTerms.size,
|
|
376
|
+
stemMatchedTerms: stemTerms,
|
|
273
377
|
phraseMatch: contentHasPhrase(content, options.query),
|
|
274
378
|
partialPhraseMatch: contentHasPartialPhrase(content, options.query),
|
|
275
379
|
perTermHits,
|
|
@@ -312,7 +416,7 @@ export async function searchVault(options) {
|
|
|
312
416
|
topicsMatch: file.topicsMatch,
|
|
313
417
|
hits: pickBestHits(file.hits, maxHitsPerFile),
|
|
314
418
|
matchedTerms: terms
|
|
315
|
-
.filter((term) => file.perTermHits.has(term.toLowerCase()))
|
|
419
|
+
.filter((term) => file.perTermHits.has(term.toLowerCase()) || file.stemMatchedTerms.has(term.toLowerCase()))
|
|
316
420
|
.sort((a, b) => b.length - a.length),
|
|
317
421
|
}));
|
|
318
422
|
return {
|