@hraness/kb 0.17.1

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 (200) hide show
  1. package/DISCLOSURE +43 -0
  2. package/LICENSE +21 -0
  3. package/README.md +545 -0
  4. package/dist/agent-context.js +35 -0
  5. package/dist/agent-guide-audit.js +18 -0
  6. package/dist/attachments.js +19 -0
  7. package/dist/authoring.js +33 -0
  8. package/dist/benchmark.js +19 -0
  9. package/dist/capture.js +49 -0
  10. package/dist/cli.js +3646 -0
  11. package/dist/clip/acquire.js +57 -0
  12. package/dist/clip/args.js +21 -0
  13. package/dist/clip/bounded-byte-buffer.js +9 -0
  14. package/dist/clip/browser-profiles.js +17 -0
  15. package/dist/clip/bundle-reader.js +11 -0
  16. package/dist/clip/cli.js +27 -0
  17. package/dist/clip/cookies.js +21 -0
  18. package/dist/clip/defuddle-worker.js +84 -0
  19. package/dist/clip/doctor.js +32 -0
  20. package/dist/clip/jobs.js +784 -0
  21. package/dist/clip/network-proxy.js +9 -0
  22. package/dist/clip/network.js +28 -0
  23. package/dist/clip/persist.js +30 -0
  24. package/dist/clip/refresh.js +11 -0
  25. package/dist/clip/terminal.js +11 -0
  26. package/dist/evaluation-builder.js +12238 -0
  27. package/dist/evaluation-kb.js +26 -0
  28. package/dist/evaluation.js +35 -0
  29. package/dist/git.js +39 -0
  30. package/dist/graph.js +39 -0
  31. package/dist/index-01jj6rbv.js +1103 -0
  32. package/dist/index-06c9ctr6.js +738 -0
  33. package/dist/index-0kavxzqj.js +2480 -0
  34. package/dist/index-1gwbassd.js +743 -0
  35. package/dist/index-1n418kb9.js +827 -0
  36. package/dist/index-1vrd1rmn.js +56 -0
  37. package/dist/index-1xxnjn0d.js +171 -0
  38. package/dist/index-3rm7cz6h.js +373 -0
  39. package/dist/index-3v2z4f0q.js +328 -0
  40. package/dist/index-48pz4jpc.js +392 -0
  41. package/dist/index-4j3tt0c3.js +234 -0
  42. package/dist/index-5n05se68.js +307 -0
  43. package/dist/index-5vwpzb5a.js +764 -0
  44. package/dist/index-6g2pv9d2.js +370 -0
  45. package/dist/index-7fzc32gf.js +712 -0
  46. package/dist/index-84x0vjjp.js +432 -0
  47. package/dist/index-adx6khj5.js +488 -0
  48. package/dist/index-b88v3vtm.js +805 -0
  49. package/dist/index-cv6fh7z5.js +462 -0
  50. package/dist/index-cxfrakt7.js +1067 -0
  51. package/dist/index-d13v9ckt.js +150 -0
  52. package/dist/index-dycjbhw2.js +1943 -0
  53. package/dist/index-dyqwejk5.js +531 -0
  54. package/dist/index-e5fbsywq.js +658 -0
  55. package/dist/index-f984hw45.js +927 -0
  56. package/dist/index-gh719d91.js +63 -0
  57. package/dist/index-hgve9rh2.js +2046 -0
  58. package/dist/index-hya40gb2.js +474 -0
  59. package/dist/index-j4zgmzjr.js +152 -0
  60. package/dist/index-jsmvyyvf.js +1166 -0
  61. package/dist/index-kh0pwvdd.js +1974 -0
  62. package/dist/index-mqx4nd6v.js +119 -0
  63. package/dist/index-mxxxytys.js +1254 -0
  64. package/dist/index-n5dd7r0v.js +711 -0
  65. package/dist/index-npg9z1a4.js +431 -0
  66. package/dist/index-pj501bh1.js +140 -0
  67. package/dist/index-qry4vhxk.js +1041 -0
  68. package/dist/index-s2gw5aw9.js +199 -0
  69. package/dist/index-vvdj6kca.js +70 -0
  70. package/dist/index-w2zc0vwa.js +571 -0
  71. package/dist/index-x3fthpsc.js +585 -0
  72. package/dist/index-zr53sf63.js +44 -0
  73. package/dist/index-zxdy5pby.js +2728 -0
  74. package/dist/index-zzhgcwyt.js +966 -0
  75. package/dist/index.js +453 -0
  76. package/dist/navigation.js +13 -0
  77. package/dist/pdf.js +85 -0
  78. package/dist/percolate.js +24 -0
  79. package/dist/portfolio.js +77 -0
  80. package/dist/query.js +29 -0
  81. package/dist/repository-memory.js +60 -0
  82. package/dist/sdk.js +36 -0
  83. package/dist/search-rules.js +34 -0
  84. package/dist/search.js +22 -0
  85. package/dist/semantic.js +39 -0
  86. package/dist/source-inbox.js +17 -0
  87. package/dist/untrusted-content.js +32 -0
  88. package/dist/url-intelligence.js +74 -0
  89. package/dist/workflow.js +23 -0
  90. package/dist/workflows/decision-context.js +20 -0
  91. package/dist/workflows/explain-change.js +8 -0
  92. package/dist/workflows/index.js +28 -0
  93. package/dist/workflows/plan-radar.js +10 -0
  94. package/package.json +409 -0
  95. package/skills/kb/AGENTS.md +19 -0
  96. package/skills/kb/SKILL.md +82 -0
  97. package/skills/kb/agents/openai.yaml +4 -0
  98. package/skills/kb/references/pdf-review.md +51 -0
  99. package/skills/kb/references/percolate.md +144 -0
  100. package/skills/kb/references/plan-structure.md +105 -0
  101. package/skills/kb/references/plan.md +115 -0
  102. package/skills/kb/references/query.md +231 -0
  103. package/skills/kb/references/refresh.md +133 -0
  104. package/skills/kb/references/save-pdf.md +125 -0
  105. package/skills/kb/references/save-url.md +178 -0
  106. package/skills/kb/references/url-authentication.md +75 -0
  107. package/skills/kb/references/url-platforms.md +25 -0
  108. package/src/agent-context.ts +1241 -0
  109. package/src/agent-guide-audit.ts +750 -0
  110. package/src/attachments.ts +723 -0
  111. package/src/authoring.ts +1725 -0
  112. package/src/benchmark.ts +362 -0
  113. package/src/capture.ts +68 -0
  114. package/src/cli.ts +3660 -0
  115. package/src/clip/acquire.ts +1273 -0
  116. package/src/clip/archive-today.ts +402 -0
  117. package/src/clip/args.ts +423 -0
  118. package/src/clip/assets.ts +262 -0
  119. package/src/clip/bounded-byte-buffer.ts +75 -0
  120. package/src/clip/browser-profiles.ts +372 -0
  121. package/src/clip/bundle-reader.ts +608 -0
  122. package/src/clip/capture.ts +1054 -0
  123. package/src/clip/cli.ts +181 -0
  124. package/src/clip/cookies.ts +512 -0
  125. package/src/clip/doctor.ts +1110 -0
  126. package/src/clip/extract.ts +868 -0
  127. package/src/clip/jobs.ts +959 -0
  128. package/src/clip/lib.ts +861 -0
  129. package/src/clip/media.ts +1474 -0
  130. package/src/clip/metadata-search-tool/Cargo.lock +2498 -0
  131. package/src/clip/metadata-search-tool/Cargo.toml +18 -0
  132. package/src/clip/metadata-search-tool/runner.ts +685 -0
  133. package/src/clip/metadata-search-tool/src/main.rs +389 -0
  134. package/src/clip/metadata-search.ts +687 -0
  135. package/src/clip/network-proxy.ts +627 -0
  136. package/src/clip/network.ts +950 -0
  137. package/src/clip/package-root.ts +54 -0
  138. package/src/clip/persist.ts +1472 -0
  139. package/src/clip/platforms.ts +1824 -0
  140. package/src/clip/refresh.ts +197 -0
  141. package/src/clip/structured.ts +432 -0
  142. package/src/clip/terminal.ts +197 -0
  143. package/src/clip/url-intelligence.ts +581 -0
  144. package/src/clip/url-metadata-backfill.ts +616 -0
  145. package/src/clip/url-metadata-cli.ts +251 -0
  146. package/src/clip/url-metadata.ts +1397 -0
  147. package/src/evaluation-analysis-v2.ts +1440 -0
  148. package/src/evaluation-builder.ts +2539 -0
  149. package/src/evaluation-corpus-authoring.ts +2052 -0
  150. package/src/evaluation-evidence.ts +1147 -0
  151. package/src/evaluation-execution-v2.ts +426 -0
  152. package/src/evaluation-implementation.ts +131 -0
  153. package/src/evaluation-kb-closure.ts +1430 -0
  154. package/src/evaluation-kb-runner-v2.ts +735 -0
  155. package/src/evaluation-kb-v2.ts +2567 -0
  156. package/src/evaluation-kb.ts +1018 -0
  157. package/src/evaluation-measurement-v2.ts +1462 -0
  158. package/src/evaluation-packing-v2.ts +452 -0
  159. package/src/evaluation-redaction.ts +28 -0
  160. package/src/evaluation-v2.ts +3857 -0
  161. package/src/evaluation.ts +1220 -0
  162. package/src/git.ts +1276 -0
  163. package/src/graph.ts +1487 -0
  164. package/src/index.ts +20 -0
  165. package/src/init.ts +123 -0
  166. package/src/navigation.ts +277 -0
  167. package/src/note-lock.ts +529 -0
  168. package/src/pdf/args.ts +202 -0
  169. package/src/pdf/capture.ts +298 -0
  170. package/src/pdf/cli.ts +159 -0
  171. package/src/pdf/extract.ts +303 -0
  172. package/src/pdf/layout.ts +618 -0
  173. package/src/pdf/markdown.ts +154 -0
  174. package/src/pdf/model.ts +234 -0
  175. package/src/pdf/ocr.ts +190 -0
  176. package/src/pdf/persist.ts +314 -0
  177. package/src/pdf/source.ts +135 -0
  178. package/src/pdf/tools.ts +146 -0
  179. package/src/pdf.ts +92 -0
  180. package/src/percolate.ts +877 -0
  181. package/src/portfolio-audit.ts +574 -0
  182. package/src/portfolio-identity.ts +162 -0
  183. package/src/portfolio-registry.ts +473 -0
  184. package/src/portfolio.ts +765 -0
  185. package/src/query.ts +646 -0
  186. package/src/repository-memory.ts +1150 -0
  187. package/src/sdk.ts +1452 -0
  188. package/src/search-rules.ts +712 -0
  189. package/src/search.ts +710 -0
  190. package/src/semantic-runtime.ts +1139 -0
  191. package/src/semantic.ts +2480 -0
  192. package/src/source-inbox.ts +232 -0
  193. package/src/untrusted-content.ts +401 -0
  194. package/src/url-intelligence.ts +104 -0
  195. package/src/vault.ts +636 -0
  196. package/src/workflow.ts +653 -0
  197. package/src/workflows/decision-context.ts +81 -0
  198. package/src/workflows/explain-change.ts +73 -0
  199. package/src/workflows/index.ts +3 -0
  200. package/src/workflows/plan-radar.ts +101 -0
@@ -0,0 +1,738 @@
1
+ // @bun
2
+ import {
3
+ normalizeRepositoryScope
4
+ } from "./index-5vwpzb5a.js";
5
+
6
+ // src/repository-memory.ts
7
+ import { lstat, realpath } from "fs/promises";
8
+ import {
9
+ isAbsolute,
10
+ join,
11
+ relative,
12
+ resolve,
13
+ sep
14
+ } from "path";
15
+ var repositoryScopesMetadataKey = "repository_scopes";
16
+ var MAX_REPOSITORY_SCOPES = 16;
17
+ var MAX_REPOSITORY_SCOPE_UTF8_BYTES = 1024;
18
+ var MAX_REPOSITORY_SCOPES_UTF8_BYTES = 8 * 1024;
19
+ var DEFAULT_REPOSITORY_MEMORY_GROUP_LIMIT = 10;
20
+ var MAX_REPOSITORY_MEMORY_GROUP_LIMIT = 100;
21
+ var DEFAULT_REPOSITORY_MEMORY_DETAIL_LIMIT = 20;
22
+ var MAX_REPOSITORY_MEMORY_DETAIL_LIMIT = 100;
23
+ var MAX_REPOSITORY_MEMORY_SUMMARY_UTF8_BYTES = 2 * 1024;
24
+ var activePlanStatuses = [
25
+ "proposed",
26
+ "accepted",
27
+ "in-progress",
28
+ "blocked"
29
+ ];
30
+ var terminalPlanStatuses = [
31
+ "completed",
32
+ "superseded",
33
+ "cancelled"
34
+ ];
35
+ var planStatuses = [
36
+ ...activePlanStatuses,
37
+ ...terminalPlanStatuses
38
+ ];
39
+ var activeStatusSet = new Set(activePlanStatuses);
40
+ var terminalStatusSet = new Set(terminalPlanStatuses);
41
+ var drivePathPattern = /^[A-Za-z]:/u;
42
+ var globCharacterPattern = /[*?[\]{}]/u;
43
+ function isActivePlanStatus(value) {
44
+ return typeof value === "string" && activeStatusSet.has(value);
45
+ }
46
+ function isTerminalPlanStatus(value) {
47
+ return typeof value === "string" && terminalStatusSet.has(value);
48
+ }
49
+ function isPlanStatus(value) {
50
+ return isActivePlanStatus(value) || isTerminalPlanStatus(value);
51
+ }
52
+
53
+ class RepositoryScopesError extends TypeError {
54
+ issues;
55
+ constructor(label, issues) {
56
+ super(`${label} is invalid: ${issues.map(({ message }) => message).join(" ")}`);
57
+ this.name = "RepositoryScopesError";
58
+ this.issues = issues;
59
+ }
60
+ }
61
+ function formattedBytes(value) {
62
+ return value.toLocaleString("en-US");
63
+ }
64
+ function hasControlCharacter(value) {
65
+ for (const character of value) {
66
+ const point = character.codePointAt(0);
67
+ if (point !== undefined && (point <= 31 || point >= 127 && point <= 159)) {
68
+ return true;
69
+ }
70
+ }
71
+ return false;
72
+ }
73
+ function pathValidationCode(value) {
74
+ if (value.trim() === "")
75
+ return "empty";
76
+ if (hasControlCharacter(value))
77
+ return "control-character";
78
+ if (/^[\\/]/u.test(value) || drivePathPattern.test(value))
79
+ return "absolute";
80
+ if (globCharacterPattern.test(value))
81
+ return "glob";
82
+ if (value.replaceAll("\\", "/").split("/").includes(".."))
83
+ return "traversal";
84
+ return "noncanonical";
85
+ }
86
+ function canonicalRepositoryPath(value, label = "Repository scope") {
87
+ if (typeof value !== "string")
88
+ throw new TypeError(`${label} must be a string.`);
89
+ const bytes = Buffer.byteLength(value, "utf8");
90
+ if (bytes > MAX_REPOSITORY_SCOPE_UTF8_BYTES) {
91
+ throw new RangeError(`${label} must be at most ${formattedBytes(MAX_REPOSITORY_SCOPE_UTF8_BYTES)} UTF-8 bytes.`);
92
+ }
93
+ let canonical;
94
+ try {
95
+ canonical = normalizeRepositoryScope(value);
96
+ } catch (error) {
97
+ throw new RepositoryScopesError(label, [{
98
+ code: pathValidationCode(value),
99
+ value,
100
+ message: error instanceof Error ? error.message : `${label} is invalid.`
101
+ }]);
102
+ }
103
+ if (canonical !== value) {
104
+ throw new RepositoryScopesError(label, [{
105
+ code: "noncanonical",
106
+ value,
107
+ message: `${label} must use its exact NFC-normalized POSIX form ${JSON.stringify(canonical)}.`
108
+ }]);
109
+ }
110
+ return value;
111
+ }
112
+ function analyzeScopeArray(value, options) {
113
+ if (!Array.isArray(value)) {
114
+ return {
115
+ present: true,
116
+ valid: false,
117
+ scopes: [],
118
+ issues: [{
119
+ code: "not-array",
120
+ value,
121
+ message: `${options.label} must be an array of canonical repository paths.`
122
+ }]
123
+ };
124
+ }
125
+ const issues = [];
126
+ if (!options.allowEmpty && value.length === 0) {
127
+ issues.push({
128
+ code: "empty",
129
+ message: `${options.label} must contain at least one repository path when present.`
130
+ });
131
+ }
132
+ if (value.length > MAX_REPOSITORY_SCOPES) {
133
+ issues.push({
134
+ code: "scope-count",
135
+ message: `${options.label} may contain at most ${MAX_REPOSITORY_SCOPES} paths.`
136
+ });
137
+ }
138
+ let totalBytes = 0;
139
+ const scopes = [];
140
+ const exact = new Map;
141
+ const folded = new Map;
142
+ for (let index = 0;index < Math.min(value.length, MAX_REPOSITORY_SCOPES); index += 1) {
143
+ const candidate = value[index];
144
+ if (typeof candidate !== "string") {
145
+ issues.push({
146
+ code: "not-string",
147
+ index,
148
+ value: candidate,
149
+ message: `${options.label} entry ${index + 1} must be a string.`
150
+ });
151
+ continue;
152
+ }
153
+ const bytes = Buffer.byteLength(candidate, "utf8");
154
+ totalBytes += bytes;
155
+ if (bytes > MAX_REPOSITORY_SCOPE_UTF8_BYTES) {
156
+ issues.push({
157
+ code: "scope-bytes",
158
+ index,
159
+ value: candidate,
160
+ message: `${options.label} entry ${index + 1} must be at most ` + `${formattedBytes(MAX_REPOSITORY_SCOPE_UTF8_BYTES)} UTF-8 bytes.`
161
+ });
162
+ continue;
163
+ }
164
+ try {
165
+ canonicalRepositoryPath(candidate, `${options.label} entry ${index + 1}`);
166
+ } catch (error) {
167
+ if (error instanceof RepositoryScopesError) {
168
+ issues.push(...error.issues.map((issue) => ({ ...issue, index })));
169
+ } else {
170
+ issues.push({
171
+ code: "noncanonical",
172
+ index,
173
+ value: candidate,
174
+ message: error instanceof Error ? error.message : "The repository path is invalid."
175
+ });
176
+ }
177
+ continue;
178
+ }
179
+ const duplicate = exact.get(candidate);
180
+ if (duplicate !== undefined) {
181
+ issues.push({
182
+ code: "duplicate",
183
+ index,
184
+ value: candidate,
185
+ message: `${options.label} entries ${duplicate + 1} and ${index + 1} are duplicates.`
186
+ });
187
+ continue;
188
+ }
189
+ exact.set(candidate, index);
190
+ const caseFolded = candidate.toLocaleLowerCase("en-US");
191
+ const collision = folded.get(caseFolded);
192
+ if (collision !== undefined && collision.scope !== candidate) {
193
+ issues.push({
194
+ code: "case-fold-collision",
195
+ index,
196
+ value: candidate,
197
+ message: `${options.label} entries ${collision.index + 1} and ${index + 1} collide under case folding.`
198
+ });
199
+ continue;
200
+ }
201
+ folded.set(caseFolded, { scope: candidate, index });
202
+ scopes.push(candidate);
203
+ }
204
+ if (totalBytes > MAX_REPOSITORY_SCOPES_UTF8_BYTES) {
205
+ issues.push({
206
+ code: "total-bytes",
207
+ message: `${options.label} may contain at most ` + `${formattedBytes(MAX_REPOSITORY_SCOPES_UTF8_BYTES)} UTF-8 bytes in total.`
208
+ });
209
+ }
210
+ return {
211
+ present: true,
212
+ valid: issues.length === 0,
213
+ scopes,
214
+ issues
215
+ };
216
+ }
217
+ function analyzeAuthoredRepositoryScopes(metadata) {
218
+ const exactKeys = Object.keys(metadata).filter((key) => key.toLocaleLowerCase("en-US") === repositoryScopesMetadataKey);
219
+ if (exactKeys.length === 0) {
220
+ return { present: false, valid: true, scopes: [], issues: [] };
221
+ }
222
+ if (exactKeys.length !== 1 || exactKeys[0] !== repositoryScopesMetadataKey) {
223
+ return {
224
+ present: true,
225
+ valid: false,
226
+ scopes: [],
227
+ issues: [{
228
+ code: "noncanonical",
229
+ message: `Repository scope metadata must use the exact key ${repositoryScopesMetadataKey}.`
230
+ }]
231
+ };
232
+ }
233
+ return analyzeScopeArray(metadata[repositoryScopesMetadataKey], {
234
+ allowEmpty: false,
235
+ label: repositoryScopesMetadataKey
236
+ });
237
+ }
238
+ function validateRepositoryScopeSelection(value) {
239
+ const analysis = analyzeScopeArray(value, {
240
+ allowEmpty: true,
241
+ label: "Repository scope selection"
242
+ });
243
+ if (!analysis.valid)
244
+ throw new RepositoryScopesError("Repository scope selection", analysis.issues);
245
+ return analysis.scopes;
246
+ }
247
+ function repositoryScopeMatchesPath(scope, fullPath) {
248
+ const checkedScope = canonicalRepositoryPath(scope, "Repository scope");
249
+ const checkedPath = canonicalRepositoryPath(fullPath, "Repository path");
250
+ return checkedScope === "." || checkedScope === checkedPath || checkedPath.startsWith(`${checkedScope}/`);
251
+ }
252
+ function scopeDepth(scope) {
253
+ return scope === "." ? 0 : scope.split("/").length;
254
+ }
255
+ function deepestRepositoryScopeMatch(scopes, fullPath) {
256
+ const checkedScopes = validateRepositoryScopeSelection(scopes);
257
+ const checkedPath = canonicalRepositoryPath(fullPath, "Repository path");
258
+ const matches = checkedScopes.filter((scope) => repositoryScopeMatchesPath(scope, checkedPath)).map((scope) => ({
259
+ scope,
260
+ kind: scope === checkedPath ? "exact" : "ancestor",
261
+ depth: scopeDepth(scope)
262
+ })).toSorted((left, right) => right.depth - left.depth || left.scope.localeCompare(right.scope));
263
+ return matches[0] ?? null;
264
+ }
265
+ function metadataMatchesExactRepositoryScopes(metadata, selectedScopes) {
266
+ const selected = validateRepositoryScopeSelection(selectedScopes);
267
+ if (selected.length === 0)
268
+ return true;
269
+ const authored = analyzeAuthoredRepositoryScopes(metadata);
270
+ if (!authored.present || !authored.valid)
271
+ return false;
272
+ const declarations = new Set(authored.scopes);
273
+ return selected.some((scope) => declarations.has(scope));
274
+ }
275
+ function isMissingFileError(error) {
276
+ return error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT";
277
+ }
278
+ function pathInside(root, candidate) {
279
+ const fromRoot = relative(root, candidate);
280
+ return fromRoot === "" || fromRoot !== ".." && !fromRoot.startsWith(`..${sep}`) && !isAbsolute(fromRoot);
281
+ }
282
+ async function canonicalRepositoryRoot(rootInput) {
283
+ const root = await realpath(resolve(rootInput));
284
+ const metadata = await lstat(root);
285
+ if (!metadata.isDirectory())
286
+ throw new TypeError("The repository root must be a directory.");
287
+ return root;
288
+ }
289
+ async function inspectScopeAtRoot(root, scope) {
290
+ if (scope === ".")
291
+ return { status: "present", scope, kind: "directory" };
292
+ let current = root;
293
+ const traversed = [];
294
+ const segments = scope.split("/");
295
+ for (const [index, segment] of segments.entries()) {
296
+ traversed.push(segment);
297
+ current = join(current, segment);
298
+ if (!pathInside(root, current)) {
299
+ throw new TypeError("A repository scope resolved outside the repository root.");
300
+ }
301
+ let metadata;
302
+ try {
303
+ metadata = await lstat(current);
304
+ } catch (error) {
305
+ if (isMissingFileError(error)) {
306
+ return {
307
+ status: "absent",
308
+ scope,
309
+ firstMissingPath: traversed.join("/")
310
+ };
311
+ }
312
+ throw error;
313
+ }
314
+ const repositoryPath = traversed.join("/");
315
+ if (metadata.isSymbolicLink()) {
316
+ return { status: "invalid", scope, path: repositoryPath, reason: "symlink" };
317
+ }
318
+ if (index < segments.length - 1 && !metadata.isDirectory()) {
319
+ return {
320
+ status: "invalid",
321
+ scope,
322
+ path: repositoryPath,
323
+ reason: "ancestor-not-directory"
324
+ };
325
+ }
326
+ if (index === segments.length - 1) {
327
+ if (metadata.isDirectory())
328
+ return { status: "present", scope, kind: "directory" };
329
+ if (metadata.isFile())
330
+ return { status: "present", scope, kind: "file" };
331
+ return {
332
+ status: "invalid",
333
+ scope,
334
+ path: repositoryPath,
335
+ reason: "unsupported-type"
336
+ };
337
+ }
338
+ }
339
+ throw new Error("Repository scope inspection did not reach its final segment.");
340
+ }
341
+ async function inspectRepositoryScopeState(repositoryRoot, scopeInput) {
342
+ const scope = canonicalRepositoryPath(scopeInput);
343
+ return await inspectScopeAtRoot(await canonicalRepositoryRoot(repositoryRoot), scope);
344
+ }
345
+ var repositoryMemoryGroupKeys = [
346
+ "maintainedKnowledge",
347
+ "activePlans",
348
+ "datedResearch",
349
+ "reports",
350
+ "historicalPlans"
351
+ ];
352
+ function metadataString(metadata, key) {
353
+ const value = metadata[key];
354
+ return typeof value === "string" ? value : undefined;
355
+ }
356
+ function validCalendarDate(value) {
357
+ if (!/^\d{4}-\d{2}-\d{2}$/u.test(value))
358
+ return false;
359
+ const [year, month, day] = value.split("-").map(Number);
360
+ if (year === undefined || month === undefined || day === undefined)
361
+ return false;
362
+ const date = new Date(Date.UTC(year, month - 1, day));
363
+ return date.getUTCFullYear() === year && date.getUTCMonth() + 1 === month && date.getUTCDate() === day;
364
+ }
365
+ function classifyRepositoryMemoryRecord(note) {
366
+ const type = metadataString(note.metadata, "type");
367
+ const status = metadataString(note.metadata, "status");
368
+ if (type === "note" || type === "concept") {
369
+ return { kind: "record", group: "maintainedKnowledge", current: true };
370
+ }
371
+ if (type === "plan") {
372
+ if (isActivePlanStatus(status)) {
373
+ return { kind: "record", group: "activePlans", current: true, status };
374
+ }
375
+ if (isTerminalPlanStatus(status)) {
376
+ return { kind: "record", group: "historicalPlans", current: false, status };
377
+ }
378
+ return { kind: "invalid", reason: "A scoped plan must declare one supported lifecycle status." };
379
+ }
380
+ if (type === "market-research") {
381
+ const asOf = metadataString(note.metadata, "as_of");
382
+ if (!/^projects\/[^/]+\/market\/.+\.md$/u.test(note.path)) {
383
+ return {
384
+ kind: "invalid",
385
+ reason: "Scoped market research must live under projects/<domain>/market/."
386
+ };
387
+ }
388
+ if (status !== "snapshot" || asOf === undefined || !validCalendarDate(asOf)) {
389
+ return {
390
+ kind: "invalid",
391
+ reason: "Scoped market research requires status snapshot and a valid as_of date."
392
+ };
393
+ }
394
+ return {
395
+ kind: "record",
396
+ group: "datedResearch",
397
+ current: true,
398
+ status,
399
+ date: asOf
400
+ };
401
+ }
402
+ if (type === "report") {
403
+ const generated = metadataString(note.metadata, "generated");
404
+ if (generated === undefined || !validCalendarDate(generated)) {
405
+ return { kind: "invalid", reason: "A scoped report requires a valid generated date." };
406
+ }
407
+ return {
408
+ kind: "record",
409
+ group: "reports",
410
+ current: true,
411
+ date: generated
412
+ };
413
+ }
414
+ return type === undefined ? { kind: "invalid", reason: "A scoped repository-memory record must declare a type." } : { kind: "ignored" };
415
+ }
416
+ function checkedLimit(value, fallback, maximum, label) {
417
+ const result = value ?? fallback;
418
+ if (!Number.isSafeInteger(result) || result < 0 || result > maximum) {
419
+ throw new RangeError(`${label} must be a safe integer from 0 through ${maximum}.`);
420
+ }
421
+ return result;
422
+ }
423
+ function utf8Prefix(value, maximumBytes) {
424
+ let bytes = 0;
425
+ let end = 0;
426
+ for (const character of value) {
427
+ const width = Buffer.byteLength(character, "utf8");
428
+ if (bytes + width > maximumBytes)
429
+ return { value: value.slice(0, end), truncated: true };
430
+ bytes += width;
431
+ end += character.length;
432
+ }
433
+ return { value, truncated: false };
434
+ }
435
+ function boundedOptionalMetadata(metadata, key) {
436
+ const value = metadataString(metadata, key);
437
+ return value === undefined ? undefined : utf8Prefix(value, 1024).value;
438
+ }
439
+ var statusOrder = new Map(planStatuses.map((status, index) => [status, index]));
440
+ function compareCandidates(left, right) {
441
+ const leftDate = left.classification.date ?? "";
442
+ const rightDate = right.classification.date ?? "";
443
+ return right.match.depth - left.match.depth || rightDate.localeCompare(leftDate) || (statusOrder.get(left.classification.status ?? "") ?? planStatuses.length) - (statusOrder.get(right.classification.status ?? "") ?? planStatuses.length) || left.note.title.localeCompare(right.note.title) || left.note.path.localeCompare(right.note.path);
444
+ }
445
+ function boundedDetails(values, limit) {
446
+ const details = values.slice(0, limit);
447
+ return {
448
+ total: values.length,
449
+ returned: details.length,
450
+ truncated: details.length < values.length,
451
+ details
452
+ };
453
+ }
454
+ function invalidRecord(path, ...issues) {
455
+ return { path, issues };
456
+ }
457
+ function addInvalidIssue(issuesByPath, path, issues) {
458
+ const existing = issuesByPath.get(path) ?? [];
459
+ existing.push(...issues);
460
+ issuesByPath.set(path, existing);
461
+ }
462
+ function analyzeScopedRepositoryMemory(notes) {
463
+ let authoredRecords = 0;
464
+ const entries = [];
465
+ const issuesByPath = new Map;
466
+ for (const note of notes) {
467
+ const scopes = analyzeAuthoredRepositoryScopes(note.metadata);
468
+ if (!scopes.present)
469
+ continue;
470
+ authoredRecords += 1;
471
+ if (!scopes.valid) {
472
+ addInvalidIssue(issuesByPath, note.path, scopes.issues.map(({ message }) => message));
473
+ continue;
474
+ }
475
+ entries.push({
476
+ note,
477
+ scopes: scopes.scopes,
478
+ classification: classifyRepositoryMemoryRecord(note)
479
+ });
480
+ }
481
+ const foldedDeclarations = new Map;
482
+ for (const entry of entries) {
483
+ for (const scope of entry.scopes) {
484
+ const folded = scope.toLocaleLowerCase("en-US");
485
+ const declarations = foldedDeclarations.get(folded) ?? new Map;
486
+ const paths = declarations.get(scope) ?? new Set;
487
+ paths.add(entry.note.path);
488
+ declarations.set(scope, paths);
489
+ foldedDeclarations.set(folded, declarations);
490
+ }
491
+ }
492
+ for (const declarations of foldedDeclarations.values()) {
493
+ if (declarations.size < 2)
494
+ continue;
495
+ const scopes = [...declarations.keys()].toSorted((left, right) => left.localeCompare(right));
496
+ const displayedScopes = scopes.slice(0, MAX_REPOSITORY_SCOPES);
497
+ const scopeSummary = displayedScopes.join(", ") + (displayedScopes.length < scopes.length ? `, and ${scopes.length - displayedScopes.length} more` : "");
498
+ for (const paths of declarations.values()) {
499
+ for (const path of paths) {
500
+ addInvalidIssue(issuesByPath, path, [`Repository scopes ${scopeSummary} collide under case folding.`]);
501
+ }
502
+ }
503
+ }
504
+ for (const entry of entries) {
505
+ if (entry.classification.kind === "invalid") {
506
+ addInvalidIssue(issuesByPath, entry.note.path, [entry.classification.reason]);
507
+ }
508
+ }
509
+ const invalid = [...issuesByPath].map(([path, issues]) => invalidRecord(path, ...issues)).toSorted((left, right) => left.path.localeCompare(right.path));
510
+ const records = entries.filter((entry) => entry.classification.kind === "record" && !issuesByPath.has(entry.note.path));
511
+ return { authoredRecords, entries, records, invalid };
512
+ }
513
+ async function inspectRepositoryScopesAtRoot(root, scopes) {
514
+ const sorted = [...new Set(scopes)].toSorted((left, right) => left.localeCompare(right));
515
+ const states = new Map;
516
+ let nextIndex = 0;
517
+ const inspectNext = async () => {
518
+ while (nextIndex < sorted.length) {
519
+ const index = nextIndex;
520
+ nextIndex += 1;
521
+ const scope = sorted[index];
522
+ if (scope === undefined)
523
+ continue;
524
+ states.set(scope, await inspectScopeAtRoot(root, scope));
525
+ }
526
+ };
527
+ await Promise.all(Array.from({ length: Math.min(8, sorted.length) }, () => inspectNext()));
528
+ return states;
529
+ }
530
+ async function buildRepositoryMemoryContext(notes, options) {
531
+ const groupLimit = checkedLimit(options.groupLimit, DEFAULT_REPOSITORY_MEMORY_GROUP_LIMIT, MAX_REPOSITORY_MEMORY_GROUP_LIMIT, "Repository-memory group limit");
532
+ const detailLimit = checkedLimit(options.detailLimit, DEFAULT_REPOSITORY_MEMORY_DETAIL_LIMIT, MAX_REPOSITORY_MEMORY_DETAIL_LIMIT, "Repository-memory detail limit");
533
+ const target = canonicalRepositoryPath(options.target, "Repository-memory target");
534
+ const repositoryRoot = await canonicalRepositoryRoot(options.repositoryRoot);
535
+ const targetState = await inspectScopeAtRoot(repositoryRoot, target);
536
+ const scoped = analyzeScopedRepositoryMemory(notes);
537
+ const invalid = [...scoped.invalid];
538
+ const candidates = [];
539
+ for (const entry of scoped.records) {
540
+ const match = deepestRepositoryScopeMatch(entry.scopes, target);
541
+ if (match === null)
542
+ continue;
543
+ candidates.push({
544
+ note: entry.note,
545
+ scopes: entry.scopes,
546
+ classification: entry.classification,
547
+ match
548
+ });
549
+ }
550
+ const states = await inspectRepositoryScopesAtRoot(repositoryRoot, candidates.map(({ match }) => match.scope));
551
+ const accepted = [];
552
+ const advisories = [];
553
+ for (const candidate of candidates) {
554
+ const state = states.get(candidate.match.scope);
555
+ if (state === undefined)
556
+ throw new Error("A matched repository scope was not inspected.");
557
+ if (state.status === "invalid") {
558
+ invalid.push(invalidRecord(candidate.note.path, `Repository scope ${state.scope} is invalid at ${state.path}: ${state.reason}.`));
559
+ continue;
560
+ }
561
+ if (state.status === "present" && state.kind === "file" && candidate.match.kind === "ancestor") {
562
+ continue;
563
+ }
564
+ accepted.push(candidate);
565
+ if (state.status === "absent" && candidate.classification.current) {
566
+ advisories.push({
567
+ kind: "absent-current-scope",
568
+ path: candidate.note.path,
569
+ scope: state.scope,
570
+ message: `Current repository memory ${candidate.note.path} declares absent scope ${state.scope}.`
571
+ });
572
+ }
573
+ }
574
+ const groups = Object.create(null);
575
+ for (const key of repositoryMemoryGroupKeys) {
576
+ const matches = accepted.filter(({ classification }) => classification.group === key).toSorted(compareCandidates);
577
+ const selected = matches.slice(0, groupLimit);
578
+ const records = selected.map(({ note, scopes, classification, match }) => {
579
+ const state = states.get(match.scope);
580
+ if (state === undefined)
581
+ throw new Error("A returned repository scope was not inspected.");
582
+ const summary = utf8Prefix(note.summary, MAX_REPOSITORY_MEMORY_SUMMARY_UTF8_BYTES);
583
+ const type = metadataString(note.metadata, "type");
584
+ if (type === undefined)
585
+ throw new Error("A classified repository-memory record lost its type.");
586
+ const area = boundedOptionalMetadata(note.metadata, "area");
587
+ const description = boundedOptionalMetadata(note.metadata, "description");
588
+ return {
589
+ id: note.id,
590
+ path: note.path,
591
+ title: note.title,
592
+ summary: summary.value,
593
+ summaryTruncated: summary.truncated,
594
+ type,
595
+ ...classification.status === undefined ? {} : { status: classification.status },
596
+ ...classification.date === undefined ? {} : { date: classification.date },
597
+ ...area === undefined ? {} : { area },
598
+ ...description === undefined ? {} : { description },
599
+ repositoryScopes: scopes,
600
+ matchedScope: match.scope,
601
+ match: match.kind,
602
+ scopeState: state
603
+ };
604
+ });
605
+ groups[key] = {
606
+ total: matches.length,
607
+ returned: records.length,
608
+ truncated: records.length < matches.length,
609
+ records
610
+ };
611
+ }
612
+ invalid.sort((left, right) => left.path.localeCompare(right.path));
613
+ advisories.sort((left, right) => left.path.localeCompare(right.path) || left.scope.localeCompare(right.scope));
614
+ const invalidRecords = boundedDetails(invalid, detailLimit);
615
+ const boundedAdvisories = boundedDetails(advisories, detailLimit);
616
+ const returned = repositoryMemoryGroupKeys.reduce((count, key) => count + groups[key].returned, 0);
617
+ return {
618
+ repositoryRoot,
619
+ target,
620
+ targetState,
621
+ groups,
622
+ counts: {
623
+ matched: accepted.length,
624
+ returned,
625
+ invalid: invalid.length,
626
+ advisories: advisories.length
627
+ },
628
+ invalidRecords,
629
+ advisories: boundedAdvisories
630
+ };
631
+ }
632
+ var MAX_REPOSITORY_MEMORY_STATE_RECORD_PATHS = 20;
633
+ function scopeRecordPaths(pathsByScope, scope) {
634
+ const paths = [...pathsByScope.get(scope) ?? []].toSorted((left, right) => left.localeCompare(right));
635
+ const recordPaths = paths.slice(0, MAX_REPOSITORY_MEMORY_STATE_RECORD_PATHS);
636
+ return {
637
+ recordCount: paths.length,
638
+ recordPaths,
639
+ recordPathsTruncated: recordPaths.length < paths.length
640
+ };
641
+ }
642
+ async function auditRepositoryMemoryScopes(notes, options) {
643
+ const detailLimit = checkedLimit(options.detailLimit, DEFAULT_REPOSITORY_MEMORY_DETAIL_LIMIT, MAX_REPOSITORY_MEMORY_DETAIL_LIMIT, "Repository-memory audit detail limit");
644
+ const repositoryRoot = await canonicalRepositoryRoot(options.repositoryRoot);
645
+ const scoped = analyzeScopedRepositoryMemory(notes);
646
+ const pathsByScope = new Map;
647
+ for (const entry of scoped.entries) {
648
+ for (const scope of entry.scopes) {
649
+ const paths = pathsByScope.get(scope) ?? new Set;
650
+ paths.add(entry.note.path);
651
+ pathsByScope.set(scope, paths);
652
+ }
653
+ }
654
+ const statesByScope = await inspectRepositoryScopesAtRoot(repositoryRoot, [...pathsByScope.keys()]);
655
+ const states = [...statesByScope].map(([scope, state]) => ({
656
+ scope,
657
+ state,
658
+ ...scopeRecordPaths(pathsByScope, scope)
659
+ })).toSorted((left, right) => left.scope.localeCompare(right.scope));
660
+ const errors = scoped.invalid.map((record) => ({
661
+ kind: "invalid-record",
662
+ path: record.path,
663
+ issues: record.issues
664
+ }));
665
+ for (const detail of states) {
666
+ if (detail.state.status !== "invalid")
667
+ continue;
668
+ errors.push({
669
+ kind: "invalid-scope-state",
670
+ scope: detail.scope,
671
+ state: detail.state,
672
+ recordCount: detail.recordCount,
673
+ recordPaths: detail.recordPaths,
674
+ recordPathsTruncated: detail.recordPathsTruncated
675
+ });
676
+ }
677
+ errors.sort((left, right) => {
678
+ const leftPath = left.kind === "invalid-record" ? left.path : left.scope;
679
+ const rightPath = right.kind === "invalid-record" ? right.path : right.scope;
680
+ return left.kind.localeCompare(right.kind) || leftPath.localeCompare(rightPath);
681
+ });
682
+ const advisories = [];
683
+ for (const entry of scoped.records) {
684
+ if (!entry.classification.current)
685
+ continue;
686
+ for (const scope of entry.scopes) {
687
+ const state = statesByScope.get(scope);
688
+ if (state?.status !== "absent")
689
+ continue;
690
+ advisories.push({
691
+ kind: "absent-current-scope",
692
+ path: entry.note.path,
693
+ scope,
694
+ message: `Current repository memory ${entry.note.path} declares absent scope ${scope}.`
695
+ });
696
+ }
697
+ }
698
+ advisories.sort((left, right) => left.path.localeCompare(right.path) || left.scope.localeCompare(right.scope));
699
+ const groupCounts = Object.create(null);
700
+ for (const key of repositoryMemoryGroupKeys) {
701
+ groupCounts[key] = scoped.entries.filter(({ classification }) => classification.kind === "record" && classification.group === key).length;
702
+ }
703
+ const records = scoped.entries.map((entry) => ({
704
+ path: entry.note.path,
705
+ repositoryScopes: entry.scopes,
706
+ classification: entry.classification
707
+ })).toSorted((left, right) => left.path.localeCompare(right.path));
708
+ const classifications = scoped.entries.map(({ classification }) => classification);
709
+ const classified = classifications.filter((classification) => classification.kind === "record");
710
+ const presentScopes = states.filter(({ state }) => state.status === "present").length;
711
+ const absentScopes = states.filter(({ state }) => state.status === "absent").length;
712
+ const invalidScopes = states.filter(({ state }) => state.status === "invalid").length;
713
+ return {
714
+ repositoryRoot,
715
+ counts: {
716
+ authoredRecords: scoped.authoredRecords,
717
+ validDeclarationRecords: scoped.entries.length,
718
+ classifiedRecords: classified.length,
719
+ currentRecords: classified.filter(({ current }) => current).length,
720
+ terminalRecords: classified.filter(({ current }) => !current).length,
721
+ ignoredRecords: classifications.filter(({ kind }) => kind === "ignored").length,
722
+ invalidRecords: scoped.invalid.length,
723
+ distinctScopes: states.length,
724
+ presentScopes,
725
+ absentScopes,
726
+ invalidScopes,
727
+ errors: errors.length,
728
+ advisories: advisories.length
729
+ },
730
+ groups: groupCounts,
731
+ records: boundedDetails(records, detailLimit),
732
+ states: boundedDetails(states, detailLimit),
733
+ errors: boundedDetails(errors, detailLimit),
734
+ advisories: boundedDetails(advisories, detailLimit)
735
+ };
736
+ }
737
+
738
+ export { repositoryScopesMetadataKey, MAX_REPOSITORY_SCOPES, MAX_REPOSITORY_SCOPE_UTF8_BYTES, MAX_REPOSITORY_SCOPES_UTF8_BYTES, DEFAULT_REPOSITORY_MEMORY_GROUP_LIMIT, MAX_REPOSITORY_MEMORY_GROUP_LIMIT, DEFAULT_REPOSITORY_MEMORY_DETAIL_LIMIT, MAX_REPOSITORY_MEMORY_DETAIL_LIMIT, MAX_REPOSITORY_MEMORY_SUMMARY_UTF8_BYTES, activePlanStatuses, terminalPlanStatuses, planStatuses, isActivePlanStatus, isTerminalPlanStatus, isPlanStatus, RepositoryScopesError, canonicalRepositoryPath, analyzeAuthoredRepositoryScopes, validateRepositoryScopeSelection, repositoryScopeMatchesPath, deepestRepositoryScopeMatch, metadataMatchesExactRepositoryScopes, inspectRepositoryScopeState, repositoryMemoryGroupKeys, classifyRepositoryMemoryRecord, buildRepositoryMemoryContext, auditRepositoryMemoryScopes };