context-mode 1.0.161 → 1.0.163

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 (153) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +1 -1
  3. package/.codex-plugin/plugin.json +1 -1
  4. package/.openclaw-plugin/openclaw.plugin.json +1 -1
  5. package/.openclaw-plugin/package.json +1 -1
  6. package/README.md +142 -28
  7. package/bin/statusline.mjs +24 -4
  8. package/build/adapters/antigravity/index.d.ts +1 -1
  9. package/build/adapters/antigravity-cli/index.d.ts +51 -0
  10. package/build/adapters/antigravity-cli/index.js +341 -0
  11. package/build/adapters/claude-code/hooks.d.ts +1 -0
  12. package/build/adapters/claude-code/hooks.js +3 -0
  13. package/build/adapters/claude-code/index.js +24 -5
  14. package/build/adapters/client-map.js +5 -0
  15. package/build/adapters/codex/hooks.d.ts +5 -1
  16. package/build/adapters/codex/hooks.js +5 -1
  17. package/build/adapters/codex/index.d.ts +9 -1
  18. package/build/adapters/codex/index.js +87 -5
  19. package/build/adapters/copilot-cli/hooks.d.ts +33 -0
  20. package/build/adapters/copilot-cli/hooks.js +64 -0
  21. package/build/adapters/copilot-cli/index.d.ts +48 -0
  22. package/build/adapters/copilot-cli/index.js +341 -0
  23. package/build/adapters/detect.d.ts +1 -1
  24. package/build/adapters/detect.js +71 -3
  25. package/build/adapters/openclaw/mcp-tools.js +1 -1
  26. package/build/adapters/opencode/index.js +31 -17
  27. package/build/adapters/opencode/zod3tov4.js +27 -6
  28. package/build/adapters/pi/extension.d.ts +2 -12
  29. package/build/adapters/pi/extension.js +114 -96
  30. package/build/adapters/types.d.ts +5 -4
  31. package/build/adapters/types.js +4 -3
  32. package/build/cache-heal.d.ts +48 -0
  33. package/build/cache-heal.js +150 -0
  34. package/build/cli.js +37 -97
  35. package/build/executor.d.ts +25 -0
  36. package/build/executor.js +143 -22
  37. package/build/opencode-plugin.js +5 -2
  38. package/build/routing-block.d.ts +8 -0
  39. package/build/routing-block.js +86 -0
  40. package/build/runtime.d.ts +0 -36
  41. package/build/runtime.js +107 -27
  42. package/build/search/flood-guard.d.ts +57 -0
  43. package/build/search/flood-guard.js +80 -0
  44. package/build/security.d.ts +8 -3
  45. package/build/security.js +155 -29
  46. package/build/server.d.ts +14 -0
  47. package/build/server.js +368 -350
  48. package/build/session/analytics.d.ts +8 -8
  49. package/build/session/analytics.js +18 -13
  50. package/build/session/db.d.ts +1 -0
  51. package/build/session/db.js +37 -4
  52. package/build/session/extract.d.ts +46 -0
  53. package/build/session/extract.js +764 -13
  54. package/build/session/project-attribution.js +14 -0
  55. package/build/store.d.ts +1 -1
  56. package/build/store.js +139 -25
  57. package/build/tool-naming.d.ts +4 -0
  58. package/build/tool-naming.js +24 -0
  59. package/build/util/jsonc.d.ts +14 -0
  60. package/build/util/jsonc.js +104 -0
  61. package/cli.bundle.mjs +260 -254
  62. package/configs/antigravity/GEMINI.md +2 -2
  63. package/configs/antigravity-cli/hooks/hooks.json +37 -0
  64. package/configs/antigravity-cli/hooks.json +37 -0
  65. package/configs/antigravity-cli/mcp_config.json +10 -0
  66. package/configs/antigravity-cli/plugin.json +14 -0
  67. package/configs/antigravity-cli/rules/context-mode.md +77 -0
  68. package/configs/antigravity-cli/skills/context-mode/SKILL.md +77 -0
  69. package/configs/claude-code/CLAUDE.md +2 -2
  70. package/configs/codex/AGENTS.md +2 -2
  71. package/configs/copilot-cli/.github/plugin/plugin.json +23 -0
  72. package/configs/copilot-cli/.mcp.json +12 -0
  73. package/configs/copilot-cli/README.md +47 -0
  74. package/configs/copilot-cli/hooks.json +41 -0
  75. package/configs/copilot-cli/skills/context-mode/SKILL.md +38 -0
  76. package/configs/gemini-cli/GEMINI.md +2 -2
  77. package/configs/jetbrains-copilot/copilot-instructions.md +2 -2
  78. package/configs/kilo/AGENTS.md +2 -2
  79. package/configs/kiro/KIRO.md +2 -2
  80. package/configs/omp/SYSTEM.md +2 -2
  81. package/configs/openclaw/AGENTS.md +2 -2
  82. package/configs/opencode/AGENTS.md +2 -2
  83. package/configs/qwen-code/QWEN.md +2 -2
  84. package/configs/vscode-copilot/copilot-instructions.md +2 -2
  85. package/configs/zed/AGENTS.md +2 -2
  86. package/hooks/antigravity-cli/payload.mjs +98 -0
  87. package/hooks/antigravity-cli/posttooluse.mjs +138 -0
  88. package/hooks/antigravity-cli/pretooluse.mjs +78 -0
  89. package/hooks/antigravity-cli/stop.mjs +58 -0
  90. package/hooks/codex/pretooluse.mjs +14 -4
  91. package/hooks/codex/stop.mjs +12 -4
  92. package/hooks/copilot-cli/posttooluse.mjs +79 -0
  93. package/hooks/copilot-cli/precompact.mjs +66 -0
  94. package/hooks/copilot-cli/pretooluse.mjs +41 -0
  95. package/hooks/copilot-cli/sessionstart.mjs +121 -0
  96. package/hooks/copilot-cli/stop.mjs +59 -0
  97. package/hooks/copilot-cli/userpromptsubmit.mjs +77 -0
  98. package/hooks/core/codex-caps.mjs +112 -0
  99. package/hooks/core/formatters.mjs +158 -7
  100. package/hooks/core/mcp-ready.mjs +37 -8
  101. package/hooks/core/routing.mjs +94 -8
  102. package/hooks/core/tool-naming.mjs +3 -0
  103. package/hooks/hooks.json +12 -1
  104. package/hooks/pretooluse.mjs +6 -2
  105. package/hooks/routing-block.mjs +2 -2
  106. package/hooks/security.bundle.mjs +2 -1
  107. package/hooks/session-db.bundle.mjs +11 -7
  108. package/hooks/session-directive.mjs +88 -20
  109. package/hooks/session-extract.bundle.mjs +2 -2
  110. package/hooks/session-helpers.mjs +21 -0
  111. package/hooks/session-loaders.mjs +8 -5
  112. package/hooks/sessionstart.mjs +53 -7
  113. package/hooks/stop.mjs +49 -0
  114. package/hooks/userpromptsubmit.mjs +9 -2
  115. package/openclaw.plugin.json +1 -1
  116. package/package.json +4 -10
  117. package/scripts/install-antigravity-cli-plugin.mjs +141 -0
  118. package/server.bundle.mjs +214 -205
  119. package/skills/ctx-insight/SKILL.md +12 -17
  120. package/build/util/db-lock.d.ts +0 -65
  121. package/build/util/db-lock.js +0 -166
  122. package/insight/index.html +0 -13
  123. package/insight/package.json +0 -55
  124. package/insight/server.mjs +0 -1265
  125. package/insight/src/components/analytics.tsx +0 -112
  126. package/insight/src/components/ui/badge.tsx +0 -52
  127. package/insight/src/components/ui/button.tsx +0 -58
  128. package/insight/src/components/ui/card.tsx +0 -103
  129. package/insight/src/components/ui/chart.tsx +0 -371
  130. package/insight/src/components/ui/collapsible.tsx +0 -19
  131. package/insight/src/components/ui/input.tsx +0 -20
  132. package/insight/src/components/ui/progress.tsx +0 -83
  133. package/insight/src/components/ui/scroll-area.tsx +0 -55
  134. package/insight/src/components/ui/separator.tsx +0 -23
  135. package/insight/src/components/ui/table.tsx +0 -114
  136. package/insight/src/components/ui/tabs.tsx +0 -82
  137. package/insight/src/components/ui/tooltip.tsx +0 -64
  138. package/insight/src/lib/api.ts +0 -144
  139. package/insight/src/lib/utils.ts +0 -6
  140. package/insight/src/main.tsx +0 -22
  141. package/insight/src/routeTree.gen.ts +0 -189
  142. package/insight/src/router.tsx +0 -19
  143. package/insight/src/routes/__root.tsx +0 -55
  144. package/insight/src/routes/enterprise.tsx +0 -316
  145. package/insight/src/routes/index.tsx +0 -1482
  146. package/insight/src/routes/knowledge.tsx +0 -221
  147. package/insight/src/routes/knowledge_.$dbHash.$sourceId.tsx +0 -137
  148. package/insight/src/routes/search.tsx +0 -97
  149. package/insight/src/routes/sessions.tsx +0 -179
  150. package/insight/src/routes/sessions_.$dbHash.$sessionId.tsx +0 -181
  151. package/insight/src/styles.css +0 -104
  152. package/insight/tsconfig.json +0 -29
  153. package/insight/vite.config.ts +0 -19
@@ -278,7 +278,7 @@ export interface AdapterDirEntry {
278
278
  * so a single call surfaces "your work everywhere on this machine across
279
279
  * all AI tools" (the marketing line).
280
280
  *
281
- * Returns ALL 15 adapters even when the dir doesn't exist on disk — the
281
+ * Returns ALL 17 adapters even when the dir doesn't exist on disk — the
282
282
  * scanner functions filter to existing dirs. That keeps the enumeration
283
283
  * pure / testable without filesystem dependencies.
284
284
  */
@@ -608,11 +608,11 @@ export declare function detectLocaleAndTz(): {
608
608
  * the section disappears cleanly on a fresh install.
609
609
  *
610
610
  * Math constants:
611
- * Opus 4 = $15.00 per 1M input tokens (fallback when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN not set)
612
- * Sonnet 4 = $3.00 per 1M input tokens
613
- * GPT-4o = $2.50 per 1M input tokens
614
- * Gemini 2 = $1.25 per 1M input tokens
615
- * Haiku 4 = $0.80 per 1M input tokens
611
+ * Opus 4.7/4.8 = $5.00 per 1M input tokens (fallback when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN not set)
612
+ * Sonnet 4.6 = $3.00 per 1M input tokens
613
+ * GPT-4o = $2.50 per 1M input tokens
614
+ * Gemini 2 = $1.25 per 1M input tokens
615
+ * Haiku 4.5 = $1.00 per 1M input tokens
616
616
  * Cursor Pro = $20 / month → "X months of Cursor Pro"
617
617
  * Claude Max = $200 / month → "X.X months of Claude Max"
618
618
  * Weekend coding ≈ $73.67 → "X weekends of nonstop API coding"
@@ -669,8 +669,8 @@ export declare function renderHorizontalTimeline(days: TimelineDay[], locale: st
669
669
  export declare function formatLocalDateTime(ms: number, locale: string, tz: string): string;
670
670
  /**
671
671
  * Per-token USD rate — resolves on every call.
672
- * Dynamic when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN is set, Opus 4 input
673
- * ($15 per 1M tokens) otherwise.
672
+ * Dynamic when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN is set, Opus 4.7/4.8 input
673
+ * ($5 per 1M tokens) otherwise.
674
674
  */
675
675
  export declare function pricePerToken(): number;
676
676
  /**
@@ -359,7 +359,7 @@ export class AnalyticsEngine {
359
359
  * so a single call surfaces "your work everywhere on this machine across
360
360
  * all AI tools" (the marketing line).
361
361
  *
362
- * Returns ALL 15 adapters even when the dir doesn't exist on disk — the
362
+ * Returns ALL 17 adapters even when the dir doesn't exist on disk — the
363
363
  * scanner functions filter to existing dirs. That keeps the enumeration
364
364
  * pure / testable without filesystem dependencies.
365
365
  */
@@ -370,10 +370,12 @@ export function enumerateAdapterDirs(opts) {
370
370
  ["claude-code", [".claude"]],
371
371
  ["gemini-cli", [".gemini"]],
372
372
  ["antigravity", [".gemini"]],
373
+ ["antigravity-cli", [".gemini"]],
373
374
  ["openclaw", [".openclaw"]],
374
375
  ["codex", [".codex"]],
375
376
  ["cursor", [".cursor"]],
376
377
  ["vscode-copilot", [".vscode"]],
378
+ ["copilot-cli", [".copilot"]],
377
379
  ["kiro", [".kiro"]],
378
380
  ["pi", [".pi"]],
379
381
  ["omp", [".omp"]],
@@ -1188,10 +1190,12 @@ export const adapterLabels = {
1188
1190
  "claude-code": "Claude Code",
1189
1191
  "gemini-cli": "Gemini CLI",
1190
1192
  "antigravity": "Antigravity",
1193
+ "antigravity-cli": "Antigravity CLI",
1191
1194
  "openclaw": "Openclaw",
1192
1195
  "codex": "Codex CLI",
1193
1196
  "cursor": "Cursor",
1194
1197
  "vscode-copilot": "VS Code Copilot",
1198
+ "copilot-cli": "GitHub Copilot CLI",
1195
1199
  "kiro": "Kiro",
1196
1200
  "pi": "Pi",
1197
1201
  "omp": "OMP",
@@ -1393,11 +1397,11 @@ function shortPath(abs) {
1393
1397
  * the section disappears cleanly on a fresh install.
1394
1398
  *
1395
1399
  * Math constants:
1396
- * Opus 4 = $15.00 per 1M input tokens (fallback when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN not set)
1397
- * Sonnet 4 = $3.00 per 1M input tokens
1398
- * GPT-4o = $2.50 per 1M input tokens
1399
- * Gemini 2 = $1.25 per 1M input tokens
1400
- * Haiku 4 = $0.80 per 1M input tokens
1400
+ * Opus 4.7/4.8 = $5.00 per 1M input tokens (fallback when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN not set)
1401
+ * Sonnet 4.6 = $3.00 per 1M input tokens
1402
+ * GPT-4o = $2.50 per 1M input tokens
1403
+ * Gemini 2 = $1.25 per 1M input tokens
1404
+ * Haiku 4.5 = $1.00 per 1M input tokens
1401
1405
  * Cursor Pro = $20 / month → "X months of Cursor Pro"
1402
1406
  * Claude Max = $200 / month → "X.X months of Claude Max"
1403
1407
  * Weekend coding ≈ $73.67 → "X weekends of nonstop API coding"
@@ -1428,7 +1432,7 @@ export function renderCostExample(lifetimeBytes, lifetimeTokens, lifetimeDays) {
1428
1432
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1429
1433
  const _geminiUsd = ((lifetimeTokens * 1.25) / 1_000_000).toFixed(2);
1430
1434
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1431
- const _haikuUsd = ((lifetimeTokens * 0.8) / 1_000_000).toFixed(2);
1435
+ const _haikuUsd = ((lifetimeTokens * 1.0) / 1_000_000).toFixed(2);
1432
1436
  const usingDynamicPrice = process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN !== undefined;
1433
1437
  const modelId = process.env.PI_CONTEXT_MODE_MODEL_ID;
1434
1438
  // Mert: "daha marketing ve business value e vermeli, math hesaplamalari ile
@@ -1442,7 +1446,7 @@ export function renderCostExample(lifetimeBytes, lifetimeTokens, lifetimeDays) {
1442
1446
  out.push(` $${usdStr(lifetimeUsd)} of tokens your team didn't burn.`);
1443
1447
  }
1444
1448
  else {
1445
- out.push(` $${usdStr(lifetimeUsd)} of Opus 4 tokens your team didn't burn.`);
1449
+ out.push(` $${usdStr(lifetimeUsd)} of Opus 4.7 tokens your team didn't burn.`);
1446
1450
  }
1447
1451
  out.push(` context-mode kept ${kb(lifetimeBytes)} out of context — that's ${cursorMonths} months of Cursor Pro paid for itself.`);
1448
1452
  if (teamUsd > 0 && teamYearUsd > 0) {
@@ -1857,7 +1861,8 @@ function fmtNum(n) {
1857
1861
  // ─────────────────────────────────────────────────────────
1858
1862
  // ── Pricing (Bug #6) — per-token USD rate ─────────────────
1859
1863
  // Reads PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN when set by a Pi host;
1860
- // falls back to the Opus 4 input rate ($15/1M) for all other adapters.
1864
+ // falls back to the Opus 4.7/4.8 input rate ($5/1M) for all other adapters.
1865
+ // Verified against platform.claude.com/docs/en/about-claude/pricing 2026-06.
1861
1866
  //
1862
1867
  // IMPORTANT: this is a FUNCTION, not a const. Pi sets the env var
1863
1868
  // AFTER the MCP server has been imported (the bridge spawns the server
@@ -1869,8 +1874,8 @@ function fmtNum(n) {
1869
1874
  // (Reverted module-load const semantics, PR #741 follow-up.)
1870
1875
  /**
1871
1876
  * Per-token USD rate — resolves on every call.
1872
- * Dynamic when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN is set, Opus 4 input
1873
- * ($15 per 1M tokens) otherwise.
1877
+ * Dynamic when PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN is set, Opus 4.7/4.8 input
1878
+ * ($5 per 1M tokens) otherwise.
1874
1879
  */
1875
1880
  export function pricePerToken() {
1876
1881
  const env = process.env.PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN;
@@ -1879,7 +1884,7 @@ export function pricePerToken() {
1879
1884
  if (Number.isFinite(parsed) && parsed > 0)
1880
1885
  return parsed;
1881
1886
  }
1882
- return 15 / 1_000_000; // Opus 4 input fallback
1887
+ return 5 / 1_000_000; // Opus 4.7/4.8 input fallback
1883
1888
  }
1884
1889
  /**
1885
1890
  * Back-compat alias for the original Opus-rate const (PR #401 architect
@@ -1890,7 +1895,7 @@ export function pricePerToken() {
1890
1895
  *
1891
1896
  * @deprecated Use pricePerToken() to honor PI_CONTEXT_MODE_PRICE_OUTPUT_PER_TOKEN.
1892
1897
  */
1893
- export const OPUS_INPUT_PRICE_PER_TOKEN = 15 / 1_000_000;
1898
+ export const OPUS_INPUT_PRICE_PER_TOKEN = 5 / 1_000_000;
1894
1899
  /** Convert a token count to a USD string at the current per-token rate. */
1895
1900
  export function tokensToUsd(tokens) {
1896
1901
  const safe = Number.isFinite(tokens) && tokens > 0 ? tokens : 0;
@@ -179,6 +179,7 @@ export interface SessionRollup {
179
179
  unique_files: number;
180
180
  max_file_edits: number;
181
181
  has_commit: 0 | 1;
182
+ commit_message: string;
182
183
  edit_test_cycles: number;
183
184
  duration_min: number;
184
185
  compact_count: number;
@@ -479,6 +479,7 @@ const S = {
479
479
  getSessionStats: "getSessionStats",
480
480
  getSessionRollup: "getSessionRollup",
481
481
  getMaxFileEdits: "getMaxFileEdits",
482
+ getLatestCommitMessage: "getLatestCommitMessage",
482
483
  incrementCompactCount: "incrementCompactCount",
483
484
  upsertResume: "upsertResume",
484
485
  getResume: "getResume",
@@ -731,7 +732,7 @@ export class SessionDB extends SQLiteBase {
731
732
  COALESCE(SUM(CASE WHEN category = 'error' THEN 1 ELSE 0 END), 0) AS errors,
732
733
  COUNT(DISTINCT type) AS unique_tools,
733
734
  COUNT(DISTINCT CASE WHEN category = 'file' THEN data END) AS unique_files,
734
- CASE WHEN SUM(CASE WHEN category = 'git' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END AS has_commit,
735
+ CASE WHEN SUM(CASE WHEN type = 'git_commit' THEN 1 ELSE 0 END) > 0 THEN 1 ELSE 0 END AS has_commit,
735
736
  CAST(COALESCE((MAX(strftime('%s', created_at)) - MIN(strftime('%s', created_at))) / 60.0, 0) AS INTEGER) AS duration_min,
736
737
  COALESCE(SUM(CASE WHEN type = 'external_ref' THEN 1 ELSE 0 END), 0) AS sources_indexed,
737
738
  CAST(COALESCE(SUM(bytes_avoided) / 1024.0, 0) AS INTEGER) AS total_chunks,
@@ -747,6 +748,16 @@ export class SessionDB extends SQLiteBase {
747
748
  WHERE session_id = ? AND category = 'file' AND type IN ('file_edit', 'file_write')
748
749
  GROUP BY data
749
750
  )`);
751
+ // v1.0.161 (Bug 2): latest commit message from session's type='git_commit'
752
+ // events. Used by rollup spread to stamp commit_message symmetric with
753
+ // has_commit on every outgoing event. Separate prepared statement (vs.
754
+ // sub-select in getSessionRollup) keeps the binding shape uniform — every
755
+ // rollup query takes a single sessionId parameter.
756
+ p(S.getLatestCommitMessage, `SELECT data
757
+ FROM session_events
758
+ WHERE session_id = ? AND type = 'git_commit'
759
+ ORDER BY id DESC
760
+ LIMIT 1`);
750
761
  p(S.incrementCompactCount, `UPDATE session_meta SET compact_count = compact_count + 1 WHERE session_id = ?`);
751
762
  // ── Resume ──
752
763
  p(S.upsertResume, `INSERT INTO session_resume (session_id, snapshot, event_count)
@@ -888,7 +899,12 @@ export class SessionDB extends SQLiteBase {
888
899
  .slice(0, 16)
889
900
  .toUpperCase();
890
901
  const attribution = attributions?.[i];
891
- const projectDir = String(attribution?.projectDir ?? event.project_dir ?? this._getSessionProjectDir(sessionId) ?? "").trim();
902
+ // #827: store project_dir in canonical path shape so the search-time
903
+ // allow-set lookup (getSessionIdsForProject) matches regardless of the
904
+ // separator / trailing-slash form the host adapter happened to emit.
905
+ // normalizeWorktreePath is the same rule used for project-hash stability.
906
+ const rawProjectDir = String(attribution?.projectDir ?? event.project_dir ?? this._getSessionProjectDir(sessionId) ?? "").trim();
907
+ const projectDir = rawProjectDir === "" ? "" : normalizeWorktreePath(rawProjectDir);
892
908
  const attributionSource = String(attribution?.source ?? event.attribution_source ?? "unknown");
893
909
  const rawConfidence = Number(attribution?.confidence ?? event.attribution_confidence ?? 0);
894
910
  const attributionConfidence = Number.isFinite(rawConfidence)
@@ -1020,11 +1036,26 @@ export class SessionDB extends SQLiteBase {
1020
1036
  */
1021
1037
  getSessionIdsForProject(projectDir) {
1022
1038
  try {
1039
+ // #827: match by canonical path shape, not raw bytes. The host adapter
1040
+ // may store `project_dir` in a different separator / trailing-slash
1041
+ // shape than the search path resolves the scope in — most visibly on
1042
+ // Windows, where attribution often carries `C:\Users\me\proj` while the
1043
+ // server resolves `C:/Users/me/proj`. An exact `project_dir = ?` match
1044
+ // then returned an EMPTY allow-set and ctx_search reported "No results
1045
+ // found" even though the content was present. We fold BOTH sides through
1046
+ // the same canonical rule used for project-hash stability
1047
+ // (normalizeWorktreePath): backslash → forward slash, then strip the
1048
+ // trailing slash. Normalizing in SQL (RTRIM(REPLACE(...))) covers rows
1049
+ // already written un-normalized without a migration, while the JS-side
1050
+ // normalize keeps the bound parameter in the identical shape. This
1051
+ // preserves the #737 project scope — distinct directories still differ
1052
+ // after normalization, so cross-project isolation is intact.
1053
+ const normalized = normalizeWorktreePath(projectDir);
1023
1054
  const rows = this.db
1024
1055
  .prepare(`SELECT DISTINCT session_id
1025
1056
  FROM session_events
1026
- WHERE project_dir = ?`)
1027
- .all(projectDir);
1057
+ WHERE RTRIM(REPLACE(project_dir, '\\', '/'), '/') = ?`)
1058
+ .all(normalized);
1028
1059
  return rows.map((r) => r.session_id);
1029
1060
  }
1030
1061
  catch {
@@ -1063,6 +1094,7 @@ export class SessionDB extends SQLiteBase {
1063
1094
  getSessionRollup(sessionId) {
1064
1095
  const main = this.stmt(S.getSessionRollup).get(sessionId);
1065
1096
  const maxRow = this.stmt(S.getMaxFileEdits).get(sessionId);
1097
+ const commitRow = this.stmt(S.getLatestCommitMessage).get(sessionId);
1066
1098
  const meta = this.getSessionStats(sessionId);
1067
1099
  // edit_test_cycles: heuristic — min(file edits, errors) approximates
1068
1100
  // the number of edit-then-test attempts in a session. Exact pattern
@@ -1080,6 +1112,7 @@ export class SessionDB extends SQLiteBase {
1080
1112
  unique_files: main?.unique_files ?? 0,
1081
1113
  max_file_edits: maxRow?.max_file_edits ?? 0,
1082
1114
  has_commit: main?.has_commit ?? 0,
1115
+ commit_message: commitRow?.data ?? "",
1083
1116
  edit_test_cycles: editTestCycles,
1084
1117
  duration_min: main?.duration_min ?? 0,
1085
1118
  compact_count: meta?.compact_count ?? 0,
@@ -61,3 +61,49 @@ export declare function extractEvents(rawInput: HookInput): SessionEvent[];
61
61
  * Returns an array of zero or more SessionEvents. Never throws.
62
62
  */
63
63
  export declare function extractUserEvents(message: string): SessionEvent[];
64
+ /**
65
+ * Issue #4 (new PRD) — SessionStart settings + MCP servers snapshot.
66
+ *
67
+ * Emits ONE session_settings_snapshot event when ≥1 setting is available
68
+ * on the SessionStart input. The data field carries key:value tokens
69
+ * (mcp_count, mcp_servers, model, permission_mode) so the platform can
70
+ * compute MCP integration counts and primary-model adoption per org.
71
+ * mcp_servers list is truncated to first 8 names.
72
+ */
73
+ export declare function extractSessionSettings(input: unknown): SessionEvent[];
74
+ /**
75
+ * §11 Layer 1 + Layer 3 — multilingual prompt features.
76
+ *
77
+ * Reference: context-mode-platform/docs/prds/2026-06-insight-data-flow/
78
+ * 11-multilingual-prompt-algorithm.md
79
+ *
80
+ * Script-agnostic via Unicode property regex (`\p{L}`, `\p{Lu}`,
81
+ * `\p{Script=X}`). No per-language tables, no franc/fasttext deps.
82
+ * Layer 1 returns 10 numeric/string features; Layer 3 appends a
83
+ * `prompt_word_tokens: string[]` array for the platform's streaming
84
+ * word-frequency UPSERT.
85
+ *
86
+ * Privacy: features carry no prose. Layer 3 tokens are deduped
87
+ * letter-only words ≥3 chars; platform aggregates by (org_id, week,
88
+ * word) so no individual token surfaces in UI.
89
+ */
90
+ export interface PromptFeatures {
91
+ prompt_length: number;
92
+ prompt_word_count: number;
93
+ prompt_uppercase_ratio: number;
94
+ prompt_file_ref_count: number;
95
+ prompt_path_ref_count: number;
96
+ prompt_script_primary: string | null;
97
+ prompt_script_count: number;
98
+ prompt_question_glyph_count: number;
99
+ prompt_code_block_count: number;
100
+ prompt_url_count: number;
101
+ prompt_word_tokens: string[];
102
+ }
103
+ /**
104
+ * Verbatim mirror of §11 Layer 1 reference implementation + Layer 3
105
+ * token extraction. Uses Unicode property regex per the spec — the
106
+ * "no regex" project default does NOT apply here because the spec
107
+ * explicitly mandates `\p{Script=X}` for script-agnostic classification.
108
+ */
109
+ export declare function extractUserPromptFeatures(prompt: unknown): PromptFeatures;