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.
Files changed (101) hide show
  1. package/CHANGELOG.md +37 -0
  2. package/LICENSE +21 -0
  3. package/README.md +259 -0
  4. package/dist/access/locks.d.ts +19 -0
  5. package/dist/access/locks.d.ts.map +1 -0
  6. package/dist/access/locks.js +112 -0
  7. package/dist/access/locks.js.map +1 -0
  8. package/dist/access/rbac.d.ts +18 -0
  9. package/dist/access/rbac.d.ts.map +1 -0
  10. package/dist/access/rbac.js +48 -0
  11. package/dist/access/rbac.js.map +1 -0
  12. package/dist/cli.d.ts +4 -0
  13. package/dist/cli.d.ts.map +1 -0
  14. package/dist/cli.js +216 -0
  15. package/dist/cli.js.map +1 -0
  16. package/dist/curation/lint.d.ts +20 -0
  17. package/dist/curation/lint.d.ts.map +1 -0
  18. package/dist/curation/lint.js +176 -0
  19. package/dist/curation/lint.js.map +1 -0
  20. package/dist/curation/provenance.d.ts +21 -0
  21. package/dist/curation/provenance.d.ts.map +1 -0
  22. package/dist/curation/provenance.js +80 -0
  23. package/dist/curation/provenance.js.map +1 -0
  24. package/dist/curation/staleness.d.ts +19 -0
  25. package/dist/curation/staleness.d.ts.map +1 -0
  26. package/dist/curation/staleness.js +67 -0
  27. package/dist/curation/staleness.js.map +1 -0
  28. package/dist/curation/tension.d.ts +20 -0
  29. package/dist/curation/tension.d.ts.map +1 -0
  30. package/dist/curation/tension.js +134 -0
  31. package/dist/curation/tension.js.map +1 -0
  32. package/dist/frontmatter/parser.d.ts +10 -0
  33. package/dist/frontmatter/parser.d.ts.map +1 -0
  34. package/dist/frontmatter/parser.js +29 -0
  35. package/dist/frontmatter/parser.js.map +1 -0
  36. package/dist/frontmatter/schema.d.ts +7 -0
  37. package/dist/frontmatter/schema.d.ts.map +1 -0
  38. package/dist/frontmatter/schema.js +115 -0
  39. package/dist/frontmatter/schema.js.map +1 -0
  40. package/dist/frontmatter/types.d.ts +41 -0
  41. package/dist/frontmatter/types.d.ts.map +1 -0
  42. package/dist/frontmatter/types.js +8 -0
  43. package/dist/frontmatter/types.js.map +1 -0
  44. package/dist/index.d.ts +4 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +94 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/search/bm25.d.ts +19 -0
  49. package/dist/search/bm25.d.ts.map +1 -0
  50. package/dist/search/bm25.js +115 -0
  51. package/dist/search/bm25.js.map +1 -0
  52. package/dist/search/hybrid.d.ts +38 -0
  53. package/dist/search/hybrid.d.ts.map +1 -0
  54. package/dist/search/hybrid.js +162 -0
  55. package/dist/search/hybrid.js.map +1 -0
  56. package/dist/search/reindex.d.ts +15 -0
  57. package/dist/search/reindex.d.ts.map +1 -0
  58. package/dist/search/reindex.js +189 -0
  59. package/dist/search/reindex.js.map +1 -0
  60. package/dist/search/vector.d.ts +9 -0
  61. package/dist/search/vector.d.ts.map +1 -0
  62. package/dist/search/vector.js +128 -0
  63. package/dist/search/vector.js.map +1 -0
  64. package/dist/server.d.ts +6 -0
  65. package/dist/server.d.ts.map +1 -0
  66. package/dist/server.js +72 -0
  67. package/dist/server.js.map +1 -0
  68. package/dist/storage/index-db.d.ts +37 -0
  69. package/dist/storage/index-db.d.ts.map +1 -0
  70. package/dist/storage/index-db.js +145 -0
  71. package/dist/storage/index-db.js.map +1 -0
  72. package/dist/storage/local.d.ts +6 -0
  73. package/dist/storage/local.d.ts.map +1 -0
  74. package/dist/storage/local.js +57 -0
  75. package/dist/storage/local.js.map +1 -0
  76. package/dist/tools/curation.d.ts +22 -0
  77. package/dist/tools/curation.d.ts.map +1 -0
  78. package/dist/tools/curation.js +202 -0
  79. package/dist/tools/curation.js.map +1 -0
  80. package/dist/tools/read.d.ts +74 -0
  81. package/dist/tools/read.d.ts.map +1 -0
  82. package/dist/tools/read.js +254 -0
  83. package/dist/tools/read.js.map +1 -0
  84. package/dist/tools/search.d.ts +13 -0
  85. package/dist/tools/search.d.ts.map +1 -0
  86. package/dist/tools/search.js +190 -0
  87. package/dist/tools/search.js.map +1 -0
  88. package/dist/tools/write.d.ts +18 -0
  89. package/dist/tools/write.d.ts.map +1 -0
  90. package/dist/tools/write.js +465 -0
  91. package/dist/tools/write.js.map +1 -0
  92. package/dist/utils/config.d.ts +12 -0
  93. package/dist/utils/config.d.ts.map +1 -0
  94. package/dist/utils/config.js +94 -0
  95. package/dist/utils/config.js.map +1 -0
  96. package/dist/utils/git.d.ts +23 -0
  97. package/dist/utils/git.d.ts.map +1 -0
  98. package/dist/utils/git.js +114 -0
  99. package/dist/utils/git.js.map +1 -0
  100. package/package.json +69 -0
  101. 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
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":";AACA,eAAe;AACf,EAAE;AACF,0DAA0D;AAC1D,0EAA0E;AAC1E,EAAE;AACF,4EAA4E;AAC5E,4EAA4E;AAC5E,0EAA0E;AAC1E,+BAA+B;AAE/B,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACnD,OAAO,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACxD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACnD,OAAO,EAAE,MAAM,EAAE,MAAM,gBAAgB,CAAC;AAExC,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAErD,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;CAeb,CAAC;AAEF,MAAM,WAAW,GAAG,CAAC,mBAAmB,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;AAE5E,MAAM,eAAe,GAAG;;;;;;;;;CASvB,CAAC;AAEF,0EAA0E;AAC1E,qDAAqD;AACrD,SAAS,WAAW,CAAC,KAAa;IAChC,OAAO;QACL;YACE,IAAI,EAAE,gDAAgD;YACtD,IAAI,EAAE;;;;;;WAMD,KAAK;WACL,KAAK;;;;;;;;;;;;;;;;;;;;;CAqBf;SACI;QACD;YACE,IAAI,EAAE,uCAAuC;YAC7C,IAAI,EAAE;;;;;;WAMD,KAAK;WACL,KAAK;;;;;;;;;;;;;;;;;;;;CAoBf;SACI;QACD;YACE,IAAI,EAAE,mCAAmC;YACzC,IAAI,EAAE;;;;;;WAMD,KAAK;WACL,KAAK;;;;;;;;;;;;;;;;;;;CAmBf;SACI;KACF,CAAC;AACJ,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,UAAkB;IAChD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IAEtC,IAAI,UAAU,CAAC,SAAS,CAAC,IAAI,WAAW,CAAC,SAAS,CAAC,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mCAAmC,SAAS,4BAA4B,CAAC,CAAC;QAC/F,OAAO,CAAC,CAAC;IACX,CAAC;IAED,oEAAoE;IACpE,4BAA4B;IAC5B,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC5D,KAAK,MAAM,CAAC,IAAI,WAAW,EAAE,CAAC;QAC5B,SAAS,CAAC,IAAI,CAAC,SAAS,EAAE,CAAC,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IACrD,CAAC;IAED,2DAA2D;IAC3D,MAAM,QAAQ,GAAG,YAAY,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,EAAE,WAAW,EAAE,aAAa,CAAC,EAAE,OAAO,CAAC,CAAC;IACxF,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,EAAE,aAAa,CAAC,EAAE,QAAQ,CAAC,CAAC;IAEpE,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,eAAe,CAAC,CAAC;IAE9D,MAAM,KAAK,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACpD,KAAK,MAAM,GAAG,IAAI,WAAW,CAAC,KAAK,CAAC,EAAE,CAAC;QACrC,aAAa,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,IAAI,CAAC,CAAC;IACrD,CAAC;IAED,0EAA0E;IAC1E,yBAAyB;IACzB,MAAM,SAAS,GAAG,MAAM,MAAM,CAC5B,SAAS,EACT,CAAC,GAAG,CAAC,EACL,0BAA0B,EAC1B,oBAAoB,CACrB,CAAC;IACF,IAAI,CAAC,SAAS,CAAC,EAAE,EAAE,CAAC;QAClB,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oDAAoD,SAAS,CAAC,KAAK,CAAC,OAAO,IAAI,CAChF,CAAC;IACJ,CAAC;IAED,6DAA6D;IAC7D,MAAM,OAAO,GAAG,MAAM,YAAY,CAAC,SAAS,CAAC,CAAC;IAC9C,IAAI,OAAO,CAAC,EAAE,EAAE,CAAC;QACf,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oBAAoB,OAAO,CAAC,KAAK,CAAC,aAAa,QAAQ;YACrD,YAAY,OAAO,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAC9D,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,iDAAiD,OAAO,CAAC,KAAK,CAAC,OAAO,IAAI,CAC3E,CAAC;IACJ,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,qCAAqC,SAAS,MAAM;QAClD,kBAAkB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI;QAC5C,uCAAuC;QACvC,yCAAyC;QACzC,SAAS;QACT,yBAAyB,UAAU,2BAA2B,CACjE,CAAC;IACF,OAAO,CAAC,CAAC;AACX,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,GAAG,CAAC,IAAc;IACtC,IAAI,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACnD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC5B,OAAO;IACT,CAAC;IAED,MAAM,SAAS,GAAG,IAAI,CAAC,QAAQ,CAAC,QAAQ,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,CAAC,CAAC;IACvF,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,MAAM,GAAG,SAAS,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,iBAAiB,CAAC;QAC5D,OAAO,CAAC,QAAQ,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,IAAI,SAAS,CAAC,IAAI,EAAE,OAAO,CAAC,EAAE,CAAC;QAC7B,MAAM,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,OAAO;IACT,CAAC;IAED,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,qDAAqD,CAAC,CAAC;IAC5E,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5B,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;AACvB,CAAC;AAED,2EAA2E;AAC3E,0EAA0E;AAC1E,MAAM,QAAQ,GAAG,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC;AAC3D,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,QAAQ,EAAE,CAAC;IACjC,GAAG,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;QACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,mBAAmB,MAAM,IAAI,CAAC,CAAC;QACpD,OAAO,CAAC,QAAQ,GAAG,CAAC,CAAC;IACvB,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -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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"}