@massa-ai/core 1.17.0 → 1.18.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 (135) hide show
  1. package/dist/services/cache/lru-evict.d.ts +63 -0
  2. package/dist/services/cache/lru-evict.d.ts.map +1 -0
  3. package/dist/services/cache/lru-evict.js +76 -0
  4. package/dist/services/cache/lru-evict.js.map +1 -0
  5. package/dist/services/file-read/file-content-cache.d.ts +41 -0
  6. package/dist/services/file-read/file-content-cache.d.ts.map +1 -0
  7. package/dist/services/file-read/file-content-cache.js +109 -0
  8. package/dist/services/file-read/file-content-cache.js.map +1 -0
  9. package/dist/services/file-read/file-metadata.d.ts +23 -0
  10. package/dist/services/file-read/file-metadata.d.ts.map +1 -0
  11. package/dist/services/file-read/file-metadata.js +127 -0
  12. package/dist/services/file-read/file-metadata.js.map +1 -0
  13. package/dist/services/file-read/line-range.d.ts +91 -0
  14. package/dist/services/file-read/line-range.d.ts.map +1 -0
  15. package/dist/services/file-read/line-range.js +146 -0
  16. package/dist/services/file-read/line-range.js.map +1 -0
  17. package/dist/services/file-read/path-containment.d.ts +49 -0
  18. package/dist/services/file-read/path-containment.d.ts.map +1 -0
  19. package/dist/services/file-read/path-containment.js +128 -0
  20. package/dist/services/file-read/path-containment.js.map +1 -0
  21. package/dist/services/file-read/project-root-cache.d.ts +21 -0
  22. package/dist/services/file-read/project-root-cache.d.ts.map +1 -0
  23. package/dist/services/file-read/project-root-cache.js +78 -0
  24. package/dist/services/file-read/project-root-cache.js.map +1 -0
  25. package/dist/services/file-read/read-file.service.d.ts +68 -0
  26. package/dist/services/file-read/read-file.service.d.ts.map +1 -0
  27. package/dist/services/file-read/read-file.service.js +222 -0
  28. package/dist/services/file-read/read-file.service.js.map +1 -0
  29. package/dist/services/hooks/co-retrieval-hook.js +1 -1
  30. package/dist/services/hooks/co-retrieval-hook.js.map +1 -1
  31. package/dist/services/index.d.ts +5 -5
  32. package/dist/services/index.d.ts.map +1 -1
  33. package/dist/services/index.js +4 -4
  34. package/dist/services/index.js.map +1 -1
  35. package/dist/services/indexing/acquire-indexing-lease.d.ts +34 -0
  36. package/dist/services/indexing/acquire-indexing-lease.d.ts.map +1 -0
  37. package/dist/services/indexing/acquire-indexing-lease.js +101 -0
  38. package/dist/services/indexing/acquire-indexing-lease.js.map +1 -0
  39. package/dist/services/indexing/execute-indexing.d.ts +33 -0
  40. package/dist/services/indexing/execute-indexing.d.ts.map +1 -0
  41. package/dist/services/indexing/execute-indexing.js +136 -0
  42. package/dist/services/indexing/execute-indexing.js.map +1 -0
  43. package/dist/services/jobs/memory-consolidation-job.js +1 -1
  44. package/dist/services/jobs/memory-consolidation-job.js.map +1 -1
  45. package/dist/services/memory/memory-controller.js +1 -1
  46. package/dist/services/memory/memory-controller.js.map +1 -1
  47. package/dist/services/memory-graph/graph-queries.d.ts.map +1 -0
  48. package/dist/services/memory-graph/graph-queries.js.map +1 -0
  49. package/dist/services/memory-graph/graph-store-factory.d.ts.map +1 -0
  50. package/dist/services/memory-graph/graph-store-factory.js.map +1 -0
  51. package/dist/services/memory-graph/graph-store-pg.d.ts.map +1 -0
  52. package/dist/services/memory-graph/graph-store-pg.js.map +1 -0
  53. package/dist/services/memory-graph/index.d.ts.map +1 -0
  54. package/dist/services/memory-graph/index.js.map +1 -0
  55. package/dist/services/memory-graph/memory-graph.service.d.ts.map +1 -0
  56. package/dist/services/memory-graph/memory-graph.service.js.map +1 -0
  57. package/dist/services/memory-graph/relation-extractor.d.ts.map +1 -0
  58. package/dist/services/memory-graph/relation-extractor.js.map +1 -0
  59. package/dist/services/memory-graph/types.d.ts.map +1 -0
  60. package/dist/services/memory-graph/types.js.map +1 -0
  61. package/dist/services/project-identity/invalidator-registry.d.ts +9 -3
  62. package/dist/services/project-identity/invalidator-registry.d.ts.map +1 -1
  63. package/dist/services/project-identity/invalidator-registry.js +9 -3
  64. package/dist/services/project-identity/invalidator-registry.js.map +1 -1
  65. package/dist/services/project-identity/production-wiring.d.ts +11 -2
  66. package/dist/services/project-identity/production-wiring.d.ts.map +1 -1
  67. package/dist/services/project-identity/production-wiring.js +11 -2
  68. package/dist/services/project-identity/production-wiring.js.map +1 -1
  69. package/dist/services/project-identity/project-root-identity.d.ts +11 -0
  70. package/dist/services/project-identity/project-root-identity.d.ts.map +1 -0
  71. package/dist/services/project-identity/project-root-identity.js +51 -0
  72. package/dist/services/project-identity/project-root-identity.js.map +1 -0
  73. package/dist/services/search/file-filter-cache.d.ts +30 -1
  74. package/dist/services/search/file-filter-cache.d.ts.map +1 -1
  75. package/dist/services/search/file-filter-cache.js +32 -13
  76. package/dist/services/search/file-filter-cache.js.map +1 -1
  77. package/dist/services/search/graph-stream.js +1 -1
  78. package/dist/services/search/graph-stream.js.map +1 -1
  79. package/dist/services/symbol/symbol-graph.service.d.ts +12 -1
  80. package/dist/services/symbol/symbol-graph.service.d.ts.map +1 -1
  81. package/dist/services/symbol/symbol-graph.service.js +14 -7
  82. package/dist/services/symbol/symbol-graph.service.js.map +1 -1
  83. package/dist/services/web/web-controller.d.ts.map +1 -1
  84. package/dist/services/web/web-controller.js +6 -7
  85. package/dist/services/web/web-controller.js.map +1 -1
  86. package/dist/tools/batch_execute.d.ts +0 -3
  87. package/dist/tools/batch_execute.d.ts.map +1 -1
  88. package/dist/tools/batch_execute.js.map +1 -1
  89. package/dist/tools/index.d.ts +1 -1
  90. package/dist/tools/index.d.ts.map +1 -1
  91. package/dist/tools/index_project.d.ts +0 -19
  92. package/dist/tools/index_project.d.ts.map +1 -1
  93. package/dist/tools/index_project.js +38 -142
  94. package/dist/tools/index_project.js.map +1 -1
  95. package/dist/tools/read_file.d.ts +9 -72
  96. package/dist/tools/read_file.d.ts.map +1 -1
  97. package/dist/tools/read_file.js +22 -487
  98. package/dist/tools/read_file.js.map +1 -1
  99. package/package.json +2 -2
  100. package/dist/data/vector/hybrid-search.d.ts +0 -71
  101. package/dist/data/vector/hybrid-search.d.ts.map +0 -1
  102. package/dist/data/vector/hybrid-search.js +0 -243
  103. package/dist/data/vector/hybrid-search.js.map +0 -1
  104. package/dist/data/vector/index.d.ts +0 -7
  105. package/dist/data/vector/index.d.ts.map +0 -1
  106. package/dist/data/vector/index.js +0 -6
  107. package/dist/data/vector/index.js.map +0 -1
  108. package/dist/services/graph/graph-queries.d.ts.map +0 -1
  109. package/dist/services/graph/graph-queries.js.map +0 -1
  110. package/dist/services/graph/graph-store-factory.d.ts.map +0 -1
  111. package/dist/services/graph/graph-store-factory.js.map +0 -1
  112. package/dist/services/graph/graph-store-pg.d.ts.map +0 -1
  113. package/dist/services/graph/graph-store-pg.js.map +0 -1
  114. package/dist/services/graph/index.d.ts.map +0 -1
  115. package/dist/services/graph/index.js.map +0 -1
  116. package/dist/services/graph/memory-graph.service.d.ts.map +0 -1
  117. package/dist/services/graph/memory-graph.service.js.map +0 -1
  118. package/dist/services/graph/relation-extractor.d.ts.map +0 -1
  119. package/dist/services/graph/relation-extractor.js.map +0 -1
  120. package/dist/services/graph/types.d.ts.map +0 -1
  121. package/dist/services/graph/types.js.map +0 -1
  122. /package/dist/services/{graph → memory-graph}/graph-queries.d.ts +0 -0
  123. /package/dist/services/{graph → memory-graph}/graph-queries.js +0 -0
  124. /package/dist/services/{graph → memory-graph}/graph-store-factory.d.ts +0 -0
  125. /package/dist/services/{graph → memory-graph}/graph-store-factory.js +0 -0
  126. /package/dist/services/{graph → memory-graph}/graph-store-pg.d.ts +0 -0
  127. /package/dist/services/{graph → memory-graph}/graph-store-pg.js +0 -0
  128. /package/dist/services/{graph → memory-graph}/index.d.ts +0 -0
  129. /package/dist/services/{graph → memory-graph}/index.js +0 -0
  130. /package/dist/services/{graph → memory-graph}/memory-graph.service.d.ts +0 -0
  131. /package/dist/services/{graph → memory-graph}/memory-graph.service.js +0 -0
  132. /package/dist/services/{graph → memory-graph}/relation-extractor.d.ts +0 -0
  133. /package/dist/services/{graph → memory-graph}/relation-extractor.js +0 -0
  134. /package/dist/services/{graph → memory-graph}/types.d.ts +0 -0
  135. /package/dist/services/{graph → memory-graph}/types.js +0 -0
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Shared insertion-order eviction for the LRU-bounded `Map`s PR-D unifies (RFS-02 AC-2).
3
+ *
4
+ * WHY A FUNCTION AND NOT A CACHE CLASS.
5
+ * The five caches this serves agree on eviction ORDER and disagree on everything else: cap
6
+ * (512 at four sites, 50 at `file-filter-cache`), TTL (enforced at `file-content-cache`'s
7
+ * `fileCache` only) and read-promotion (absent at `file-filter-cache`). A shared cache class would impose
8
+ * one policy on all five, and that IS the behavior change `spec.md` §3.B measured the eviction
9
+ * itself is not. RFS-02 AC-2 therefore fixes this as an eviction function; every site keeps its
10
+ * own cap, its own TTL and its own promotion policy.
11
+ *
12
+ * WHY THE SECOND PARAMETER IS A POST-CALL BOUND AND NOT "the cap".
13
+ * `design.md` §5.1 specifies only "a function taking `(cache, cap)`", and that phrasing does
14
+ * not determine the predicate, because the five sites do not share one:
15
+ *
16
+ * file-content-cache.ts (T10) pre-insert while (size >= 512)
17
+ * project-root-cache.ts (T9) pre-insert while (size >= 512)
18
+ * symbol-graph.service.ts pre-insert while (size >= 512)
19
+ * web-controller.ts post-insert while (size > 512)
20
+ * file-filter-cache.ts post-insert if (size > 50) evict-one
21
+ *
22
+ * `spec.md` §3.B establishes that pre-insert `>=` and post-insert `>` retain the same number.
23
+ * They do — but only while each site keeps BOTH its operator and its call position. Collapsing
24
+ * them onto one operator with every site passing its own literal cap moves the retained count,
25
+ * and it moves in opposite directions depending on which operator wins. Measured against T1's
26
+ * characterization oracle (`tasks.md` §10.7): a shared `>` breaks the three pre-insert sites, a
27
+ * shared `>=` breaks the two post-insert ones.
28
+ *
29
+ * Stating the contract as a POST-CALL BOUND is what lets one function serve both call
30
+ * positions without moving any retained count, because `size > cap - 1` and `size >= cap` are
31
+ * the same predicate over integers:
32
+ *
33
+ * pre-insert caller passes `CAP - 1` — reserves the slot the pending insert will take
34
+ * post-insert caller passes `CAP` — the insert already happened
35
+ *
36
+ * WHAT THIS MODULE DOES NOT CERTIFY, on RFS-01 AC-6's precedent of naming blind spots.
37
+ * It evicts in `Map` insertion order and knows nothing about TTL, read-promotion, or whether a
38
+ * caller's insertion order matches that caller's own notion of "oldest". `file-filter-cache`
39
+ * evicts by `min(createdAt)` today and coincides with insertion order only because it never
40
+ * re-inserts an entry on a read — an invariant unstated in that file, guarded by T1's
41
+ * characterization suite (mutation M5b) and by nothing here. A future read-promotion added
42
+ * there would silently defeat the shared eviction and no structural gate could see it.
43
+ *
44
+ * WHY IT IMPORTS NOTHING, AND WHY THAT IS ASSERTED IN A TEST RATHER THAN BY CI.
45
+ * `spec.md` RFS-02 AC-3 originally placed this in `kernel/`, where `check-core-layering`'s
46
+ * leaf-ness clause would have enforced import-freedom. C30 (`design.md` §5.2) moved it to
47
+ * `services/cache/` because `kernel/`'s admission rule is "serves >= 2 tiers" and 11 of 11
48
+ * shipped members keep it, while this module serves `services/` alone after Phase 3. Nothing
49
+ * in CI now prevents this file from growing a dependency. That is a real loss of enforcement
50
+ * (R-29), recorded rather than glossed, and the replacement is the AST assertion in this
51
+ * module's own unit test.
52
+ */
53
+ /**
54
+ * Evict oldest-first from `cache` until `cache.size <= maxRetained`.
55
+ *
56
+ * @param cache any `Map`; eviction order is the Map's own insertion order, so a caller
57
+ * that promotes on read (delete + re-set) gets LRU and one that does not
58
+ * gets FIFO. That choice stays with the caller.
59
+ * @param maxRetained the number of entries that may remain when this returns. Pre-insert
60
+ * callers pass `CAP - 1`; post-insert callers pass `CAP`.
61
+ */
62
+ export declare function evictOldest<K, V>(cache: Map<K, V>, maxRetained: number): void;
63
+ //# sourceMappingURL=lru-evict.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lru-evict.d.ts","sourceRoot":"","sources":["../../../src/services/cache/lru-evict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH;;;;;;;;GAQG;AACH,wBAAgB,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,CAY7E"}
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared insertion-order eviction for the LRU-bounded `Map`s PR-D unifies (RFS-02 AC-2).
3
+ *
4
+ * WHY A FUNCTION AND NOT A CACHE CLASS.
5
+ * The five caches this serves agree on eviction ORDER and disagree on everything else: cap
6
+ * (512 at four sites, 50 at `file-filter-cache`), TTL (enforced at `file-content-cache`'s
7
+ * `fileCache` only) and read-promotion (absent at `file-filter-cache`). A shared cache class would impose
8
+ * one policy on all five, and that IS the behavior change `spec.md` §3.B measured the eviction
9
+ * itself is not. RFS-02 AC-2 therefore fixes this as an eviction function; every site keeps its
10
+ * own cap, its own TTL and its own promotion policy.
11
+ *
12
+ * WHY THE SECOND PARAMETER IS A POST-CALL BOUND AND NOT "the cap".
13
+ * `design.md` §5.1 specifies only "a function taking `(cache, cap)`", and that phrasing does
14
+ * not determine the predicate, because the five sites do not share one:
15
+ *
16
+ * file-content-cache.ts (T10) pre-insert while (size >= 512)
17
+ * project-root-cache.ts (T9) pre-insert while (size >= 512)
18
+ * symbol-graph.service.ts pre-insert while (size >= 512)
19
+ * web-controller.ts post-insert while (size > 512)
20
+ * file-filter-cache.ts post-insert if (size > 50) evict-one
21
+ *
22
+ * `spec.md` §3.B establishes that pre-insert `>=` and post-insert `>` retain the same number.
23
+ * They do — but only while each site keeps BOTH its operator and its call position. Collapsing
24
+ * them onto one operator with every site passing its own literal cap moves the retained count,
25
+ * and it moves in opposite directions depending on which operator wins. Measured against T1's
26
+ * characterization oracle (`tasks.md` §10.7): a shared `>` breaks the three pre-insert sites, a
27
+ * shared `>=` breaks the two post-insert ones.
28
+ *
29
+ * Stating the contract as a POST-CALL BOUND is what lets one function serve both call
30
+ * positions without moving any retained count, because `size > cap - 1` and `size >= cap` are
31
+ * the same predicate over integers:
32
+ *
33
+ * pre-insert caller passes `CAP - 1` — reserves the slot the pending insert will take
34
+ * post-insert caller passes `CAP` — the insert already happened
35
+ *
36
+ * WHAT THIS MODULE DOES NOT CERTIFY, on RFS-01 AC-6's precedent of naming blind spots.
37
+ * It evicts in `Map` insertion order and knows nothing about TTL, read-promotion, or whether a
38
+ * caller's insertion order matches that caller's own notion of "oldest". `file-filter-cache`
39
+ * evicts by `min(createdAt)` today and coincides with insertion order only because it never
40
+ * re-inserts an entry on a read — an invariant unstated in that file, guarded by T1's
41
+ * characterization suite (mutation M5b) and by nothing here. A future read-promotion added
42
+ * there would silently defeat the shared eviction and no structural gate could see it.
43
+ *
44
+ * WHY IT IMPORTS NOTHING, AND WHY THAT IS ASSERTED IN A TEST RATHER THAN BY CI.
45
+ * `spec.md` RFS-02 AC-3 originally placed this in `kernel/`, where `check-core-layering`'s
46
+ * leaf-ness clause would have enforced import-freedom. C30 (`design.md` §5.2) moved it to
47
+ * `services/cache/` because `kernel/`'s admission rule is "serves >= 2 tiers" and 11 of 11
48
+ * shipped members keep it, while this module serves `services/` alone after Phase 3. Nothing
49
+ * in CI now prevents this file from growing a dependency. That is a real loss of enforcement
50
+ * (R-29), recorded rather than glossed, and the replacement is the AST assertion in this
51
+ * module's own unit test.
52
+ */
53
+ /**
54
+ * Evict oldest-first from `cache` until `cache.size <= maxRetained`.
55
+ *
56
+ * @param cache any `Map`; eviction order is the Map's own insertion order, so a caller
57
+ * that promotes on read (delete + re-set) gets LRU and one that does not
58
+ * gets FIFO. That choice stays with the caller.
59
+ * @param maxRetained the number of entries that may remain when this returns. Pre-insert
60
+ * callers pass `CAP - 1`; post-insert callers pass `CAP`.
61
+ */
62
+ export function evictOldest(cache, maxRetained) {
63
+ while (cache.size > maxRetained) {
64
+ const oldest = cache.keys().next().value;
65
+ // Defensive and unreachable for any `maxRetained >= 0`: the loop condition forces
66
+ // `cache.size >= 1`, so `keys().next()` always yields an entry. Kept because all four
67
+ // original sites carry it, and dropping it inside a behavior-preserving PR would be an
68
+ // unprovable change. It is NOT here for the 90% line-coverage floor — that gate parses
69
+ // `DA:` records only (`scripts/check-coverage.ts:320`), never `BRDA:`, so this line counts
70
+ // as covered the moment it is evaluated, whichever way the branch goes.
71
+ if (oldest === undefined)
72
+ break;
73
+ cache.delete(oldest);
74
+ }
75
+ }
76
+ //# sourceMappingURL=lru-evict.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lru-evict.js","sourceRoot":"","sources":["../../../src/services/cache/lru-evict.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AAEH;;;;;;;;GAQG;AACH,MAAM,UAAU,WAAW,CAAO,KAAgB,EAAE,WAAmB;IACrE,OAAO,KAAK,CAAC,IAAI,GAAG,WAAW,EAAE,CAAC;QAChC,MAAM,MAAM,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC;QACzC,kFAAkF;QAClF,sFAAsF;QACtF,uFAAuF;QACvF,uFAAuF;QACvF,2FAA2F;QAC3F,wEAAwE;QACxE,IAAI,MAAM,KAAK,SAAS;YAAE,MAAM;QAChC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;IACvB,CAAC;AACH,CAAC"}
@@ -0,0 +1,41 @@
1
+ import type { FileMetadata } from "./file-metadata.js";
2
+ export interface CachedFile {
3
+ content: string;
4
+ timestamp: number;
5
+ metadata?: FileMetadata;
6
+ }
7
+ /**
8
+ * The 4 → 5 edge. Module 7 (`read-file.service.ts`) binds this with the
9
+ * `symbolGraph` forwarded to it from `ReadFileTool`'s constructor parameter.
10
+ * Bound there since T12; `ReadFileTool` itself bound it from T10 until then.
11
+ */
12
+ export type MetadataExtractor = (content: string, filePath: string, options: {
13
+ includeSymbols: boolean;
14
+ includeImports: boolean;
15
+ projectId?: string;
16
+ relativePath?: string;
17
+ }) => Promise<FileMetadata>;
18
+ export declare class FileContentCache {
19
+ private readonly extractMetadata;
20
+ private fileCache;
21
+ private readonly CACHE_TTL;
22
+ /**
23
+ * Maximum entries retained in the cache. Without a cap, an adversarial caller
24
+ * cycling distinct cache keys grows the map for the process lifetime. Map
25
+ * preserves INSERTION order in JS; we promote a key to most-recently-used on
26
+ * GET via delete+set, and evict the oldest key on SET while over the cap.
27
+ * Mirrors WebController's WEB_CACHE_MAX_ENTRIES.
28
+ */
29
+ private readonly FILE_CACHE_MAX_ENTRIES;
30
+ constructor(extractMetadata: MetadataExtractor);
31
+ readFileWithCache(filePath: string, options: {
32
+ includeSymbols: boolean;
33
+ includeImports: boolean;
34
+ projectId?: string;
35
+ relativePath?: string;
36
+ }): Promise<{
37
+ content: string;
38
+ metadata: FileMetadata;
39
+ }>;
40
+ }
41
+ //# sourceMappingURL=file-content-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-content-cache.d.ts","sourceRoot":"","sources":["../../../src/services/file-read/file-content-cache.ts"],"names":[],"mappings":"AA2CA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAEvD,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,YAAY,CAAC;CACzB;AAED;;;;GAIG;AACH,MAAM,MAAM,iBAAiB,GAAG,CAC9B,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;IACP,cAAc,EAAE,OAAO,CAAC;IACxB,cAAc,EAAE,OAAO,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,KACE,OAAO,CAAC,YAAY,CAAC,CAAC;AAE3B,qBAAa,gBAAgB;IAYf,OAAO,CAAC,QAAQ,CAAC,eAAe;IAX5C,OAAO,CAAC,SAAS,CAAsC;IACvD,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAS;IACnC;;;;;;OAMG;IACH,OAAO,CAAC,QAAQ,CAAC,sBAAsB,CAAO;gBAEjB,eAAe,EAAE,iBAAiB;IAEzD,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,EAAE,OAAO,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,GACA,OAAO,CAAC;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,YAAY,CAAA;KAAE,CAAC;CAuDxD"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * @massa-ai/core - File content cache
3
+ *
4
+ * Module 4 of the `tools/read_file.ts` extraction (PR-D, T10). Owns the
5
+ * TTL-and-LRU-bounded file CONTENT cache `ReadFileTool` used to carry as
6
+ * private state: `readFileWithCache`, the `fileCache` Map, `CACHE_TTL`,
7
+ * `FILE_CACHE_MAX_ENTRIES` and the `CachedFile` shape.
8
+ *
9
+ * `readFileWithCache`'s BODY IS MOVED VERBATIM apart from one line. The private
10
+ * `ReadFileTool.evictOldest` wrapper it used to call no longer exists — its body
11
+ * read `this.FILE_CACHE_MAX_ENTRIES`, which this module takes, so the wrapper
12
+ * could not survive this task even as dead code — and the call is now the shared
13
+ * operator directly, on module 3's exact shape:
14
+ * `evictOldest(map, CAP - 1)`. `lru-evict.ts`'s second parameter is a POST-CALL
15
+ * BOUND rather than the cap, so a pre-insert caller passes `CAP - 1` to reserve
16
+ * the slot the pending `set()` takes; `size > CAP - 1` and `size >= CAP` are the
17
+ * same predicate over the integers, so the retained count at this call position
18
+ * is unchanged (C44).
19
+ *
20
+ * IT NEVER NAMES SymbolGraphService. Metadata extraction arrives as a
21
+ * constructor callback (`tasks.md` §4.1, decided there with its rejected
22
+ * option), and the parameter is deliberately named `extractMetadata` so the
23
+ * moved body's two call sites — the legacy-entry repair path and the cache
24
+ * miss — read exactly as they did before the move. The type-only import of
25
+ * `FileMetadata` from module 5 is unavoidable and is not what §4.1 forbids:
26
+ * `SymbolGraphService` is the database-backed collaborator the callback exists
27
+ * to keep out of this file.
28
+ *
29
+ * INSTANTIATED PER ReadFileTool, on module 3's precedent — each tool owned its
30
+ * own `fileCache` Map before the move, so a module singleton would leak cached
31
+ * file content between independently-constructed tools. That is a behavior
32
+ * change, and this extraction is behavior-preserving by contract (RFS-02 AC-1).
33
+ *
34
+ * CACHE_TTL IS ENFORCED HERE and that is the half of RFS-02 AC-4 that holds.
35
+ * `__tests__/read-file-project-root-rename-pin.test.ts` measured it: past 60 s
36
+ * the content re-reads. Its sibling `ROOT_CACHE_TTL` in
37
+ * `project-root-cache.ts` is declared and read nowhere, so that cache is
38
+ * LRU-bounded only. The two constants were never related and are now not even
39
+ * in the same file.
40
+ */
41
+ import { logger } from "@massa-ai/shared";
42
+ import fs from "fs/promises";
43
+ import { evictOldest } from "../cache/lru-evict.js";
44
+ export class FileContentCache {
45
+ extractMetadata;
46
+ fileCache = new Map();
47
+ CACHE_TTL = 60000; // 1 minute
48
+ /**
49
+ * Maximum entries retained in the cache. Without a cap, an adversarial caller
50
+ * cycling distinct cache keys grows the map for the process lifetime. Map
51
+ * preserves INSERTION order in JS; we promote a key to most-recently-used on
52
+ * GET via delete+set, and evict the oldest key on SET while over the cap.
53
+ * Mirrors WebController's WEB_CACHE_MAX_ENTRIES.
54
+ */
55
+ FILE_CACHE_MAX_ENTRIES = 512;
56
+ constructor(extractMetadata) {
57
+ this.extractMetadata = extractMetadata;
58
+ }
59
+ async readFileWithCache(filePath, options) {
60
+ // Cache key MUST include every option that changes metadata extraction.
61
+ // Keying on filePath alone returned stale, options-baked metadata for a
62
+ // later read of the same file with different includeSymbols/includeImports/
63
+ // projectId/relativePath (the only e2e red — 08.search F33). Deliberately
64
+ // exclude offset/limit/lineStart/lineEnd/compress/targetRatio/format: those
65
+ // are applied AFTER the cache in handle(), and coalescing them across reads
66
+ // is the cache's purpose.
67
+ const cacheKey = JSON.stringify({
68
+ filePath,
69
+ includeSymbols: options.includeSymbols ?? false,
70
+ includeImports: options.includeImports ?? false,
71
+ projectId: options.projectId ?? null,
72
+ relativePath: options.relativePath ?? null,
73
+ });
74
+ const cached = this.fileCache.get(cacheKey);
75
+ // Check cache validity
76
+ if (cached && Date.now() - cached.timestamp < this.CACHE_TTL) {
77
+ logger.debug("File cache hit", { filePath });
78
+ // LRU touch: promote this key to most-recently-used so hot files survive
79
+ // eviction. delete+set reorders the key to the end (newest).
80
+ this.fileCache.delete(cacheKey);
81
+ this.fileCache.set(cacheKey, cached);
82
+ // Legacy/edge entries may have undefined metadata. Re-extract once and
83
+ // WRITE IT BACK into the cache entry so subsequent hits are served from
84
+ // cache instead of re-extracting on every request.
85
+ if (!cached.metadata) {
86
+ const metadata = await this.extractMetadata(cached.content, filePath, options);
87
+ this.fileCache.set(cacheKey, { ...cached, metadata });
88
+ return { content: cached.content, metadata };
89
+ }
90
+ return {
91
+ content: cached.content,
92
+ metadata: cached.metadata,
93
+ };
94
+ }
95
+ // Read file
96
+ const content = await fs.readFile(filePath, "utf-8");
97
+ const metadata = await this.extractMetadata(content, filePath, options);
98
+ // Update cache (evict oldest if at cap).
99
+ evictOldest(this.fileCache, this.FILE_CACHE_MAX_ENTRIES - 1);
100
+ this.fileCache.set(cacheKey, {
101
+ content,
102
+ timestamp: Date.now(),
103
+ metadata,
104
+ });
105
+ logger.debug("File read and cached", { filePath });
106
+ return { content, metadata };
107
+ }
108
+ }
109
+ //# sourceMappingURL=file-content-cache.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-content-cache.js","sourceRoot":"","sources":["../../../src/services/file-read/file-content-cache.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { SymbolGraphService } from "../symbol/symbol-graph.service.js";
2
+ export interface FileMetadata {
3
+ totalLines: number;
4
+ language?: string;
5
+ symbols?: {
6
+ definitions: number;
7
+ references: number;
8
+ };
9
+ imports?: string[];
10
+ }
11
+ export declare class FileMetadataExtractor {
12
+ private readonly symbolGraph?;
13
+ constructor(symbolGraph?: SymbolGraphService | undefined);
14
+ extractMetadata(content: string, filePath: string, options: {
15
+ includeSymbols: boolean;
16
+ includeImports: boolean;
17
+ projectId?: string;
18
+ relativePath?: string;
19
+ }): Promise<FileMetadata>;
20
+ private detectLanguage;
21
+ private extractImports;
22
+ }
23
+ //# sourceMappingURL=file-metadata.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-metadata.d.ts","sourceRoot":"","sources":["../../../src/services/file-read/file-metadata.ts"],"names":[],"mappings":"AA+BA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,mCAAmC,CAAC;AAE5E,MAAM,WAAW,YAAY;IAC3B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE;QACR,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,MAAM,CAAC;KACpB,CAAC;IACF,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;CACpB;AAED,qBAAa,qBAAqB;IACpB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAC;gBAAZ,WAAW,CAAC,EAAE,kBAAkB,YAAA;IAEvD,eAAe,CACnB,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE;QACP,cAAc,EAAE,OAAO,CAAC;QACxB,cAAc,EAAE,OAAO,CAAC;QACxB,SAAS,CAAC,EAAE,MAAM,CAAC;QACnB,YAAY,CAAC,EAAE,MAAM,CAAC;KACvB,GACA,OAAO,CAAC,YAAY,CAAC;IAuCxB,OAAO,CAAC,cAAc;IAsCtB,OAAO,CAAC,cAAc;CAwBvB"}
@@ -0,0 +1,127 @@
1
+ /**
2
+ * @massa-ai/core - File metadata extraction
3
+ *
4
+ * Module 5 of the `tools/read_file.ts` extraction (PR-D, T10). Owns the three
5
+ * private methods `ReadFileTool` used to carry for describing a file's content:
6
+ * `extractMetadata` (the composer), `detectLanguage` (extension → language) and
7
+ * `extractImports` (per-language import scan), together with the `FileMetadata`
8
+ * shape they produce.
9
+ *
10
+ * ALL THREE BODIES ARE MOVED VERBATIM, and so is `interface FileMetadata`.
11
+ *
12
+ * IT NAMES SymbolGraphService AND MODULE 4 DOES NOT, WHICH IS THE WHOLE POINT
13
+ * OF THE 4 → 5 EDGE. `design.md` §5.1 records that `readFileWithCache` calls
14
+ * `extractMetadata` on two of three paths and that `extractMetadata` reaches
15
+ * `symbolGraph.listDefinitions`, so a direct edge would make the file CONTENT
16
+ * cache thread a database-backed collaborator through purely to satisfy this
17
+ * module. `tasks.md` §4.1 decided the mechanism — module 4 takes a
18
+ * `(content, filePath, options) => Promise<FileMetadata>` callback — and
19
+ * rejected inverting the call order on measurement rather than on taste: module
20
+ * 7 pre-computing metadata is eager on all three paths, so every cache HIT
21
+ * would newly run a `listDefinitions` query. That is a behavior change inside a
22
+ * behavior-preserving PR, and the callback keeps the laziness and the
23
+ * legacy-entry write-back exactly where they were.
24
+ *
25
+ * INSTANTIATED PER ReadFileTool, on module 3's precedent. `symbolGraph` is the
26
+ * tool's own optional constructor parameter, forwarded unchanged; a module
27
+ * singleton would have to pick one graph for every tool ever constructed, and
28
+ * the parameter is public surface (`design.md` §3.2, 41 construction sites).
29
+ */
30
+ import { logger } from "@massa-ai/shared";
31
+ import path from "path";
32
+ export class FileMetadataExtractor {
33
+ symbolGraph;
34
+ constructor(symbolGraph) {
35
+ this.symbolGraph = symbolGraph;
36
+ }
37
+ async extractMetadata(content, filePath, options) {
38
+ const lines = content.split("\n");
39
+ const language = this.detectLanguage(filePath);
40
+ const metadata = {
41
+ totalLines: lines.length,
42
+ language,
43
+ };
44
+ // Extract imports if requested
45
+ if (options.includeImports && language) {
46
+ metadata.imports = this.extractImports(lines, language);
47
+ }
48
+ // Get symbol metadata if symbol graph available
49
+ if (options.includeSymbols && this.symbolGraph && options.projectId) {
50
+ try {
51
+ // Symbol DB stores relative paths — use original relative path for queries
52
+ const queryPath = options.relativePath || filePath;
53
+ const { definitions } = await this.symbolGraph.listDefinitions(options.projectId, {
54
+ file: queryPath,
55
+ limit: 100,
56
+ });
57
+ metadata.symbols = {
58
+ definitions: definitions.length,
59
+ references: 0, // Would need separate query
60
+ };
61
+ }
62
+ catch (error) {
63
+ logger.debug("Failed to get symbol metadata", { filePath, error });
64
+ }
65
+ }
66
+ return metadata;
67
+ }
68
+ detectLanguage(filePath) {
69
+ const ext = path.extname(filePath).toLowerCase();
70
+ const languageMap = {
71
+ ".ts": "TypeScript",
72
+ ".tsx": "TypeScript",
73
+ ".js": "JavaScript",
74
+ ".jsx": "JavaScript",
75
+ ".vue": "Vue",
76
+ ".py": "Python",
77
+ ".go": "Go",
78
+ ".rs": "Rust",
79
+ ".java": "Java",
80
+ ".cpp": "C++",
81
+ ".c": "C",
82
+ ".h": "C",
83
+ ".hpp": "C++",
84
+ ".cs": "C#",
85
+ ".rb": "Ruby",
86
+ ".php": "PHP",
87
+ ".swift": "Swift",
88
+ ".kt": "Kotlin",
89
+ ".kts": "Kotlin",
90
+ ".scala": "Scala",
91
+ ".md": "Markdown",
92
+ ".json": "JSON",
93
+ ".yaml": "YAML",
94
+ ".yml": "YAML",
95
+ ".xml": "XML",
96
+ ".html": "HTML",
97
+ ".css": "CSS",
98
+ ".scss": "SCSS",
99
+ ".sql": "SQL",
100
+ ".sh": "Shell",
101
+ ".bash": "Shell",
102
+ };
103
+ return languageMap[ext];
104
+ }
105
+ extractImports(lines, language) {
106
+ const imports = [];
107
+ const importPatterns = {
108
+ TypeScript: /^(import\s+.*?from\s+['"]|import\s+['"])/,
109
+ JavaScript: /^(import\s+.*?from\s+['"]|import\s+['"]|require\s*\(\s*['"])/,
110
+ Python: /^(import\s+|from\s+\S+\s+import)/,
111
+ Go: /^import\s+/,
112
+ Java: /^import\s+/,
113
+ Rust: /^use\s+/,
114
+ };
115
+ const pattern = importPatterns[language];
116
+ if (!pattern)
117
+ return imports;
118
+ for (const line of lines) {
119
+ const trimmed = line.trim();
120
+ if (pattern.test(trimmed)) {
121
+ imports.push(trimmed);
122
+ }
123
+ }
124
+ return imports;
125
+ }
126
+ }
127
+ //# sourceMappingURL=file-metadata.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-metadata.js","sourceRoot":"","sources":["../../../src/services/file-read/file-metadata.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,91 @@
1
+ /**
2
+ * @massa-ai/core - Line range selection
3
+ *
4
+ * Module 6 of the `tools/read_file.ts` extraction (PR-D, T11). Owns everything
5
+ * that turns a request into the slice of a file the caller actually receives:
6
+ * `calculateRange` (request → range), `adjustRange` (range → range clamped to
7
+ * the file), `extractLines` (range → line-numbered text), the `ReadRange` shape
8
+ * they pass around, and the N9 output cap — the `MASSA_AI_READ_FILE_MAX_LINES`
9
+ * ceiling together with the clipping block that used to sit inside `handle()`.
10
+ *
11
+ * FREE FUNCTIONS, NOT A CLASS, and that is a decision rather than a default.
12
+ * Modules 2-5 are classes because each owns per-tool state — a Map, a
13
+ * subscription, an injected collaborator. This module owns none: every function
14
+ * here is pure, and the one piece of state is a process-wide constant read from
15
+ * the environment. `services/cache/lru-evict.ts` (module 1) is a bare function
16
+ * for exactly this reason (`design.md` §5.2). A class would also have to be
17
+ * constructed in `ReadFileTool`'s constructor, which is the one surface C64 is
18
+ * about, and would falsely imply per-instance state.
19
+ *
20
+ * IT IMPORTS NOTHING AT ALL, which is `lru-evict.ts`'s property and is asserted
21
+ * by this module's own unit test rather than by any tier rule.
22
+ *
23
+ * `LineRangeRequest` EXISTS BECAUSE `ReadFileParams` IS MODULE 7's. `design.md`
24
+ * §5.1 assigns `interface ReadFileParams` to `read-file.service.ts`, which T12
25
+ * built and which COMPOSES this module — so importing it here would be a 6 → 7
26
+ * edge against the composition direction. It exists now and the rule is
27
+ * unchanged. `calculateRange` reads exactly four fields off its argument, so
28
+ * this module declares those four and `ReadFileParams` satisfies it
29
+ * structurally. The alternative Design's module table hides is that module 6
30
+ * silently depends on module 7's type; naming the subset is what keeps 6
31
+ * standalone. Same shape as the 4 → 5 edge §4.1 decided, which Design handed to
32
+ * Tasks explicitly — this one it handed to nobody.
33
+ *
34
+ * THE CLIPPED PATH RETURNS UNNUMBERED TEXT, AND THAT IS PRESERVED DELIBERATELY.
35
+ * `extractLines` prefixes every line with a padded line number; the cap does NOT
36
+ * slice that string — it re-slices the RAW `lines` array and joins it, so a
37
+ * clipped response loses the prefixes an unclipped one carries. Found by the
38
+ * Plan Challenge gate's red-team lens before this file was written, and
39
+ * confirmed by the evidence audit: no test anywhere asserts content on the
40
+ * clipped path, so composing this as "extract, then slice the extracted text"
41
+ * would have been a silent behavior change inside a behavior-preserving PR.
42
+ * `__tests__/line-range.test.ts` pins both branches' exact format. Whether the
43
+ * asymmetry is intentional is not PR-D's question — it is logged, not fixed
44
+ * (`spec.md` §6, R-07's precedent).
45
+ */
46
+ /** An inclusive, 1-indexed line range. `end` may be `Infinity` before adjustment. */
47
+ export interface ReadRange {
48
+ start: number;
49
+ end: number;
50
+ }
51
+ /**
52
+ * The four fields `calculateRange` reads. `ReadFileParams` (module 7, T12)
53
+ * satisfies this structurally; declaring the subset is what stops module 6
54
+ * depending on the module that composes it.
55
+ */
56
+ export interface LineRangeRequest {
57
+ offset?: number;
58
+ limit?: number;
59
+ lineStart?: number;
60
+ lineEnd?: number;
61
+ }
62
+ /** The result of selecting lines, after the N9 cap has been applied. */
63
+ export interface LineSelection {
64
+ /** The text served to the caller. Line-numbered unless `clipped`. */
65
+ content: string;
66
+ /** `content.split("\n").length` — recomputed after any clipping. */
67
+ lineCount: number;
68
+ /** True when the N9 cap trimmed the selection. Surfaces as `source_clipped`. */
69
+ clipped: boolean;
70
+ }
71
+ export declare function calculateRange(params: LineRangeRequest): ReadRange;
72
+ export declare function adjustRange(range: ReadRange, totalLines: number): ReadRange;
73
+ export declare function extractLines(lines: string[], range: ReadRange): string;
74
+ /**
75
+ * Extract the requested lines and apply the N9 cap, returning the clipped flag
76
+ * rather than mutating the caller's locals.
77
+ *
78
+ * The two steps ship as one function because they were never separable at the
79
+ * call site: before this task the cap read and reassigned the very
80
+ * `selectedContent` and `selectedLineCount` that the `extractLines` call one
81
+ * line above it had produced, inside `handle()`. Deliberately unnumbered — a
82
+ * line citation into `read_file.ts` written here would name a revision that no
83
+ * longer exists the moment this commit lands, and T12 moves that whole region
84
+ * again (T8b's de-numbering precedent). Splitting them would mean passing
85
+ * the already-extracted string and its line count back in as parameters, which
86
+ * duplicates the `.split("\n")` at the call site and adds an argument derivable
87
+ * from another. `extractLines` stays exported so its own contract is pinnable
88
+ * independently.
89
+ */
90
+ export declare function selectLines(lines: string[], range: ReadRange): LineSelection;
91
+ //# sourceMappingURL=line-range.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"line-range.d.ts","sourceRoot":"","sources":["../../../src/services/file-read/line-range.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4CG;AA2BH,qFAAqF;AACrF,MAAM,WAAW,SAAS;IACxB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED;;;;GAIG;AACH,MAAM,WAAW,gBAAgB;IAC/B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,wEAAwE;AACxE,MAAM,WAAW,aAAa;IAC5B,qEAAqE;IACrE,OAAO,EAAE,MAAM,CAAC;IAChB,oEAAoE;IACpE,SAAS,EAAE,MAAM,CAAC;IAClB,gFAAgF;IAChF,OAAO,EAAE,OAAO,CAAC;CAClB;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,gBAAgB,GAAG,SAAS,CAsBlE;AAED,wBAAgB,WAAW,CAAC,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,GAAG,SAAS,CAK3E;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,GAAG,MAAM,CAatE;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,SAAS,GAAG,aAAa,CA0B5E"}