@hraness/wordcell 0.21.2 → 0.22.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 (87) hide show
  1. package/README.md +196 -66
  2. package/dist/{authority-4sp9d552.js → authority-1pgftmwq.js} +55 -10
  3. package/dist/benchmark.js +3 -3
  4. package/dist/capture.js +6 -5
  5. package/dist/cli.js +61 -3800
  6. package/dist/clip/acquire.js +3 -2
  7. package/dist/clip/cli.js +6 -5
  8. package/dist/clip/doctor.js +4 -3
  9. package/dist/clip/jobs.js +3 -3
  10. package/dist/clip/refresh.js +1 -1
  11. package/dist/evaluation-builder.js +18 -17
  12. package/dist/evaluation-kb.js +14 -13
  13. package/dist/graph-authority.js +3 -3
  14. package/dist/graph-percolation.js +5 -5
  15. package/dist/index-0dqfnrz9.js +1985 -0
  16. package/dist/{index-0k2x4nn9.js → index-1tm7bgx7.js} +16 -4
  17. package/dist/{index-vdw5yjfa.js → index-2hf27mws.js} +108 -18
  18. package/dist/index-3agn8scn.js +306 -0
  19. package/dist/index-4ag64rc1.js +4299 -0
  20. package/dist/index-4knsp9qj.js +34 -0
  21. package/dist/index-66pshdtx.js +341 -0
  22. package/dist/index-9rf81m0p.js +368 -0
  23. package/dist/{index-59ssmqqm.js → index-a6kbv718.js} +1 -1
  24. package/dist/{index-51bap0mq.js → index-avbce90h.js} +9 -9
  25. package/dist/{index-180zbkkt.js → index-de2w8crk.js} +14 -13
  26. package/dist/{index-pbr6rv47.js → index-e6p9e926.js} +6 -6
  27. package/dist/{index-bcfn9xah.js → index-famy7fhs.js} +5 -5
  28. package/dist/{index-by0pbant.js → index-fc4dr114.js} +5 -2
  29. package/dist/{index-x6m57nfc.js → index-g5vsqmdy.js} +28 -57
  30. package/dist/{index-115b07ap.js → index-hcw140eb.js} +5 -5
  31. package/dist/index-j70m75wd.js +217 -0
  32. package/dist/{index-3ehtt59c.js → index-qwgsmtsz.js} +1 -1
  33. package/dist/{index-bgfzwt4h.js → index-t2bs9xdr.js} +4 -4
  34. package/dist/index.js +285 -93
  35. package/dist/portfolio.js +14 -13
  36. package/dist/publish-model.js +36 -0
  37. package/dist/publish-reader/reader.css +1042 -0
  38. package/dist/publish-reader/reader.js +1534 -0
  39. package/dist/publish-reader/theme.js +92 -0
  40. package/dist/publish-search.js +52 -0
  41. package/dist/publish.js +36 -0
  42. package/dist/rerank-typesafe.js +13 -0
  43. package/dist/rerank.js +14 -0
  44. package/dist/sdk.js +14 -11
  45. package/dist/search.js +2 -2
  46. package/dist/semantic.js +4 -4
  47. package/dist/support-mywv1em0.js +140 -0
  48. package/dist/workflows/decision-context.js +13 -12
  49. package/dist/workflows/index.js +13 -12
  50. package/package.json +68 -7
  51. package/skills/wordcell/AGENTS.md +1 -0
  52. package/skills/wordcell/SKILL.md +21 -1
  53. package/skills/wordcell/agents/openai.yaml +1 -1
  54. package/skills/wordcell/references/publish.md +93 -0
  55. package/skills/wordcell/references/query.md +22 -0
  56. package/src/cli-program.ts +353 -9
  57. package/src/cli.ts +12 -1
  58. package/src/clip/acquire.ts +2 -0
  59. package/src/clip/doctor.ts +2 -0
  60. package/src/clip/media.ts +2 -0
  61. package/src/index.ts +4 -0
  62. package/src/oh/authority.ts +14 -5
  63. package/src/oh/canonical-rust.ts +127 -0
  64. package/src/oh/projection-rust.ts +63 -0
  65. package/src/oh-adoption.ts +7 -1
  66. package/src/publish-graph.ts +0 -0
  67. package/src/publish-index.ts +195 -0
  68. package/src/publish-markdown.ts +617 -0
  69. package/src/publish-model.ts +599 -0
  70. package/src/publish-nav.ts +204 -0
  71. package/src/publish-pages.ts +382 -0
  72. package/src/publish-reader/reader.css +1042 -0
  73. package/src/publish-reader/reader.ts +1017 -0
  74. package/src/publish-reader/theme.ts +97 -0
  75. package/src/publish-search.ts +418 -0
  76. package/src/publish-select.ts +419 -0
  77. package/src/publish-theme.ts +106 -0
  78. package/src/publish.ts +904 -0
  79. package/src/rerank-credentials.ts +56 -0
  80. package/src/rerank-typesafe.ts +472 -0
  81. package/src/rerank.ts +389 -0
  82. package/src/sdk.ts +157 -6
  83. package/src/serve.ts +180 -0
  84. package/src/support-profile.ts +7 -0
  85. package/src/support.ts +73 -0
  86. package/src/vault.ts +23 -9
  87. package/dist/{index-why54q5p.js → index-gm9t95d9.js} +3 -3
package/src/serve.ts ADDED
@@ -0,0 +1,180 @@
1
+ import { realpath, stat } from "node:fs/promises";
2
+ import { join, sep } from "node:path";
3
+
4
+ const MAX_PATH_BYTES = 8 * 1_024;
5
+
6
+ const CONTENT_TYPES: Readonly<Record<string, string>> = Object.freeze({
7
+ ".avif": "image/avif",
8
+ ".css": "text/css; charset=utf-8",
9
+ ".gif": "image/gif",
10
+ ".html": "text/html; charset=utf-8",
11
+ ".ico": "image/x-icon",
12
+ ".jpeg": "image/jpeg",
13
+ ".jpg": "image/jpeg",
14
+ ".js": "text/javascript; charset=utf-8",
15
+ ".json": "application/json; charset=utf-8",
16
+ ".map": "application/json; charset=utf-8",
17
+ ".mp3": "audio/mpeg",
18
+ ".mp4": "video/mp4",
19
+ ".pdf": "application/pdf",
20
+ ".png": "image/png",
21
+ ".svg": "image/svg+xml",
22
+ ".txt": "text/plain; charset=utf-8",
23
+ ".wasm": "application/wasm",
24
+ ".wav": "audio/wav",
25
+ ".webm": "video/webm",
26
+ ".webp": "image/webp",
27
+ ".woff": "font/woff",
28
+ ".woff2": "font/woff2",
29
+ ".xml": "application/xml; charset=utf-8",
30
+ });
31
+
32
+ export type ServeSiteOptions = Readonly<{
33
+ root: string;
34
+ host?: string;
35
+ port?: number;
36
+ }>;
37
+
38
+ export type ServeListener = Readonly<{
39
+ hostname: string;
40
+ port: number;
41
+ stop: () => void;
42
+ }>;
43
+
44
+ export type ServeListen = (init: Readonly<{
45
+ hostname: string;
46
+ port: number;
47
+ fetch: (request: Request) => Response | Promise<Response>;
48
+ }>) => ServeListener;
49
+
50
+ export type ServeIo = Readonly<{
51
+ listen?: ServeListen;
52
+ }>;
53
+
54
+ export type ServedSite = Readonly<{
55
+ root: string;
56
+ host: string;
57
+ port: number;
58
+ url: string;
59
+ close: () => void;
60
+ }>;
61
+
62
+ function contentType(path: string): string {
63
+ const extension = path.slice(path.lastIndexOf("."));
64
+ return CONTENT_TYPES[extension] ?? "application/octet-stream";
65
+ }
66
+
67
+ async function regularFileUnder(rootReal: string, candidate: string): Promise<string | null> {
68
+ let resolved: string;
69
+ try {
70
+ resolved = await realpath(candidate);
71
+ } catch {
72
+ return null;
73
+ }
74
+ if (resolved !== rootReal && !resolved.startsWith(`${rootReal}${sep}`)) return null;
75
+ try {
76
+ return (await stat(resolved)).isFile() ? resolved : null;
77
+ } catch {
78
+ return null;
79
+ }
80
+ }
81
+
82
+ async function resolveRequest(rootReal: string, pathname: string): Promise<string | null> {
83
+ let decoded: string;
84
+ try {
85
+ decoded = decodeURIComponent(pathname);
86
+ } catch {
87
+ return null;
88
+ }
89
+ if (decoded.includes("\0") || decoded.includes("\\")) return null;
90
+ const segments = decoded.split("/").filter((segment) => segment !== "" && segment !== ".");
91
+ if (segments.some((segment) => segment === "..")) return null;
92
+ const relative = segments.join("/");
93
+ const candidates = [
94
+ join(rootReal, relative),
95
+ join(rootReal, relative, "index.html"),
96
+ ];
97
+ for (const candidate of candidates) {
98
+ const file = await regularFileUnder(rootReal, candidate);
99
+ if (file !== null) return file;
100
+ }
101
+ return null;
102
+ }
103
+
104
+ export async function createServeHandler(
105
+ root: string,
106
+ ): Promise<(request: Request) => Promise<Response>> {
107
+ const rootReal = await realpath(root);
108
+ return async (request) => {
109
+ const method = request.method.toUpperCase();
110
+ if (method !== "GET" && method !== "HEAD") {
111
+ return new Response("Method not allowed", { status: 405 });
112
+ }
113
+ let pathname: string;
114
+ try {
115
+ pathname = new URL(request.url).pathname;
116
+ } catch {
117
+ return new Response("Bad request", { status: 400 });
118
+ }
119
+ if (pathname.length > MAX_PATH_BYTES) {
120
+ return new Response("URI too long", { status: 414 });
121
+ }
122
+ const head = method === "HEAD";
123
+ const file = await resolveRequest(rootReal, pathname);
124
+ if (file !== null) {
125
+ const body = Bun.file(file);
126
+ return new Response(head ? null : body, {
127
+ headers: {
128
+ "Cache-Control": "no-store",
129
+ "Content-Length": String(body.size),
130
+ "Content-Type": contentType(file),
131
+ },
132
+ });
133
+ }
134
+ const notFound = await regularFileUnder(rootReal, join(rootReal, "404.html"));
135
+ if (notFound !== null) {
136
+ const body = Bun.file(notFound);
137
+ return new Response(head ? null : body, {
138
+ status: 404,
139
+ headers: {
140
+ "Cache-Control": "no-store",
141
+ "Content-Length": String(body.size),
142
+ "Content-Type": "text/html; charset=utf-8",
143
+ },
144
+ });
145
+ }
146
+ return new Response(head ? null : "Not found", {
147
+ status: 404,
148
+ headers: { "Cache-Control": "no-store" },
149
+ });
150
+ };
151
+ }
152
+
153
+ export async function serveSite(
154
+ options: ServeSiteOptions,
155
+ io: ServeIo = {},
156
+ ): Promise<ServedSite> {
157
+ const host = options.host ?? "127.0.0.1";
158
+ const port = options.port ?? 8080;
159
+ if (!Number.isSafeInteger(port) || port < 0 || port > 65_535) {
160
+ throw new Error("Port must be an integer from 0 through 65535");
161
+ }
162
+ const rootReal = await realpath(options.root);
163
+ if (!(await stat(rootReal)).isDirectory()) {
164
+ throw new Error(`Site root is not a directory: ${options.root}`);
165
+ }
166
+ const listen = io.listen ?? ((init) => Bun.serve(init));
167
+ const server = listen({
168
+ hostname: host,
169
+ port,
170
+ fetch: await createServeHandler(rootReal),
171
+ });
172
+ const boundPort = server.port ?? port;
173
+ return Object.freeze({
174
+ root: rootReal,
175
+ host,
176
+ port: boundPort,
177
+ url: `http://${host}:${String(boundPort)}/`,
178
+ close: () => server.stop(),
179
+ });
180
+ }
@@ -0,0 +1,7 @@
1
+ /** Public presentation data only. Accounts owns current plans and consent. */
2
+ export const supportProfile = {
3
+ id: "kb",
4
+ name: "Wordcell",
5
+ valueProposition: "Support ongoing development of inspectable Markdown memory for coding agents.",
6
+ updates: false,
7
+ } as const;
package/src/support.ts ADDED
@@ -0,0 +1,73 @@
1
+ import { maybeShowSupportInvitation, runSupportCommand, type SupportCommandOptions } from "@hraness/support-foundation/node";
2
+ import { parseArguments } from "./cli-program.js";
3
+ import { parseArguments as parseCaptureArguments } from "./clip/args.js";
4
+ import { parseUrlMetadataArguments } from "./clip/url-metadata-cli.js";
5
+ import { parsePdfArguments } from "./pdf/args.js";
6
+ import { supportProfile } from "./support-profile.js";
7
+
8
+ /** Called only by the standalone executable. Nested children inherit quiet mode. */
9
+ export function standaloneSupportEnvironment(): Readonly<Record<string, string | undefined>> {
10
+ const environment = { ...process.env };
11
+ process.env.HRANESS_SUPPORT_AUDIENCE = "off";
12
+ return environment;
13
+ }
14
+
15
+ /** Positive completed-work classification. New command kinds stay quiet by default. */
16
+ export function isUsefulSupportResult(args: readonly string[], environment: Readonly<Record<string, string | undefined>>): boolean {
17
+ const parsed = parseArguments(args);
18
+ if (!parsed.ok) return false;
19
+ const command = parsed.value;
20
+ switch (command.kind) {
21
+ case "init":
22
+ case "refresh":
23
+ case "graph":
24
+ case "graph-rebuild":
25
+ case "graph-query":
26
+ case "backlinks":
27
+ case "links":
28
+ case "catalog":
29
+ case "context":
30
+ case "index":
31
+ case "search":
32
+ case "history":
33
+ case "list":
34
+ case "inbox":
35
+ case "note-create":
36
+ case "relation":
37
+ case "percolate":
38
+ case "portfolio-search":
39
+ case "capture-diff":
40
+ return true;
41
+ case "capture-bundle":
42
+ return command.action === "show";
43
+ case "clip": {
44
+ const capture = parseCaptureArguments(command.arguments, environment);
45
+ return capture.ok && (capture.value.command === "capture" || capture.value.command === "inspect") && !capture.value.quiet;
46
+ }
47
+ case "pdf": {
48
+ const pdf = parsePdfArguments(command.arguments, environment);
49
+ return pdf.ok && pdf.value.command === "capture" && !pdf.value.quiet;
50
+ }
51
+ case "url-metadata": {
52
+ const metadata = parseUrlMetadataArguments(command.arguments, environment);
53
+ return metadata.ok && metadata.value.kind === "backfill";
54
+ }
55
+ default:
56
+ return false;
57
+ }
58
+ }
59
+
60
+ export async function runProductSupportCommand(args: readonly string[], options: SupportCommandOptions = {}): Promise<number> {
61
+ const result = await runSupportCommand(supportProfile, args, { command: ["wordcell"], gitEmail: false, ...options });
62
+ if (result.stdout !== "") process.stdout.write(result.stdout);
63
+ if (result.stderr !== "") process.stderr.write(result.stderr);
64
+ return result.exitCode;
65
+ }
66
+
67
+ export async function showProductSupportInvitation(options: SupportCommandOptions = {}): Promise<void> {
68
+ try {
69
+ await maybeShowSupportInvitation(supportProfile, { command: ["wordcell"], usefulResult: true, gitEmail: false, ...options });
70
+ } catch {
71
+ // Optional support must preserve the completed command's output and exit status.
72
+ }
73
+ }
package/src/vault.ts CHANGED
@@ -548,20 +548,34 @@ async function snapshot(
548
548
  ? {}
549
549
  : { maxTotalBytes: options.maxTotalBytes }),
550
550
  });
551
- const indexRevision = await readIndexRevision(
552
- root,
553
- indexPath,
554
- options.maxNoteBytes ?? MAX_NOTE_UTF8_BYTES,
555
- );
556
- const currentIndex = indexRevision.content;
557
- const indexNote = parseNote(vaultIndexPath, currentIndex);
558
- const catalogMode = parsedCatalogMode(
551
+ const configuredCatalogMode = parsedCatalogMode(
559
552
  options.catalogMode,
560
553
  "ScanVaultOptions.catalogMode",
561
- ) ?? declaredCatalogMode(indexNote) ?? "managed";
554
+ );
555
+ let indexRevision: IndexRevision | undefined;
556
+ try {
557
+ indexRevision = await readIndexRevision(
558
+ root,
559
+ indexPath,
560
+ options.maxNoteBytes ?? MAX_NOTE_UTF8_BYTES,
561
+ );
562
+ } catch (error: unknown) {
563
+ const absent = error instanceof Error && "code" in error && error.code === "ENOENT";
564
+ // Ordinary Markdown folders can be queried without creating a front door.
565
+ // Explicit index contracts and writes still require the real authored file.
566
+ if (!absent || options.index !== undefined || configuredCatalogMode === "managed"
567
+ || notes.some((note) => note.path === vaultIndexPath)) throw error;
568
+ if (writeIndex) {
569
+ throw new Error("Refresh requires an index.md front door. Add one with kb_catalog: authored to preserve its contents, or catalog markers for a managed catalog. Search and graph commands can read this folder without one.", { cause: error });
570
+ }
571
+ }
572
+ const currentIndex = indexRevision?.content ?? "";
573
+ const catalogMode = configuredCatalogMode
574
+ ?? (indexRevision === undefined ? "authored" : declaredCatalogMode(parseNote(vaultIndexPath, currentIndex)) ?? "managed");
562
575
  let index: VaultIndexState = "authored";
563
576
 
564
577
  if (catalogMode === "managed") {
578
+ if (indexRevision === undefined) throw new Error("A managed catalog requires an existing index file.");
565
579
  const expectedIndex = replaceCatalog(
566
580
  currentIndex,
567
581
  renderCatalog(notes, catalogNoteId),
@@ -1,10 +1,10 @@
1
1
  // @bun
2
- import {
3
- queryVault
4
- } from "./index-48pz4jpc.js";
5
2
  import {
6
3
  navigateLinks
7
4
  } from "./index-d13v9ckt.js";
5
+ import {
6
+ queryVault
7
+ } from "./index-48pz4jpc.js";
8
8
  import {
9
9
  lookupNote
10
10
  } from "./index-ekpwvbra.js";