@balpal4495/quorum 3.0.3 → 3.1.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 (243) hide show
  1. package/bin/commands/compass.js +4 -4
  2. package/bin/shared/llm.js +2 -2
  3. package/dist/advisor/ask.d.ts +13 -0
  4. package/dist/advisor/ask.d.ts.map +1 -0
  5. package/dist/advisor/ask.js +67 -0
  6. package/dist/advisor/ask.js.map +1 -0
  7. package/dist/advisor/index.d.ts +3 -0
  8. package/dist/advisor/index.d.ts.map +1 -0
  9. package/dist/advisor/index.js +2 -0
  10. package/dist/advisor/index.js.map +1 -0
  11. package/dist/advisor/prompt.d.ts +5 -0
  12. package/dist/advisor/prompt.d.ts.map +1 -0
  13. package/{modules/advisor/prompt.ts → dist/advisor/prompt.js} +22 -26
  14. package/dist/advisor/prompt.js.map +1 -0
  15. package/dist/advisor/types.d.ts +23 -0
  16. package/dist/advisor/types.d.ts.map +1 -0
  17. package/dist/advisor/types.js +2 -0
  18. package/dist/advisor/types.js.map +1 -0
  19. package/dist/compass/behavior.d.ts +4 -0
  20. package/dist/compass/behavior.d.ts.map +1 -0
  21. package/dist/compass/behavior.js +138 -0
  22. package/dist/compass/behavior.js.map +1 -0
  23. package/dist/compass/create.d.ts +3 -0
  24. package/dist/compass/create.d.ts.map +1 -0
  25. package/dist/compass/create.js +289 -0
  26. package/dist/compass/create.js.map +1 -0
  27. package/dist/compass/evidence/collect.d.ts +11 -0
  28. package/dist/compass/evidence/collect.d.ts.map +1 -0
  29. package/dist/compass/evidence/collect.js +86 -0
  30. package/dist/compass/evidence/collect.js.map +1 -0
  31. package/dist/compass/index.d.ts +8 -0
  32. package/dist/compass/index.d.ts.map +1 -0
  33. package/dist/compass/index.js +8 -0
  34. package/dist/compass/index.js.map +1 -0
  35. package/dist/compass/prompts/index.d.ts +28 -0
  36. package/dist/compass/prompts/index.d.ts.map +1 -0
  37. package/{modules/compass/prompts/index.ts → dist/compass/prompts/index.js} +13 -38
  38. package/dist/compass/prompts/index.js.map +1 -0
  39. package/dist/compass/prompts/system.d.ts +2 -0
  40. package/dist/compass/prompts/system.d.ts.map +1 -0
  41. package/{modules/compass/prompts/system.ts → dist/compass/prompts/system.js} +2 -1
  42. package/dist/compass/prompts/system.js.map +1 -0
  43. package/dist/compass/propose.d.ts +15 -0
  44. package/dist/compass/propose.d.ts.map +1 -0
  45. package/dist/compass/propose.js +128 -0
  46. package/dist/compass/propose.js.map +1 -0
  47. package/dist/compass/schemas.d.ts +1271 -0
  48. package/dist/compass/schemas.d.ts.map +1 -0
  49. package/dist/compass/schemas.js +113 -0
  50. package/dist/compass/schemas.js.map +1 -0
  51. package/dist/compass/score.d.ts +25 -0
  52. package/dist/compass/score.d.ts.map +1 -0
  53. package/dist/compass/score.js +89 -0
  54. package/dist/compass/score.js.map +1 -0
  55. package/dist/compass/sources/index.d.ts +9 -0
  56. package/dist/compass/sources/index.d.ts.map +1 -0
  57. package/dist/compass/sources/index.js +408 -0
  58. package/dist/compass/sources/index.js.map +1 -0
  59. package/dist/compass/types.d.ts +334 -0
  60. package/dist/compass/types.d.ts.map +1 -0
  61. package/dist/compass/types.js +2 -0
  62. package/dist/compass/types.js.map +1 -0
  63. package/dist/council/advisors.d.ts +15 -0
  64. package/dist/council/advisors.d.ts.map +1 -0
  65. package/dist/council/advisors.js +46 -0
  66. package/dist/council/advisors.js.map +1 -0
  67. package/dist/council/chairman.d.ts +13 -0
  68. package/dist/council/chairman.d.ts.map +1 -0
  69. package/dist/council/chairman.js +145 -0
  70. package/dist/council/chairman.js.map +1 -0
  71. package/dist/council/deliberate.d.ts +22 -0
  72. package/dist/council/deliberate.d.ts.map +1 -0
  73. package/dist/council/deliberate.js +99 -0
  74. package/dist/council/deliberate.js.map +1 -0
  75. package/dist/council/frame.d.ts +8 -0
  76. package/dist/council/frame.d.ts.map +1 -0
  77. package/dist/council/frame.js +40 -0
  78. package/dist/council/frame.js.map +1 -0
  79. package/dist/council/index.d.ts +6 -0
  80. package/dist/council/index.d.ts.map +1 -0
  81. package/dist/council/index.js +4 -0
  82. package/dist/council/index.js.map +1 -0
  83. package/dist/council/personas.d.ts +18 -0
  84. package/dist/council/personas.d.ts.map +1 -0
  85. package/dist/council/personas.js +44 -0
  86. package/dist/council/personas.js.map +1 -0
  87. package/dist/council/reviewers.d.ts +13 -0
  88. package/dist/council/reviewers.d.ts.map +1 -0
  89. package/dist/council/reviewers.js +59 -0
  90. package/dist/council/reviewers.js.map +1 -0
  91. package/dist/council/risk.d.ts +16 -0
  92. package/dist/council/risk.d.ts.map +1 -0
  93. package/dist/council/risk.js +74 -0
  94. package/dist/council/risk.js.map +1 -0
  95. package/dist/council/types.d.ts +95 -0
  96. package/dist/council/types.d.ts.map +1 -0
  97. package/dist/council/types.js +2 -0
  98. package/dist/council/types.js.map +1 -0
  99. package/dist/jury/evaluate.d.ts +13 -0
  100. package/dist/jury/evaluate.d.ts.map +1 -0
  101. package/{modules/jury/evaluate.ts → dist/jury/evaluate.js} +60 -84
  102. package/dist/jury/evaluate.js.map +1 -0
  103. package/dist/jury/index.d.ts +6 -0
  104. package/dist/jury/index.d.ts.map +1 -0
  105. package/dist/jury/index.js +4 -0
  106. package/dist/jury/index.js.map +1 -0
  107. package/dist/jury/preflight.d.ts +26 -0
  108. package/dist/jury/preflight.d.ts.map +1 -0
  109. package/dist/jury/preflight.js +71 -0
  110. package/dist/jury/preflight.js.map +1 -0
  111. package/dist/jury/schema.d.ts +57 -0
  112. package/dist/jury/schema.d.ts.map +1 -0
  113. package/dist/jury/schema.js +21 -0
  114. package/dist/jury/schema.js.map +1 -0
  115. package/dist/jury/types.d.ts +47 -0
  116. package/dist/jury/types.d.ts.map +1 -0
  117. package/dist/jury/types.js +2 -0
  118. package/dist/jury/types.js.map +1 -0
  119. package/dist/oracle/adapters/lance-db.d.ts +15 -0
  120. package/dist/oracle/adapters/lance-db.d.ts.map +1 -0
  121. package/dist/oracle/adapters/lance-db.js +68 -0
  122. package/dist/oracle/adapters/lance-db.js.map +1 -0
  123. package/dist/oracle/adapters/xenova-embedder.d.ts +21 -0
  124. package/dist/oracle/adapters/xenova-embedder.d.ts.map +1 -0
  125. package/dist/oracle/adapters/xenova-embedder.js +36 -0
  126. package/dist/oracle/adapters/xenova-embedder.js.map +1 -0
  127. package/dist/oracle/bm25.d.ts +20 -0
  128. package/dist/oracle/bm25.d.ts.map +1 -0
  129. package/dist/oracle/bm25.js +82 -0
  130. package/dist/oracle/bm25.js.map +1 -0
  131. package/dist/oracle/index.d.ts +21 -0
  132. package/dist/oracle/index.d.ts.map +1 -0
  133. package/dist/oracle/index.js +25 -0
  134. package/dist/oracle/index.js.map +1 -0
  135. package/dist/oracle/log.d.ts +6 -0
  136. package/dist/oracle/log.d.ts.map +1 -0
  137. package/dist/oracle/log.js +12 -0
  138. package/dist/oracle/log.js.map +1 -0
  139. package/dist/oracle/propose.d.ts +25 -0
  140. package/dist/oracle/propose.d.ts.map +1 -0
  141. package/dist/oracle/propose.js +133 -0
  142. package/dist/oracle/propose.js.map +1 -0
  143. package/dist/oracle/query.d.ts +17 -0
  144. package/dist/oracle/query.d.ts.map +1 -0
  145. package/dist/oracle/query.js +106 -0
  146. package/dist/oracle/query.js.map +1 -0
  147. package/dist/oracle/summary.d.ts +11 -0
  148. package/dist/oracle/summary.d.ts.map +1 -0
  149. package/dist/oracle/summary.js +102 -0
  150. package/dist/oracle/summary.js.map +1 -0
  151. package/dist/oracle/types.d.ts +31 -0
  152. package/dist/oracle/types.d.ts.map +1 -0
  153. package/dist/oracle/types.js +2 -0
  154. package/dist/oracle/types.js.map +1 -0
  155. package/dist/sentinel/assert.d.ts +28 -0
  156. package/dist/sentinel/assert.d.ts.map +1 -0
  157. package/dist/sentinel/assert.js +63 -0
  158. package/dist/sentinel/assert.js.map +1 -0
  159. package/dist/sentinel/coverage.d.ts +14 -0
  160. package/dist/sentinel/coverage.d.ts.map +1 -0
  161. package/dist/sentinel/coverage.js +96 -0
  162. package/dist/sentinel/coverage.js.map +1 -0
  163. package/dist/sentinel/drift.d.ts +12 -0
  164. package/dist/sentinel/drift.d.ts.map +1 -0
  165. package/dist/sentinel/drift.js +149 -0
  166. package/dist/sentinel/drift.js.map +1 -0
  167. package/dist/sentinel/index.d.ts +7 -0
  168. package/dist/sentinel/index.d.ts.map +1 -0
  169. package/dist/sentinel/index.js +5 -0
  170. package/dist/sentinel/index.js.map +1 -0
  171. package/dist/sentinel/review.d.ts +15 -0
  172. package/dist/sentinel/review.d.ts.map +1 -0
  173. package/dist/sentinel/review.js +177 -0
  174. package/dist/sentinel/review.js.map +1 -0
  175. package/dist/setup.d.ts +103 -0
  176. package/dist/setup.d.ts.map +1 -0
  177. package/dist/setup.js +87 -0
  178. package/dist/setup.js.map +1 -0
  179. package/dist/shared/types.d.ts +173 -0
  180. package/dist/shared/types.d.ts.map +1 -0
  181. package/dist/shared/types.js +16 -0
  182. package/dist/shared/types.js.map +1 -0
  183. package/package.json +13 -8
  184. package/.github/copilot-instructions.md +0 -117
  185. package/CLAUDE.md +0 -146
  186. package/GEMINI.md +0 -73
  187. package/SETUP.md +0 -264
  188. package/evals/__tests__/eval.test.ts +0 -31
  189. package/evals/cases/auth_hs256_rejected.json +0 -46
  190. package/evals/cases/auth_rs256_valid.json +0 -30
  191. package/evals/cases/cache_missing_lock.json +0 -31
  192. package/evals/cases/db_naive_not_null.json +0 -32
  193. package/evals/cases/logging_pii_leak.json +0 -32
  194. package/evals/cases/migration_with_rollback.json +0 -43
  195. package/evals/cases/no_evidence_novel_design.json +0 -16
  196. package/evals/cases/payment_no_idempotency.json +0 -33
  197. package/evals/cases/redis_session_rejected.json +0 -32
  198. package/evals/cases/safe_refactor.json +0 -17
  199. package/evals/runner.ts +0 -226
  200. package/modules/AGENTS.md +0 -78
  201. package/modules/CLAUDE.md +0 -93
  202. package/modules/README.md +0 -504
  203. package/modules/advisor/ask.ts +0 -87
  204. package/modules/advisor/index.ts +0 -2
  205. package/modules/advisor/types.ts +0 -26
  206. package/modules/compass/behavior.ts +0 -161
  207. package/modules/compass/create.ts +0 -365
  208. package/modules/compass/evidence/collect.ts +0 -109
  209. package/modules/compass/index.ts +0 -7
  210. package/modules/compass/propose.ts +0 -152
  211. package/modules/compass/schemas.ts +0 -121
  212. package/modules/compass/score.ts +0 -77
  213. package/modules/compass/sources/index.ts +0 -413
  214. package/modules/compass/types.ts +0 -431
  215. package/modules/council/advisors.ts +0 -71
  216. package/modules/council/chairman.ts +0 -183
  217. package/modules/council/deliberate.ts +0 -141
  218. package/modules/council/frame.ts +0 -54
  219. package/modules/council/index.ts +0 -9
  220. package/modules/council/personas.ts +0 -57
  221. package/modules/council/reviewers.ts +0 -82
  222. package/modules/council/risk.ts +0 -89
  223. package/modules/council/types.ts +0 -107
  224. package/modules/jury/index.ts +0 -5
  225. package/modules/jury/preflight.ts +0 -101
  226. package/modules/jury/schema.ts +0 -24
  227. package/modules/jury/types.ts +0 -50
  228. package/modules/oracle/adapters/lance-db.ts +0 -81
  229. package/modules/oracle/adapters/xenova-embedder.ts +0 -43
  230. package/modules/oracle/bm25.ts +0 -92
  231. package/modules/oracle/index.ts +0 -36
  232. package/modules/oracle/log.ts +0 -15
  233. package/modules/oracle/propose.ts +0 -164
  234. package/modules/oracle/query.ts +0 -146
  235. package/modules/oracle/summary.ts +0 -116
  236. package/modules/oracle/types.ts +0 -32
  237. package/modules/sentinel/assert.ts +0 -95
  238. package/modules/sentinel/coverage.ts +0 -106
  239. package/modules/sentinel/drift.ts +0 -163
  240. package/modules/sentinel/index.ts +0 -6
  241. package/modules/sentinel/review.ts +0 -208
  242. package/modules/setup.ts +0 -202
  243. package/modules/shared/types.ts +0 -193
@@ -1,50 +0,0 @@
1
- import type { OracleResult, LLMProvider } from "../shared/types"
2
-
3
- export interface JuryInput {
4
- /** What needs to be achieved. */
5
- outcome: string
6
- /** Proposed approach from the Designer. */
7
- design: string
8
- /** Evidence retrieved from Oracle. */
9
- evidence: OracleResult[]
10
- }
11
-
12
- /** Per-dimension breakdown of the 0–1 confidence score. */
13
- export interface ConfidenceBreakdown {
14
- /** Do validated Oracle entries confirm this approach works here? */
15
- evidence_support: number
16
- /** Do Oracle entries suggest this is achievable in this codebase? */
17
- feasibility: number
18
- /** How well does the design address known failure modes? (1 = fully addressed) */
19
- risk: number
20
- /** Does the design cover the full outcome, or only part of it? */
21
- completeness: number
22
- }
23
-
24
- export interface JuryOutput {
25
- /** 0–1 confidence score. Average of the four breakdown dimensions. */
26
- confidence: number
27
- /** Per-dimension breakdown of the confidence score. */
28
- confidence_breakdown: ConfidenceBreakdown
29
- /** What the evidence supports or contradicts. */
30
- assessment: string
31
- /** Evidence missing from Oracle that would improve confidence. */
32
- gaps: string[]
33
- /**
34
- * Gaps that are hard blockers — must be resolved before Council should proceed.
35
- * Subset of gaps where the missing information is critical (auth, rollback, data safety).
36
- */
37
- blocking_gaps: string[]
38
- /**
39
- * Council brief derived from confidence:
40
- * < 0.6 → "challenge" (find what is wrong — broader scope)
41
- * ≥ 0.6 → "pressure-test" (assume correct, try to break it)
42
- */
43
- council_brief: "challenge" | "pressure-test"
44
- recommendation: "proceed" | "investigate-more" | "redesign"
45
- }
46
-
47
- export interface JuryDeps {
48
- llm: LLMProvider
49
- model?: string
50
- }
@@ -1,81 +0,0 @@
1
- /**
2
- * LanceDB vector store adapter.
3
- *
4
- * Required package: npm install vectordb
5
- *
6
- * Chronicle entries are stored in .chronicle/entries/ (LanceDB table directory).
7
- * Vectors are indexed with cosine metric — no need to pre-normalise embeddings.
8
- *
9
- * Note: this adapter targets the `vectordb` package (LanceDB v0.x).
10
- * If your project uses `@lancedb/lancedb` (v0.4+), the connect/createTable API
11
- * is nearly identical but table.query() replaces table.search() for non-vector queries.
12
- */
13
-
14
- import type { VectorStore } from "../types"
15
- import type { ChronicleEntry } from "../../shared/types"
16
- import path from "path"
17
-
18
- // eslint-disable-next-line @typescript-eslint/no-require-imports
19
- const lancedb = require("vectordb")
20
-
21
- interface LanceRow {
22
- id: string
23
- vector: number[]
24
- /** ChronicleEntry serialised as JSON string. */
25
- payload: string
26
- _distance?: number
27
- }
28
-
29
- export async function createLanceDBStore(chronicleDir: string): Promise<VectorStore> {
30
- const tableDir = path.join(chronicleDir, "entries")
31
- const db = await lancedb.connect(tableDir)
32
- let table: any = null
33
-
34
- async function getOrCreateTable(firstRow?: LanceRow): Promise<any> {
35
- if (table) return table
36
- const names: string[] = await db.tableNames()
37
- if (names.includes("entries")) {
38
- table = await db.openTable("entries")
39
- } else if (firstRow) {
40
- table = await db.createTable("entries", [firstRow], { metric: "cosine" })
41
- }
42
- return table
43
- }
44
-
45
- return {
46
- async upsert(id, vector, metadata) {
47
- const row: LanceRow = { id, vector, payload: JSON.stringify(metadata) }
48
- const t = await getOrCreateTable(row)
49
- if (t !== table) {
50
- // table was just created with this row — already inserted
51
- return
52
- }
53
- // LanceDB does not have native upsert — delete existing then insert
54
- await t.delete(`id = '${sanitiseId(id)}'`)
55
- await t.add([row])
56
- },
57
-
58
- async search(vector, limit) {
59
- const t = await getOrCreateTable()
60
- if (!t) return []
61
- const rows: LanceRow[] = await t.search(vector).limit(limit).execute()
62
- return rows.map(row => ({
63
- entry: JSON.parse(row.payload) as ChronicleEntry,
64
- // Convert L2 distance (cosine metric stores 1 - cosine_sim as distance)
65
- score: row._distance !== undefined ? 1 - row._distance : 0,
66
- }))
67
- },
68
-
69
- async getAll() {
70
- const t = await getOrCreateTable()
71
- if (!t) return []
72
- const rows: LanceRow[] = await t.query().execute()
73
- return rows.map(row => JSON.parse(row.payload) as ChronicleEntry)
74
- },
75
- }
76
- }
77
-
78
- /** Prevent SQL injection in the delete filter. LanceDB uses SQL-like WHERE clauses. */
79
- function sanitiseId(id: string): string {
80
- return id.replace(/'/g, "''")
81
- }
@@ -1,43 +0,0 @@
1
- /**
2
- * Local ONNX embedder using @xenova/transformers (all-MiniLM-L6-v2).
3
- *
4
- * Required package: npm install @xenova/transformers
5
- *
6
- * Runs entirely locally — no API key, no network dependency after first use.
7
- * First call downloads and caches the model (~25 MB).
8
- * Produces 384-dimensional unit vectors (mean pooling + L2 normalisation).
9
- *
10
- * For production use, pre-warm the embedder on startup:
11
- * import { warmEmbedder } from "./adapters/xenova-embedder"
12
- * await warmEmbedder()
13
- */
14
-
15
- // eslint-disable-next-line @typescript-eslint/no-require-imports
16
- const { pipeline } = require("@xenova/transformers")
17
-
18
- let embedderPipeline: any = null
19
-
20
- async function getPipeline(): Promise<any> {
21
- if (!embedderPipeline) {
22
- embedderPipeline = await pipeline(
23
- "feature-extraction",
24
- "Xenova/all-MiniLM-L6-v2",
25
- )
26
- }
27
- return embedderPipeline
28
- }
29
-
30
- /**
31
- * Embed text using all-MiniLM-L6-v2.
32
- * Returns a 384-dimensional unit vector.
33
- */
34
- export async function xenovaEmbed(text: string): Promise<number[]> {
35
- const embedder = await getPipeline()
36
- const output = await embedder(text, { pooling: "mean", normalize: true })
37
- return Array.from(output.data) as number[]
38
- }
39
-
40
- /** Pre-warm the model so the first real query is not slow. */
41
- export async function warmEmbedder(): Promise<void> {
42
- await getPipeline()
43
- }
@@ -1,92 +0,0 @@
1
- /**
2
- * Lightweight BM25 implementation for Pass 2 re-ranking.
3
- *
4
- * k1 = 1.5 (term frequency saturation)
5
- * b = 0.75 (length normalization)
6
- *
7
- * Formula: score(q, d) = Σ IDF(qi) * f(qi, d) * (k1 + 1) / (f(qi, d) + k1 * (1 − b + b * |d| / avgdl))
8
- */
9
-
10
- const K1 = 1.5
11
- const B = 0.75
12
-
13
- function tokenize(text: string): string[] {
14
- return text.toLowerCase().match(/\b\w+\b/g) ?? []
15
- }
16
-
17
- /** Robertson–Sparck Jones IDF with smoothing. */
18
- function computeIdf(N: number, df: number): number {
19
- return Math.log(1 + (N - df + 0.5) / (df + 0.5))
20
- }
21
-
22
- /**
23
- * Score each document string against the query using BM25.
24
- * Returns a score array parallel to `documents`.
25
- */
26
- export function bm25Score(query: string, documents: string[]): number[] {
27
- if (documents.length === 0) return []
28
-
29
- const queryTokens = tokenize(query)
30
- const docTokenLists = documents.map(tokenize)
31
- const totalLength = docTokenLists.reduce((sum, d) => sum + d.length, 0)
32
- const avgdl = totalLength / docTokenLists.length
33
- const N = documents.length
34
-
35
- // Precompute document frequency for each unique query token
36
- const df = new Map<string, number>()
37
- for (const token of queryTokens) {
38
- if (!df.has(token)) {
39
- df.set(token, docTokenLists.filter(doc => doc.includes(token)).length)
40
- }
41
- }
42
-
43
- return docTokenLists.map(docTokenList => {
44
- const docLength = docTokenList.length
45
-
46
- const tf = new Map<string, number>()
47
- for (const token of docTokenList) {
48
- tf.set(token, (tf.get(token) ?? 0) + 1)
49
- }
50
-
51
- let score = 0
52
- for (const token of queryTokens) {
53
- const termFreq = tf.get(token) ?? 0
54
- if (termFreq === 0) continue
55
- const idfScore = computeIdf(N, df.get(token) ?? 0)
56
- const normTf =
57
- (termFreq * (K1 + 1)) /
58
- (termFreq + K1 * (1 - B + B * (docLength / avgdl)))
59
- score += idfScore * normTf
60
- }
61
-
62
- return score
63
- })
64
- }
65
-
66
- const BM25_STOP_WORDS = new Set([
67
- "a", "an", "the", "is", "are", "was", "were", "be", "been", "being",
68
- "have", "has", "had", "do", "does", "did", "will", "would", "should",
69
- "could", "may", "might", "shall", "can", "to", "of", "in", "for", "on",
70
- "with", "at", "by", "from", "as", "into", "through", "and", "or", "but",
71
- "if", "then", "this", "that", "these", "those", "it", "its", "we", "they",
72
- "their", "there", "when", "where", "what", "which", "who", "how", "not", "no",
73
- ])
74
-
75
- /**
76
- * Extract domain terms from Chronicle key insights for Pass 2 query enrichment.
77
- * Bridges the vocabulary gap between natural language queries and technical identifiers.
78
- * Strips stop words, returns the most frequent distinctive tokens.
79
- */
80
- export function extractDomainTerms(insights: string[]): string[] {
81
- const allTokens = insights.flatMap(s => tokenize(s))
82
- const freq = new Map<string, number>()
83
- for (const token of allTokens) {
84
- if (!BM25_STOP_WORDS.has(token) && token.length > 2) {
85
- freq.set(token, (freq.get(token) ?? 0) + 1)
86
- }
87
- }
88
- return [...freq.entries()]
89
- .sort((a, b) => b[1] - a[1])
90
- .slice(0, 10)
91
- .map(([token]) => token)
92
- }
@@ -1,36 +0,0 @@
1
- export { query } from "./query"
2
- export { propose, commit } from "./propose"
3
- export type { OracleDeps, VectorStore } from "./types"
4
- export type {
5
- OracleResult,
6
- QueryOptions,
7
- ChronicleEntry,
8
- OracleClient,
9
- } from "../shared/types"
10
-
11
- export { createLanceDBStore } from "./adapters/lance-db"
12
- export { xenovaEmbed, warmEmbedder } from "./adapters/xenova-embedder"
13
-
14
- import type { OracleClient } from "../shared/types"
15
- import type { OracleDeps } from "./types"
16
- import { query } from "./query"
17
- import { propose, commit } from "./propose"
18
-
19
- /**
20
- * Create a bound OracleClient from injected deps.
21
- * Pass this to Jury and Council — they only need the OracleClient interface,
22
- * not the raw Oracle functions.
23
- *
24
- * @example
25
- * const oracle = createOracleClient({
26
- * embedder: xenovaEmbed,
27
- * vectorStore: await createLanceDBStore(".chronicle"),
28
- * })
29
- */
30
- export function createOracleClient(deps: OracleDeps): OracleClient {
31
- return {
32
- query: (text, options) => query(text, options ?? {}, deps),
33
- propose: entry => propose(entry, deps),
34
- commit: proposalId => commit(proposalId, deps),
35
- }
36
- }
@@ -1,15 +0,0 @@
1
- import { promises as fs } from "fs"
2
- import path from "path"
3
-
4
- /**
5
- * Append a query log entry to .chronicle/query-log.jsonl.
6
- * Best-effort — callers should swallow errors from this.
7
- */
8
- export async function appendQueryLog(
9
- entry: Record<string, unknown>,
10
- chronicleDir: string,
11
- ): Promise<void> {
12
- await fs.mkdir(chronicleDir, { recursive: true })
13
- const logPath = path.join(chronicleDir, "query-log.jsonl")
14
- await fs.appendFile(logPath, JSON.stringify(entry) + "\n", "utf8")
15
- }
@@ -1,164 +0,0 @@
1
- import { promises as fs } from "fs"
2
- import path from "path"
3
- import { randomUUID } from "crypto"
4
- import { exec } from "child_process"
5
- import { promisify } from "util"
6
- import type { ChronicleEntry, SimilarityWarning } from "../shared/types"
7
- import { entryText } from "../shared/types"
8
- import type { OracleDeps } from "./types"
9
- import { updateSummary } from "./summary"
10
-
11
- const execAsync = promisify(exec)
12
-
13
- const INSIGHT_MIN_LENGTH = 20
14
- const INSIGHT_MAX_LENGTH = 200
15
- const SIMILARITY_WARNING_THRESHOLD = 0.85
16
-
17
- function validateEntry(entry: Omit<ChronicleEntry, "id" | "timestamp">): void {
18
- const insight = entry.key_insight?.trim() ?? ""
19
- if (insight.length < INSIGHT_MIN_LENGTH) {
20
- throw new Error(
21
- `key_insight too short (${insight.length} chars, min ${INSIGHT_MIN_LENGTH}). ` +
22
- `Write a specific, complete sentence naming the module or area affected.`,
23
- )
24
- }
25
- if (insight.length > INSIGHT_MAX_LENGTH) {
26
- throw new Error(
27
- `key_insight too long (${insight.length} chars, max ${INSIGHT_MAX_LENGTH}). ` +
28
- `Distil to a single clear sentence.`,
29
- )
30
- }
31
- if (entry.decision !== undefined) {
32
- const d = entry.decision.trim()
33
- if (d.length < INSIGHT_MIN_LENGTH) {
34
- throw new Error(
35
- `decision too short (${d.length} chars, min ${INSIGHT_MIN_LENGTH}). ` +
36
- `Write a specific, complete sentence describing the decision.`,
37
- )
38
- }
39
- if (d.length > INSIGHT_MAX_LENGTH) {
40
- throw new Error(
41
- `decision too long (${d.length} chars, max ${INSIGHT_MAX_LENGTH}). ` +
42
- `Distil to a single clear sentence.`,
43
- )
44
- }
45
- }
46
- if (!entry.affected_areas || entry.affected_areas.filter(a => a.trim()).length === 0) {
47
- throw new Error(`affected_areas must contain at least one non-empty entry.`)
48
- }
49
- if (entry.confidence < 0 || entry.confidence > 1) {
50
- throw new Error(`confidence must be between 0 and 1, got ${entry.confidence}.`)
51
- }
52
- }
53
-
54
- async function checkSimilarity(
55
- entry: Omit<ChronicleEntry, "id" | "timestamp">,
56
- deps: OracleDeps,
57
- ): Promise<SimilarityWarning | undefined> {
58
- try {
59
- const text = [entryText(entry), ...entry.affected_areas, ...(entry.scope ?? [])].join(" ")
60
- const vector = await deps.embedder(text)
61
- const results = await deps.vectorStore.search(vector, 3)
62
- if (results.length === 0) return undefined
63
- const top = results[0]
64
- if (top.score < SIMILARITY_WARNING_THRESHOLD) return undefined
65
- return {
66
- entry: top.entry,
67
- score: top.score,
68
- // If the existing entry is validated, a near-duplicate is likely a correction
69
- warning: top.entry.status === "validated" ? "potential-supersession" : "potential-duplicate",
70
- }
71
- } catch {
72
- // Similarity check is best-effort — never block a proposal because of it
73
- return undefined
74
- }
75
- }
76
-
77
- /**
78
- * Propose a new Chronicle entry for human review.
79
- * Validates entry quality and checks for similar existing entries before writing.
80
- * Writes the entry to .chronicle/proposals/<id>.json — NOT yet indexed.
81
- * The proposal sits pending until a human calls commit() to approve it.
82
- *
83
- * Throws if key_insight is too short/long or affected_areas is empty.
84
- * Returns a SimilarityWarning if a near-identical entry already exists —
85
- * the human gate should surface this before approving the commit.
86
- */
87
- export async function propose(
88
- entry: Omit<ChronicleEntry, "id" | "timestamp">,
89
- deps: OracleDeps,
90
- ): Promise<{ proposalId: string; similarity?: SimilarityWarning }> {
91
- validateEntry(entry)
92
-
93
- const similarity = await checkSimilarity(entry, deps)
94
-
95
- const chronicleDir = deps.chronicleDir ?? ".chronicle"
96
- const proposalsDir = path.join(chronicleDir, "proposals")
97
- await fs.mkdir(proposalsDir, { recursive: true })
98
-
99
- const proposalId = randomUUID()
100
- const proposalPath = path.join(proposalsDir, `${proposalId}.json`)
101
- await fs.writeFile(proposalPath, JSON.stringify(entry, null, 2), "utf8")
102
-
103
- return { proposalId, ...(similarity ? { similarity } : {}) }
104
- }
105
-
106
- /**
107
- * Commit a pending proposal after human approval.
108
- * Reads the proposal file, assigns an ID and timestamp, embeds the entry,
109
- * upserts it into the vector store, and deletes the proposal file.
110
- *
111
- * Throws if the proposal does not exist.
112
- */
113
- export async function commit(
114
- proposalId: string,
115
- deps: OracleDeps,
116
- ): Promise<ChronicleEntry> {
117
- const chronicleDir = deps.chronicleDir ?? ".chronicle"
118
- const proposalPath = path.join(chronicleDir, "proposals", `${proposalId}.json`)
119
-
120
- let raw: string
121
- try {
122
- raw = await fs.readFile(proposalPath, "utf8")
123
- } catch {
124
- throw new Error(`Proposal not found: ${proposalId}`)
125
- }
126
-
127
- const partial = JSON.parse(raw) as Omit<ChronicleEntry, "id" | "timestamp">
128
-
129
- const entry: ChronicleEntry = {
130
- ...partial,
131
- id: randomUUID(),
132
- timestamp: new Date().toISOString(),
133
- }
134
-
135
- // Embed the primary text + areas + scope tags for richer retrieval
136
- const embeddingText = [entryText(entry), ...entry.affected_areas, ...(entry.scope ?? [])].join(" ")
137
- const vector = await deps.embedder(embeddingText)
138
- await deps.vectorStore.upsert(entry.id, vector, entry)
139
-
140
- // Write to committed/ — the git-tracked source of truth shared across the team
141
- const committedDir = path.join(chronicleDir, "committed")
142
- await fs.mkdir(committedDir, { recursive: true })
143
- const committedPath = path.join(committedDir, `${entry.id}.json`)
144
- await fs.writeFile(committedPath, JSON.stringify(entry, null, 2), "utf8")
145
-
146
- // Stage the committed entry for the next git commit — best-effort
147
- try {
148
- await execAsync(`git add "${committedPath}"`)
149
- } catch {
150
- // Not in a git repo, or git is unavailable — silently continue
151
- }
152
-
153
- // Rebuild SUMMARY.md — best-effort, never fail a commit
154
- try {
155
- await updateSummary(chronicleDir)
156
- } catch {
157
- // Summary generation failure must not fail a commit
158
- }
159
-
160
- // Remove the proposal — it has been committed
161
- await fs.unlink(proposalPath)
162
-
163
- return entry
164
- }
@@ -1,146 +0,0 @@
1
- import type { ChronicleEntry, OracleResult, QueryOptions } from "../shared/types"
2
- import { entryText } from "../shared/types"
3
- import type { OracleDeps } from "./types"
4
- import { bm25Score, extractDomainTerms } from "./bm25"
5
- import { appendQueryLog } from "./log"
6
-
7
- const DEFAULT_LIMIT = 10
8
- const DEFAULT_SCORE_THRESHOLD = 0.031
9
- const RRF_K = 60
10
- /** Retrieve this many vector candidates before BM25 re-ranking. */
11
- const CANDIDATE_MULTIPLIER = 3
12
-
13
- /**
14
- * Reciprocal Rank Fusion score.
15
- * score = Σ 1 / (k + rank_i) summed across all rank lists.
16
- * k = 60 (standard constant).
17
- */
18
- function rrfScore(ranks: number[]): number {
19
- return ranks.reduce((sum, rank) => sum + 1 / (RRF_K + rank), 0)
20
- }
21
-
22
- /**
23
- * Two-pass retrieval with Reciprocal Rank Fusion.
24
- *
25
- * Pass 1 — vector similarity:
26
- * Embed the query, retrieve top (limit × CANDIDATE_MULTIPLIER) candidates.
27
- *
28
- * Pass 2 — BM25 re-ranking with query enrichment:
29
- * Extract domain terms from Pass 1 key insights, enrich the query,
30
- * score candidates with BM25, fuse ranks via RRF.
31
- *
32
- * Results below scoreThreshold are dropped entirely.
33
- * All queries are appended to .chronicle/query-log.jsonl.
34
- */
35
- export async function query(
36
- text: string,
37
- options: QueryOptions = {},
38
- deps: OracleDeps,
39
- ): Promise<OracleResult[]> {
40
- const {
41
- statusFilter,
42
- limit = DEFAULT_LIMIT,
43
- scoreThreshold = DEFAULT_SCORE_THRESHOLD,
44
- } = options
45
-
46
- const startTime = Date.now()
47
-
48
- // ── Pass 1: vector similarity ──────────────────────────────────────────────
49
- const queryVector = await deps.embedder(text)
50
- const candidateLimit = limit * CANDIDATE_MULTIPLIER
51
- let candidates = await deps.vectorStore.search(queryVector, candidateLimit)
52
-
53
- // Status filter applied before BM25 to avoid scoring irrelevant entries
54
- if (statusFilter && statusFilter.length > 0) {
55
- candidates = candidates.filter(c => statusFilter.includes(c.entry.status))
56
- }
57
-
58
- if (candidates.length === 0) {
59
- await tryLogQuery(text, [], startTime, deps)
60
- return []
61
- }
62
-
63
- // ── Pass 2: BM25 re-ranking with query enrichment ─────────────────────────
64
- const topInsights = candidates
65
- .slice(0, Math.min(5, candidates.length))
66
- .map(c => entryText(c.entry))
67
- const domainTerms = extractDomainTerms(topInsights)
68
- const enrichedQuery =
69
- domainTerms.length > 0 ? `${text} ${domainTerms.join(" ")}` : text
70
-
71
- const documents = candidates.map(c =>
72
- [entryText(c.entry), ...c.entry.affected_areas, ...(c.entry.scope ?? [])].join(" "),
73
- )
74
- const bm25Scores = bm25Score(enrichedQuery, documents)
75
-
76
- // Build BM25 rank lookup (index → rank)
77
- const bm25RankOf: number[] = new Array(candidates.length)
78
- bm25Scores
79
- .map((score, i) => ({ i, score }))
80
- .sort((a, b) => b.score - a.score)
81
- .forEach(({ i }, rank) => {
82
- bm25RankOf[i] = rank
83
- })
84
-
85
- // ── RRF fusion ─────────────────────────────────────────────────────────────
86
- const fused: Array<ChronicleEntry & { score: number }> = candidates.map(
87
- (candidate, vectorRank) => ({
88
- ...candidate.entry,
89
- score: rrfScore([vectorRank, bm25RankOf[vectorRank]]),
90
- }),
91
- )
92
-
93
- fused.sort((a, b) => b.score - a.score)
94
-
95
- const filtered = fused
96
- .filter(r => r.score >= scoreThreshold)
97
- .slice(0, limit)
98
-
99
- const results = assignTiers(filtered)
100
-
101
- await tryLogQuery(text, results, startTime, deps)
102
- return results
103
- }
104
-
105
- /**
106
- * Assign relevance tiers within the result set using relative rank.
107
- * Top ~30% → primary, next ~40% → supporting, remainder → background.
108
- * Thresholds are relative so they self-calibrate as Chronicle grows.
109
- */
110
- function assignTiers(
111
- results: Array<ChronicleEntry & { score: number }>,
112
- ): OracleResult[] {
113
- const n = results.length
114
- if (n === 0) return []
115
- const primaryCount = Math.max(1, Math.ceil(n * 0.3))
116
- const supportingCount = Math.max(1, Math.ceil(n * 0.4))
117
- return results.map((r, i) => ({
118
- ...r,
119
- tier:
120
- i < primaryCount ? "primary"
121
- : i < primaryCount + supportingCount ? "supporting"
122
- : "background",
123
- }))
124
- }
125
-
126
- async function tryLogQuery(
127
- text: string,
128
- results: OracleResult[],
129
- startTime: number,
130
- deps: OracleDeps,
131
- ): Promise<void> {
132
- try {
133
- await appendQueryLog(
134
- {
135
- query: text,
136
- results: results.map(r => ({ id: r.id, score: r.score, status: r.status })),
137
- resultCount: results.length,
138
- durationMs: Date.now() - startTime,
139
- timestamp: new Date().toISOString(),
140
- },
141
- deps.chronicleDir ?? ".chronicle",
142
- )
143
- } catch {
144
- // Query logging is best-effort — never fail a query because of a log write failure
145
- }
146
- }