byterover-cli 3.6.1 → 3.7.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 (50) hide show
  1. package/README.md +1 -1
  2. package/dist/agent/core/interfaces/cipher-services.d.ts +7 -0
  3. package/dist/agent/infra/agent/cipher-agent.js +4 -2
  4. package/dist/agent/infra/agent/service-initializer.js +28 -14
  5. package/dist/agent/infra/sandbox/curate-service.d.ts +4 -2
  6. package/dist/agent/infra/sandbox/curate-service.js +6 -4
  7. package/dist/agent/infra/tools/implementations/curate-tool.d.ts +4 -2
  8. package/dist/agent/infra/tools/implementations/curate-tool.js +169 -25
  9. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.d.ts +2 -0
  10. package/dist/agent/infra/tools/implementations/expand-knowledge-tool.js +1 -1
  11. package/dist/agent/infra/tools/implementations/memory-symbol-tree.d.ts +0 -8
  12. package/dist/agent/infra/tools/implementations/memory-symbol-tree.js +3 -15
  13. package/dist/agent/infra/tools/implementations/search-knowledge-service.d.ts +49 -4
  14. package/dist/agent/infra/tools/implementations/search-knowledge-service.js +123 -53
  15. package/dist/agent/infra/tools/implementations/search-knowledge-tool.d.ts +2 -0
  16. package/dist/agent/infra/tools/tool-provider.js +1 -0
  17. package/dist/agent/infra/tools/tool-registry.d.ts +7 -0
  18. package/dist/agent/infra/tools/tool-registry.js +13 -6
  19. package/dist/oclif/commands/dream.d.ts +4 -0
  20. package/dist/oclif/commands/dream.js +31 -13
  21. package/dist/server/constants.d.ts +1 -1
  22. package/dist/server/constants.js +20 -1
  23. package/dist/server/core/domain/knowledge/markdown-writer.d.ts +12 -42
  24. package/dist/server/core/domain/knowledge/markdown-writer.js +55 -96
  25. package/dist/server/core/domain/knowledge/memory-scoring.d.ts +18 -37
  26. package/dist/server/core/domain/knowledge/memory-scoring.js +36 -85
  27. package/dist/server/core/domain/knowledge/runtime-signals-schema.d.ts +59 -0
  28. package/dist/server/core/domain/knowledge/runtime-signals-schema.js +46 -0
  29. package/dist/server/core/domain/knowledge/sidecar-logging.d.ts +14 -0
  30. package/dist/server/core/domain/knowledge/sidecar-logging.js +18 -0
  31. package/dist/server/core/interfaces/storage/i-runtime-signal-store.d.ts +111 -0
  32. package/dist/server/core/interfaces/storage/i-runtime-signal-store.js +38 -0
  33. package/dist/server/infra/context-tree/file-context-tree-archive-service.d.ts +16 -6
  34. package/dist/server/infra/context-tree/file-context-tree-archive-service.js +91 -32
  35. package/dist/server/infra/context-tree/file-context-tree-manifest-service.d.ts +14 -0
  36. package/dist/server/infra/context-tree/file-context-tree-manifest-service.js +20 -7
  37. package/dist/server/infra/context-tree/runtime-signal-store.d.ts +46 -0
  38. package/dist/server/infra/context-tree/runtime-signal-store.js +118 -0
  39. package/dist/server/infra/daemon/agent-process.js +25 -4
  40. package/dist/server/infra/dream/operations/consolidate.d.ts +17 -0
  41. package/dist/server/infra/dream/operations/consolidate.js +40 -19
  42. package/dist/server/infra/dream/operations/prune.d.ts +18 -0
  43. package/dist/server/infra/dream/operations/prune.js +31 -20
  44. package/dist/server/infra/dream/operations/synthesize.d.ts +13 -0
  45. package/dist/server/infra/dream/operations/synthesize.js +15 -3
  46. package/dist/server/infra/executor/dream-executor.d.ts +8 -0
  47. package/dist/server/infra/executor/dream-executor.js +3 -0
  48. package/dist/server/templates/skill/SKILL.md +79 -22
  49. package/oclif.manifest.json +429 -429
  50. package/package.json +1 -1
@@ -95,13 +95,32 @@ export const OVERVIEW_EXTENSION = '.overview.md';
95
95
  export const MANIFEST_FILE = '_manifest.json';
96
96
  export const ARCHIVE_IMPORTANCE_THRESHOLD = 35;
97
97
  export const DEFAULT_GHOST_CUE_MAX_TOKENS = 220;
98
- /** Patterns the context-tree .gitignore must contain (derived artifacts only). */
98
+ /** Patterns the context-tree .gitignore must contain. */
99
99
  export const CONTEXT_TREE_GITIGNORE_PATTERNS = [
100
+ // Derived artifacts
100
101
  '.gitignore',
101
102
  '.snapshot.json',
102
103
  '_manifest.json',
103
104
  '_index.md',
104
105
  '*.abstract.md',
105
106
  '*.overview.md',
107
+ // macOS
108
+ '.DS_Store',
109
+ '._*',
110
+ // Windows
111
+ 'Thumbs.db',
112
+ 'ehthumbs.db',
113
+ 'Desktop.ini',
114
+ // Linux
115
+ '.directory',
116
+ '.fuse_hidden*',
117
+ '.nfs*',
118
+ // Editor swap / backup / temp
119
+ '*.swp',
120
+ '*.swo',
121
+ '*~',
122
+ '.#*',
123
+ '*.bak',
124
+ '*.tmp',
106
125
  ];
107
126
  export const CONTEXT_TREE_GITIGNORE_HEADER = '# Derived artifacts — do not track';
@@ -30,16 +30,14 @@ export interface Fact {
30
30
  value?: string;
31
31
  }
32
32
  /**
33
- * Scoring metadata for knowledge lifecycle management (FinMem-inspired).
34
- * Stored in YAML frontmatter alongside existing fields.
33
+ * Content timestamps kept in markdown frontmatter. `createdAt` is the
34
+ * immutable creation time; `updatedAt` reflects the last content
35
+ * modification. Runtime ranking signals (importance, recency, maturity,
36
+ * accessCount, updateCount) live in the sidecar — see
37
+ * `features/runtime-signals/plan.md`.
35
38
  */
36
- export interface FrontmatterScoring {
37
- accessCount?: number;
39
+ export interface ContextTimestamps {
38
40
  createdAt?: string;
39
- importance?: number;
40
- maturity?: 'core' | 'draft' | 'validated';
41
- recency?: number;
42
- updateCount?: number;
43
41
  updatedAt?: string;
44
42
  }
45
43
  export interface ContextData {
@@ -50,48 +48,20 @@ export interface ContextData {
50
48
  rawConcept?: RawConcept;
51
49
  reason?: string;
52
50
  relations?: string[];
53
- scoring?: FrontmatterScoring;
54
51
  snippets: string[];
55
52
  summary?: string;
56
53
  tags: string[];
54
+ timestamps?: ContextTimestamps;
57
55
  }
58
- interface Frontmatter {
59
- accessCount?: number;
60
- createdAt?: string;
61
- importance?: number;
62
- keywords: string[];
63
- maturity?: 'core' | 'draft' | 'validated';
64
- recency?: number;
65
- related: string[];
66
- summary?: string;
67
- tags: string[];
68
- title?: string;
69
- updateCount?: number;
70
- updatedAt?: string;
71
- }
72
- /**
73
- * Parse content extracting relations from either frontmatter or legacy @ format.
74
- * Returns parsed frontmatter metadata and the body content for further parsing.
75
- */
76
- /**
77
- * Extract scoring metadata from parsed frontmatter.
78
- * Returns defaults for missing fields.
79
- */
80
- export declare function extractScoring(fm: Frontmatter): FrontmatterScoring;
81
- /**
82
- * Parse frontmatter from raw markdown content and return scoring metadata.
83
- * Exported for use by search-knowledge-service to extract scoring without
84
- * going through the full parseContent path.
85
- */
86
- export declare function parseFrontmatterScoring(content: string): FrontmatterScoring | undefined;
87
56
  /**
88
- * Replace scoring fields in existing markdown content without touching the body.
89
- * If no frontmatter exists, returns the original content unchanged.
57
+ * Extract the createdAt timestamp from a raw markdown file's frontmatter.
58
+ * Used by callers (e.g. curate UPDATE) that need to preserve the immutable
59
+ * creation time across a write without round-tripping through the full
60
+ * parsed-content shape.
90
61
  */
91
- export declare function updateScoringInContent(content: string, scoring: FrontmatterScoring): string;
62
+ export declare function parseCreatedAt(content: string): string | undefined;
92
63
  export declare const MarkdownWriter: {
93
64
  generateContext(data: ContextData): string;
94
65
  mergeContexts(sourceContent: string, targetContent: string, reason?: string, summary?: string): string;
95
66
  parseContent(content: string, name?: string): ContextData;
96
67
  };
97
- export {};
@@ -1,11 +1,14 @@
1
1
  import { dump as yamlDump, load as yamlLoad } from 'js-yaml';
2
- import { determineTier, mergeScoring as mergeScoringFn } from './memory-scoring.js';
3
2
  import { normalizeRelationPath, parseRelations } from './relation-parser.js';
4
3
  /**
5
4
  * Generate YAML frontmatter block from context data.
6
- * Only includes fields that have values.
5
+ *
6
+ * Emits only semantic fields and content timestamps. Runtime ranking
7
+ * signals (importance, recency, maturity, accessCount, updateCount) are
8
+ * not written — they live in the sidecar store since commit 5 of the
9
+ * runtime-signals migration.
7
10
  */
8
- function generateFrontmatter(title, relations, tags = [], keywords = [], scoring, summary) {
11
+ function generateFrontmatter(title, relations, tags = [], keywords = [], timestamps, summary) {
9
12
  const normalizedRelations = (relations || []).map(rel => normalizeRelationPath(rel));
10
13
  const fm = {};
11
14
  if (title) {
@@ -19,29 +22,11 @@ function generateFrontmatter(title, relations, tags = [], keywords = [], scoring
19
22
  fm.related = normalizedRelations;
20
23
  }
21
24
  fm.keywords = keywords;
22
- // Scoring fields — only emit when present (backward compatible)
23
- if (scoring) {
24
- if (scoring.importance !== undefined) {
25
- fm.importance = Math.round(scoring.importance * 100) / 100;
26
- }
27
- if (scoring.recency !== undefined) {
28
- fm.recency = Math.round(scoring.recency * 1000) / 1000;
29
- }
30
- if (scoring.maturity) {
31
- fm.maturity = scoring.maturity;
32
- }
33
- if (scoring.accessCount !== undefined && scoring.accessCount > 0) {
34
- fm.accessCount = scoring.accessCount;
35
- }
36
- if (scoring.updateCount !== undefined && scoring.updateCount > 0) {
37
- fm.updateCount = scoring.updateCount;
38
- }
39
- if (scoring.createdAt) {
40
- fm.createdAt = scoring.createdAt;
41
- }
42
- if (scoring.updatedAt) {
43
- fm.updatedAt = scoring.updatedAt;
44
- }
25
+ if (timestamps?.createdAt) {
26
+ fm.createdAt = timestamps.createdAt;
27
+ }
28
+ if (timestamps?.updatedAt) {
29
+ fm.updatedAt = timestamps.updatedAt;
45
30
  }
46
31
  // Always generate frontmatter since tags and keywords are required
47
32
  const yamlContent = yamlDump(fm, { flowLevel: 1, lineWidth: -1, sortKeys: false }).trimEnd();
@@ -80,28 +65,17 @@ function parseFrontmatter(content) {
80
65
  if (typeof parsed.summary === 'string') {
81
66
  frontmatter.summary = parsed.summary;
82
67
  }
83
- // Scoring fields (backward compatible absent in old files)
84
- if (typeof parsed.importance === 'number') {
85
- frontmatter.importance = parsed.importance;
86
- }
87
- if (typeof parsed.recency === 'number') {
88
- frontmatter.recency = parsed.recency;
89
- }
90
- if (typeof parsed.accessCount === 'number') {
91
- frontmatter.accessCount = parsed.accessCount;
92
- }
93
- if (typeof parsed.updateCount === 'number') {
94
- frontmatter.updateCount = parsed.updateCount;
95
- }
68
+ // Content timestamps (createdAt is immutable, updatedAt tracks real
69
+ // content modification). Pre-migration files may also carry legacy
70
+ // scoring fields (importance, recency, maturity, accessCount,
71
+ // updateCount) — those are silently ignored here; the runtime signals
72
+ // they represented now live in the sidecar.
96
73
  if (typeof parsed.createdAt === 'string') {
97
74
  frontmatter.createdAt = parsed.createdAt;
98
75
  }
99
76
  if (typeof parsed.updatedAt === 'string') {
100
77
  frontmatter.updatedAt = parsed.updatedAt;
101
78
  }
102
- if (parsed.maturity === 'draft' || parsed.maturity === 'validated' || parsed.maturity === 'core') {
103
- frontmatter.maturity = parsed.maturity;
104
- }
105
79
  return { body, frontmatter };
106
80
  }
107
81
  catch {
@@ -500,59 +474,29 @@ function mergeFacts(source, target) {
500
474
  return merged.length > 0 ? merged : undefined;
501
475
  }
502
476
  /**
503
- * Parse content extracting relations from either frontmatter or legacy @ format.
504
- * Returns parsed frontmatter metadata and the body content for further parsing.
505
- */
506
- /**
507
- * Extract scoring metadata from parsed frontmatter.
508
- * Returns defaults for missing fields.
509
- */
510
- export function extractScoring(fm) {
511
- return {
512
- accessCount: fm.accessCount ?? 0,
513
- createdAt: fm.createdAt,
514
- importance: fm.importance ?? 50,
515
- maturity: fm.maturity ?? 'draft',
516
- recency: fm.recency ?? 1,
517
- updateCount: fm.updateCount ?? 0,
518
- updatedAt: fm.updatedAt,
519
- };
520
- }
521
- /**
522
- * Parse frontmatter from raw markdown content and return scoring metadata.
523
- * Exported for use by search-knowledge-service to extract scoring without
524
- * going through the full parseContent path.
525
- */
526
- export function parseFrontmatterScoring(content) {
527
- const parsed = parseFrontmatter(content);
528
- if (!parsed) {
529
- return undefined;
530
- }
531
- return extractScoring(parsed.frontmatter);
532
- }
533
- /**
534
- * Replace scoring fields in existing markdown content without touching the body.
535
- * If no frontmatter exists, returns the original content unchanged.
477
+ * Extract the createdAt timestamp from a raw markdown file's frontmatter.
478
+ * Used by callers (e.g. curate UPDATE) that need to preserve the immutable
479
+ * creation time across a write without round-tripping through the full
480
+ * parsed-content shape.
536
481
  */
537
- export function updateScoringInContent(content, scoring) {
538
- const parsed = parseFrontmatter(content);
539
- if (!parsed) {
540
- return content;
541
- }
542
- const { body, frontmatter } = parsed;
543
- const updatedFrontmatter = generateFrontmatter(frontmatter.title ?? '', frontmatter.related, frontmatter.tags, frontmatter.keywords, scoring, frontmatter.summary);
544
- return updatedFrontmatter + body;
482
+ export function parseCreatedAt(content) {
483
+ return parseFrontmatter(content)?.frontmatter.createdAt;
545
484
  }
546
485
  function parseContentWithFrontmatter(content) {
547
486
  const parsed = parseFrontmatter(content);
548
487
  if (parsed) {
488
+ const timestamps = {};
489
+ if (parsed.frontmatter.createdAt)
490
+ timestamps.createdAt = parsed.frontmatter.createdAt;
491
+ if (parsed.frontmatter.updatedAt)
492
+ timestamps.updatedAt = parsed.frontmatter.updatedAt;
549
493
  return {
550
494
  body: parsed.body,
551
495
  keywords: parsed.frontmatter.keywords,
552
496
  relations: parsed.frontmatter.related,
553
- scoring: extractScoring(parsed.frontmatter),
554
497
  summary: parsed.frontmatter.summary,
555
498
  tags: parsed.frontmatter.tags,
499
+ timestamps: Object.keys(timestamps).length > 0 ? timestamps : undefined,
556
500
  title: parsed.frontmatter.title,
557
501
  };
558
502
  }
@@ -568,7 +512,7 @@ export const MarkdownWriter = {
568
512
  generateContext(data) {
569
513
  const snippets = (data.snippets || []).filter(s => s && s.trim());
570
514
  const relations = data.relations || [];
571
- const frontmatter = generateFrontmatter(data.name, relations, data.tags, data.keywords, data.scoring, data.summary);
515
+ const frontmatter = generateFrontmatter(data.name, relations, data.tags, data.keywords, data.timestamps, data.summary);
572
516
  const reasonSection = generateReasonSection(data.reason);
573
517
  const rawConceptSection = generateRawConceptSection(data.rawConcept);
574
518
  const narrativeSection = generateNarrativeSection(data.narrative);
@@ -602,14 +546,10 @@ export const MarkdownWriter = {
602
546
  const mergedKeywords = [...new Set([...sourceParsed.keywords, ...targetParsed.keywords])];
603
547
  // reason: explicit override wins, then source (newer), then target (older)
604
548
  const mergedReason = reason ?? parseReasonSection(sourceParsed.body) ?? parseReasonSection(targetParsed.body);
605
- // Merge scoring metadata (FinMem-inspired lifecycle)
606
- const defaultScoring = { importance: 50, maturity: 'draft', recency: 1 };
607
- let mergedScoringData;
608
- if (sourceParsed.scoring || targetParsed.scoring) {
609
- const merged = mergeScoringFn(sourceParsed.scoring ?? defaultScoring, targetParsed.scoring ?? defaultScoring);
610
- const recalculatedTier = determineTier(merged.importance ?? 50, (merged.maturity ?? 'draft'));
611
- mergedScoringData = { ...merged, maturity: recalculatedTier };
612
- }
549
+ // Merge timestamps: preserve the earliest createdAt and stamp a fresh
550
+ // updatedAt. Scoring signals (importance/recency/maturity/counts) are
551
+ // merged at the sidecar layer by the merge caller — not here.
552
+ const mergedTimestamps = mergeTimestamps(sourceParsed.timestamps, targetParsed.timestamps);
613
553
  const sourceRawConcept = parseRawConceptSection(sourceParsed.body);
614
554
  const targetRawConcept = parseRawConceptSection(targetParsed.body);
615
555
  const mergedRawConcept = mergeRawConcepts(sourceRawConcept, targetRawConcept);
@@ -637,14 +577,14 @@ export const MarkdownWriter = {
637
577
  rawConcept: mergedRawConcept,
638
578
  reason: mergedReason,
639
579
  relations: mergedRelations,
640
- scoring: mergedScoringData,
641
580
  snippets: mergedSnippets,
642
581
  summary: summary ?? sourceParsed.summary ?? targetParsed.summary,
643
582
  tags: mergedTags,
583
+ timestamps: mergedTimestamps,
644
584
  });
645
585
  },
646
586
  parseContent(content, name = '') {
647
- const { body, keywords, relations, scoring, summary, tags, title } = parseContentWithFrontmatter(content);
587
+ const { body, keywords, relations, summary, tags, timestamps, title } = parseContentWithFrontmatter(content);
648
588
  return {
649
589
  facts: parseFactsSection(body),
650
590
  keywords,
@@ -653,10 +593,29 @@ export const MarkdownWriter = {
653
593
  rawConcept: parseRawConceptSection(body),
654
594
  reason: parseReasonSection(body),
655
595
  relations,
656
- scoring,
657
596
  snippets: extractSnippetsFromContent(body),
658
597
  summary,
659
598
  tags,
599
+ timestamps,
660
600
  };
661
601
  },
662
602
  };
603
+ /**
604
+ * Merge two timestamp records: earliest createdAt, fresh updatedAt.
605
+ *
606
+ * Always stamps a fresh `updatedAt` — merge is a content modification, so
607
+ * the output always carries an updated timestamp regardless of input shape.
608
+ * `createdAt` only appears in the output when at least one input had it.
609
+ */
610
+ function mergeTimestamps(a, b) {
611
+ const out = { updatedAt: new Date().toISOString() };
612
+ const aCreated = a?.createdAt;
613
+ const bCreated = b?.createdAt;
614
+ if (aCreated && bCreated) {
615
+ out.createdAt = new Date(aCreated).getTime() <= new Date(bCreated).getTime() ? aCreated : bCreated;
616
+ }
617
+ else if (aCreated ?? bCreated) {
618
+ out.createdAt = aCreated ?? bCreated;
619
+ }
620
+ return out;
621
+ }
@@ -9,7 +9,7 @@
9
9
  *
10
10
  * All functions are stateless and side-effect free.
11
11
  */
12
- import type { FrontmatterScoring } from './markdown-writer.js';
12
+ import type { RuntimeSignals } from './runtime-signals-schema.js';
13
13
  /** Days for recency half-life (~21 days to halve) */
14
14
  export declare const DECAY_RECENCY_FACTOR = 30;
15
15
  /** Per-day importance multiplier (~78% after 50 days of non-use) */
@@ -41,23 +41,21 @@ export declare const TIER_BOOST: Record<string, number>;
41
41
  * then applies a tier-based boost.
42
42
  *
43
43
  * @param bm25Normalized - Normalized BM25 score in [0, 1)
44
- * @param importance - Importance score in [0, 100]
45
- * @param recency - Recency score in [0, 1]
46
- * @param maturity - Maturity tier ('draft' | 'validated' | 'core')
44
+ * @param signals - RuntimeSignals snapshot (importance, recency, maturity)
47
45
  * @returns Compound score (typically in [0, ~1.15])
48
46
  */
49
- export declare function compoundScore(bm25Normalized: number, importance: number, recency: number, maturity: string): number;
47
+ export declare function compoundScore(bm25Normalized: number, signals: RuntimeSignals): number;
50
48
  /**
51
- * Apply time-based exponential decay to scoring fields.
49
+ * Apply time-based exponential decay to a signals snapshot.
52
50
  *
53
51
  * Recency decays as exp(-days / DECAY_RECENCY_FACTOR).
54
52
  * Importance decays as importance * DECAY_IMPORTANCE_FACTOR^days.
55
53
  *
56
- * @param scoring - Current scoring state
54
+ * @param signals - Current RuntimeSignals snapshot
57
55
  * @param daysSinceLastUpdate - Days since the file was last updated
58
- * @returns New scoring with decayed values (original not mutated)
56
+ * @returns New signals with decayed values (original not mutated)
59
57
  */
60
- export declare function applyDecay(scoring: FrontmatterScoring, daysSinceLastUpdate: number): FrontmatterScoring;
58
+ export declare function applyDecay(signals: RuntimeSignals, daysSinceLastUpdate: number): RuntimeSignals;
61
59
  /**
62
60
  * Determine the maturity tier based on importance score.
63
61
  *
@@ -72,47 +70,30 @@ export declare function applyDecay(scoring: FrontmatterScoring, daysSinceLastUpd
72
70
  * @returns The determined tier
73
71
  */
74
72
  export declare function determineTier(importance: number, currentTier: 'core' | 'draft' | 'validated'): 'core' | 'draft' | 'validated';
75
- /**
76
- * Record a search access hit on a knowledge file.
77
- *
78
- * Increments access count and adds an importance bonus.
79
- *
80
- * @param scoring - Current scoring state
81
- * @returns Updated scoring (original not mutated)
82
- */
83
- export declare function recordAccessHit(scoring: FrontmatterScoring): FrontmatterScoring;
84
73
  /**
85
74
  * Record multiple accumulated access hits at once.
86
75
  *
87
- * @param scoring - Current scoring state
88
- * @param hitCount - Number of hits to record
89
- * @returns Updated scoring (original not mutated)
76
+ * Increments accessCount by `hitCount` and importance by
77
+ * `ACCESS_IMPORTANCE_BONUS * hitCount` (capped at 100). Caller is
78
+ * responsible for recomputing maturity via `determineTier` if the
79
+ * importance delta may cross a hysteresis threshold.
90
80
  */
91
- export declare function recordAccessHits(scoring: FrontmatterScoring, hitCount: number): FrontmatterScoring;
81
+ export declare function recordAccessHits(signals: RuntimeSignals, hitCount: number): RuntimeSignals;
92
82
  /**
93
83
  * Record a curate update on a knowledge file.
94
84
  *
95
- * Increments update count, adds an importance bonus, resets recency to 1.0,
96
- * and updates the timestamp.
97
- *
98
- * @param scoring - Current scoring state
99
- * @returns Updated scoring (original not mutated)
100
- */
101
- export declare function recordCurateUpdate(scoring: FrontmatterScoring): FrontmatterScoring;
102
- /**
103
- * Return default scoring values for a new or unscored knowledge file.
85
+ * Increments updateCount, adds an importance bonus, resets recency to 1.0.
86
+ * Caller is responsible for recomputing maturity via `determineTier`.
104
87
  */
105
- export declare function applyDefaultScoring(): FrontmatterScoring;
88
+ export declare function recordCurateUpdate(signals: RuntimeSignals): RuntimeSignals;
106
89
  /**
107
- * Merge two scoring states during a MERGE operation.
90
+ * Merge two runtime-signal snapshots during a MERGE operation.
108
91
  *
109
92
  * Strategy:
110
93
  * - importance: max of both
111
94
  * - recency: max of both
112
95
  * - accessCount: sum
113
96
  * - updateCount: sum + 1 (for the merge itself)
114
- * - maturity: higher tier
115
- * - createdAt: earlier date
116
- * - updatedAt: current time
97
+ * - maturity: higher tier (caller may refine via `determineTier`)
117
98
  */
118
- export declare function mergeScoring(source: FrontmatterScoring, target: FrontmatterScoring): FrontmatterScoring;
99
+ export declare function mergeScoring(source: RuntimeSignals, target: RuntimeSignals): RuntimeSignals;
@@ -50,36 +50,33 @@ export const TIER_BOOST = {
50
50
  * then applies a tier-based boost.
51
51
  *
52
52
  * @param bm25Normalized - Normalized BM25 score in [0, 1)
53
- * @param importance - Importance score in [0, 100]
54
- * @param recency - Recency score in [0, 1]
55
- * @param maturity - Maturity tier ('draft' | 'validated' | 'core')
53
+ * @param signals - RuntimeSignals snapshot (importance, recency, maturity)
56
54
  * @returns Compound score (typically in [0, ~1.15])
57
55
  */
58
- export function compoundScore(bm25Normalized, importance, recency, maturity) {
59
- const normalizedImportance = Math.min(importance, 100) / 100;
60
- const base = W_RELEVANCE * bm25Normalized + W_IMPORTANCE * normalizedImportance + W_RECENCY * recency;
61
- const boost = TIER_BOOST[maturity] ?? TIER_BOOST.draft;
56
+ export function compoundScore(bm25Normalized, signals) {
57
+ const normalizedImportance = Math.min(signals.importance, 100) / 100;
58
+ const base = W_RELEVANCE * bm25Normalized + W_IMPORTANCE * normalizedImportance + W_RECENCY * signals.recency;
59
+ const boost = TIER_BOOST[signals.maturity] ?? TIER_BOOST.draft;
62
60
  return base * boost;
63
61
  }
64
62
  /**
65
- * Apply time-based exponential decay to scoring fields.
63
+ * Apply time-based exponential decay to a signals snapshot.
66
64
  *
67
65
  * Recency decays as exp(-days / DECAY_RECENCY_FACTOR).
68
66
  * Importance decays as importance * DECAY_IMPORTANCE_FACTOR^days.
69
67
  *
70
- * @param scoring - Current scoring state
68
+ * @param signals - Current RuntimeSignals snapshot
71
69
  * @param daysSinceLastUpdate - Days since the file was last updated
72
- * @returns New scoring with decayed values (original not mutated)
70
+ * @returns New signals with decayed values (original not mutated)
73
71
  */
74
- export function applyDecay(scoring, daysSinceLastUpdate) {
72
+ export function applyDecay(signals, daysSinceLastUpdate) {
75
73
  if (daysSinceLastUpdate <= 0) {
76
- return scoring;
74
+ return signals;
77
75
  }
78
- const currentImportance = scoring.importance ?? 50;
79
76
  const newRecency = Math.exp(-daysSinceLastUpdate / DECAY_RECENCY_FACTOR);
80
- const newImportance = currentImportance * DECAY_IMPORTANCE_FACTOR ** daysSinceLastUpdate;
77
+ const newImportance = signals.importance * DECAY_IMPORTANCE_FACTOR ** daysSinceLastUpdate;
81
78
  return {
82
- ...scoring,
79
+ ...signals,
83
80
  importance: Math.max(0, newImportance),
84
81
  recency: newRecency,
85
82
  };
@@ -114,104 +111,58 @@ export function determineTier(importance, currentTier) {
114
111
  }
115
112
  return currentTier;
116
113
  }
117
- /**
118
- * Record a search access hit on a knowledge file.
119
- *
120
- * Increments access count and adds an importance bonus.
121
- *
122
- * @param scoring - Current scoring state
123
- * @returns Updated scoring (original not mutated)
124
- */
125
- export function recordAccessHit(scoring) {
126
- const newAccessCount = (scoring.accessCount ?? 0) + 1;
127
- const newImportance = Math.min(100, (scoring.importance ?? 50) + ACCESS_IMPORTANCE_BONUS);
128
- return {
129
- ...scoring,
130
- accessCount: newAccessCount,
131
- importance: newImportance,
132
- };
133
- }
134
114
  /**
135
115
  * Record multiple accumulated access hits at once.
136
116
  *
137
- * @param scoring - Current scoring state
138
- * @param hitCount - Number of hits to record
139
- * @returns Updated scoring (original not mutated)
117
+ * Increments accessCount by `hitCount` and importance by
118
+ * `ACCESS_IMPORTANCE_BONUS * hitCount` (capped at 100). Caller is
119
+ * responsible for recomputing maturity via `determineTier` if the
120
+ * importance delta may cross a hysteresis threshold.
140
121
  */
141
- export function recordAccessHits(scoring, hitCount) {
122
+ export function recordAccessHits(signals, hitCount) {
142
123
  if (hitCount <= 0) {
143
- return scoring;
124
+ return signals;
144
125
  }
145
- const newAccessCount = (scoring.accessCount ?? 0) + hitCount;
146
- const newImportance = Math.min(100, (scoring.importance ?? 50) + ACCESS_IMPORTANCE_BONUS * hitCount);
147
126
  return {
148
- ...scoring,
149
- accessCount: newAccessCount,
150
- importance: newImportance,
127
+ ...signals,
128
+ accessCount: signals.accessCount + hitCount,
129
+ importance: Math.min(100, signals.importance + ACCESS_IMPORTANCE_BONUS * hitCount),
151
130
  };
152
131
  }
153
132
  /**
154
133
  * Record a curate update on a knowledge file.
155
134
  *
156
- * Increments update count, adds an importance bonus, resets recency to 1.0,
157
- * and updates the timestamp.
158
- *
159
- * @param scoring - Current scoring state
160
- * @returns Updated scoring (original not mutated)
161
- */
162
- export function recordCurateUpdate(scoring) {
163
- const newUpdateCount = (scoring.updateCount ?? 0) + 1;
164
- const newImportance = Math.min(100, (scoring.importance ?? 50) + UPDATE_IMPORTANCE_BONUS);
165
- const now = new Date().toISOString();
166
- return {
167
- ...scoring,
168
- importance: newImportance,
169
- recency: 1,
170
- updateCount: newUpdateCount,
171
- updatedAt: now,
172
- };
173
- }
174
- /**
175
- * Return default scoring values for a new or unscored knowledge file.
135
+ * Increments updateCount, adds an importance bonus, resets recency to 1.0.
136
+ * Caller is responsible for recomputing maturity via `determineTier`.
176
137
  */
177
- export function applyDefaultScoring() {
178
- const now = new Date().toISOString();
138
+ export function recordCurateUpdate(signals) {
179
139
  return {
180
- accessCount: 0,
181
- createdAt: now,
182
- importance: 50,
183
- maturity: 'draft',
140
+ ...signals,
141
+ importance: Math.min(100, signals.importance + UPDATE_IMPORTANCE_BONUS),
184
142
  recency: 1,
185
- updateCount: 0,
186
- updatedAt: now,
143
+ updateCount: signals.updateCount + 1,
187
144
  };
188
145
  }
189
146
  /**
190
- * Merge two scoring states during a MERGE operation.
147
+ * Merge two runtime-signal snapshots during a MERGE operation.
191
148
  *
192
149
  * Strategy:
193
150
  * - importance: max of both
194
151
  * - recency: max of both
195
152
  * - accessCount: sum
196
153
  * - updateCount: sum + 1 (for the merge itself)
197
- * - maturity: higher tier
198
- * - createdAt: earlier date
199
- * - updatedAt: current time
154
+ * - maturity: higher tier (caller may refine via `determineTier`)
200
155
  */
201
156
  export function mergeScoring(source, target) {
202
157
  const tierRank = { core: 3, draft: 1, validated: 2 };
203
- const sourceRank = tierRank[source.maturity ?? 'draft'] ?? 1;
204
- const targetRank = tierRank[target.maturity ?? 'draft'] ?? 1;
205
- const higherTier = sourceRank >= targetRank ? (source.maturity ?? 'draft') : (target.maturity ?? 'draft');
206
- const sourceCreated = source.createdAt ? new Date(source.createdAt).getTime() : Date.now();
207
- const targetCreated = target.createdAt ? new Date(target.createdAt).getTime() : Date.now();
158
+ const sourceRank = tierRank[source.maturity] ?? 1;
159
+ const targetRank = tierRank[target.maturity] ?? 1;
160
+ const higherTier = sourceRank >= targetRank ? source.maturity : target.maturity;
208
161
  return {
209
- accessCount: (source.accessCount ?? 0) + (target.accessCount ?? 0),
210
- createdAt: sourceCreated <= targetCreated ? source.createdAt : target.createdAt,
211
- importance: Math.max(source.importance ?? 50, target.importance ?? 50),
162
+ accessCount: source.accessCount + target.accessCount,
163
+ importance: Math.max(source.importance, target.importance),
212
164
  maturity: higherTier,
213
- recency: Math.max(source.recency ?? 1, target.recency ?? 1),
214
- updateCount: (source.updateCount ?? 0) + (target.updateCount ?? 0) + 1,
215
- updatedAt: new Date().toISOString(),
165
+ recency: Math.max(source.recency, target.recency),
166
+ updateCount: source.updateCount + target.updateCount + 1,
216
167
  };
217
168
  }