@hraness/wordcell 0.25.0 → 0.26.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 (49) hide show
  1. package/README.md +37 -35
  2. package/dist/capture.js +6 -6
  3. package/dist/cli-bin.js +14 -0
  4. package/dist/cli-release-c74c5m3k.js +799 -0
  5. package/dist/cli.js +26 -23
  6. package/dist/clip/acquire.js +3 -3
  7. package/dist/clip/cli.js +6 -6
  8. package/dist/clip/doctor.js +4 -4
  9. package/dist/evaluation-builder-bin.js +14 -0
  10. package/dist/evaluation-builder.js +12 -17
  11. package/dist/evaluation-kb.js +6 -6
  12. package/dist/graph-percolation.js +2 -2
  13. package/dist/index-3t0v457d.js +18 -0
  14. package/dist/{index-47893rgw.js → index-5h5awwh4.js} +4 -4
  15. package/dist/index-8pabwzqg.js +909 -0
  16. package/dist/{index-1wccy765.js → index-8w1zsgcm.js} +63 -930
  17. package/dist/{index-7drercgk.js → index-9fywek7g.js} +1 -1
  18. package/dist/{index-hz2wyggk.js → index-9paxge84.js} +1 -1
  19. package/dist/{index-mcpppwbq.js → index-9zkba8hr.js} +6 -6
  20. package/dist/{index-59bcxqee.js → index-j2jt49gr.js} +1 -1
  21. package/dist/index-ncgdaja4.js +64 -0
  22. package/dist/{index-nwrehke7.js → index-nd8v7r0z.js} +1 -1
  23. package/dist/{index-t3abkwk4.js → index-zbv70qmk.js} +15 -15
  24. package/dist/index.js +40 -40
  25. package/dist/pdf.js +2 -2
  26. package/dist/portfolio.js +5 -5
  27. package/dist/sdk.js +4 -4
  28. package/dist/{support-adj7bbge.js → support-ym4mpf7w.js} +19 -18
  29. package/dist/url-intelligence.js +3 -3
  30. package/dist/workflows/decision-context.js +5 -5
  31. package/dist/workflows/index.js +5 -5
  32. package/package.json +13 -6
  33. package/skills/wordcell/SKILL.md +10 -1
  34. package/src/cli-bin.ts +5 -0
  35. package/src/cli-help.ts +12 -0
  36. package/src/cli-program.ts +3 -57
  37. package/src/cli-release.ts +732 -0
  38. package/src/cli-startup.ts +76 -0
  39. package/src/cli-update.ts +53 -0
  40. package/src/cli.ts +5 -4
  41. package/src/clip/metadata-search-tool/src/openalex.rs +156 -8
  42. package/src/evaluation-builder-bin.ts +5 -0
  43. package/src/evaluation-builder-cli-args.ts +30 -0
  44. package/src/evaluation-builder.ts +3 -27
  45. package/src/import-supermemory.ts +3 -1
  46. package/dist/{index-hxyjer3x.js → index-hacpnaew.js} +3 -3
  47. package/dist/{index-qfqjfxaa.js → index-m9p2a4mj.js} +4 -4
  48. package/dist/{index-xwxy71ew.js → index-ncwzmsge.js} +5 -5
  49. package/dist/{index-e26mdwxz.js → index-wqx1x32f.js} +3 -3
@@ -0,0 +1,76 @@
1
+ import { commandWordCount, resolveCommandId, valueOptions } from "./cli-help.js";
2
+
3
+ export type StartupCommand =
4
+ | { readonly kind: "help"; readonly topic?: string }
5
+ | { readonly kind: "version"; readonly json: boolean }
6
+ | { readonly kind: "clip"; readonly arguments: readonly string[] }
7
+ | { readonly kind: "pdf"; readonly arguments: readonly string[] }
8
+ | { readonly kind: "url-metadata"; readonly arguments: readonly string[] };
9
+ export type StartupParseResult =
10
+ | { readonly ok: true; readonly value: StartupCommand }
11
+ | { readonly ok: false; readonly message: string };
12
+
13
+ function isHelpFlag(argument: string | undefined): boolean {
14
+ return argument === "--help" || argument === "-h";
15
+ }
16
+
17
+ /** Help for delegated commands comes from their own parsers. */
18
+ function delegatedHelp(first: string): StartupCommand | undefined {
19
+ if (first === "clip" || first === "inspect") return { kind: "clip", arguments: ["help"] };
20
+ if (first === "pdf") return { kind: "pdf", arguments: ["--help"] };
21
+ if (first === "url-metadata") return { kind: "url-metadata", arguments: ["--help"] };
22
+ return undefined;
23
+ }
24
+
25
+ /** `help`, `help advanced`, and `help <command...>`. */
26
+ function parseHelpTopic(words: readonly string[]): StartupParseResult {
27
+ const topic = words.filter((word) => !isHelpFlag(word) && word !== "--json");
28
+ if (topic.length === 0) return { ok: true, value: { kind: "help" } };
29
+ if (topic.length === 1 && topic[0] === "advanced") return { ok: true, value: { kind: "help", topic: "advanced" } };
30
+ const delegated = delegatedHelp(topic[0] ?? "");
31
+ if (delegated !== undefined) return { ok: true, value: delegated };
32
+ const id = resolveCommandId(topic);
33
+ if (id === undefined) return { ok: false, message: "unknown help topic" };
34
+ return { ok: true, value: { kind: "help", topic: id } };
35
+ }
36
+
37
+ /**
38
+ * `<command> --help` anywhere a person would type it: right after the command
39
+ * words, or as the last argument when it is not the value of an option.
40
+ */
41
+ function embeddedHelp(arguments_: readonly string[]): StartupParseResult | undefined {
42
+ const separator = arguments_.indexOf("--");
43
+ const end = separator === -1 ? arguments_.length : separator;
44
+ const index = arguments_.slice(0, end).findIndex(isHelpFlag);
45
+ if (index <= 0) return undefined;
46
+ const delegated = delegatedHelp(arguments_[0] ?? "");
47
+ if (delegated !== undefined) {
48
+ return index === 1 || index === end - 1 ? { ok: true, value: delegated } : undefined;
49
+ }
50
+ const id = resolveCommandId(arguments_.slice(0, index).filter((word) => !word.startsWith("-")));
51
+ if (id === undefined) return undefined;
52
+ const words = commandWordCount(id);
53
+ const previous = arguments_[index - 1] ?? "";
54
+ // `--help` is never an option value; `-h` could be one, as in `--body -h`.
55
+ const flag = arguments_[index];
56
+ const lastArgument = index === end - 1 && (flag === "--help" || !valueOptions(id).has(previous));
57
+ if (index !== words && !lastArgument) return undefined;
58
+ return { ok: true, value: { kind: "help", topic: id } };
59
+ }
60
+
61
+ /** The executable and public parser share the same help/version grammar. */
62
+ export function parseCliStartupCommand(arguments_: readonly string[]): StartupParseResult | undefined {
63
+ const command = arguments_[0];
64
+ if (command === undefined) return { ok: true, value: { kind: "help", topic: "start" } };
65
+ if (command === "help" || isHelpFlag(command)) return parseHelpTopic(arguments_.slice(1));
66
+ if (command === "--version" || command === "-V" || command === "-v" || command === "version") {
67
+ return { ok: true, value: { kind: "version", json: arguments_.includes("--json") } };
68
+ }
69
+ const help = embeddedHelp(arguments_);
70
+ if (help !== undefined) return help;
71
+ if ((command === "clip" || command === "capture" || command === "inspect")
72
+ && ["--help", "-h", "help"].includes(arguments_[1] ?? "")) {
73
+ return { ok: true, value: { kind: "clip", arguments: ["help"] } };
74
+ }
75
+ return undefined;
76
+ }
@@ -0,0 +1,53 @@
1
+ import { runCliUpdate, type CliUpdateOptions } from "@hraness/cli-update";
2
+ import { readFileSync } from "node:fs";
3
+ import { dirname, join } from "node:path";
4
+ import { findKbPackageRoot } from "./clip/package-root.js";
5
+ import { parseCliStartupCommand } from "./cli-startup.js";
6
+ import { isEvaluationBuilderHelp } from "./evaluation-builder-cli-args.js";
7
+
8
+ export type WordcellBin = "wordcell" | "wordcell-evaluation-builder";
9
+
10
+ export function wordcellUpdateOptions(bin: WordcellBin, entrypoint: string, argv: readonly string[]): CliUpdateOptions {
11
+ const packageRoot = findKbPackageRoot(dirname(entrypoint));
12
+ const manifest: unknown = JSON.parse(readFileSync(join(packageRoot, "package.json"), "utf8"));
13
+ if (manifest === null || typeof manifest !== "object" || !("version" in manifest) || typeof manifest.version !== "string") {
14
+ throw new Error("Wordcell package version is missing.");
15
+ }
16
+ const effectFree = bin === "wordcell" ? parseCliStartupCommand(argv) !== undefined : isEvaluationBuilderHelp(argv);
17
+ return {
18
+ packageName: "@hraness/wordcell", version: manifest.version, binName: bin, entrypoint,
19
+ argv: effectFree && argv[0] === "update" ? ["--help"] : argv,
20
+ provider: { kind: "github", repository: "hraness/wordcell", tagPrefix: "v", assetName: "hraness-wordcell-{version}.tgz" },
21
+ ignoreScripts: true,
22
+ effectFree,
23
+ suppressAutomatic: bin === "wordcell" && argv[0] === "support",
24
+ async verifyArtifact(artifact) {
25
+ const { verifyUpdateArtifact } = await import("./cli-release.js");
26
+ await verifyUpdateArtifact(artifact);
27
+ },
28
+ };
29
+ }
30
+
31
+ /** Admission precedes imports of vault, capture, support, or evaluation code. */
32
+ export async function runWordcellBin(
33
+ bin: WordcellBin,
34
+ entrypoint: string,
35
+ argv: readonly string[],
36
+ dependencies: { update?: typeof runCliUpdate; run?: () => Promise<number> } = {},
37
+ ): Promise<number> {
38
+ const update = await (dependencies.update ?? runCliUpdate)(wordcellUpdateOptions(bin, entrypoint, argv));
39
+ if (update.handled) return update.exitCode;
40
+ try {
41
+ if (dependencies.run) return await dependencies.run();
42
+ if (bin === "wordcell") {
43
+ const { runStandaloneCli } = await import("./cli.js");
44
+ return await runStandaloneCli(argv);
45
+ }
46
+ const { runKbEvidenceRoutingBuildCli, kbEvidenceRoutingBuildUsage } = await import("./evaluation-builder.js");
47
+ if (isEvaluationBuilderHelp(argv)) console.log(kbEvidenceRoutingBuildUsage);
48
+ else await runKbEvidenceRoutingBuildCli(argv);
49
+ return 0;
50
+ } finally {
51
+ await update.release();
52
+ }
53
+ }
package/src/cli.ts CHANGED
@@ -3,15 +3,16 @@ import { runExecutable } from "./cli-program.js";
3
3
 
4
4
  export * from "./cli-program.js";
5
5
 
6
- if (import.meta.main) {
6
+ export async function runStandaloneCli(args: readonly string[] = process.argv.slice(2)): Promise<number> {
7
7
  const { standaloneSupportEnvironment, isUsefulSupportResult, runProductSupportCommand, showProductSupportInvitation } = await import("./support.js");
8
8
  const env = standaloneSupportEnvironment();
9
- const args = process.argv.slice(2);
10
9
  if (args[0] === "support") {
11
- process.exitCode = await runProductSupportCommand(args.slice(1), { env });
10
+ return await runProductSupportCommand(args.slice(1), { env });
12
11
  } else {
13
12
  const exitCode = await runExecutable(args);
14
- process.exitCode = exitCode;
15
13
  if (exitCode === 0 && isUsefulSupportResult(args, env)) await showProductSupportInvitation({ env });
14
+ return exitCode;
16
15
  }
17
16
  }
17
+
18
+ if (import.meta.main) process.exitCode = await runStandaloneCli();
@@ -170,8 +170,8 @@ fn parse(body: &str, limit: usize, query: &str) -> Outcome {
170
170
  Some(cost)
171
171
  }
172
172
  };
173
- let mut seen = std::collections::HashSet::new();
174
- let mut seen_ids = std::collections::HashSet::new();
173
+ let mut known_retracted_ids = std::collections::HashSet::new();
174
+ let mut observed_ids = Vec::with_capacity(rows.len());
175
175
  let mut results = Vec::new();
176
176
  for row in rows {
177
177
  let id = row
@@ -191,6 +191,18 @@ fn parse(body: &str, limit: usize, query: &str) -> Outcome {
191
191
  if title.trim().is_empty() || title.len() > MAX_TEXT {
192
192
  return Err(FailureCode::Parse);
193
193
  }
194
+ let title = title.split_whitespace().collect::<Vec<_>>().join(" ");
195
+ // Absence is unknown; only an explicit flag or title marker excludes a work.
196
+ let retracted = match row.get("is_retracted") {
197
+ None => false,
198
+ Some(value) => value.as_bool().ok_or(FailureCode::Parse)?,
199
+ };
200
+ const RETRACTED_PREFIX: &str = "RETRACTED ARTICLE:";
201
+ let retracted = retracted
202
+ || title
203
+ .get(..RETRACTED_PREFIX.len())
204
+ .is_some_and(|prefix| prefix.eq_ignore_ascii_case(RETRACTED_PREFIX));
205
+ observed_ids.push(id);
194
206
  let mut candidates = Vec::new();
195
207
  let mut locations = Vec::new();
196
208
  for field in ["best_oa_location", "primary_location"] {
@@ -270,21 +282,41 @@ fn parse(body: &str, limit: usize, query: &str) -> Outcome {
270
282
  }
271
283
  _ => return Err(FailureCode::Parse),
272
284
  }
285
+ // Validate every row's bounded fields even when its work will be omitted.
286
+ if retracted {
287
+ known_retracted_ids.insert(id);
288
+ continue;
289
+ }
273
290
  // Prefer observed direct destinations over DOI redirectors; preserve stable location order.
274
291
  candidates.sort_by_key(|(priority, _)| *priority);
275
292
  let Some((_, url)) = candidates.into_iter().next() else {
276
293
  continue;
277
294
  };
278
- if seen_ids.insert(id.to_owned()) && seen.insert(url.clone()) {
279
- results.push(SearchResult {
280
- title: title.split_whitespace().collect::<Vec<_>>().join(" "),
295
+ results.push((
296
+ id,
297
+ SearchResult {
298
+ title,
281
299
  url,
282
300
  snippet: Some("Scholarly index metadata; full text not fetched.".into()),
283
301
  source_engine: "openalex".into(),
284
- });
285
- }
302
+ },
303
+ ));
286
304
  }
287
- if !rows.is_empty() && results.is_empty() {
305
+ // A duplicate row cannot reintroduce an identity explicitly marked retracted.
306
+ // Deduplicate only admitted works so excluded identities cannot hide usable rows.
307
+ let mut seen = std::collections::HashSet::new();
308
+ let mut seen_ids = std::collections::HashSet::new();
309
+ let results: Vec<_> = results
310
+ .into_iter()
311
+ .filter(|(id, _)| !known_retracted_ids.contains(id))
312
+ .filter(|(id, result)| seen_ids.insert(*id) && seen.insert(result.url.clone()))
313
+ .map(|(_, result)| result)
314
+ .collect();
315
+ if results.is_empty()
316
+ && observed_ids
317
+ .iter()
318
+ .any(|id| !known_retracted_ids.contains(id))
319
+ {
288
320
  return Err(FailureCode::UnverifiedEmpty);
289
321
  }
290
322
  Ok(Discovery {
@@ -307,6 +339,122 @@ mod tests {
307
339
  parse(&value.to_string(), 3, "microalgae")
308
340
  }
309
341
  #[test]
342
+ fn omits_known_retractions_and_preserves_cost_for_policy_empty_results() {
343
+ let mut retracted = row();
344
+ retracted["is_retracted"] = json!(true);
345
+ let mut value = response(vec![retracted.clone()]);
346
+ value["meta"]["cost_usd"] = json!(0.0001);
347
+ let result = check(value).unwrap();
348
+ assert!(result.results.is_empty());
349
+ assert_eq!(result.reported_cost_usd, Some(0.0001));
350
+
351
+ let mut good = row();
352
+ good["id"] = json!("https://openalex.org/W456");
353
+ good["is_retracted"] = json!(false);
354
+ let result = check(response(vec![retracted, good])).unwrap();
355
+ assert_eq!(result.results.len(), 1);
356
+ assert_eq!(
357
+ result.results[0].title,
358
+ "Microalgae harvesting energy efficiency"
359
+ );
360
+ // Missing status remains admissible metadata, without a verified-status claim.
361
+ let result = check(response(vec![row()])).unwrap();
362
+ assert_eq!(result.results.len(), 1);
363
+ assert_eq!(
364
+ result.results[0].snippet.as_deref(),
365
+ Some("Scholarly index metadata; full text not fetched.")
366
+ );
367
+ }
368
+ #[test]
369
+ fn retraction_marker_is_anchored_and_does_not_exclude_scholarship_about_retractions() {
370
+ for title in [
371
+ "RETRACTED ARTICLE: Microalgae",
372
+ " retracted article:Microalgae",
373
+ "RETRACTED\n ARTICLE: Microalgae",
374
+ ] {
375
+ let mut r = row();
376
+ r["title"] = json!(title);
377
+ r["is_retracted"] = json!(false);
378
+ assert!(
379
+ check(response(vec![r])).unwrap().results.is_empty(),
380
+ "{title}"
381
+ );
382
+ }
383
+ for title in [
384
+ "A study of retractions",
385
+ "Analysis of RETRACTED ARTICLE: labels",
386
+ "RETRACTED ARTICLES: a review",
387
+ "Retraction notices in science",
388
+ "論文について",
389
+ ] {
390
+ let mut r = row();
391
+ r["title"] = json!(title);
392
+ assert_eq!(
393
+ check(response(vec![r])).unwrap().results.len(),
394
+ 1,
395
+ "{title}"
396
+ );
397
+ }
398
+ }
399
+ #[test]
400
+ fn rejects_malformed_status_even_with_an_explicit_title_marker() {
401
+ for status in [Value::Null, json!("true"), json!(1), json!([]), json!({})] {
402
+ for title in ["Microalgae harvesting", "RETRACTED ARTICLE: Microalgae"] {
403
+ let mut r = row();
404
+ r["title"] = json!(title);
405
+ r["is_retracted"] = status.clone();
406
+ assert_eq!(check(response(vec![r])).unwrap_err(), FailureCode::Parse);
407
+ }
408
+ }
409
+ }
410
+ #[test]
411
+ fn policy_filter_preserves_unusable_link_failures_and_row_validation() {
412
+ let mut retracted = row();
413
+ retracted["is_retracted"] = json!(true);
414
+ let mut unusable = row();
415
+ unusable["id"] = json!("https://openalex.org/W456");
416
+ unusable["best_oa_location"] = Value::Null;
417
+ unusable["doi"] = Value::Null;
418
+ assert_eq!(
419
+ check(response(vec![retracted.clone(), unusable])).unwrap_err(),
420
+ FailureCode::UnverifiedEmpty
421
+ );
422
+ retracted["doi"] = json!(42);
423
+ assert_eq!(
424
+ check(response(vec![retracted.clone()])).unwrap_err(),
425
+ FailureCode::Parse
426
+ );
427
+ retracted["doi"] = Value::Null;
428
+ retracted["locations"] = json!([{"is_oa":true,"landing_page_url":"x".repeat(8193)}]);
429
+ assert_eq!(
430
+ check(response(vec![retracted])).unwrap_err(),
431
+ FailureCode::BodyLimit
432
+ );
433
+ }
434
+ #[test]
435
+ fn duplicate_work_cannot_reintroduce_a_known_retraction_in_either_order() {
436
+ let mut retracted = row();
437
+ retracted["is_retracted"] = json!(true);
438
+ for rows in [vec![row(), retracted.clone()], vec![retracted, row()]] {
439
+ assert!(check(response(rows)).unwrap().results.is_empty());
440
+ }
441
+ }
442
+ #[test]
443
+ fn excluded_identity_cannot_consume_a_valid_works_url_deduplication_slot() {
444
+ let mut retracted = row();
445
+ retracted["is_retracted"] = json!(true);
446
+ let mut good = row();
447
+ good["id"] = json!("https://openalex.org/W456");
448
+ good["title"] = json!("Allowed work at the shared destination");
449
+ let result = check(response(vec![row(), good, retracted])).unwrap();
450
+ assert_eq!(result.results.len(), 1);
451
+ assert_eq!(
452
+ result.results[0].title,
453
+ "Allowed work at the shared destination"
454
+ );
455
+ assert_eq!(result.results[0].url, "https://repository.example/paper");
456
+ }
457
+ #[test]
310
458
  fn discovery_prefers_observed_oa_landing_and_deduplicates() {
311
459
  let results = check(response(vec![row(), row()])).unwrap().results;
312
460
  assert_eq!(results.len(), 1);
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env bun
2
+ import { fileURLToPath } from "node:url";
3
+ import { runWordcellBin } from "./cli-update.js";
4
+
5
+ if (import.meta.main) process.exitCode = await runWordcellBin("wordcell-evaluation-builder", fileURLToPath(import.meta.url), process.argv.slice(2));
@@ -0,0 +1,30 @@
1
+ export const kbEvidenceRoutingBuildUsage =
2
+ "Usage: wordcell-evaluation-builder <--anchor-seal|--build> --config <checked-config.json> --artifact-root <artifact-B>\n"
3
+ + " wordcell-evaluation-builder update [check|status|enable|disable] [--json]";
4
+
5
+ export function parseKbEvidenceRoutingBuildCliArguments(arguments_: readonly string[]): {
6
+ readonly mode: "anchor-seal" | "build";
7
+ readonly configPath: string;
8
+ readonly artifactRoot: string;
9
+ } {
10
+ const mode = arguments_[0];
11
+ const configPath = arguments_[2];
12
+ const artifactRoot = arguments_[4];
13
+ if (
14
+ arguments_.length !== 5
15
+ || (mode !== "--anchor-seal" && mode !== "--build")
16
+ || arguments_[1] !== "--config"
17
+ || arguments_[3] !== "--artifact-root"
18
+ || configPath === undefined
19
+ || configPath.trim() === ""
20
+ || artifactRoot === undefined
21
+ || artifactRoot.trim() === ""
22
+ ) {
23
+ throw new Error(kbEvidenceRoutingBuildUsage);
24
+ }
25
+ return Object.freeze({ mode: mode.slice(2) as "anchor-seal" | "build", configPath, artifactRoot });
26
+ }
27
+
28
+ export function isEvaluationBuilderHelp(arguments_: readonly string[]): boolean {
29
+ return arguments_.length === 1 && (arguments_[0] === "--help" || arguments_[0] === "-h");
30
+ }
@@ -1,4 +1,6 @@
1
1
  #!/usr/bin/env bun
2
+ import { isEvaluationBuilderHelp, kbEvidenceRoutingBuildUsage, parseKbEvidenceRoutingBuildCliArguments } from "./evaluation-builder-cli-args.js";
3
+ export { kbEvidenceRoutingBuildUsage, parseKbEvidenceRoutingBuildCliArguments } from "./evaluation-builder-cli-args.js";
2
4
 
3
5
  import { createHash, randomBytes } from "node:crypto";
4
6
  import { constants as fsConstants } from "node:fs";
@@ -2459,32 +2461,6 @@ export async function buildKbEvidenceRoutingEvaluation(
2459
2461
  };
2460
2462
  }
2461
2463
 
2462
- export const kbEvidenceRoutingBuildUsage =
2463
- "Usage: wordcell-evaluation-builder <--anchor-seal|--build> --config <checked-config.json> --artifact-root <artifact-B>";
2464
-
2465
- export function parseKbEvidenceRoutingBuildCliArguments(arguments_: readonly string[]): {
2466
- readonly mode: "anchor-seal" | "build";
2467
- readonly configPath: string;
2468
- readonly artifactRoot: string;
2469
- } {
2470
- const mode = arguments_[0];
2471
- const configPath = arguments_[2];
2472
- const artifactRoot = arguments_[4];
2473
- if (
2474
- arguments_.length !== 5
2475
- || (mode !== "--anchor-seal" && mode !== "--build")
2476
- || arguments_[1] !== "--config"
2477
- || arguments_[3] !== "--artifact-root"
2478
- || configPath === undefined
2479
- || configPath.trim() === ""
2480
- || artifactRoot === undefined
2481
- || artifactRoot.trim() === ""
2482
- ) {
2483
- throw new Error(kbEvidenceRoutingBuildUsage);
2484
- }
2485
- return Object.freeze({ mode: mode.slice(2) as "anchor-seal" | "build", configPath, artifactRoot });
2486
- }
2487
-
2488
2464
  export async function runKbEvidenceRoutingBuildCli(
2489
2465
  arguments_: readonly string[],
2490
2466
  dependencies: Pick<KbEvidenceRoutingEvaluationBuildDependencies, "runGit"> = {},
@@ -2518,7 +2494,7 @@ export async function runKbEvidenceRoutingBuildCli(
2518
2494
 
2519
2495
  if (import.meta.main) {
2520
2496
  const arguments_ = process.argv.slice(2);
2521
- if (arguments_.length === 1 && (arguments_[0] === "--help" || arguments_[0] === "-h")) {
2497
+ if (isEvaluationBuilderHelp(arguments_)) {
2522
2498
  console.log(kbEvidenceRoutingBuildUsage);
2523
2499
  } else {
2524
2500
  await runKbEvidenceRoutingBuildCli(arguments_);
@@ -828,7 +828,9 @@ export function occupancyKey(value: string): string {
828
828
 
829
829
  /** Validate `--prefix` as a vault-relative directory note-ID prefix. */
830
830
  export function normalizePrefix(prefix: string): string {
831
- const trimmed = prefix.replace(/\/+$/u, "");
831
+ let end = prefix.length;
832
+ while (end > 0 && prefix.charCodeAt(end - 1) === 47) end -= 1;
833
+ const trimmed = prefix.slice(0, end);
832
834
  let canonical: string | undefined;
833
835
  try {
834
836
  canonical = canonicalNoteId(trimmed);
@@ -1,13 +1,13 @@
1
1
  // @bun
2
+ import {
3
+ sanitizeArtifactUrl
4
+ } from "./index-mxxxytys.js";
2
5
  import {
3
6
  assertSafeNetworkUrl,
4
7
  decodeBytes,
5
8
  isPrivateHostname,
6
9
  safeFetch
7
10
  } from "./index-e5fbsywq.js";
8
- import {
9
- sanitizeArtifactUrl
10
- } from "./index-mxxxytys.js";
11
11
 
12
12
  // src/clip/url-intelligence.ts
13
13
  var MAX_METADATA_SEARCH_RESULTS = 20;
@@ -11,16 +11,16 @@ import {
11
11
  terminalOutput,
12
12
  yamlString
13
13
  } from "./index-cd75vky9.js";
14
+ import {
15
+ redactSensitiveText,
16
+ sanitizeArtifactUrl
17
+ } from "./index-mxxxytys.js";
14
18
  import {
15
19
  safeFetch
16
20
  } from "./index-e5fbsywq.js";
17
21
  import {
18
22
  BoundedByteBuffer
19
23
  } from "./index-gh719d91.js";
20
- import {
21
- redactSensitiveText,
22
- sanitizeArtifactUrl
23
- } from "./index-mxxxytys.js";
24
24
  import {
25
25
  sanitizeTerminalLine,
26
26
  sanitizeTerminalText
@@ -8,6 +8,9 @@ import {
8
8
  readCookieFile,
9
9
  renderCookieHeader
10
10
  } from "./index-2gv8y733.js";
11
+ import {
12
+ sanitizeArtifactUrl
13
+ } from "./index-mxxxytys.js";
11
14
  import {
12
15
  startNetworkProxy
13
16
  } from "./index-w2zc0vwa.js";
@@ -16,15 +19,12 @@ import {
16
19
  decodeBytes,
17
20
  safeFetch
18
21
  } from "./index-e5fbsywq.js";
19
- import {
20
- captureUrl
21
- } from "./index-byz4kzww.js";
22
22
  import {
23
23
  readBoundedByteStream
24
24
  } from "./index-gh719d91.js";
25
25
  import {
26
- sanitizeArtifactUrl
27
- } from "./index-mxxxytys.js";
26
+ captureUrl
27
+ } from "./index-byz4kzww.js";
28
28
  import {
29
29
  findKbPackageRoot,
30
30
  resolvePackageDirectory
@@ -1,7 +1,4 @@
1
1
  // @bun
2
- import {
3
- percolateVault
4
- } from "./index-bdwcjvr4.js";
5
2
  import {
6
3
  createGraphSnapshot,
7
4
  openGraphAuthority
@@ -10,6 +7,9 @@ import {
10
7
  GraphAuthorityError,
11
8
  validateGraphPercolationOptions
12
9
  } from "./index-pgtm2nhf.js";
10
+ import {
11
+ percolateVault
12
+ } from "./index-bdwcjvr4.js";
13
13
  import {
14
14
  analyzeVault,
15
15
  lookupNote,