laminark 0.1.0 → 2.21.7

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 (52) hide show
  1. package/.claude-plugin/marketplace.json +15 -0
  2. package/README.md +71 -36
  3. package/package.json +7 -9
  4. package/plugin/.claude-plugin/plugin.json +1 -1
  5. package/plugin/dist/hooks/handler.d.ts +1 -3
  6. package/plugin/dist/hooks/handler.d.ts.map +1 -1
  7. package/plugin/dist/hooks/handler.js +22 -310
  8. package/plugin/dist/hooks/handler.js.map +1 -1
  9. package/plugin/dist/index.d.ts +1 -3
  10. package/plugin/dist/index.d.ts.map +1 -1
  11. package/plugin/dist/index.js +392 -1895
  12. package/plugin/dist/index.js.map +1 -1
  13. package/plugin/dist/{observations-CorAAc1A.d.mts → observations-Ch0nc47i.d.mts} +1 -23
  14. package/plugin/dist/observations-Ch0nc47i.d.mts.map +1 -0
  15. package/plugin/dist/{tool-registry-e710BvXq.mjs → tool-registry-CZ3mJ4iR.mjs} +13 -932
  16. package/plugin/dist/tool-registry-CZ3mJ4iR.mjs.map +1 -0
  17. package/plugin/hooks/hooks.json +6 -6
  18. package/plugin/scripts/README.md +1 -19
  19. package/plugin/scripts/bump-version.sh +3 -1
  20. package/plugin/scripts/ensure-deps.sh +2 -5
  21. package/plugin/scripts/install.sh +39 -115
  22. package/plugin/scripts/local-install.sh +58 -93
  23. package/plugin/scripts/setup-tmpdir.sh +65 -0
  24. package/plugin/scripts/uninstall.sh +38 -76
  25. package/plugin/scripts/update.sh +69 -20
  26. package/plugin/scripts/verify-install.sh +25 -69
  27. package/plugin/ui/activity.js +0 -12
  28. package/plugin/ui/app.js +54 -24
  29. package/plugin/ui/graph.js +186 -413
  30. package/plugin/ui/help.js +172 -876
  31. package/plugin/ui/index.html +242 -506
  32. package/plugin/ui/settings.js +17 -781
  33. package/plugin/ui/styles.css +44 -990
  34. package/plugin/ui/timeline.js +2 -2
  35. package/plugin/CLAUDE.md +0 -10
  36. package/plugin/commands/recall.md +0 -55
  37. package/plugin/commands/remember.md +0 -34
  38. package/plugin/commands/resume.md +0 -45
  39. package/plugin/commands/stash.md +0 -34
  40. package/plugin/commands/status.md +0 -33
  41. package/plugin/dist/observations-CorAAc1A.d.mts.map +0 -1
  42. package/plugin/dist/tool-registry-e710BvXq.mjs.map +0 -1
  43. package/plugin/laminark.db +0 -0
  44. package/plugin/package.json +0 -17
  45. package/plugin/scripts/dev-sync.sh +0 -58
  46. package/plugin/ui/help/activity-feed.png +0 -0
  47. package/plugin/ui/help/analysis-panel.png +0 -0
  48. package/plugin/ui/help/graph-toolbar.png +0 -0
  49. package/plugin/ui/help/graph-view.png +0 -0
  50. package/plugin/ui/help/settings.png +0 -0
  51. package/plugin/ui/help/timeline.png +0 -0
  52. package/plugin/ui/tools.js +0 -826
@@ -1,8 +1,8 @@
1
- import { a as isDebugEnabled, t as getConfigDir } from "./config-t8LZeB-u.mjs";
1
+ import { a as isDebugEnabled } from "./config-t8LZeB-u.mjs";
2
2
  import Database from "better-sqlite3";
3
3
  import * as sqliteVec from "sqlite-vec";
4
- import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
5
- import { dirname, join } from "node:path";
4
+ import { mkdirSync } from "node:fs";
5
+ import { dirname } from "node:path";
6
6
  import { randomBytes } from "node:crypto";
7
7
  import { z } from "zod";
8
8
 
@@ -81,8 +81,6 @@ function debugTimed(category, message, fn) {
81
81
  * Migration 018: Tool registry FTS5 + vec0 tables for hybrid search on tool descriptions.
82
82
  * Migration 019: Add status column (active/stale/demoted) to tool_registry for staleness management.
83
83
  * Migration 020: Debug path tables (debug_paths + path_waypoints) for resolution path tracking.
84
- * Migration 021: Thought branch tables for coherent work unit tracking.
85
- * Migration 022: Add trigger_hints column to tool_registry for proactive suggestion matching.
86
84
  */
87
85
  const MIGRATIONS = [
88
86
  {
@@ -579,60 +577,6 @@ const MIGRATIONS = [
579
577
 
580
578
  CREATE INDEX IF NOT EXISTS idx_path_waypoints_path_order
581
579
  ON path_waypoints(path_id, sequence_order);
582
- `
583
- },
584
- {
585
- version: 21,
586
- name: "create_thought_branch_tables",
587
- up: `
588
- CREATE TABLE IF NOT EXISTS thought_branches (
589
- id TEXT PRIMARY KEY,
590
- project_hash TEXT NOT NULL,
591
- session_id TEXT,
592
- status TEXT NOT NULL DEFAULT 'active'
593
- CHECK(status IN ('active', 'completed', 'abandoned', 'merged')),
594
- branch_type TEXT NOT NULL DEFAULT 'unknown'
595
- CHECK(branch_type IN ('investigation', 'bug_fix', 'feature', 'refactor', 'research', 'unknown')),
596
- arc_stage TEXT NOT NULL DEFAULT 'investigation'
597
- CHECK(arc_stage IN ('investigation', 'diagnosis', 'planning', 'execution', 'verification', 'completed')),
598
- title TEXT,
599
- summary TEXT,
600
- parent_branch_id TEXT REFERENCES thought_branches(id),
601
- linked_debug_path_id TEXT,
602
- trigger_source TEXT,
603
- trigger_observation_id TEXT,
604
- observation_count INTEGER NOT NULL DEFAULT 0,
605
- tool_pattern TEXT DEFAULT '{}',
606
- started_at TEXT NOT NULL DEFAULT (datetime('now')),
607
- ended_at TEXT,
608
- created_at TEXT NOT NULL DEFAULT (datetime('now'))
609
- );
610
-
611
- CREATE TABLE IF NOT EXISTS branch_observations (
612
- branch_id TEXT NOT NULL REFERENCES thought_branches(id) ON DELETE CASCADE,
613
- observation_id TEXT NOT NULL,
614
- sequence_order INTEGER NOT NULL,
615
- tool_name TEXT,
616
- arc_stage_at_add TEXT,
617
- created_at TEXT NOT NULL DEFAULT (datetime('now')),
618
- PRIMARY KEY (branch_id, observation_id)
619
- );
620
-
621
- CREATE INDEX IF NOT EXISTS idx_thought_branches_project_status
622
- ON thought_branches(project_hash, status);
623
- CREATE INDEX IF NOT EXISTS idx_thought_branches_session
624
- ON thought_branches(session_id);
625
- CREATE INDEX IF NOT EXISTS idx_thought_branches_started
626
- ON thought_branches(started_at DESC);
627
- CREATE INDEX IF NOT EXISTS idx_branch_observations_obs
628
- ON branch_observations(observation_id);
629
- `
630
- },
631
- {
632
- version: 22,
633
- name: "add_tool_registry_trigger_hints",
634
- up: `
635
- ALTER TABLE tool_registry ADD COLUMN trigger_hints TEXT;
636
580
  `
637
581
  }
638
582
  ];
@@ -861,32 +805,11 @@ var ObservationRepository = class {
861
805
  return rowToObservation(row);
862
806
  }
863
807
  /**
864
- * Resolves a full or prefix ID to the full 32-char ID.
865
- * Observation IDs are 32-char hex strings. Search results display only the
866
- * first 8 chars via shortId(). This method allows callers to pass either
867
- * a full ID or an 8-char (or any-length) prefix and get the full ID back.
868
- * Returns null if no unique match is found.
869
- */
870
- resolveId(id) {
871
- if (id.length === 32) return id;
872
- const rows = this.db.prepare("SELECT id FROM observations WHERE project_hash = ? AND id LIKE ? ESCAPE '\\' LIMIT 2").all(this.projectHash, id.replace(/[%_\\]/g, "\\$&") + "%");
873
- if (rows.length === 1) return rows[0].id;
874
- if (rows.length > 1) debug("obs", "Ambiguous ID prefix - multiple matches", {
875
- prefix: id,
876
- count: rows.length
877
- });
878
- return null;
879
- }
880
- /**
881
808
  * Gets an observation by ID, scoped to this project.
882
- * Accepts full 32-char IDs or shorter prefix strings (e.g. the 8-char
883
- * display IDs shown in search results).
884
809
  * Returns null if not found or soft-deleted.
885
810
  */
886
811
  getById(id) {
887
- const resolvedId = this.resolveId(id);
888
- if (!resolvedId) return null;
889
- const row = this.stmtGetById.get(resolvedId, this.projectHash);
812
+ const row = this.stmtGetById.get(id, this.projectHash);
890
813
  return row ? rowToObservation(row) : null;
891
814
  }
892
815
  /**
@@ -955,26 +878,20 @@ var ObservationRepository = class {
955
878
  }
956
879
  /**
957
880
  * Soft-deletes an observation by setting deleted_at.
958
- * Accepts full 32-char IDs or shorter prefix strings (e.g. the 8-char
959
- * display IDs shown in search results).
960
881
  * Returns true if the observation was found and deleted.
961
882
  */
962
883
  softDelete(id) {
963
884
  debug("obs", "Soft-deleting observation", { id });
964
- const resolvedId = this.resolveId(id) ?? id;
965
- const result = this.stmtSoftDelete.run(resolvedId, this.projectHash);
966
- debug("obs", result.changes > 0 ? "Observation soft-deleted" : "Observation not found for delete", { id: resolvedId });
885
+ const result = this.stmtSoftDelete.run(id, this.projectHash);
886
+ debug("obs", result.changes > 0 ? "Observation soft-deleted" : "Observation not found for delete", { id });
967
887
  return result.changes > 0;
968
888
  }
969
889
  /**
970
890
  * Restores a soft-deleted observation by clearing deleted_at.
971
- * Accepts full 32-char IDs or shorter prefix strings (e.g. the 8-char
972
- * display IDs shown in search results).
973
891
  * Returns true if the observation was found and restored.
974
892
  */
975
893
  restore(id) {
976
- const resolvedId = this.resolveId(id) ?? id;
977
- return this.stmtRestore.run(resolvedId, this.projectHash).changes > 0;
894
+ return this.stmtRestore.run(id, this.projectHash).changes > 0;
978
895
  }
979
896
  /**
980
897
  * Updates the classification of an observation.
@@ -1011,18 +928,6 @@ var ObservationRepository = class {
1011
928
  ORDER BY created_at ASC
1012
929
  LIMIT ?
1013
930
  `).all(this.projectHash, limit).map(rowToObservation);
1014
- }
1015
- /**
1016
- * Lists unclassified observations across ALL projects.
1017
- * Used by HaikuProcessor to avoid missing observations from other projects.
1018
- */
1019
- static listAllUnclassified(db, limit = 20) {
1020
- return db.prepare(`
1021
- SELECT * FROM observations
1022
- WHERE classification IS NULL AND deleted_at IS NULL
1023
- ORDER BY created_at ASC
1024
- LIMIT ?
1025
- `).all(limit).map(rowToObservation);
1026
931
  }
1027
932
  /**
1028
933
  * Fetches observations surrounding a given timestamp for classification context.
@@ -1057,28 +962,11 @@ var ObservationRepository = class {
1057
962
  }
1058
963
  /**
1059
964
  * Gets an observation by ID, including soft-deleted observations.
1060
- * Accepts full 32-char IDs or shorter prefix strings (e.g. the 8-char
1061
- * display IDs shown in search results).
1062
965
  * Used by the recall tool for restore operations (must find purged items).
1063
966
  */
1064
967
  getByIdIncludingDeleted(id) {
1065
968
  debug("obs", "Getting observation including deleted", { id });
1066
- const FULL_ID_LENGTH = 32;
1067
- let resolvedId;
1068
- if (id.length === FULL_ID_LENGTH) resolvedId = id;
1069
- else {
1070
- const rows = this.db.prepare("SELECT id FROM observations WHERE project_hash = ? AND id LIKE ? ESCAPE '\\' LIMIT 2").all(this.projectHash, id.replace(/[%_\\]/g, "\\$&") + "%");
1071
- if (rows.length === 0) return null;
1072
- if (rows.length > 1) {
1073
- debug("obs", "Ambiguous ID prefix for getByIdIncludingDeleted", {
1074
- prefix: id,
1075
- count: rows.length
1076
- });
1077
- return null;
1078
- }
1079
- resolvedId = rows[0].id;
1080
- }
1081
- const row = this.stmtGetByIdIncludingDeleted.get(resolvedId, this.projectHash);
969
+ const row = this.stmtGetByIdIncludingDeleted.get(id, this.projectHash);
1082
970
  return row ? rowToObservation(row) : null;
1083
971
  }
1084
972
  /**
@@ -1857,570 +1745,6 @@ function insertEdge(db, edge) {
1857
1745
  return rowToEdge(db.prepare("SELECT * FROM graph_edges WHERE source_id = ? AND target_id = ? AND type = ?").get(edge.source_id, edge.target_id, edge.type));
1858
1746
  }
1859
1747
 
1860
- //#endregion
1861
- //#region src/graph/staleness.ts
1862
- /**
1863
- * Negation patterns: newer observation negates older one.
1864
- * Matches when newer text contains negation keywords absent in older text
1865
- * and both discuss similar subjects.
1866
- */
1867
- const NEGATION_KEYWORDS = [
1868
- "not",
1869
- "don't",
1870
- "no longer",
1871
- "stopped",
1872
- "never",
1873
- "doesn't",
1874
- "won't",
1875
- "isn't",
1876
- "aren't",
1877
- "discontinued"
1878
- ];
1879
- /**
1880
- * Replacement patterns: newer observation explicitly replaces older approach.
1881
- */
1882
- const REPLACEMENT_PATTERNS = [
1883
- /switched\s+(?:from\s+\S+\s+)?to\b/i,
1884
- /migrated\s+(?:from\s+\S+\s+)?to\b/i,
1885
- /replaced\s+(?:\S+\s+)?with\b/i,
1886
- /changed\s+from\b/i,
1887
- /moved\s+(?:from\s+\S+\s+)?to\b/i,
1888
- /upgraded\s+(?:from\s+\S+\s+)?to\b/i,
1889
- /swapped\s+(?:\S+\s+)?(?:for|with)\b/i
1890
- ];
1891
- /**
1892
- * Status change patterns: newer observation marks something as inactive.
1893
- */
1894
- const STATUS_CHANGE_KEYWORDS = [
1895
- "removed",
1896
- "deleted",
1897
- "deprecated",
1898
- "archived",
1899
- "dropped",
1900
- "disabled",
1901
- "decommissioned",
1902
- "sunset",
1903
- "abandoned"
1904
- ];
1905
- /**
1906
- * Creates the staleness_flags table if it doesn't exist.
1907
- * Uses a separate table rather than modifying the observations table,
1908
- * keeping staleness metadata decoupled from core observation storage.
1909
- */
1910
- function initStalenessSchema(db) {
1911
- db.exec(`
1912
- CREATE TABLE IF NOT EXISTS staleness_flags (
1913
- observation_id TEXT PRIMARY KEY,
1914
- flagged_at TEXT NOT NULL DEFAULT (datetime('now')),
1915
- reason TEXT NOT NULL,
1916
- resolved INTEGER NOT NULL DEFAULT 0
1917
- );
1918
- CREATE INDEX IF NOT EXISTS idx_staleness_resolved ON staleness_flags(resolved);
1919
- `);
1920
- }
1921
- /**
1922
- * Detects potential staleness (contradictions) between observations
1923
- * linked to a specific entity.
1924
- *
1925
- * Compares consecutive observation pairs chronologically and checks for:
1926
- * 1. Negation patterns (newer negates older)
1927
- * 2. Replacement patterns (newer replaces older approach)
1928
- * 3. Status change patterns (newer marks something as inactive)
1929
- *
1930
- * This is DETECTION ONLY -- no data is modified.
1931
- *
1932
- * @param db - better-sqlite3 Database handle
1933
- * @param entityId - Graph node ID to check observations for
1934
- * @returns Array of StalenessReport for each detected contradiction
1935
- */
1936
- function detectStaleness(db, entityId) {
1937
- const node = db.prepare("SELECT id, name, type, observation_ids FROM graph_nodes WHERE id = ?").get(entityId);
1938
- if (!node) return [];
1939
- const obsIds = JSON.parse(node.observation_ids);
1940
- if (obsIds.length < 2) return [];
1941
- const placeholders = obsIds.map(() => "?").join(", ");
1942
- const observations = db.prepare(`SELECT * FROM observations WHERE id IN (${placeholders}) AND deleted_at IS NULL ORDER BY created_at ASC`).all(...obsIds).map(rowToObservation);
1943
- if (observations.length < 2) return [];
1944
- const reports = [];
1945
- const now = (/* @__PURE__ */ new Date()).toISOString();
1946
- for (let i = 0; i < observations.length - 1; i++) {
1947
- const older = observations[i];
1948
- const newer = observations[i + 1];
1949
- const reason = detectContradiction(older.content, newer.content);
1950
- if (reason) reports.push({
1951
- entityId: node.id,
1952
- entityName: node.name,
1953
- entityType: node.type,
1954
- newerObservation: {
1955
- id: newer.id,
1956
- text: newer.content,
1957
- created_at: newer.createdAt
1958
- },
1959
- olderObservation: {
1960
- id: older.id,
1961
- text: older.content,
1962
- created_at: older.createdAt
1963
- },
1964
- reason,
1965
- detectedAt: now
1966
- });
1967
- }
1968
- return reports;
1969
- }
1970
- /**
1971
- * Detects contradiction between two observation texts.
1972
- * Returns a human-readable reason string, or null if no contradiction found.
1973
- */
1974
- function detectContradiction(olderText, newerText) {
1975
- const olderLower = olderText.toLowerCase();
1976
- const newerLower = newerText.toLowerCase();
1977
- const negationResult = detectNegation(olderLower, newerLower);
1978
- if (negationResult) return negationResult;
1979
- const replacementResult = detectReplacement(newerLower);
1980
- if (replacementResult) return replacementResult;
1981
- const statusResult = detectStatusChange(olderLower, newerLower);
1982
- if (statusResult) return statusResult;
1983
- return null;
1984
- }
1985
- /**
1986
- * Detects negation: newer text contains negation keywords that are absent
1987
- * in the older text, suggesting the newer observation contradicts the older.
1988
- */
1989
- function detectNegation(olderLower, newerLower) {
1990
- for (const keyword of NEGATION_KEYWORDS) if (newerLower.includes(keyword) && !olderLower.includes(keyword)) return `Newer observation contains negation ("${keyword}") not present in older observation`;
1991
- return null;
1992
- }
1993
- /**
1994
- * Detects replacement: newer text explicitly mentions switching/replacing.
1995
- */
1996
- function detectReplacement(newerLower) {
1997
- for (const pattern of REPLACEMENT_PATTERNS) {
1998
- const match = newerLower.match(pattern);
1999
- if (match) return `Newer observation indicates replacement ("${match[0].trim()}")`;
2000
- }
2001
- return null;
2002
- }
2003
- /**
2004
- * Detects status change: newer text marks something as removed/deprecated
2005
- * when the older text described it as active/present.
2006
- */
2007
- function detectStatusChange(olderLower, newerLower) {
2008
- for (const keyword of STATUS_CHANGE_KEYWORDS) if (newerLower.includes(keyword) && !olderLower.includes(keyword)) return `Newer observation indicates status change ("${keyword}")`;
2009
- return null;
2010
- }
2011
- /**
2012
- * Flags an observation as stale with an advisory reason.
2013
- *
2014
- * This flag is advisory -- search can use it to deprioritize but never hide
2015
- * the observation. The observation remains fully queryable.
2016
- *
2017
- * Uses INSERT OR REPLACE to allow re-flagging with an updated reason.
2018
- *
2019
- * @param db - better-sqlite3 Database handle
2020
- * @param observationId - ID of the observation to flag
2021
- * @param reason - Human-readable explanation of why it's stale
2022
- */
2023
- function flagStaleObservation(db, observationId, reason) {
2024
- initStalenessSchema(db);
2025
- db.prepare(`INSERT OR REPLACE INTO staleness_flags (observation_id, reason, resolved)
2026
- VALUES (?, ?, 0)`).run(observationId, reason);
2027
- }
2028
-
2029
- //#endregion
2030
- //#region src/config/hygiene-config.ts
2031
- /**
2032
- * Database Hygiene Configuration
2033
- *
2034
- * Controls signal weights and tier thresholds used by the hygiene
2035
- * analyzer to score observations for deletion candidacy.
2036
- *
2037
- * Configuration is loaded from .laminark/hygiene.json with
2038
- * a 5-second cache to avoid repeated disk reads.
2039
- */
2040
- const DEFAULT_AUTO_CLEANUP = {
2041
- enabled: true,
2042
- tier: "high",
2043
- maxOrphanNodes: 500
2044
- };
2045
- const DEFAULTS = {
2046
- signalWeights: {
2047
- orphaned: .3,
2048
- islandNode: .25,
2049
- noiseClassified: .25,
2050
- shortContent: .1,
2051
- autoCaptured: .1,
2052
- stale: .1
2053
- },
2054
- tierThresholds: {
2055
- high: .7,
2056
- medium: .5
2057
- },
2058
- shortContentThreshold: 50,
2059
- autoCleanup: { ...DEFAULT_AUTO_CLEANUP }
2060
- };
2061
- const CACHE_TTL_MS = 5e3;
2062
- let cachedConfig = null;
2063
- let cachedAt = 0;
2064
- function clamp(value, min, max) {
2065
- return Math.max(min, Math.min(max, value));
2066
- }
2067
- function validate(raw) {
2068
- const config = { ...DEFAULTS };
2069
- if (raw.signalWeights && typeof raw.signalWeights === "object" && !Array.isArray(raw.signalWeights)) {
2070
- const sw = raw.signalWeights;
2071
- const weights = { ...DEFAULTS.signalWeights };
2072
- for (const key of Object.keys(DEFAULTS.signalWeights)) if (typeof sw[key] === "number") weights[key] = clamp(sw[key], 0, 1);
2073
- config.signalWeights = weights;
2074
- }
2075
- if (raw.tierThresholds && typeof raw.tierThresholds === "object" && !Array.isArray(raw.tierThresholds)) {
2076
- const tt = raw.tierThresholds;
2077
- let high = typeof tt.high === "number" ? clamp(tt.high, 0, 1) : DEFAULTS.tierThresholds.high;
2078
- let medium = typeof tt.medium === "number" ? clamp(tt.medium, 0, 1) : DEFAULTS.tierThresholds.medium;
2079
- if (medium >= high) medium = Math.max(0, high - .1);
2080
- config.tierThresholds = {
2081
- high,
2082
- medium
2083
- };
2084
- }
2085
- if (typeof raw.shortContentThreshold === "number") config.shortContentThreshold = Math.max(0, Math.round(raw.shortContentThreshold));
2086
- if (raw.autoCleanup && typeof raw.autoCleanup === "object" && !Array.isArray(raw.autoCleanup)) {
2087
- const ac = raw.autoCleanup;
2088
- const cleanup = { ...DEFAULT_AUTO_CLEANUP };
2089
- if (typeof ac.enabled === "boolean") cleanup.enabled = ac.enabled;
2090
- if (ac.tier === "high" || ac.tier === "medium" || ac.tier === "all") cleanup.tier = ac.tier;
2091
- if (typeof ac.maxOrphanNodes === "number") cleanup.maxOrphanNodes = Math.max(0, Math.round(ac.maxOrphanNodes));
2092
- config.autoCleanup = cleanup;
2093
- }
2094
- return config;
2095
- }
2096
- /**
2097
- * Loads hygiene configuration from disk with a 5-second cache.
2098
- */
2099
- function loadHygieneConfig() {
2100
- const now = Date.now();
2101
- if (cachedConfig && now - cachedAt < CACHE_TTL_MS) return cachedConfig;
2102
- const configPath = join(getConfigDir(), "hygiene.json");
2103
- try {
2104
- const content = readFileSync(configPath, "utf-8");
2105
- cachedConfig = validate(JSON.parse(content));
2106
- debug("config", "Loaded hygiene config", cachedConfig);
2107
- } catch {
2108
- cachedConfig = {
2109
- ...DEFAULTS,
2110
- signalWeights: { ...DEFAULTS.signalWeights },
2111
- tierThresholds: { ...DEFAULTS.tierThresholds }
2112
- };
2113
- }
2114
- cachedAt = now;
2115
- return cachedConfig;
2116
- }
2117
- /**
2118
- * Saves hygiene configuration to disk and invalidates cache.
2119
- */
2120
- function saveHygieneConfig(config) {
2121
- writeFileSync(join(getConfigDir(), "hygiene.json"), JSON.stringify(config, null, 2), "utf-8");
2122
- cachedConfig = config;
2123
- cachedAt = Date.now();
2124
- }
2125
- /**
2126
- * Resets hygiene config to defaults by invalidating cache.
2127
- */
2128
- function resetHygieneConfig() {
2129
- cachedConfig = null;
2130
- cachedAt = 0;
2131
- return {
2132
- ...DEFAULTS,
2133
- signalWeights: { ...DEFAULTS.signalWeights },
2134
- tierThresholds: { ...DEFAULTS.tierThresholds },
2135
- autoCleanup: { ...DEFAULT_AUTO_CLEANUP }
2136
- };
2137
- }
2138
-
2139
- //#endregion
2140
- //#region src/graph/hygiene-analyzer.ts
2141
- function buildSignalLookups(db) {
2142
- const linkedObsIds = /* @__PURE__ */ new Set();
2143
- const islandObsIds = /* @__PURE__ */ new Set();
2144
- const allNodes = db.prepare("SELECT id, type, name, observation_ids FROM graph_nodes").all();
2145
- const edgeCounts = /* @__PURE__ */ new Map();
2146
- const edgeRows = db.prepare(`SELECT source_id AS nid, COUNT(*) AS cnt FROM graph_edges GROUP BY source_id
2147
- UNION ALL
2148
- SELECT target_id AS nid, COUNT(*) AS cnt FROM graph_edges GROUP BY target_id`).all();
2149
- for (const row of edgeRows) edgeCounts.set(row.nid, (edgeCounts.get(row.nid) ?? 0) + row.cnt);
2150
- for (const node of allNodes) {
2151
- let obsIds;
2152
- try {
2153
- obsIds = JSON.parse(node.observation_ids);
2154
- } catch {
2155
- continue;
2156
- }
2157
- const degree = edgeCounts.get(node.id) ?? 0;
2158
- for (const oid of obsIds) {
2159
- linkedObsIds.add(oid);
2160
- if (degree === 0) islandObsIds.add(oid);
2161
- }
2162
- }
2163
- const staleIds = /* @__PURE__ */ new Set();
2164
- try {
2165
- initStalenessSchema(db);
2166
- const staleRows = db.prepare("SELECT observation_id FROM staleness_flags WHERE resolved = 0").all();
2167
- for (const row of staleRows) staleIds.add(row.observation_id);
2168
- } catch {}
2169
- return {
2170
- linkedObsIds,
2171
- islandObsIds,
2172
- staleIds,
2173
- allNodes,
2174
- edgeCounts
2175
- };
2176
- }
2177
- function scoreObservation(obs, lookups, config) {
2178
- const weights = config.signalWeights;
2179
- const thresholds = config.tierThresholds;
2180
- const signals = {
2181
- orphaned: !lookups.linkedObsIds.has(obs.id),
2182
- islandNode: lookups.islandObsIds.has(obs.id),
2183
- noiseClassified: obs.classification === "noise",
2184
- shortContent: obs.content.length < config.shortContentThreshold,
2185
- autoCaptured: obs.source.startsWith("hook:"),
2186
- stale: lookups.staleIds.has(obs.id)
2187
- };
2188
- const confidence = (signals.orphaned ? weights.orphaned : 0) + (signals.islandNode ? weights.islandNode : 0) + (signals.noiseClassified ? weights.noiseClassified : 0) + (signals.shortContent ? weights.shortContent : 0) + (signals.autoCaptured ? weights.autoCaptured : 0) + (signals.stale ? weights.stale : 0);
2189
- const tier = confidence >= thresholds.high ? "high" : confidence >= thresholds.medium ? "medium" : "low";
2190
- return {
2191
- signals,
2192
- confidence: Math.round(confidence * 100) / 100,
2193
- tier
2194
- };
2195
- }
2196
- /**
2197
- * Analyzes all active observations and scores each on deletion signals.
2198
- * Pure read-only — no data is modified.
2199
- */
2200
- function analyzeObservations(db, projectHash, opts) {
2201
- const limit = opts?.limit ?? 50;
2202
- const minTier = opts?.minTier ?? "medium";
2203
- const config = opts?.config ?? loadHygieneConfig();
2204
- debug("hygiene", "Starting analysis", {
2205
- projectHash,
2206
- sessionId: opts?.sessionId
2207
- });
2208
- let obsSql = `
2209
- SELECT id, content, title, source, kind, session_id, classification, created_at
2210
- FROM observations
2211
- WHERE project_hash = ? AND deleted_at IS NULL
2212
- `;
2213
- const obsParams = [projectHash];
2214
- if (opts?.sessionId) {
2215
- obsSql += " AND session_id = ?";
2216
- obsParams.push(opts.sessionId);
2217
- }
2218
- obsSql += " ORDER BY created_at DESC";
2219
- const observations = db.prepare(obsSql).all(...obsParams);
2220
- const lookups = buildSignalLookups(db);
2221
- const allCandidates = [];
2222
- for (const obs of observations) {
2223
- const { signals, confidence, tier } = scoreObservation(obs, lookups, config);
2224
- if (minTier === "high" && tier !== "high") continue;
2225
- if (minTier === "medium" && tier === "low") continue;
2226
- const preview = obs.content.length > 80 ? obs.content.substring(0, 80) + "..." : obs.content;
2227
- allCandidates.push({
2228
- id: obs.id,
2229
- shortId: obs.id.substring(0, 8),
2230
- sessionId: obs.session_id,
2231
- kind: obs.kind,
2232
- source: obs.source,
2233
- contentPreview: preview,
2234
- createdAt: obs.created_at,
2235
- signals,
2236
- confidence,
2237
- tier
2238
- });
2239
- }
2240
- allCandidates.sort((a, b) => b.confidence - a.confidence);
2241
- const activeObsIds = new Set(observations.map((o) => o.id));
2242
- const orphanNodes = [];
2243
- for (const node of lookups.allNodes) {
2244
- if ((lookups.edgeCounts.get(node.id) ?? 0) > 0) continue;
2245
- let obsIds;
2246
- try {
2247
- obsIds = JSON.parse(node.observation_ids);
2248
- } catch {
2249
- continue;
2250
- }
2251
- const allDead = obsIds.length === 0 || obsIds.every((oid) => !activeObsIds.has(oid));
2252
- orphanNodes.push({
2253
- id: node.id,
2254
- type: node.type,
2255
- name: node.name,
2256
- reason: allDead ? "zero edges, dead observation refs" : "zero edges (island node)"
2257
- });
2258
- }
2259
- const limited = allCandidates.slice(0, limit);
2260
- const highCount = allCandidates.filter((c) => c.tier === "high").length;
2261
- const mediumCount = allCandidates.filter((c) => c.tier === "medium").length;
2262
- const lowCount = allCandidates.filter((c) => c.tier === "low").length;
2263
- debug("hygiene", "Analysis complete", {
2264
- total: observations.length,
2265
- high: highCount,
2266
- medium: mediumCount,
2267
- orphanNodes: orphanNodes.length
2268
- });
2269
- return {
2270
- analyzedAt: (/* @__PURE__ */ new Date()).toISOString(),
2271
- totalObservations: observations.length,
2272
- candidates: limited,
2273
- orphanNodes: orphanNodes.slice(0, limit),
2274
- summary: {
2275
- high: highCount,
2276
- medium: mediumCount,
2277
- low: lowCount,
2278
- orphanNodeCount: orphanNodes.length
2279
- }
2280
- };
2281
- }
2282
- /**
2283
- * Produces a score distribution report across all observations.
2284
- * Shows signal counts, confidence histogram, and island node summary
2285
- * so users can tune thresholds to catch the right candidates.
2286
- */
2287
- function findAnalysis(db, projectHash, config) {
2288
- const cfg = config ?? loadHygieneConfig();
2289
- const observations = db.prepare(`
2290
- SELECT id, content, title, source, kind, session_id, classification, created_at
2291
- FROM observations
2292
- WHERE project_hash = ? AND deleted_at IS NULL
2293
- ORDER BY created_at DESC
2294
- `).all(projectHash);
2295
- const lookups = buildSignalLookups(db);
2296
- const bySignal = {
2297
- orphaned: 0,
2298
- islandNode: 0,
2299
- noiseClassified: 0,
2300
- shortContent: 0,
2301
- autoCaptured: 0,
2302
- stale: 0
2303
- };
2304
- const buckets = new Array(10).fill(0);
2305
- const islandConfidences = [];
2306
- for (const obs of observations) {
2307
- const { signals, confidence } = scoreObservation(obs, lookups, cfg);
2308
- if (signals.orphaned) bySignal.orphaned++;
2309
- if (signals.islandNode) bySignal.islandNode++;
2310
- if (signals.noiseClassified) bySignal.noiseClassified++;
2311
- if (signals.shortContent) bySignal.shortContent++;
2312
- if (signals.autoCaptured) bySignal.autoCaptured++;
2313
- if (signals.stale) bySignal.stale++;
2314
- const bucketIdx = Math.min(Math.floor(confidence * 10), 9);
2315
- buckets[bucketIdx]++;
2316
- if (signals.islandNode) islandConfidences.push(confidence);
2317
- }
2318
- const distribution = buckets.map((count, i) => ({
2319
- range: `${(i / 10).toFixed(1)}-${((i + 1) / 10).toFixed(1)}`,
2320
- count
2321
- }));
2322
- islandConfidences.sort((a, b) => a - b);
2323
- const islandTotal = islandConfidences.length;
2324
- const minConf = islandTotal > 0 ? islandConfidences[0] : 0;
2325
- const maxConf = islandTotal > 0 ? islandConfidences[islandTotal - 1] : 0;
2326
- const medianConf = islandTotal > 0 ? islandConfidences[Math.floor(islandTotal / 2)] : 0;
2327
- const capturedHigh = islandConfidences.filter((c) => c >= cfg.tierThresholds.high).length;
2328
- const capturedMedium = islandConfidences.filter((c) => c >= cfg.tierThresholds.medium).length;
2329
- return {
2330
- total: observations.length,
2331
- bySignal,
2332
- distribution,
2333
- islandNodes: {
2334
- total: islandTotal,
2335
- minConfidence: Math.round(minConf * 100) / 100,
2336
- maxConfidence: Math.round(maxConf * 100) / 100,
2337
- medianConfidence: Math.round(medianConf * 100) / 100,
2338
- capturedAtCurrentThresholds: {
2339
- high: capturedHigh,
2340
- medium: capturedMedium,
2341
- all: islandTotal
2342
- }
2343
- }
2344
- };
2345
- }
2346
- /**
2347
- * Runs automatic hygiene cleanup at session end.
2348
- *
2349
- * Analyzes observations and purges candidates matching the configured tier.
2350
- * Orphan graph node removal is capped by autoCleanup.maxOrphanNodes.
2351
- * Safe to call on every session end — skips quickly if disabled.
2352
- */
2353
- function runAutoCleanup(db, projectHash, config) {
2354
- const cfg = config ?? loadHygieneConfig();
2355
- const auto = cfg.autoCleanup;
2356
- if (!auto.enabled) return {
2357
- skipped: true,
2358
- reason: "disabled",
2359
- observationsPurged: 0,
2360
- orphanNodesRemoved: 0
2361
- };
2362
- debug("hygiene", "Auto-cleanup starting", {
2363
- tier: auto.tier,
2364
- maxOrphanNodes: auto.maxOrphanNodes
2365
- });
2366
- const report = analyzeObservations(db, projectHash, {
2367
- limit: 200,
2368
- minTier: auto.tier === "all" ? "low" : auto.tier,
2369
- config: cfg
2370
- });
2371
- if (report.orphanNodes.length > auto.maxOrphanNodes) report.orphanNodes = report.orphanNodes.slice(0, auto.maxOrphanNodes);
2372
- if (report.candidates.length + report.orphanNodes.length === 0) {
2373
- debug("hygiene", "Auto-cleanup: nothing to clean");
2374
- return {
2375
- skipped: false,
2376
- observationsPurged: 0,
2377
- orphanNodesRemoved: 0
2378
- };
2379
- }
2380
- const result = executePurge(db, projectHash, report, auto.tier);
2381
- debug("hygiene", "Auto-cleanup complete", {
2382
- observationsPurged: result.observationsPurged,
2383
- orphanNodesRemoved: result.orphanNodesRemoved
2384
- });
2385
- return {
2386
- skipped: false,
2387
- ...result
2388
- };
2389
- }
2390
- function executePurge(db, projectHash, report, tier) {
2391
- const candidateIds = report.candidates.filter((c) => {
2392
- if (tier === "high") return c.tier === "high";
2393
- if (tier === "medium") return c.tier === "high" || c.tier === "medium";
2394
- return true;
2395
- }).map((c) => c.id);
2396
- debug("hygiene", "Executing purge", {
2397
- tier,
2398
- candidates: candidateIds.length
2399
- });
2400
- let observationsPurged = 0;
2401
- const softDeleteStmt = db.prepare(`
2402
- UPDATE observations
2403
- SET deleted_at = datetime('now'), updated_at = datetime('now')
2404
- WHERE id = ? AND project_hash = ? AND deleted_at IS NULL
2405
- `);
2406
- return db.transaction(() => {
2407
- for (const id of candidateIds) {
2408
- const result = softDeleteStmt.run(id, projectHash);
2409
- observationsPurged += result.changes;
2410
- }
2411
- let orphanNodesRemoved = 0;
2412
- const deleteNodeStmt = db.prepare("DELETE FROM graph_nodes WHERE id = ?");
2413
- for (const node of report.orphanNodes) {
2414
- const result = deleteNodeStmt.run(node.id);
2415
- orphanNodesRemoved += result.changes;
2416
- }
2417
- return {
2418
- observationsPurged,
2419
- orphanNodesRemoved
2420
- };
2421
- })();
2422
- }
2423
-
2424
1748
  //#endregion
2425
1749
  //#region src/hooks/tool-name-parser.ts
2426
1750
  /**
@@ -2466,248 +1790,6 @@ function extractServerName(toolName) {
2466
1790
  return null;
2467
1791
  }
2468
1792
 
2469
- //#endregion
2470
- //#region src/branches/branch-repository.ts
2471
- var BranchRepository = class {
2472
- db;
2473
- projectHash;
2474
- stmtCreate;
2475
- stmtComplete;
2476
- stmtAbandon;
2477
- stmtGetActive;
2478
- stmtGetById;
2479
- stmtList;
2480
- stmtListByStatus;
2481
- stmtListByType;
2482
- stmtUpdateArcStage;
2483
- stmtUpdateToolPattern;
2484
- stmtUpdateClassification;
2485
- stmtUpdateSummary;
2486
- stmtIncrementObsCount;
2487
- stmtLinkDebugPath;
2488
- stmtAddObservation;
2489
- stmtGetObservations;
2490
- stmtMaxSequence;
2491
- stmtFindStale;
2492
- stmtFindUnclassified;
2493
- stmtFindRecentCompleted;
2494
- stmtFindRecentActive;
2495
- stmtListRecent;
2496
- constructor(db, projectHash) {
2497
- this.db = db;
2498
- this.projectHash = projectHash;
2499
- this.stmtCreate = db.prepare(`
2500
- INSERT INTO thought_branches
2501
- (id, project_hash, session_id, status, trigger_source, trigger_observation_id, started_at)
2502
- VALUES (?, ?, ?, 'active', ?, ?, datetime('now'))
2503
- `);
2504
- this.stmtComplete = db.prepare(`
2505
- UPDATE thought_branches
2506
- SET status = 'completed', arc_stage = 'completed', ended_at = datetime('now')
2507
- WHERE id = ? AND project_hash = ?
2508
- `);
2509
- this.stmtAbandon = db.prepare(`
2510
- UPDATE thought_branches
2511
- SET status = 'abandoned', ended_at = datetime('now')
2512
- WHERE id = ? AND project_hash = ?
2513
- `);
2514
- this.stmtGetActive = db.prepare(`
2515
- SELECT * FROM thought_branches
2516
- WHERE project_hash = ? AND status = 'active'
2517
- ORDER BY started_at DESC
2518
- LIMIT 1
2519
- `);
2520
- this.stmtGetById = db.prepare(`
2521
- SELECT * FROM thought_branches
2522
- WHERE id = ? AND project_hash = ?
2523
- `);
2524
- this.stmtList = db.prepare(`
2525
- SELECT * FROM thought_branches
2526
- WHERE project_hash = ?
2527
- ORDER BY started_at DESC
2528
- LIMIT ?
2529
- `);
2530
- this.stmtListByStatus = db.prepare(`
2531
- SELECT * FROM thought_branches
2532
- WHERE project_hash = ? AND status = ?
2533
- ORDER BY started_at DESC
2534
- LIMIT ?
2535
- `);
2536
- this.stmtListByType = db.prepare(`
2537
- SELECT * FROM thought_branches
2538
- WHERE project_hash = ? AND branch_type = ?
2539
- ORDER BY started_at DESC
2540
- LIMIT ?
2541
- `);
2542
- this.stmtUpdateArcStage = db.prepare(`
2543
- UPDATE thought_branches SET arc_stage = ? WHERE id = ? AND project_hash = ?
2544
- `);
2545
- this.stmtUpdateToolPattern = db.prepare(`
2546
- UPDATE thought_branches SET tool_pattern = ? WHERE id = ? AND project_hash = ?
2547
- `);
2548
- this.stmtUpdateClassification = db.prepare(`
2549
- UPDATE thought_branches SET branch_type = ?, title = ? WHERE id = ? AND project_hash = ?
2550
- `);
2551
- this.stmtUpdateSummary = db.prepare(`
2552
- UPDATE thought_branches SET summary = ? WHERE id = ? AND project_hash = ?
2553
- `);
2554
- this.stmtIncrementObsCount = db.prepare(`
2555
- UPDATE thought_branches SET observation_count = observation_count + 1 WHERE id = ? AND project_hash = ?
2556
- `);
2557
- this.stmtLinkDebugPath = db.prepare(`
2558
- UPDATE thought_branches SET linked_debug_path_id = ? WHERE id = ? AND project_hash = ?
2559
- `);
2560
- this.stmtAddObservation = db.prepare(`
2561
- INSERT OR IGNORE INTO branch_observations
2562
- (branch_id, observation_id, sequence_order, tool_name, arc_stage_at_add)
2563
- VALUES (?, ?, ?, ?, ?)
2564
- `);
2565
- this.stmtGetObservations = db.prepare(`
2566
- SELECT * FROM branch_observations
2567
- WHERE branch_id = ?
2568
- ORDER BY sequence_order ASC
2569
- `);
2570
- this.stmtMaxSequence = db.prepare(`
2571
- SELECT COALESCE(MAX(sequence_order), 0) AS max_seq FROM branch_observations
2572
- WHERE branch_id = ?
2573
- `);
2574
- this.stmtFindStale = db.prepare(`
2575
- SELECT * FROM thought_branches
2576
- WHERE project_hash = ? AND status = 'active'
2577
- AND started_at < datetime('now', '-24 hours')
2578
- `);
2579
- this.stmtFindUnclassified = db.prepare(`
2580
- SELECT * FROM thought_branches
2581
- WHERE project_hash = ? AND branch_type = 'unknown'
2582
- AND observation_count >= 3
2583
- ORDER BY started_at DESC
2584
- LIMIT ?
2585
- `);
2586
- this.stmtFindRecentCompleted = db.prepare(`
2587
- SELECT * FROM thought_branches
2588
- WHERE project_hash = ? AND status = 'completed' AND summary IS NULL
2589
- AND ended_at > datetime('now', '-1 hour')
2590
- ORDER BY ended_at DESC
2591
- LIMIT ?
2592
- `);
2593
- this.stmtFindRecentActive = db.prepare(`
2594
- SELECT * FROM thought_branches
2595
- WHERE project_hash = ? AND status = 'active'
2596
- AND started_at > datetime('now', '-24 hours')
2597
- ORDER BY started_at DESC
2598
- LIMIT 1
2599
- `);
2600
- this.stmtListRecent = db.prepare(`
2601
- SELECT * FROM thought_branches
2602
- WHERE project_hash = ?
2603
- AND started_at > datetime('now', ? || ' hours')
2604
- ORDER BY started_at DESC
2605
- `);
2606
- }
2607
- createBranch(sessionId, triggerSource, triggerObservationId) {
2608
- const id = randomBytes(16).toString("hex");
2609
- this.stmtCreate.run(id, this.projectHash, sessionId, triggerSource, triggerObservationId ?? null);
2610
- return this.getBranch(id);
2611
- }
2612
- completeBranch(branchId) {
2613
- this.stmtComplete.run(branchId, this.projectHash);
2614
- }
2615
- abandonBranch(branchId) {
2616
- this.stmtAbandon.run(branchId, this.projectHash);
2617
- }
2618
- getActiveBranch() {
2619
- const row = this.stmtGetActive.get(this.projectHash);
2620
- return row ? rowToBranch(row) : null;
2621
- }
2622
- getBranch(branchId) {
2623
- const row = this.stmtGetById.get(branchId, this.projectHash);
2624
- return row ? rowToBranch(row) : null;
2625
- }
2626
- listBranches(limit = 20) {
2627
- return this.stmtList.all(this.projectHash, limit).map(rowToBranch);
2628
- }
2629
- listByStatus(status, limit = 20) {
2630
- return this.stmtListByStatus.all(this.projectHash, status, limit).map(rowToBranch);
2631
- }
2632
- listByType(branchType, limit = 20) {
2633
- return this.stmtListByType.all(this.projectHash, branchType, limit).map(rowToBranch);
2634
- }
2635
- updateArcStage(branchId, stage) {
2636
- this.stmtUpdateArcStage.run(stage, branchId, this.projectHash);
2637
- }
2638
- updateToolPattern(branchId, pattern) {
2639
- this.stmtUpdateToolPattern.run(JSON.stringify(pattern), branchId, this.projectHash);
2640
- }
2641
- updateClassification(branchId, branchType, title) {
2642
- this.stmtUpdateClassification.run(branchType, title, branchId, this.projectHash);
2643
- }
2644
- updateSummary(branchId, summary) {
2645
- this.stmtUpdateSummary.run(summary, branchId, this.projectHash);
2646
- }
2647
- linkDebugPath(branchId, debugPathId) {
2648
- this.stmtLinkDebugPath.run(debugPathId, branchId, this.projectHash);
2649
- }
2650
- addObservation(branchId, observationId, toolName, arcStage) {
2651
- const { max_seq } = this.stmtMaxSequence.get(branchId);
2652
- this.stmtAddObservation.run(branchId, observationId, max_seq + 1, toolName, arcStage);
2653
- this.stmtIncrementObsCount.run(branchId, this.projectHash);
2654
- }
2655
- getObservations(branchId) {
2656
- return this.stmtGetObservations.all(branchId).map(rowToBranchObservation);
2657
- }
2658
- findStaleBranches() {
2659
- return this.stmtFindStale.all(this.projectHash).map(rowToBranch);
2660
- }
2661
- findUnclassifiedBranches(limit = 5) {
2662
- return this.stmtFindUnclassified.all(this.projectHash, limit).map(rowToBranch);
2663
- }
2664
- findRecentCompletedUnsummarized(limit = 3) {
2665
- return this.stmtFindRecentCompleted.all(this.projectHash, limit).map(rowToBranch);
2666
- }
2667
- findRecentActiveBranch() {
2668
- const row = this.stmtFindRecentActive.get(this.projectHash);
2669
- return row ? rowToBranch(row) : null;
2670
- }
2671
- listRecentBranches(hours) {
2672
- return this.stmtListRecent.all(this.projectHash, `-${hours}`).map(rowToBranch);
2673
- }
2674
- };
2675
- function rowToBranch(row) {
2676
- let toolPattern = {};
2677
- try {
2678
- toolPattern = JSON.parse(row.tool_pattern);
2679
- } catch {}
2680
- return {
2681
- id: row.id,
2682
- project_hash: row.project_hash,
2683
- session_id: row.session_id,
2684
- status: row.status,
2685
- branch_type: row.branch_type,
2686
- arc_stage: row.arc_stage,
2687
- title: row.title,
2688
- summary: row.summary,
2689
- parent_branch_id: row.parent_branch_id,
2690
- linked_debug_path_id: row.linked_debug_path_id,
2691
- trigger_source: row.trigger_source,
2692
- trigger_observation_id: row.trigger_observation_id,
2693
- observation_count: row.observation_count,
2694
- tool_pattern: toolPattern,
2695
- started_at: row.started_at,
2696
- ended_at: row.ended_at,
2697
- created_at: row.created_at
2698
- };
2699
- }
2700
- function rowToBranchObservation(row) {
2701
- return {
2702
- branch_id: row.branch_id,
2703
- observation_id: row.observation_id,
2704
- sequence_order: row.sequence_order,
2705
- tool_name: row.tool_name,
2706
- arc_stage_at_add: row.arc_stage_at_add,
2707
- created_at: row.created_at
2708
- };
2709
- }
2710
-
2711
1793
  //#endregion
2712
1794
  //#region src/storage/research-buffer.ts
2713
1795
  /**
@@ -3091,12 +2173,11 @@ var ToolRegistryRepository = class {
3091
2173
  this.db = db;
3092
2174
  try {
3093
2175
  this.stmtUpsert = db.prepare(`
3094
- INSERT INTO tool_registry (name, tool_type, scope, source, project_hash, description, server_name, trigger_hints, discovered_at)
3095
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, datetime('now'))
2176
+ INSERT INTO tool_registry (name, tool_type, scope, source, project_hash, description, server_name, discovered_at)
2177
+ VALUES (?, ?, ?, ?, ?, ?, ?, datetime('now'))
3096
2178
  ON CONFLICT (name, COALESCE(project_hash, ''))
3097
2179
  DO UPDATE SET
3098
2180
  description = COALESCE(excluded.description, tool_registry.description),
3099
- trigger_hints = COALESCE(excluded.trigger_hints, tool_registry.trigger_hints),
3100
2181
  source = excluded.source,
3101
2182
  status = 'active',
3102
2183
  updated_at = datetime('now')
@@ -3227,7 +2308,7 @@ var ToolRegistryRepository = class {
3227
2308
  */
3228
2309
  upsert(tool) {
3229
2310
  try {
3230
- this.stmtUpsert.run(tool.name, tool.toolType, tool.scope, tool.source, tool.projectHash, tool.description, tool.serverName, tool.triggerHints);
2311
+ this.stmtUpsert.run(tool.name, tool.toolType, tool.scope, tool.source, tool.projectHash, tool.description, tool.serverName);
3231
2312
  debug("tool-registry", "Upserted tool", {
3232
2313
  name: tool.name,
3233
2314
  scope: tool.scope
@@ -3570,5 +2651,5 @@ var ToolRegistryRepository = class {
3570
2651
  };
3571
2652
 
3572
2653
  //#endregion
3573
- export { hybridSearch as A, getNodesByType as C, upsertNode as D, traverseFrom as E, openDatabase as F, MIGRATIONS as I, runMigrations as L, SessionRepository as M, ObservationRepository as N, SaveGuard as O, rowToObservation as P, debug as R, getNodeByNameAndType as S, insertEdge as T, detectStaleness as _, ResearchBufferRepository as a, countEdgesForNode as b, inferScope as c, executePurge as d, findAnalysis as f, saveHygieneConfig as g, resetHygieneConfig as h, NotificationStore as i, SearchEngine as j, jaccardSimilarity as k, inferToolType as l, loadHygieneConfig as m, PathRepository as n, BranchRepository as o, runAutoCleanup as p, initPathSchema as r, extractServerName as s, ToolRegistryRepository as t, analyzeObservations as u, flagStaleObservation as v, initGraphSchema as w, getEdgesForNode as x, initStalenessSchema as y, debugTimed as z };
3574
- //# sourceMappingURL=tool-registry-e710BvXq.mjs.map
2654
+ export { rowToObservation as C, debug as D, runMigrations as E, debugTimed as O, ObservationRepository as S, MIGRATIONS as T, SaveGuard as _, ResearchBufferRepository as a, SearchEngine as b, inferToolType as c, getNodeByNameAndType as d, getNodesByType as f, upsertNode as g, traverseFrom as h, NotificationStore as i, countEdgesForNode as l, insertEdge as m, PathRepository as n, extractServerName as o, initGraphSchema as p, initPathSchema as r, inferScope as s, ToolRegistryRepository as t, getEdgesForNode as u, jaccardSimilarity as v, openDatabase as w, SessionRepository as x, hybridSearch as y };
2655
+ //# sourceMappingURL=tool-registry-CZ3mJ4iR.mjs.map