daftari 1.0.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/CHANGELOG.md +37 -0
- package/LICENSE +21 -0
- package/README.md +259 -0
- package/dist/access/locks.d.ts +19 -0
- package/dist/access/locks.d.ts.map +1 -0
- package/dist/access/locks.js +112 -0
- package/dist/access/locks.js.map +1 -0
- package/dist/access/rbac.d.ts +18 -0
- package/dist/access/rbac.d.ts.map +1 -0
- package/dist/access/rbac.js +48 -0
- package/dist/access/rbac.js.map +1 -0
- package/dist/cli.d.ts +4 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +216 -0
- package/dist/cli.js.map +1 -0
- package/dist/curation/lint.d.ts +20 -0
- package/dist/curation/lint.d.ts.map +1 -0
- package/dist/curation/lint.js +176 -0
- package/dist/curation/lint.js.map +1 -0
- package/dist/curation/provenance.d.ts +21 -0
- package/dist/curation/provenance.d.ts.map +1 -0
- package/dist/curation/provenance.js +80 -0
- package/dist/curation/provenance.js.map +1 -0
- package/dist/curation/staleness.d.ts +19 -0
- package/dist/curation/staleness.d.ts.map +1 -0
- package/dist/curation/staleness.js +67 -0
- package/dist/curation/staleness.js.map +1 -0
- package/dist/curation/tension.d.ts +20 -0
- package/dist/curation/tension.d.ts.map +1 -0
- package/dist/curation/tension.js +134 -0
- package/dist/curation/tension.js.map +1 -0
- package/dist/frontmatter/parser.d.ts +10 -0
- package/dist/frontmatter/parser.d.ts.map +1 -0
- package/dist/frontmatter/parser.js +29 -0
- package/dist/frontmatter/parser.js.map +1 -0
- package/dist/frontmatter/schema.d.ts +7 -0
- package/dist/frontmatter/schema.d.ts.map +1 -0
- package/dist/frontmatter/schema.js +115 -0
- package/dist/frontmatter/schema.js.map +1 -0
- package/dist/frontmatter/types.d.ts +41 -0
- package/dist/frontmatter/types.d.ts.map +1 -0
- package/dist/frontmatter/types.js +8 -0
- package/dist/frontmatter/types.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +94 -0
- package/dist/index.js.map +1 -0
- package/dist/search/bm25.d.ts +19 -0
- package/dist/search/bm25.d.ts.map +1 -0
- package/dist/search/bm25.js +115 -0
- package/dist/search/bm25.js.map +1 -0
- package/dist/search/hybrid.d.ts +38 -0
- package/dist/search/hybrid.d.ts.map +1 -0
- package/dist/search/hybrid.js +162 -0
- package/dist/search/hybrid.js.map +1 -0
- package/dist/search/reindex.d.ts +15 -0
- package/dist/search/reindex.d.ts.map +1 -0
- package/dist/search/reindex.js +189 -0
- package/dist/search/reindex.js.map +1 -0
- package/dist/search/vector.d.ts +9 -0
- package/dist/search/vector.d.ts.map +1 -0
- package/dist/search/vector.js +128 -0
- package/dist/search/vector.js.map +1 -0
- package/dist/server.d.ts +6 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +72 -0
- package/dist/server.js.map +1 -0
- package/dist/storage/index-db.d.ts +37 -0
- package/dist/storage/index-db.d.ts.map +1 -0
- package/dist/storage/index-db.js +145 -0
- package/dist/storage/index-db.js.map +1 -0
- package/dist/storage/local.d.ts +6 -0
- package/dist/storage/local.d.ts.map +1 -0
- package/dist/storage/local.js +57 -0
- package/dist/storage/local.js.map +1 -0
- package/dist/tools/curation.d.ts +22 -0
- package/dist/tools/curation.d.ts.map +1 -0
- package/dist/tools/curation.js +202 -0
- package/dist/tools/curation.js.map +1 -0
- package/dist/tools/read.d.ts +74 -0
- package/dist/tools/read.d.ts.map +1 -0
- package/dist/tools/read.js +254 -0
- package/dist/tools/read.js.map +1 -0
- package/dist/tools/search.d.ts +13 -0
- package/dist/tools/search.d.ts.map +1 -0
- package/dist/tools/search.js +190 -0
- package/dist/tools/search.js.map +1 -0
- package/dist/tools/write.d.ts +18 -0
- package/dist/tools/write.d.ts.map +1 -0
- package/dist/tools/write.js +465 -0
- package/dist/tools/write.js.map +1 -0
- package/dist/utils/config.d.ts +12 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +94 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/git.d.ts +23 -0
- package/dist/utils/git.d.ts.map +1 -0
- package/dist/utils/git.js +114 -0
- package/dist/utils/git.js.map +1 -0
- package/package.json +69 -0
- package/templates/config.yaml +31 -0
package/dist/cli.js
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Daftari CLI.
|
|
3
|
+
//
|
|
4
|
+
// daftari --init [path] scaffold a new vault
|
|
5
|
+
// daftari --vault <path> ... start the MCP server against a vault
|
|
6
|
+
//
|
|
7
|
+
// Server flags (--vault, --user, --role, --reindex) are parsed by main() in
|
|
8
|
+
// index.ts; this entry point only adds the --init scaffolding command and a
|
|
9
|
+
// usage screen. Diagnostics go to stderr so stdout stays a clean JSON-RPC
|
|
10
|
+
// stream when the server runs.
|
|
11
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join, resolve } from "node:path";
|
|
13
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
14
|
+
import { main, parseFlag } from "./index.js";
|
|
15
|
+
import { reindexVault } from "./search/reindex.js";
|
|
16
|
+
import { commit } from "./utils/git.js";
|
|
17
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
18
|
+
const USAGE = `daftari — an MCP server that exposes a curated markdown vault to AI agents.
|
|
19
|
+
|
|
20
|
+
Usage:
|
|
21
|
+
daftari --init [path] Scaffold a new vault (default: ./daftari-vault)
|
|
22
|
+
daftari --vault <path> [options] Start the MCP server (stdio) against a vault
|
|
23
|
+
|
|
24
|
+
Server options:
|
|
25
|
+
--user <username> Identity the server runs as (default: guest)
|
|
26
|
+
--role <rolename> RBAC role from .daftari/config.yaml (default: deny-all guest)
|
|
27
|
+
--reindex Rebuild the SQLite index from scratch, then exit
|
|
28
|
+
|
|
29
|
+
Examples:
|
|
30
|
+
npx daftari --init ./my-vault
|
|
31
|
+
npx daftari --vault ./my-vault --user me --role admin
|
|
32
|
+
npx daftari --vault ./my-vault --reindex
|
|
33
|
+
`;
|
|
34
|
+
const COLLECTIONS = ["competitive-intel", "pricing", "moonshot", "_drafts"];
|
|
35
|
+
const VAULT_GITIGNORE = `# Daftari rebuilds these from the markdown files — never commit them.
|
|
36
|
+
.daftari/index.db
|
|
37
|
+
.daftari/index.db-journal
|
|
38
|
+
.daftari/index.db-wal
|
|
39
|
+
.daftari/index.db-shm
|
|
40
|
+
.daftari/locks.db
|
|
41
|
+
.daftari/locks.db-journal
|
|
42
|
+
.daftari/locks.db-wal
|
|
43
|
+
.daftari/locks.db-shm
|
|
44
|
+
`;
|
|
45
|
+
// Example documents written by --init. Content is fictional: "Aurora" and
|
|
46
|
+
// "Helios" are made-up products, not real companies.
|
|
47
|
+
function exampleDocs(today) {
|
|
48
|
+
return [
|
|
49
|
+
{
|
|
50
|
+
path: "competitive-intel/aurora-pipelines-overview.md",
|
|
51
|
+
body: `---
|
|
52
|
+
title: "Aurora Pipelines — Positioning Overview"
|
|
53
|
+
domain: accumulation
|
|
54
|
+
collection: competitive-intel
|
|
55
|
+
status: canonical
|
|
56
|
+
confidence: medium
|
|
57
|
+
created: ${today}
|
|
58
|
+
updated: ${today}
|
|
59
|
+
updated_by: agent:daftari-init
|
|
60
|
+
provenance: direct
|
|
61
|
+
sources:
|
|
62
|
+
- aurora-product-page
|
|
63
|
+
superseded_by: null
|
|
64
|
+
ttl_days: 120
|
|
65
|
+
tags: [aurora, ingestion, competitive]
|
|
66
|
+
---
|
|
67
|
+
|
|
68
|
+
# Aurora Pipelines — Positioning Overview
|
|
69
|
+
|
|
70
|
+
Aurora Pipelines is a fictional data-movement product used here as an example.
|
|
71
|
+
Its pitch: ingestion is an authored artifact you version and review, not a
|
|
72
|
+
managed black box.
|
|
73
|
+
|
|
74
|
+
## Questions Answered
|
|
75
|
+
- How does Aurora frame the ingestion-vs-transformation boundary?
|
|
76
|
+
|
|
77
|
+
## Questions Raised
|
|
78
|
+
- Does an authored-pipeline model slow teams down at small scale?
|
|
79
|
+
`,
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
path: "pricing/helios-consumption-pricing.md",
|
|
83
|
+
body: `---
|
|
84
|
+
title: "Helios Consumption Pricing (Compute Credit Model)"
|
|
85
|
+
domain: accumulation
|
|
86
|
+
collection: pricing
|
|
87
|
+
status: canonical
|
|
88
|
+
confidence: high
|
|
89
|
+
created: ${today}
|
|
90
|
+
updated: ${today}
|
|
91
|
+
updated_by: agent:daftari-init
|
|
92
|
+
provenance: direct
|
|
93
|
+
sources:
|
|
94
|
+
- helios-pricing-page
|
|
95
|
+
superseded_by: null
|
|
96
|
+
ttl_days: 45
|
|
97
|
+
tags: [helios, pricing, consumption]
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
# Helios Consumption Pricing (Compute Credit Model)
|
|
101
|
+
|
|
102
|
+
Helios is a fictional platform used here as an example. It bills in compute
|
|
103
|
+
credits — a normalized compute-hour unit whose rate varies by workload tier.
|
|
104
|
+
|
|
105
|
+
## Questions Answered
|
|
106
|
+
- What is the unit of consumption billing?
|
|
107
|
+
|
|
108
|
+
## Questions Raised
|
|
109
|
+
- How predictable is monthly spend for spiky, agent-driven workloads?
|
|
110
|
+
`,
|
|
111
|
+
},
|
|
112
|
+
{
|
|
113
|
+
path: "moonshot/zero-config-ingestion.md",
|
|
114
|
+
body: `---
|
|
115
|
+
title: "Moonshot: Zero-Config Ingestion"
|
|
116
|
+
domain: generative
|
|
117
|
+
collection: moonshot
|
|
118
|
+
status: draft
|
|
119
|
+
confidence: low
|
|
120
|
+
created: ${today}
|
|
121
|
+
updated: ${today}
|
|
122
|
+
updated_by: agent:daftari-init
|
|
123
|
+
provenance: inferred
|
|
124
|
+
sources: []
|
|
125
|
+
superseded_by: null
|
|
126
|
+
ttl_days: 30
|
|
127
|
+
tags: [moonshot, ingestion, speculative]
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
# Moonshot: Zero-Config Ingestion
|
|
131
|
+
|
|
132
|
+
A speculative sketch. Generative-domain notes are summaries, not compiled
|
|
133
|
+
canon — the agent flags tensions here but does not resolve them.
|
|
134
|
+
|
|
135
|
+
## Questions Answered
|
|
136
|
+
- (none yet — this is a draft)
|
|
137
|
+
|
|
138
|
+
## Questions Raised
|
|
139
|
+
- What would ingestion look like with no authored schema at all?
|
|
140
|
+
`,
|
|
141
|
+
},
|
|
142
|
+
];
|
|
143
|
+
}
|
|
144
|
+
export async function initVault(targetPath) {
|
|
145
|
+
const vaultRoot = resolve(targetPath);
|
|
146
|
+
if (existsSync(vaultRoot) && readdirSync(vaultRoot).length > 0) {
|
|
147
|
+
process.stderr.write(`daftari: refusing to scaffold — ${vaultRoot} exists and is not empty\n`);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
// Directory structure: the vault root holds .daftari/ alongside one
|
|
151
|
+
// directory per collection.
|
|
152
|
+
mkdirSync(join(vaultRoot, ".daftari"), { recursive: true });
|
|
153
|
+
for (const c of COLLECTIONS) {
|
|
154
|
+
mkdirSync(join(vaultRoot, c), { recursive: true });
|
|
155
|
+
}
|
|
156
|
+
// RBAC config: copied from the package's bundled template.
|
|
157
|
+
const template = readFileSync(resolve(HERE, "..", "templates", "config.yaml"), "utf-8");
|
|
158
|
+
writeFileSync(join(vaultRoot, ".daftari", "config.yaml"), template);
|
|
159
|
+
writeFileSync(join(vaultRoot, ".gitignore"), VAULT_GITIGNORE);
|
|
160
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
161
|
+
for (const doc of exampleDocs(today)) {
|
|
162
|
+
writeFileSync(join(vaultRoot, doc.path), doc.body);
|
|
163
|
+
}
|
|
164
|
+
// Git is the version layer — commit the scaffold so the vault has history
|
|
165
|
+
// from its first moment.
|
|
166
|
+
const committed = await commit(vaultRoot, ["."], "Initialize Daftari vault", "agent:daftari-init");
|
|
167
|
+
if (!committed.ok) {
|
|
168
|
+
process.stderr.write(`daftari: warning: could not commit the scaffold: ${committed.error.message}\n`);
|
|
169
|
+
}
|
|
170
|
+
// Initial index build so search works on first server start.
|
|
171
|
+
const indexed = await reindexVault(vaultRoot);
|
|
172
|
+
if (indexed.ok) {
|
|
173
|
+
process.stderr.write(`daftari: indexed ${indexed.value.documentCount} docs ` +
|
|
174
|
+
`(vectors ${indexed.value.vectorEnabled ? "on" : "off"})\n`);
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
process.stderr.write(`daftari: warning: initial index build failed: ${indexed.error.message}\n`);
|
|
178
|
+
}
|
|
179
|
+
process.stdout.write(`Scaffolded a new Daftari vault at ${vaultRoot}\n\n` +
|
|
180
|
+
` collections: ${COLLECTIONS.join(", ")}\n` +
|
|
181
|
+
` config: .daftari/config.yaml\n` +
|
|
182
|
+
` examples: 3 markdown documents\n\n` +
|
|
183
|
+
`Next:\n` +
|
|
184
|
+
` npx daftari --vault ${targetPath} --user me --role admin\n`);
|
|
185
|
+
return 0;
|
|
186
|
+
}
|
|
187
|
+
export async function run(argv) {
|
|
188
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
189
|
+
process.stdout.write(USAGE);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const wantsInit = argv.includes("--init") || argv.some((a) => a.startsWith("--init="));
|
|
193
|
+
if (wantsInit) {
|
|
194
|
+
const target = parseFlag(argv, "init") ?? "./daftari-vault";
|
|
195
|
+
process.exitCode = await initVault(target);
|
|
196
|
+
return;
|
|
197
|
+
}
|
|
198
|
+
if (parseFlag(argv, "vault")) {
|
|
199
|
+
await main(argv);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
process.stderr.write("daftari: nothing to do — pass --init or --vault\n\n");
|
|
203
|
+
process.stderr.write(USAGE);
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
}
|
|
206
|
+
// Auto-run only when this module is the process entry point. When imported
|
|
207
|
+
// (by tests) it stays inert so initVault / run can be exercised directly.
|
|
208
|
+
const entryUrl = pathToFileURL(process.argv[1] ?? "").href;
|
|
209
|
+
if (import.meta.url === entryUrl) {
|
|
210
|
+
run(process.argv.slice(2)).catch((e) => {
|
|
211
|
+
const reason = e instanceof Error ? (e.stack ?? e.message) : String(e);
|
|
212
|
+
process.stderr.write(`daftari: fatal: ${reason}\n`);
|
|
213
|
+
process.exitCode = 1;
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=cli.js.map
|
package/dist/cli.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { type Result } from "../frontmatter/types.js";
|
|
2
|
+
export declare const LINT_CHECKS: readonly ["staleFiles", "orphanFiles", "oldDrafts", "stagnantLowConfidence", "deprecatedStillLinked"];
|
|
3
|
+
export type LintCheckName = (typeof LINT_CHECKS)[number];
|
|
4
|
+
export interface LintFinding {
|
|
5
|
+
path: string;
|
|
6
|
+
detail: string;
|
|
7
|
+
}
|
|
8
|
+
export interface LintReport {
|
|
9
|
+
generatedAt: string;
|
|
10
|
+
checks: Record<LintCheckName, LintFinding[]>;
|
|
11
|
+
totalFindings: number;
|
|
12
|
+
}
|
|
13
|
+
export interface LintOptions {
|
|
14
|
+
now?: Date;
|
|
15
|
+
draftMaxDays?: number;
|
|
16
|
+
lowConfidenceMaxDays?: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function extractLinks(content: string): string[];
|
|
19
|
+
export declare function runLint(vaultRoot: string, opts?: LintOptions): Promise<Result<LintReport, Error>>;
|
|
20
|
+
//# sourceMappingURL=lint.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.d.ts","sourceRoot":"","sources":["../../src/curation/lint.ts"],"names":[],"mappings":"AASA,OAAO,EAAwB,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAI5E,eAAO,MAAM,WAAW,uGAMd,CAAC;AACX,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,WAAW,CAAC,CAAC,MAAM,CAAC,CAAC;AAEzD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,UAAU;IACzB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,MAAM,CAAC,aAAa,EAAE,WAAW,EAAE,CAAC,CAAC;IAC7C,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,CAAC,EAAE,IAAI,CAAC;IACX,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oBAAoB,CAAC,EAAE,MAAM,CAAC;CAC/B;AAYD,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAiBtD;AAuED,wBAAsB,OAAO,CAC3B,SAAS,EAAE,MAAM,EACjB,IAAI,GAAE,WAAgB,GACrB,OAAO,CAAC,MAAM,CAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAkFpC"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
// vault_lint's engine — advisory cross-vault curation checks.
|
|
2
|
+
//
|
|
3
|
+
// Lint loads every document once, builds the inter-document link graph, then
|
|
4
|
+
// runs five checks. It only ever *reports*: no file is edited, no status is
|
|
5
|
+
// changed, nothing is auto-fixed. The output is a structured report grouped by
|
|
6
|
+
// check, for a human (or an agent acting on a human's behalf) to triage.
|
|
7
|
+
import { posix } from "node:path";
|
|
8
|
+
import { parseDocument } from "../frontmatter/parser.js";
|
|
9
|
+
import { ok } from "../frontmatter/types.js";
|
|
10
|
+
import { listFiles, readFile, resolveVaultPath } from "../storage/local.js";
|
|
11
|
+
import { ageInDays, computeStaleness } from "./staleness.js";
|
|
12
|
+
export const LINT_CHECKS = [
|
|
13
|
+
"staleFiles",
|
|
14
|
+
"orphanFiles",
|
|
15
|
+
"oldDrafts",
|
|
16
|
+
"stagnantLowConfidence",
|
|
17
|
+
"deprecatedStillLinked",
|
|
18
|
+
];
|
|
19
|
+
// --- link extraction ------------------------------------------------------
|
|
20
|
+
// Pulls every internal link target out of a markdown body: both [[wikilinks]]
|
|
21
|
+
// and [text](target) markdown links. External URLs and anchors are dropped.
|
|
22
|
+
export function extractLinks(content) {
|
|
23
|
+
const targets = [];
|
|
24
|
+
for (const m of content.matchAll(/\[\[([^\]]+)\]\]/g)) {
|
|
25
|
+
// A wikilink may carry a |display alias and/or a #heading anchor.
|
|
26
|
+
const raw = m[1].split("|")[0]?.split("#")[0]?.trim();
|
|
27
|
+
if (raw)
|
|
28
|
+
targets.push(raw);
|
|
29
|
+
}
|
|
30
|
+
for (const m of content.matchAll(/\[[^\]]*\]\(([^)\s]+)\)/g)) {
|
|
31
|
+
const raw = m[1].split("#")[0]?.trim();
|
|
32
|
+
if (!raw)
|
|
33
|
+
continue;
|
|
34
|
+
if (/^(https?:|mailto:|#)/i.test(raw))
|
|
35
|
+
continue;
|
|
36
|
+
targets.push(raw);
|
|
37
|
+
}
|
|
38
|
+
return targets;
|
|
39
|
+
}
|
|
40
|
+
// Resolves a raw link target to a vault-relative path, or null if it points
|
|
41
|
+
// nowhere. Tries, in order: the target as-is, with a .md suffix, resolved
|
|
42
|
+
// relative to the linking file's directory, then a bare basename match (the
|
|
43
|
+
// common [[note-name]] wikilink form).
|
|
44
|
+
function resolveLink(rawTarget, fromPath, byPath, byBasename) {
|
|
45
|
+
const withMd = (p) => (p.endsWith(".md") ? p : `${p}.md`);
|
|
46
|
+
if (byPath.has(rawTarget))
|
|
47
|
+
return rawTarget;
|
|
48
|
+
if (byPath.has(withMd(rawTarget)))
|
|
49
|
+
return withMd(rawTarget);
|
|
50
|
+
const relual = posix.normalize(posix.join(posix.dirname(fromPath), rawTarget));
|
|
51
|
+
if (byPath.has(relual))
|
|
52
|
+
return relual;
|
|
53
|
+
if (byPath.has(withMd(relual)))
|
|
54
|
+
return withMd(relual);
|
|
55
|
+
const base = posix.basename(rawTarget).replace(/\.md$/, "");
|
|
56
|
+
return byBasename.get(base) ?? null;
|
|
57
|
+
}
|
|
58
|
+
// --- check orchestration --------------------------------------------------
|
|
59
|
+
async function loadDocuments(vaultRoot) {
|
|
60
|
+
const list = await listFiles(vaultRoot);
|
|
61
|
+
if (!list.ok)
|
|
62
|
+
return list;
|
|
63
|
+
const docs = [];
|
|
64
|
+
for (const relPath of list.value) {
|
|
65
|
+
const resolved = resolveVaultPath(vaultRoot, relPath);
|
|
66
|
+
if (!resolved.ok)
|
|
67
|
+
continue;
|
|
68
|
+
const file = await readFile(resolved.value);
|
|
69
|
+
if (!file.ok)
|
|
70
|
+
continue;
|
|
71
|
+
const parsed = parseDocument(file.value);
|
|
72
|
+
if (!parsed.ok)
|
|
73
|
+
continue;
|
|
74
|
+
docs.push({
|
|
75
|
+
path: relPath,
|
|
76
|
+
frontmatter: parsed.value.frontmatter,
|
|
77
|
+
content: parsed.value.content,
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
return ok(docs);
|
|
81
|
+
}
|
|
82
|
+
// Maps each document to the set of documents that link to it.
|
|
83
|
+
function buildInboundMap(docs) {
|
|
84
|
+
const byPath = new Set(docs.map((d) => d.path));
|
|
85
|
+
const byBasename = new Map();
|
|
86
|
+
for (const d of docs) {
|
|
87
|
+
const base = posix.basename(d.path).replace(/\.md$/, "");
|
|
88
|
+
// First write wins, so a basename collision resolves deterministically.
|
|
89
|
+
if (!byBasename.has(base))
|
|
90
|
+
byBasename.set(base, d.path);
|
|
91
|
+
}
|
|
92
|
+
const inbound = new Map();
|
|
93
|
+
for (const d of docs) {
|
|
94
|
+
for (const raw of extractLinks(d.content)) {
|
|
95
|
+
const target = resolveLink(raw, d.path, byPath, byBasename);
|
|
96
|
+
if (!target || target === d.path)
|
|
97
|
+
continue;
|
|
98
|
+
if (!inbound.has(target))
|
|
99
|
+
inbound.set(target, new Set());
|
|
100
|
+
inbound.get(target).add(d.path);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return inbound;
|
|
104
|
+
}
|
|
105
|
+
// Runs every lint check across the vault and returns a grouped report.
|
|
106
|
+
export async function runLint(vaultRoot, opts = {}) {
|
|
107
|
+
const loaded = await loadDocuments(vaultRoot);
|
|
108
|
+
if (!loaded.ok)
|
|
109
|
+
return loaded;
|
|
110
|
+
const docs = loaded.value;
|
|
111
|
+
const now = opts.now ?? new Date();
|
|
112
|
+
const draftMaxDays = opts.draftMaxDays ?? 30;
|
|
113
|
+
const lowConfidenceMaxDays = opts.lowConfidenceMaxDays ?? 30;
|
|
114
|
+
const inbound = buildInboundMap(docs);
|
|
115
|
+
const byPath = new Map(docs.map((d) => [d.path, d]));
|
|
116
|
+
const checks = {
|
|
117
|
+
staleFiles: [],
|
|
118
|
+
orphanFiles: [],
|
|
119
|
+
oldDrafts: [],
|
|
120
|
+
stagnantLowConfidence: [],
|
|
121
|
+
deprecatedStillLinked: [],
|
|
122
|
+
};
|
|
123
|
+
for (const doc of docs) {
|
|
124
|
+
const fm = doc.frontmatter;
|
|
125
|
+
// 1. Stale: a document at or past its TTL.
|
|
126
|
+
const staleness = computeStaleness({ updated: fm.updated, ttl_days: fm.ttl_days }, now);
|
|
127
|
+
if (staleness.expired) {
|
|
128
|
+
checks.staleFiles.push({
|
|
129
|
+
path: doc.path,
|
|
130
|
+
detail: `${staleness.ageDays}d since update, ttl ${staleness.ttlDays}d ` +
|
|
131
|
+
`(decay score ${staleness.score.toFixed(2)})`,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
// 2. Orphan: no other document links to it.
|
|
135
|
+
if (!inbound.has(doc.path)) {
|
|
136
|
+
checks.orphanFiles.push({
|
|
137
|
+
path: doc.path,
|
|
138
|
+
detail: "no inbound links from any vault document",
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// 3. Old draft: still a draft well past the draft age limit.
|
|
142
|
+
if (fm.status === "draft") {
|
|
143
|
+
const anchor = fm.created || fm.updated;
|
|
144
|
+
const draftAge = ageInDays(anchor, now);
|
|
145
|
+
if (draftAge > draftMaxDays) {
|
|
146
|
+
checks.oldDrafts.push({
|
|
147
|
+
path: doc.path,
|
|
148
|
+
detail: `draft for ${draftAge}d (limit ${draftMaxDays}d)`,
|
|
149
|
+
});
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
// 4. Stagnant low-confidence: low confidence and untouched too long.
|
|
153
|
+
if (fm.confidence === "low") {
|
|
154
|
+
const idleDays = ageInDays(fm.updated, now);
|
|
155
|
+
if (idleDays >= lowConfidenceMaxDays) {
|
|
156
|
+
checks.stagnantLowConfidence.push({
|
|
157
|
+
path: doc.path,
|
|
158
|
+
detail: `low confidence, unchanged for ${idleDays}d ` + `(limit ${lowConfidenceMaxDays}d)`,
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
// 5. Deprecated but still linked from a canonical document.
|
|
163
|
+
if (fm.status === "deprecated") {
|
|
164
|
+
const linkers = [...(inbound.get(doc.path) ?? [])].filter((from) => byPath.get(from)?.frontmatter.status === "canonical");
|
|
165
|
+
if (linkers.length > 0) {
|
|
166
|
+
checks.deprecatedStillLinked.push({
|
|
167
|
+
path: doc.path,
|
|
168
|
+
detail: `still linked from canonical: ${linkers.sort().join(", ")}`,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
const totalFindings = LINT_CHECKS.reduce((n, name) => n + checks[name].length, 0);
|
|
174
|
+
return ok({ generatedAt: now.toISOString(), checks, totalFindings });
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=lint.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lint.js","sourceRoot":"","sources":["../../src/curation/lint.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { Frontmatter } from "../frontmatter/types.js";
|
|
2
|
+
import { type Result } from "../frontmatter/types.js";
|
|
3
|
+
export type FrontmatterDiff = Record<string, {
|
|
4
|
+
before: unknown;
|
|
5
|
+
after: unknown;
|
|
6
|
+
}>;
|
|
7
|
+
export interface ProvenanceEntry {
|
|
8
|
+
timestamp: string;
|
|
9
|
+
tool: string;
|
|
10
|
+
file: string;
|
|
11
|
+
agent: string;
|
|
12
|
+
action: string;
|
|
13
|
+
frontmatter_diff?: FrontmatterDiff;
|
|
14
|
+
}
|
|
15
|
+
export declare function curationLogPath(vaultRoot: string): string;
|
|
16
|
+
export declare function frontmatterDiff(before: Frontmatter | null, after: Frontmatter): FrontmatterDiff;
|
|
17
|
+
export declare function recordProvenance(vaultRoot: string, entry: Omit<ProvenanceEntry, "timestamp"> & {
|
|
18
|
+
timestamp?: string;
|
|
19
|
+
}): Promise<Result<ProvenanceEntry, Error>>;
|
|
20
|
+
export declare function readProvenanceLog(vaultRoot: string): Promise<Result<ProvenanceEntry[], Error>>;
|
|
21
|
+
//# sourceMappingURL=provenance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.d.ts","sourceRoot":"","sources":["../../src/curation/provenance.ts"],"names":[],"mappings":"AAUA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3D,OAAO,EAAW,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAG/D,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,EAAE,OAAO,CAAA;CAAE,CAAC,CAAC;AAElF,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,CAAC,EAAE,eAAe,CAAC;CACpC;AAED,wBAAgB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAEzD;AAKD,wBAAgB,eAAe,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,EAAE,KAAK,EAAE,WAAW,GAAG,eAAe,CAW/F;AAID,wBAAsB,gBAAgB,CACpC,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,IAAI,CAAC,eAAe,EAAE,WAAW,CAAC,GAAG;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAE,GACjE,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,KAAK,CAAC,CAAC,CAmBzC;AAKD,wBAAsB,iBAAiB,CACrC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,CAAC,eAAe,EAAE,EAAE,KAAK,CAAC,CAAC,CAoB3C"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Provenance log — an append-only audit trail of every write to the vault.
|
|
2
|
+
//
|
|
3
|
+
// Each write tool appends one JSON line to .daftari/curation-log.jsonl. The
|
|
4
|
+
// log is advisory: it records what happened, who did it, and how the
|
|
5
|
+
// frontmatter changed, but it never blocks or alters a write. It is local
|
|
6
|
+
// audit state, not vault content — it is git-ignored, not committed.
|
|
7
|
+
import { mkdirSync } from "node:fs";
|
|
8
|
+
import { appendFile, readFile } from "node:fs/promises";
|
|
9
|
+
import { join } from "node:path";
|
|
10
|
+
import { err, ok } from "../frontmatter/types.js";
|
|
11
|
+
export function curationLogPath(vaultRoot) {
|
|
12
|
+
return join(vaultRoot, ".daftari", "curation-log.jsonl");
|
|
13
|
+
}
|
|
14
|
+
// Diffs two frontmatter blocks, returning only the fields that changed. A
|
|
15
|
+
// `before` of null means the document is newly created — every field counts
|
|
16
|
+
// as a change from `undefined`.
|
|
17
|
+
export function frontmatterDiff(before, after) {
|
|
18
|
+
const diff = {};
|
|
19
|
+
const keys = new Set([...(before ? Object.keys(before) : []), ...Object.keys(after)]);
|
|
20
|
+
for (const key of keys) {
|
|
21
|
+
const b = before ? before[key] : undefined;
|
|
22
|
+
const a = after[key];
|
|
23
|
+
if (JSON.stringify(b) !== JSON.stringify(a)) {
|
|
24
|
+
diff[key] = { before: b, after: a };
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
return diff;
|
|
28
|
+
}
|
|
29
|
+
// Appends one entry to the curation log. The timestamp is stamped here so
|
|
30
|
+
// callers cannot forget it. Creating the .daftari directory is idempotent.
|
|
31
|
+
export async function recordProvenance(vaultRoot, entry) {
|
|
32
|
+
const full = {
|
|
33
|
+
timestamp: entry.timestamp ?? new Date().toISOString(),
|
|
34
|
+
tool: entry.tool,
|
|
35
|
+
file: entry.file,
|
|
36
|
+
agent: entry.agent,
|
|
37
|
+
action: entry.action,
|
|
38
|
+
...(entry.frontmatter_diff && Object.keys(entry.frontmatter_diff).length > 0
|
|
39
|
+
? { frontmatter_diff: entry.frontmatter_diff }
|
|
40
|
+
: {}),
|
|
41
|
+
};
|
|
42
|
+
try {
|
|
43
|
+
mkdirSync(join(vaultRoot, ".daftari"), { recursive: true });
|
|
44
|
+
await appendFile(curationLogPath(vaultRoot), `${JSON.stringify(full)}\n`);
|
|
45
|
+
return ok(full);
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
49
|
+
return err(new Error(`cannot append to curation log: ${reason}`));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Reads the curation log back, oldest entry first. A missing log is not an
|
|
53
|
+
// error — it just means nothing has been written yet. Malformed lines are
|
|
54
|
+
// skipped rather than aborting the read.
|
|
55
|
+
export async function readProvenanceLog(vaultRoot) {
|
|
56
|
+
let raw;
|
|
57
|
+
try {
|
|
58
|
+
raw = await readFile(curationLogPath(vaultRoot), "utf-8");
|
|
59
|
+
}
|
|
60
|
+
catch (e) {
|
|
61
|
+
if (e.code === "ENOENT")
|
|
62
|
+
return ok([]);
|
|
63
|
+
const reason = e instanceof Error ? e.message : String(e);
|
|
64
|
+
return err(new Error(`cannot read curation log: ${reason}`));
|
|
65
|
+
}
|
|
66
|
+
const entries = [];
|
|
67
|
+
for (const line of raw.split("\n")) {
|
|
68
|
+
const trimmed = line.trim();
|
|
69
|
+
if (!trimmed)
|
|
70
|
+
continue;
|
|
71
|
+
try {
|
|
72
|
+
entries.push(JSON.parse(trimmed));
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
// skip a corrupt line; the log is append-only and best-effort.
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return ok(entries);
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=provenance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provenance.js","sourceRoot":"","sources":["../../src/curation/provenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type Result } from "../frontmatter/types.js";
|
|
2
|
+
export declare function ageInDays(dateISO: string, now?: Date): number;
|
|
3
|
+
export interface StalenessResult {
|
|
4
|
+
score: number;
|
|
5
|
+
ageDays: number;
|
|
6
|
+
ttlDays: number | null;
|
|
7
|
+
expired: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function computeStaleness(input: {
|
|
10
|
+
updated: string;
|
|
11
|
+
ttl_days: number | null;
|
|
12
|
+
}, now?: Date): StalenessResult;
|
|
13
|
+
export interface StaleFile {
|
|
14
|
+
path: string;
|
|
15
|
+
title: string;
|
|
16
|
+
staleness: StalenessResult;
|
|
17
|
+
}
|
|
18
|
+
export declare function listStaleFiles(vaultRoot: string, threshold?: number, now?: Date): Promise<Result<StaleFile[], Error>>;
|
|
19
|
+
//# sourceMappingURL=staleness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.d.ts","sourceRoot":"","sources":["../../src/curation/staleness.ts"],"names":[],"mappings":"AAWA,OAAO,EAAM,KAAK,MAAM,EAAE,MAAM,yBAAyB,CAAC;AAO1D,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,GAAE,IAAiB,GAAG,MAAM,CAIzE;AAED,MAAM,WAAW,eAAe;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;CAClB;AAKD,wBAAgB,gBAAgB,CAC9B,KAAK,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,EACnD,GAAG,GAAE,IAAiB,GACrB,eAAe,CAejB;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,eAAe,CAAC;CAC5B;AAKD,wBAAsB,cAAc,CAClC,SAAS,EAAE,MAAM,EACjB,SAAS,SAAI,EACb,GAAG,GAAE,IAAiB,GACrB,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,EAAE,KAAK,CAAC,CAAC,CAsBrC"}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Staleness — the time-decay half of the advisory curation engine.
|
|
2
|
+
//
|
|
3
|
+
// A document carries an optional `ttl_days` in its frontmatter: a soft
|
|
4
|
+
// expectation of how long the note stays accurate. Staleness measures how far
|
|
5
|
+
// past that expectation a document has drifted, as a decay score from 0.0
|
|
6
|
+
// (just updated) to 1.0 (fully stale — at or past its TTL).
|
|
7
|
+
//
|
|
8
|
+
// This is advisory only. Nothing here edits a file or changes a status; it
|
|
9
|
+
// reports a number the curator (vault_lint, vault_status) can act on.
|
|
10
|
+
import { parseDocument } from "../frontmatter/parser.js";
|
|
11
|
+
import { ok } from "../frontmatter/types.js";
|
|
12
|
+
import { listFiles, readFile, resolveVaultPath } from "../storage/local.js";
|
|
13
|
+
const MS_PER_DAY = 86_400_000;
|
|
14
|
+
// Whole days between an ISO date (YYYY-MM-DD) and `now`. Negative if the date
|
|
15
|
+
// is in the future; NaN-safe — an unparseable date yields 0.
|
|
16
|
+
export function ageInDays(dateISO, now = new Date()) {
|
|
17
|
+
const then = Date.parse(`${dateISO}T00:00:00Z`);
|
|
18
|
+
if (Number.isNaN(then))
|
|
19
|
+
return 0;
|
|
20
|
+
return Math.floor((now.getTime() - then) / MS_PER_DAY);
|
|
21
|
+
}
|
|
22
|
+
// Computes the decay score for a document from its `updated` date and
|
|
23
|
+
// `ttl_days`. A document with no TTL never goes stale (score 0): it has made
|
|
24
|
+
// no freshness promise to break.
|
|
25
|
+
export function computeStaleness(input, now = new Date()) {
|
|
26
|
+
const ageDays = ageInDays(input.updated, now);
|
|
27
|
+
const ttlDays = input.ttl_days;
|
|
28
|
+
if (ttlDays === null) {
|
|
29
|
+
return { score: 0, ageDays, ttlDays: null, expired: false };
|
|
30
|
+
}
|
|
31
|
+
if (ttlDays <= 0) {
|
|
32
|
+
// A non-positive TTL means "stale the moment it ages at all".
|
|
33
|
+
const expired = ageDays > 0;
|
|
34
|
+
return { score: expired ? 1 : 0, ageDays, ttlDays, expired };
|
|
35
|
+
}
|
|
36
|
+
const ratio = ageDays / ttlDays;
|
|
37
|
+
const score = Math.min(1, Math.max(0, ratio));
|
|
38
|
+
return { score, ageDays, ttlDays, expired: ageDays >= ttlDays };
|
|
39
|
+
}
|
|
40
|
+
// Scans the whole vault and returns documents whose decay score is at or above
|
|
41
|
+
// `threshold`, most stale first. The default threshold of 1.0 reports only
|
|
42
|
+
// documents at or past their TTL.
|
|
43
|
+
export async function listStaleFiles(vaultRoot, threshold = 1, now = new Date()) {
|
|
44
|
+
const list = await listFiles(vaultRoot);
|
|
45
|
+
if (!list.ok)
|
|
46
|
+
return list;
|
|
47
|
+
const stale = [];
|
|
48
|
+
for (const relPath of list.value) {
|
|
49
|
+
const resolved = resolveVaultPath(vaultRoot, relPath);
|
|
50
|
+
if (!resolved.ok)
|
|
51
|
+
continue;
|
|
52
|
+
const file = await readFile(resolved.value);
|
|
53
|
+
if (!file.ok)
|
|
54
|
+
continue;
|
|
55
|
+
const parsed = parseDocument(file.value);
|
|
56
|
+
if (!parsed.ok)
|
|
57
|
+
continue;
|
|
58
|
+
const fm = parsed.value.frontmatter;
|
|
59
|
+
const staleness = computeStaleness({ updated: fm.updated, ttl_days: fm.ttl_days }, now);
|
|
60
|
+
if (staleness.score >= threshold) {
|
|
61
|
+
stale.push({ path: relPath, title: fm.title, staleness });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
stale.sort((a, b) => b.staleness.score - a.staleness.score);
|
|
65
|
+
return ok(stale);
|
|
66
|
+
}
|
|
67
|
+
//# sourceMappingURL=staleness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"staleness.js","sourceRoot":"","sources":["../../src/curation/staleness.ts"],"names":[],"mappings":"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"}
|