@mrciphersmith/keryx 0.3.15 → 0.3.17

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/core.js CHANGED
@@ -1327,6 +1327,23 @@ function tokenize(text) {
1327
1327
  function tokenSet(text) {
1328
1328
  return new Set(tokenize(text));
1329
1329
  }
1330
+ function stem(token) {
1331
+ let base = token;
1332
+ if (base.length >= 6 && base.endsWith("ies")) {
1333
+ base = `${base.slice(0, -3)}y`;
1334
+ } else {
1335
+ for (const suffix of ["ing", "edly", "ed", "es", "s"]) {
1336
+ if (base.length - suffix.length >= 3 && base.endsWith(suffix)) {
1337
+ base = base.slice(0, base.length - suffix.length);
1338
+ break;
1339
+ }
1340
+ }
1341
+ }
1342
+ if (base.length >= 4 && base.endsWith("e")) {
1343
+ base = base.slice(0, -1);
1344
+ }
1345
+ return base;
1346
+ }
1330
1347
  function jaccard(a, b) {
1331
1348
  if (a.size === 0 || b.size === 0) {
1332
1349
  return 0;
@@ -1749,7 +1766,7 @@ var init_lifecycle = __esm(() => {
1749
1766
  function searchEntries(entries, query, filters, config, now) {
1750
1767
  validateQuery(query);
1751
1768
  validateSearchFilters(filters, now);
1752
- const queryTokens = [...new Set(tokenize(query))];
1769
+ const queryTokens = [...new Set(tokenize(query).map(stem))];
1753
1770
  const today = now.toISOString().slice(0, 10);
1754
1771
  const filtered = entries.filter((entry) => matchesFilters(entry, filters) && classMatch(entry, filters.class) && temporalMatch(entry, filters.asOf ?? null, config, today, Boolean(filters.status)));
1755
1772
  const scored = filtered.map((entry) => scoreEntry(entry, queryTokens, filters, config, now));
@@ -1758,7 +1775,7 @@ function searchEntries(entries, query, filters, config, now) {
1758
1775
  function candidatePool(entries, query, filters, config, now, k) {
1759
1776
  validateQuery(query);
1760
1777
  validateSearchFilters(filters, now);
1761
- const queryTokens = [...new Set(tokenize(query))];
1778
+ const queryTokens = [...new Set(tokenize(query).map(stem))];
1762
1779
  const today = now.toISOString().slice(0, 10);
1763
1780
  return entries.filter((entry) => matchesFilters(entry, filters) && classMatch(entry, filters.class) && temporalMatch(entry, filters.asOf ?? null, config, today, Boolean(filters.status))).map((entry) => scoreEntry(entry, queryTokens, filters, config, now)).sort((a, b) => b.score - a.score).slice(0, Math.min(MAX_GENERAL_RESULTS, Math.max(0, k)));
1764
1781
  }
@@ -1809,8 +1826,8 @@ function temporalMatch(entry, asOf, config, today, hasStatusFilter) {
1809
1826
  }, new Date(`${today}T00:00:00.000Z`)).current;
1810
1827
  }
1811
1828
  function scoreEntry(entry, queryTokens, filters, config, now) {
1812
- const bodyTokens = tokenSet(`${entry.title} ${entry.summary} ${entry.details} ${entry.tags.join(" ")}`);
1813
- const titleTokens = tokenSet(entry.title);
1829
+ const bodyTokens = new Set([...tokenSet(`${entry.title} ${entry.summary} ${entry.details} ${entry.tags.join(" ")}`)].map(stem));
1830
+ const titleTokens = new Set([...tokenSet(entry.title)].map(stem));
1814
1831
  let hits = 0;
1815
1832
  let titleHits = 0;
1816
1833
  for (const token of queryTokens) {
@@ -20454,6 +20471,7 @@ __export(exports_service4, {
20454
20471
  createMemoryService: () => createMemoryService,
20455
20472
  filterEntriesForHarness: () => filterEntriesForHarness,
20456
20473
  isMemoryHarnessId: () => isMemoryHarnessId,
20474
+ loadEmbeddingIndex: () => loadEmbeddingIndex,
20457
20475
  memoryAllowedRelativePaths: () => memoryAllowedRelativePaths,
20458
20476
  memoryHandoff: () => memoryHandoff,
20459
20477
  memoryHarnessVisibilityByPath: () => memoryHarnessVisibilityByPath,
@@ -20775,6 +20793,7 @@ var init_service5 = __esm(() => {
20775
20793
  init_harness_identity();
20776
20794
  init_embedding();
20777
20795
  init_types();
20796
+ init_embedding();
20778
20797
  });
20779
20798
 
20780
20799
  // src/wiki/ask.ts
@@ -27208,6 +27227,7 @@ var exports_service6 = {};
27208
27227
  __export(exports_service6, {
27209
27228
  UnknownGraphTargetError: () => UnknownGraphTargetError,
27210
27229
  buildAffectedReport: () => buildAffectedReport,
27230
+ checkGraphStaleness: () => checkGraphStaleness,
27211
27231
  createGdgraphService: () => createGdgraphService
27212
27232
  });
27213
27233
 
@@ -28124,6 +28144,7 @@ async function writeRepomap(cwd, graph, config, options = {}) {
28124
28144
 
28125
28145
  // src/gdgraph/service.ts
28126
28146
  init_affected_report();
28147
+ init_staleness();
28127
28148
 
28128
28149
  class UnknownGraphTargetError extends Error {
28129
28150
  target;
@@ -28169,7 +28190,8 @@ init_json();
28169
28190
  var exports_service7 = {};
28170
28191
  __export(exports_service7, {
28171
28192
  createCodeHealthService: () => createCodeHealthService,
28172
- gateExitCode: () => gateExitCode
28193
+ gateExitCode: () => gateExitCode,
28194
+ readWikiFreshnessMetric: () => readWikiFreshnessMetric
28173
28195
  });
28174
28196
  import path95 from "path";
28175
28197
 
@@ -28331,7 +28353,8 @@ function computeGate2(input) {
28331
28353
  }
28332
28354
  const missingOptional = sources.filter((s) => !s.required && s.status === "missing");
28333
28355
  for (const source of missingOptional) {
28334
- reasons.push(`OPTIONAL: ${source.source} source missing`);
28356
+ const detail = source.error ? `: ${source.error}` : "";
28357
+ reasons.push(`OPTIONAL: ${source.source} source missing${detail}`);
28335
28358
  }
28336
28359
  const brokenOptional = sources.filter((s) => !s.required && s.status === "configured-but-failed");
28337
28360
  if (brokenOptional.length > 0) {
@@ -30148,6 +30171,12 @@ function errorCodeSuffix(error) {
30148
30171
  const code = safeErrorCode(error);
30149
30172
  return code ? ` (${code})` : "";
30150
30173
  }
30174
+ function missingSourceReason(sourceId) {
30175
+ if (sourceId === "tests") {
30176
+ return "no `bun` binary found (checked node_modules/.bin and PATH)";
30177
+ }
30178
+ return;
30179
+ }
30151
30180
  var KNOWN_VALIDATION_ERRORS = new Set([
30152
30181
  "ESLint JSON format was not recognized",
30153
30182
  "ESLint JSON parse failed",
@@ -30189,7 +30218,8 @@ async function runAdapter(adapter, ctx, cfg, stamp) {
30189
30218
  };
30190
30219
  }
30191
30220
  if (status === "skipped" || status === "missing") {
30192
- return { info: { ...base, status }, findings: [] };
30221
+ const reason = missingSourceReason(adapter.id);
30222
+ return { info: { ...base, status, ...reason !== undefined ? { error: reason } : {} }, findings: [] };
30193
30223
  }
30194
30224
  let raw;
30195
30225
  try {
@@ -30203,7 +30233,7 @@ async function runAdapter(adapter, ctx, cfg, stamp) {
30203
30233
  } catch (error) {
30204
30234
  if (error instanceof NoImportError) {
30205
30235
  if (adapter.id === "tests") {
30206
- return { info: { ...base, status: "missing" }, findings: [] };
30236
+ return { info: { ...base, status }, findings: [] };
30207
30237
  }
30208
30238
  raw = await adapter.run(ctx);
30209
30239
  } else {
@@ -37016,6 +37046,18 @@ var HELP_GROUPS = [
37016
37046
  summary: "Grouped command help by task \u2014 keryx help [group|command]."
37017
37047
  },
37018
37048
  { kind: "slash", name: "/help", group: "Start here", summary: "Show available commands, grouped by task." },
37049
+ {
37050
+ kind: "cli",
37051
+ name: "doctor",
37052
+ group: "Start here",
37053
+ summary: "One-page health check: version, Bun floor, ripgrep, sandbox, providers, MCP, integrations, standard, worktrees, graph/wiki freshness."
37054
+ },
37055
+ {
37056
+ kind: "slash",
37057
+ name: "/doctor",
37058
+ group: "Start here",
37059
+ summary: "Same one-page health check, inside the session."
37060
+ },
37019
37061
  {
37020
37062
  kind: "cli",
37021
37063
  name: "auth",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mrciphersmith/keryx",
3
- "version": "0.3.15",
3
+ "version": "0.3.17",
4
4
  "description": "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -18,6 +18,13 @@ import type { GraphData } from "./types";
18
18
  // facade rule).
19
19
  export { buildAffectedReport, type AffectedReport, type AffectedReportOptions } from "./affected-report";
20
20
 
21
+ // Flow 353 (P0 W5, AC1): re-exported so `commands/doctor.ts`'s aggregate
22
+ // "graph/wiki freshness" check can ask the SAME staleness question
23
+ // `commands/gdgraph.ts` already does, through this facade rather than a
24
+ // second direct edge into `gdgraph/staleness.ts` (the same facade rule
25
+ // `buildAffectedReport` above already follows).
26
+ export { checkGraphStaleness, type StalenessCheck, type StalenessStatus } from "./staleness";
27
+
21
28
  export interface GdgraphService {
22
29
  build(cwd: string): Promise<{ nodes: number; edges: number; summaryPath: string }>;
23
30
  loadGraph(cwd: string): Promise<GraphData>;