@iworkforces/tracelattice 2.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -2
- package/dist/cli.js +10 -7
- package/dist/contracts/reasoning-types.d.ts +3 -3
- package/dist/contracts/reasoning-types.d.ts.map +1 -1
- package/dist/core/HistoryManager.d.ts +6 -1
- package/dist/core/HistoryManager.d.ts.map +1 -1
- package/dist/core/HistoryManager.js +38 -6
- package/dist/core/HistoryManager.js.map +1 -1
- package/dist/core/IHistoryManager.d.ts +2 -0
- package/dist/core/IHistoryManager.d.ts.map +1 -1
- package/dist/core/SessionErrors.d.ts +4 -0
- package/dist/core/SessionErrors.d.ts.map +1 -1
- package/dist/core/SessionErrors.js +9 -1
- package/dist/core/SessionErrors.js.map +1 -1
- package/dist/core/SessionResetCoordinator.d.ts +4 -0
- package/dist/core/SessionResetCoordinator.d.ts.map +1 -1
- package/dist/core/SessionResetCoordinator.js +6 -0
- package/dist/core/SessionResetCoordinator.js.map +1 -1
- package/dist/core/ThoughtEvaluator.d.ts +9 -2
- package/dist/core/ThoughtEvaluator.d.ts.map +1 -1
- package/dist/core/ThoughtEvaluator.js +9 -2
- package/dist/core/ThoughtEvaluator.js.map +1 -1
- package/dist/core/ThoughtProcessor.d.ts +1 -0
- package/dist/core/ThoughtProcessor.d.ts.map +1 -1
- package/dist/core/ThoughtProcessor.js +47 -20
- package/dist/core/ThoughtProcessor.js.map +1 -1
- package/dist/core/evaluator/GraphPatterns.d.ts +8 -0
- package/dist/core/evaluator/GraphPatterns.d.ts.map +1 -0
- package/dist/core/evaluator/GraphPatterns.js +88 -0
- package/dist/core/evaluator/GraphPatterns.js.map +1 -0
- package/dist/core/evaluator/GraphSignalComputer.d.ts +12 -0
- package/dist/core/evaluator/GraphSignalComputer.d.ts.map +1 -0
- package/dist/core/evaluator/GraphSignalComputer.js +17 -0
- package/dist/core/evaluator/GraphSignalComputer.js.map +1 -0
- package/dist/core/evaluator/PatternDetector.d.ts +4 -1
- package/dist/core/evaluator/PatternDetector.d.ts.map +1 -1
- package/dist/core/evaluator/PatternDetector.js +8 -2
- package/dist/core/evaluator/PatternDetector.js.map +1 -1
- package/dist/core/graph/EdgeEmitter.js +1 -1
- package/dist/core/graph/EdgeEmitter.js.map +1 -1
- package/dist/core/graph/GraphView.d.ts +60 -2
- package/dist/core/graph/GraphView.d.ts.map +1 -1
- package/dist/core/graph/GraphView.js +127 -11
- package/dist/core/graph/GraphView.js.map +1 -1
- package/dist/core/reasoning/GraphContext.d.ts +25 -0
- package/dist/core/reasoning/GraphContext.d.ts.map +1 -0
- package/dist/core/reasoning/GraphContext.js +32 -0
- package/dist/core/reasoning/GraphContext.js.map +1 -0
- package/dist/core/reasoning.d.ts +10 -0
- package/dist/core/reasoning.d.ts.map +1 -1
- package/dist/errors.d.ts +1 -0
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +1 -0
- package/dist/errors.js.map +1 -1
- package/dist/persistence/FileSnapshotV2.d.ts +6 -6
- package/dist/schema.d.ts +3 -3
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +9 -7
- package/dist/schema.js.map +1 -1
- package/package.json +12 -12
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"core/evaluator/GraphSignalComputer.js","sources":["../../../src/core/evaluator/GraphSignalComputer.ts"],"sourcesContent":["import type { SessionId } from '../../contracts/ids.js';\nimport type { GraphView } from '../graph/GraphView.js';\nimport type { GraphSignals } from '../reasoning.js';\n\nexport interface ActiveGraphContext {\n\treadonly sessionId: SessionId;\n\treadonly view: GraphView;\n}\n\n/** Stateless, additive graph analytics; does not affect structural scoring. */\nexport class GraphSignalComputer {\n\tpublic compute(ctx: ActiveGraphContext): GraphSignals | undefined {\n\t\tconst metrics = ctx.view.metrics(ctx.sessionId);\n\t\tif (metrics.edge_count === 0) return undefined;\n\t\treturn {\n\t\t\t...metrics,\n\t\t\tbranching_factor: roundToPrecision(\n\t\t\t\tmetrics.edge_count / (metrics.node_count - metrics.leaf_count)\n\t\t\t),\n\t\t\trelational_density: roundToPrecision(\n\t\t\t\t(metrics.edge_count - metrics.edge_kind_counts.sequence) / metrics.edge_count\n\t\t\t),\n\t\t};\n\t}\n}\n\nfunction roundToPrecision(value: number): number {\n\treturn Math.round(value * 1e10) / 1e10;\n}\n"],"names":["GraphSignalComputer","ctx","metrics","roundToPrecision","value","Math"],"mappings":"AAUO,MAAMA;IACL,QAAQC,GAAuB,EAA4B;QACjE,MAAMC,UAAUD,IAAI,IAAI,CAAC,OAAO,CAACA,IAAI,SAAS;QAC9C,IAAIC,AAAuB,MAAvBA,QAAQ,UAAU,EAAQ;QAC9B,OAAO;YACN,GAAGA,OAAO;YACV,kBAAkBC,iBACjBD,QAAQ,UAAU,GAAIA,CAAAA,QAAQ,UAAU,GAAGA,QAAQ,UAAS;YAE7D,oBAAoBC,iBAClBD,AAAAA,CAAAA,QAAQ,UAAU,GAAGA,QAAQ,gBAAgB,CAAC,QAAO,IAAKA,QAAQ,UAAU;QAE/E;IACD;AACD;AAEA,SAASC,iBAAiBC,KAAa;IACtC,OAAOC,KAAK,KAAK,CAACD,AAAQ,OAARA,SAAgB;AACnC"}
|
|
@@ -17,12 +17,15 @@
|
|
|
17
17
|
* - `confidence_drift` (warning) — 3+ consecutive thoughts with strictly decreasing
|
|
18
18
|
* confidence
|
|
19
19
|
* - `healthy_verification` (info) — hypothesis verified within 3 subsequent thoughts
|
|
20
|
+
* - `refuted_hypothesis_dependency` (warning) — unaddressed graph dependents of a refuted hypothesis
|
|
21
|
+
* - `unaddressed_critique` (warning) — graph critique not addressed after 3 active thoughts
|
|
20
22
|
*
|
|
21
23
|
* @module core/evaluator/PatternDetector
|
|
22
24
|
*/
|
|
23
25
|
import type { PatternSignal } from '../reasoning.js';
|
|
24
26
|
import type { ThoughtData } from '../thought.js';
|
|
25
27
|
import type { VerificationLinks } from './VerificationLinks.js';
|
|
28
|
+
import type { ActiveGraphContext } from './GraphSignalComputer.js';
|
|
26
29
|
/**
|
|
27
30
|
* Stateless service that detects reasoning patterns from thought history.
|
|
28
31
|
*
|
|
@@ -46,7 +49,7 @@ export declare class PatternDetector {
|
|
|
46
49
|
* @param branches - Map of branch IDs to their thought arrays
|
|
47
50
|
* @returns Array of detected pattern signals (possibly empty)
|
|
48
51
|
*/
|
|
49
|
-
computePatternSignals(history: ThoughtData[], branches: Record<string, ThoughtData[]>, links: VerificationLinks): PatternSignal[];
|
|
52
|
+
computePatternSignals(history: ThoughtData[], branches: Record<string, ThoughtData[]>, links: VerificationLinks, activeGraph?: ActiveGraphContext): PatternSignal[];
|
|
50
53
|
/** Detect runs of 3+ consecutive thoughts without verification. */
|
|
51
54
|
private _detectConsecutiveWithoutVerification;
|
|
52
55
|
/** Detect hypothesis thoughts not verified within 3 subsequent thoughts. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PatternDetector.d.ts","sourceRoot":"","sources":["../../../src/core/evaluator/PatternDetector.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"PatternDetector.d.ts","sourceRoot":"","sources":["../../../src/core/evaluator/PatternDetector.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AACrD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AACjD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAGnE;;;;;;;;;;;;;;GAcG;AACH,qBAAa,eAAe;IAC3B;;;;;;OAMG;IACI,qBAAqB,CAC3B,OAAO,EAAE,WAAW,EAAE,EACtB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,WAAW,EAAE,CAAC,EACvC,KAAK,EAAE,iBAAiB,EACxB,WAAW,CAAC,EAAE,kBAAkB,GAC9B,aAAa,EAAE;IAoBlB,mEAAmE;IACnE,OAAO,CAAC,qCAAqC;IAwB7C,4EAA4E;IAC5E,OAAO,CAAC,2BAA2B;IA+BnC,2DAA2D;IAC3D,OAAO,CAAC,6BAA6B;IAmBrC;;;OAGG;IACH,OAAO,CAAC,oBAAoB;IAyB5B,gEAAgE;IAChE,OAAO,CAAC,kBAAkB;IAkB1B,kFAAkF;IAClF,OAAO,CAAC,sBAAsB;IAiC9B,kEAAkE;IAClE,OAAO,CAAC,cAAc;IAuBtB,iFAAiF;IACjF,OAAO,CAAC,0BAA0B;CA6BlC"}
|
|
@@ -1,6 +1,10 @@
|
|
|
1
|
+
import { detectRefutedHypothesisDependency, detectUnaddressedCritique } from "./GraphPatterns.js";
|
|
1
2
|
class PatternDetector {
|
|
2
|
-
computePatternSignals(history, branches, links) {
|
|
3
|
-
if (0 === history.length) return [
|
|
3
|
+
computePatternSignals(history, branches, links, activeGraph) {
|
|
4
|
+
if (0 === history.length) return [
|
|
5
|
+
...detectRefutedHypothesisDependency(links, activeGraph),
|
|
6
|
+
...detectUnaddressedCritique(links, activeGraph)
|
|
7
|
+
];
|
|
4
8
|
const signals = [];
|
|
5
9
|
signals.push(...this._detectConsecutiveWithoutVerification(history));
|
|
6
10
|
signals.push(...this._detectUnverifiedHypothesis(history, links));
|
|
@@ -8,6 +12,8 @@ class PatternDetector {
|
|
|
8
12
|
signals.push(...this._detectMonotonicType(history));
|
|
9
13
|
signals.push(...this._detectConfidenceDrift(history));
|
|
10
14
|
signals.push(...this._detectHealthyVerification(history, links));
|
|
15
|
+
signals.push(...detectRefutedHypothesisDependency(links, activeGraph));
|
|
16
|
+
signals.push(...detectUnaddressedCritique(links, activeGraph));
|
|
11
17
|
return signals;
|
|
12
18
|
}
|
|
13
19
|
_detectConsecutiveWithoutVerification(history) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core/evaluator/PatternDetector.js","sources":["../../../src/core/evaluator/PatternDetector.ts"],"sourcesContent":["/**\n * Pattern detection for sequential thinking reasoning analytics.\n *\n * Provides the {@link PatternDetector} class — a stateless service that detects\n * reasoning patterns (anti-patterns and positive signals) from thought history.\n * Extracted from {@link ThoughtEvaluator} to keep concerns isolated and reduce\n * cyclomatic complexity per function.\n *\n * Detected patterns:\n * - `consecutive_without_verification` (warning) — 3+ consecutive regular thoughts\n * without a verification step\n * - `unverified_hypothesis` (warning) — hypothesis not verified within 3 subsequent\n * thoughts\n * - `no_alternatives_explored` (info) — 5+ thoughts with no critique and no branches\n * - `monotonic_type` (info) — 4+ consecutive thoughts with the same thought_type\n * (requires ≥1 explicit type and ≥5 thoughts)\n * - `confidence_drift` (warning) — 3+ consecutive thoughts with strictly decreasing\n * confidence\n * - `healthy_verification` (info) — hypothesis verified within 3 subsequent thoughts\n *\n * @module core/evaluator/PatternDetector\n */\n\nimport type { PatternSignal } from '../reasoning.js';\nimport type { ThoughtData } from '../thought.js';\nimport type { VerificationLinks } from './VerificationLinks.js';\n\n/**\n * Stateless service that detects reasoning patterns from thought history.\n *\n * @remarks\n * All methods are pure computations — no side effects, no I/O, no internal state.\n * Designed to be registered as transient in the DI container or composed directly\n * into {@link ThoughtEvaluator}.\n *\n * @example\n * ```typescript\n * const detector = new PatternDetector();\n * const signals = detector.computePatternSignals(history, branches);\n * const warnings = signals.filter((s) => s.severity === 'warning');\n * ```\n */\nexport class PatternDetector {\n\t/**\n\t * Detect all reasoning patterns from history and branches.\n\t *\n\t * @param history - All thoughts in the current session\n\t * @param branches - Map of branch IDs to their thought arrays\n\t * @returns Array of detected pattern signals (possibly empty)\n\t */\n\tpublic computePatternSignals(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>,\n\t\tlinks: VerificationLinks\n\t): PatternSignal[] {\n\t\tif (history.length === 0) return [];\n\n\t\tconst signals: PatternSignal[] = [];\n\t\tsignals.push(...this._detectConsecutiveWithoutVerification(history));\n\t\tsignals.push(...this._detectUnverifiedHypothesis(history, links));\n\t\tsignals.push(...this._detectNoAlternativesExplored(history, branches));\n\t\tsignals.push(...this._detectMonotonicType(history));\n\t\tsignals.push(...this._detectConfidenceDrift(history));\n\t\tsignals.push(...this._detectHealthyVerification(history, links));\n\t\treturn signals;\n\t}\n\n\t/** Detect runs of 3+ consecutive thoughts without verification. */\n\tprivate _detectConsecutiveWithoutVerification(history: ThoughtData[]): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tlet runStart = 0;\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst type = history[i]!.thought_type ?? 'regular';\n\t\t\tif (type === 'verification') {\n\t\t\t\trunStart = i + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (i - runStart + 1 >= 3) {\n\t\t\t\tconst start = history[runStart]!.thought_number ?? runStart + 1;\n\t\t\t\tconst end = history[i]!.thought_number ?? i + 1;\n\t\t\t\tsignals.push({\n\t\t\t\t\tpattern: 'consecutive_without_verification',\n\t\t\t\t\tseverity: 'warning',\n\t\t\t\t\tmessage: `3+ consecutive thoughts (${start}-${end}) without verification`,\n\t\t\t\t\tthought_range: [start, end],\n\t\t\t\t});\n\t\t\t\trunStart = i + 1;\n\t\t\t}\n\t\t}\n\t\treturn signals;\n\t}\n\n\t/** Detect hypothesis thoughts not verified within 3 subsequent thoughts. */\n\tprivate _detectUnverifiedHypothesis(\n\t\thistory: ThoughtData[],\n\t\tlinks: VerificationLinks\n\t): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst hypothesis = history[i]!;\n\t\t\tif (\n\t\t\t\thypothesis.thought_type !== 'hypothesis' ||\n\t\t\t\t!hypothesis.id ||\n\t\t\t\t!links.hypothesisIds.has(hypothesis.id)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst remaining = history.length - i - 1;\n\t\t\tif (remaining < 3) continue;\n\t\t\tconst lookahead = history.slice(i + 1, i + 4);\n\t\t\tconst hasVerification = lookahead.some((verifier) => links.verifies(verifier, hypothesis));\n\t\t\tif (!hasVerification) {\n\t\t\t\tconst n = hypothesis.thought_number ?? i + 1;\n\t\t\t\tsignals.push({\n\t\t\t\t\tpattern: 'unverified_hypothesis',\n\t\t\t\t\tseverity: 'warning',\n\t\t\t\t\tmessage: `Hypothesis at thought ${n} has not been verified within 3 thoughts`,\n\t\t\t\t\tthought_range: [n, n],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn signals;\n\t}\n\n\t/** Detect 5+ thoughts with no critique and no branches. */\n\tprivate _detectNoAlternativesExplored(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): PatternSignal[] {\n\t\tif (history.length < 5) return [];\n\t\tif (history.some((t) => t.thought_type === 'critique')) return [];\n\t\tif (Object.keys(branches).length > 0) return [];\n\t\tconst start = history[0]!.thought_number ?? 1;\n\t\tconst end = history[history.length - 1]!.thought_number ?? history.length;\n\t\treturn [\n\t\t\t{\n\t\t\t\tpattern: 'no_alternatives_explored',\n\t\t\t\tseverity: 'warning',\n\t\t\t\tmessage: '5+ thoughts with no critique or branching — consider exploring alternatives',\n\t\t\t\tthought_range: [start, end],\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * Detect runs of 4+ consecutive thoughts with the same thought_type.\n\t * Only fires when history has ≥5 thoughts and at least one explicitly set thought_type.\n\t */\n\tprivate _detectMonotonicType(history: ThoughtData[]): PatternSignal[] {\n\t\tif (history.length < 5) return [];\n\t\tconst hasExplicitType = history.some((t) => t.thought_type !== undefined);\n\t\tif (!hasExplicitType) return [];\n\n\t\tconst signals: PatternSignal[] = [];\n\t\tlet runType = history[0]!.thought_type ?? 'regular';\n\t\tlet runStart = 0;\n\t\tlet runLength = 1;\n\n\t\tfor (let i = 1; i < history.length; i++) {\n\t\t\tconst type = history[i]!.thought_type ?? 'regular';\n\t\t\tif (type === runType) {\n\t\t\t\trunLength++;\n\t\t\t} else {\n\t\t\t\tthis._flushMonotonicRun(history, runStart, runLength, runType, signals);\n\t\t\t\trunType = type;\n\t\t\t\trunStart = i;\n\t\t\t\trunLength = 1;\n\t\t\t}\n\t\t}\n\t\tthis._flushMonotonicRun(history, runStart, runLength, runType, signals);\n\t\treturn signals;\n\t}\n\n\t/** Emit a monotonic_type signal if the run length qualifies. */\n\tprivate _flushMonotonicRun(\n\t\thistory: ThoughtData[],\n\t\trunStart: number,\n\t\trunLength: number,\n\t\trunType: string,\n\t\tsignals: PatternSignal[]\n\t): void {\n\t\tif (runLength < 4) return;\n\t\tconst start = history[runStart]!.thought_number ?? runStart + 1;\n\t\tconst end = history[runStart + runLength - 1]!.thought_number ?? runStart + runLength;\n\t\tsignals.push({\n\t\t\tpattern: 'monotonic_type',\n\t\t\tseverity: 'warning',\n\t\t\tmessage: `4+ consecutive '${runType}' thoughts (${start}-${end}) — consider varying approach`,\n\t\t\tthought_range: [start, end],\n\t\t});\n\t}\n\n\t/** Detect runs of 3+ consecutive thoughts with strictly decreasing confidence. */\n\tprivate _detectConfidenceDrift(history: ThoughtData[]): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tlet runStart = -1;\n\t\tlet runLength = 0;\n\t\tlet prevConf = -1;\n\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst conf = history[i]!.confidence;\n\t\t\tif (conf === undefined) {\n\t\t\t\tthis._flushDriftRun(history, runStart, runLength, signals);\n\t\t\t\trunStart = -1;\n\t\t\t\trunLength = 0;\n\t\t\t\tprevConf = -1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (runLength === 0) {\n\t\t\t\trunStart = i;\n\t\t\t\trunLength = 1;\n\t\t\t\tprevConf = conf;\n\t\t\t} else if (conf < prevConf) {\n\t\t\t\trunLength++;\n\t\t\t\tprevConf = conf;\n\t\t\t} else {\n\t\t\t\tthis._flushDriftRun(history, runStart, runLength, signals);\n\t\t\t\trunStart = i;\n\t\t\t\trunLength = 1;\n\t\t\t\tprevConf = conf;\n\t\t\t}\n\t\t}\n\t\tthis._flushDriftRun(history, runStart, runLength, signals);\n\t\treturn signals;\n\t}\n\n\t/** Emit a confidence_drift signal if the run length qualifies. */\n\tprivate _flushDriftRun(\n\t\thistory: ThoughtData[],\n\t\trunStart: number,\n\t\trunLength: number,\n\t\tsignals: PatternSignal[]\n\t): void {\n\t\tif (runLength < 3) return;\n\t\tconst first = history[runStart];\n\t\tconst last = history[runStart + runLength - 1];\n\t\tif (!first || !last) return;\n\t\tif (first.confidence === undefined || last.confidence === undefined) return;\n\t\tconst start = first.thought_number ?? runStart + 1;\n\t\tconst end = last.thought_number ?? runStart + runLength;\n\t\tconst firstConf = first.confidence;\n\t\tconst lastConf = last.confidence;\n\t\tsignals.push({\n\t\t\tpattern: 'confidence_drift',\n\t\t\tseverity: 'warning',\n\t\t\tmessage: `Confidence decreasing across thoughts ${start}-${end} (${firstConf} → ${lastConf})`,\n\t\t\tthought_range: [start, end],\n\t\t});\n\t}\n\n\t/** Detect hypothesis verified within 3 subsequent thoughts — positive signal. */\n\tprivate _detectHealthyVerification(\n\t\thistory: ThoughtData[],\n\t\tlinks: VerificationLinks\n\t): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst hypothesis = history[i]!;\n\t\t\tif (\n\t\t\t\thypothesis.thought_type !== 'hypothesis' ||\n\t\t\t\t!hypothesis.id ||\n\t\t\t\t!links.hypothesisIds.has(hypothesis.id)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst lookahead = history.slice(i + 1, i + 4);\n\t\t\tconst verifier = lookahead.find((candidate) => links.verifies(candidate, hypothesis));\n\t\t\tif (verifier) {\n\t\t\t\tconst n = hypothesis.thought_number ?? i + 1;\n\t\t\t\tconst m = verifier.thought_number ?? history.indexOf(verifier) + 1;\n\t\t\t\tsignals.push({\n\t\t\t\t\tpattern: 'healthy_verification',\n\t\t\t\t\tseverity: 'info',\n\t\t\t\t\tmessage: `Hypothesis at thought ${n} verified at thought ${m} — good practice`,\n\t\t\t\t\tthought_range: [n, m],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn signals;\n\t}\n}\n"],"names":["PatternDetector","history","branches","links","signals","runStart","i","type","start","end","hypothesis","remaining","lookahead","hasVerification","verifier","n","t","Object","hasExplicitType","undefined","runType","runLength","prevConf","conf","first","last","firstConf","lastConf","candidate","m"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core/evaluator/PatternDetector.js","sources":["../../../src/core/evaluator/PatternDetector.ts"],"sourcesContent":["/**\n * Pattern detection for sequential thinking reasoning analytics.\n *\n * Provides the {@link PatternDetector} class — a stateless service that detects\n * reasoning patterns (anti-patterns and positive signals) from thought history.\n * Extracted from {@link ThoughtEvaluator} to keep concerns isolated and reduce\n * cyclomatic complexity per function.\n *\n * Detected patterns:\n * - `consecutive_without_verification` (warning) — 3+ consecutive regular thoughts\n * without a verification step\n * - `unverified_hypothesis` (warning) — hypothesis not verified within 3 subsequent\n * thoughts\n * - `no_alternatives_explored` (info) — 5+ thoughts with no critique and no branches\n * - `monotonic_type` (info) — 4+ consecutive thoughts with the same thought_type\n * (requires ≥1 explicit type and ≥5 thoughts)\n * - `confidence_drift` (warning) — 3+ consecutive thoughts with strictly decreasing\n * confidence\n * - `healthy_verification` (info) — hypothesis verified within 3 subsequent thoughts\n * - `refuted_hypothesis_dependency` (warning) — unaddressed graph dependents of a refuted hypothesis\n * - `unaddressed_critique` (warning) — graph critique not addressed after 3 active thoughts\n *\n * @module core/evaluator/PatternDetector\n */\n\nimport type { PatternSignal } from '../reasoning.js';\nimport type { ThoughtData } from '../thought.js';\nimport type { VerificationLinks } from './VerificationLinks.js';\nimport type { ActiveGraphContext } from './GraphSignalComputer.js';\nimport { detectRefutedHypothesisDependency, detectUnaddressedCritique } from './GraphPatterns.js';\n\n/**\n * Stateless service that detects reasoning patterns from thought history.\n *\n * @remarks\n * All methods are pure computations — no side effects, no I/O, no internal state.\n * Designed to be registered as transient in the DI container or composed directly\n * into {@link ThoughtEvaluator}.\n *\n * @example\n * ```typescript\n * const detector = new PatternDetector();\n * const signals = detector.computePatternSignals(history, branches);\n * const warnings = signals.filter((s) => s.severity === 'warning');\n * ```\n */\nexport class PatternDetector {\n\t/**\n\t * Detect all reasoning patterns from history and branches.\n\t *\n\t * @param history - All thoughts in the current session\n\t * @param branches - Map of branch IDs to their thought arrays\n\t * @returns Array of detected pattern signals (possibly empty)\n\t */\n\tpublic computePatternSignals(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>,\n\t\tlinks: VerificationLinks,\n\t\tactiveGraph?: ActiveGraphContext\n\t): PatternSignal[] {\n\t\tif (history.length === 0) {\n\t\t\treturn [\n\t\t\t\t...detectRefutedHypothesisDependency(links, activeGraph),\n\t\t\t\t...detectUnaddressedCritique(links, activeGraph),\n\t\t\t];\n\t\t}\n\n\t\tconst signals: PatternSignal[] = [];\n\t\tsignals.push(...this._detectConsecutiveWithoutVerification(history));\n\t\tsignals.push(...this._detectUnverifiedHypothesis(history, links));\n\t\tsignals.push(...this._detectNoAlternativesExplored(history, branches));\n\t\tsignals.push(...this._detectMonotonicType(history));\n\t\tsignals.push(...this._detectConfidenceDrift(history));\n\t\tsignals.push(...this._detectHealthyVerification(history, links));\n\t\tsignals.push(...detectRefutedHypothesisDependency(links, activeGraph));\n\t\tsignals.push(...detectUnaddressedCritique(links, activeGraph));\n\t\treturn signals;\n\t}\n\n\t/** Detect runs of 3+ consecutive thoughts without verification. */\n\tprivate _detectConsecutiveWithoutVerification(history: ThoughtData[]): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tlet runStart = 0;\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst type = history[i]!.thought_type ?? 'regular';\n\t\t\tif (type === 'verification') {\n\t\t\t\trunStart = i + 1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (i - runStart + 1 >= 3) {\n\t\t\t\tconst start = history[runStart]!.thought_number ?? runStart + 1;\n\t\t\t\tconst end = history[i]!.thought_number ?? i + 1;\n\t\t\t\tsignals.push({\n\t\t\t\t\tpattern: 'consecutive_without_verification',\n\t\t\t\t\tseverity: 'warning',\n\t\t\t\t\tmessage: `3+ consecutive thoughts (${start}-${end}) without verification`,\n\t\t\t\t\tthought_range: [start, end],\n\t\t\t\t});\n\t\t\t\trunStart = i + 1;\n\t\t\t}\n\t\t}\n\t\treturn signals;\n\t}\n\n\t/** Detect hypothesis thoughts not verified within 3 subsequent thoughts. */\n\tprivate _detectUnverifiedHypothesis(\n\t\thistory: ThoughtData[],\n\t\tlinks: VerificationLinks\n\t): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst hypothesis = history[i]!;\n\t\t\tif (\n\t\t\t\thypothesis.thought_type !== 'hypothesis' ||\n\t\t\t\t!hypothesis.id ||\n\t\t\t\t!links.hypothesisIds.has(hypothesis.id)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst remaining = history.length - i - 1;\n\t\t\tif (remaining < 3) continue;\n\t\t\tconst lookahead = history.slice(i + 1, i + 4);\n\t\t\tconst hasVerification = lookahead.some((verifier) => links.verifies(verifier, hypothesis));\n\t\t\tif (!hasVerification) {\n\t\t\t\tconst n = hypothesis.thought_number ?? i + 1;\n\t\t\t\tsignals.push({\n\t\t\t\t\tpattern: 'unverified_hypothesis',\n\t\t\t\t\tseverity: 'warning',\n\t\t\t\t\tmessage: `Hypothesis at thought ${n} has not been verified within 3 thoughts`,\n\t\t\t\t\tthought_range: [n, n],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn signals;\n\t}\n\n\t/** Detect 5+ thoughts with no critique and no branches. */\n\tprivate _detectNoAlternativesExplored(\n\t\thistory: ThoughtData[],\n\t\tbranches: Record<string, ThoughtData[]>\n\t): PatternSignal[] {\n\t\tif (history.length < 5) return [];\n\t\tif (history.some((t) => t.thought_type === 'critique')) return [];\n\t\tif (Object.keys(branches).length > 0) return [];\n\t\tconst start = history[0]!.thought_number ?? 1;\n\t\tconst end = history[history.length - 1]!.thought_number ?? history.length;\n\t\treturn [\n\t\t\t{\n\t\t\t\tpattern: 'no_alternatives_explored',\n\t\t\t\tseverity: 'warning',\n\t\t\t\tmessage: '5+ thoughts with no critique or branching — consider exploring alternatives',\n\t\t\t\tthought_range: [start, end],\n\t\t\t},\n\t\t];\n\t}\n\n\t/**\n\t * Detect runs of 4+ consecutive thoughts with the same thought_type.\n\t * Only fires when history has ≥5 thoughts and at least one explicitly set thought_type.\n\t */\n\tprivate _detectMonotonicType(history: ThoughtData[]): PatternSignal[] {\n\t\tif (history.length < 5) return [];\n\t\tconst hasExplicitType = history.some((t) => t.thought_type !== undefined);\n\t\tif (!hasExplicitType) return [];\n\n\t\tconst signals: PatternSignal[] = [];\n\t\tlet runType = history[0]!.thought_type ?? 'regular';\n\t\tlet runStart = 0;\n\t\tlet runLength = 1;\n\n\t\tfor (let i = 1; i < history.length; i++) {\n\t\t\tconst type = history[i]!.thought_type ?? 'regular';\n\t\t\tif (type === runType) {\n\t\t\t\trunLength++;\n\t\t\t} else {\n\t\t\t\tthis._flushMonotonicRun(history, runStart, runLength, runType, signals);\n\t\t\t\trunType = type;\n\t\t\t\trunStart = i;\n\t\t\t\trunLength = 1;\n\t\t\t}\n\t\t}\n\t\tthis._flushMonotonicRun(history, runStart, runLength, runType, signals);\n\t\treturn signals;\n\t}\n\n\t/** Emit a monotonic_type signal if the run length qualifies. */\n\tprivate _flushMonotonicRun(\n\t\thistory: ThoughtData[],\n\t\trunStart: number,\n\t\trunLength: number,\n\t\trunType: string,\n\t\tsignals: PatternSignal[]\n\t): void {\n\t\tif (runLength < 4) return;\n\t\tconst start = history[runStart]!.thought_number ?? runStart + 1;\n\t\tconst end = history[runStart + runLength - 1]!.thought_number ?? runStart + runLength;\n\t\tsignals.push({\n\t\t\tpattern: 'monotonic_type',\n\t\t\tseverity: 'warning',\n\t\t\tmessage: `4+ consecutive '${runType}' thoughts (${start}-${end}) — consider varying approach`,\n\t\t\tthought_range: [start, end],\n\t\t});\n\t}\n\n\t/** Detect runs of 3+ consecutive thoughts with strictly decreasing confidence. */\n\tprivate _detectConfidenceDrift(history: ThoughtData[]): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tlet runStart = -1;\n\t\tlet runLength = 0;\n\t\tlet prevConf = -1;\n\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst conf = history[i]!.confidence;\n\t\t\tif (conf === undefined) {\n\t\t\t\tthis._flushDriftRun(history, runStart, runLength, signals);\n\t\t\t\trunStart = -1;\n\t\t\t\trunLength = 0;\n\t\t\t\tprevConf = -1;\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tif (runLength === 0) {\n\t\t\t\trunStart = i;\n\t\t\t\trunLength = 1;\n\t\t\t\tprevConf = conf;\n\t\t\t} else if (conf < prevConf) {\n\t\t\t\trunLength++;\n\t\t\t\tprevConf = conf;\n\t\t\t} else {\n\t\t\t\tthis._flushDriftRun(history, runStart, runLength, signals);\n\t\t\t\trunStart = i;\n\t\t\t\trunLength = 1;\n\t\t\t\tprevConf = conf;\n\t\t\t}\n\t\t}\n\t\tthis._flushDriftRun(history, runStart, runLength, signals);\n\t\treturn signals;\n\t}\n\n\t/** Emit a confidence_drift signal if the run length qualifies. */\n\tprivate _flushDriftRun(\n\t\thistory: ThoughtData[],\n\t\trunStart: number,\n\t\trunLength: number,\n\t\tsignals: PatternSignal[]\n\t): void {\n\t\tif (runLength < 3) return;\n\t\tconst first = history[runStart];\n\t\tconst last = history[runStart + runLength - 1];\n\t\tif (!first || !last) return;\n\t\tif (first.confidence === undefined || last.confidence === undefined) return;\n\t\tconst start = first.thought_number ?? runStart + 1;\n\t\tconst end = last.thought_number ?? runStart + runLength;\n\t\tconst firstConf = first.confidence;\n\t\tconst lastConf = last.confidence;\n\t\tsignals.push({\n\t\t\tpattern: 'confidence_drift',\n\t\t\tseverity: 'warning',\n\t\t\tmessage: `Confidence decreasing across thoughts ${start}-${end} (${firstConf} → ${lastConf})`,\n\t\t\tthought_range: [start, end],\n\t\t});\n\t}\n\n\t/** Detect hypothesis verified within 3 subsequent thoughts — positive signal. */\n\tprivate _detectHealthyVerification(\n\t\thistory: ThoughtData[],\n\t\tlinks: VerificationLinks\n\t): PatternSignal[] {\n\t\tconst signals: PatternSignal[] = [];\n\t\tfor (let i = 0; i < history.length; i++) {\n\t\t\tconst hypothesis = history[i]!;\n\t\t\tif (\n\t\t\t\thypothesis.thought_type !== 'hypothesis' ||\n\t\t\t\t!hypothesis.id ||\n\t\t\t\t!links.hypothesisIds.has(hypothesis.id)\n\t\t\t) {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst lookahead = history.slice(i + 1, i + 4);\n\t\t\tconst verifier = lookahead.find((candidate) => links.verifies(candidate, hypothesis));\n\t\t\tif (verifier) {\n\t\t\t\tconst n = hypothesis.thought_number ?? i + 1;\n\t\t\t\tconst m = verifier.thought_number ?? history.indexOf(verifier) + 1;\n\t\t\t\tsignals.push({\n\t\t\t\t\tpattern: 'healthy_verification',\n\t\t\t\t\tseverity: 'info',\n\t\t\t\t\tmessage: `Hypothesis at thought ${n} verified at thought ${m} — good practice`,\n\t\t\t\t\tthought_range: [n, m],\n\t\t\t\t});\n\t\t\t}\n\t\t}\n\t\treturn signals;\n\t}\n}\n"],"names":["PatternDetector","history","branches","links","activeGraph","detectRefutedHypothesisDependency","detectUnaddressedCritique","signals","runStart","i","type","start","end","hypothesis","remaining","lookahead","hasVerification","verifier","n","t","Object","hasExplicitType","undefined","runType","runLength","prevConf","conf","first","last","firstConf","lastConf","candidate","m"],"mappings":"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"}
|
|
@@ -98,7 +98,7 @@ class EdgeEmitter {
|
|
|
98
98
|
});
|
|
99
99
|
return false;
|
|
100
100
|
}
|
|
101
|
-
const duplicate = this._edgeStore.
|
|
101
|
+
const duplicate = this._edgeStore.outgoing(sessionId, from).some((existing)=>existing.kind === kind && existing.to === to);
|
|
102
102
|
if (duplicate) return false;
|
|
103
103
|
try {
|
|
104
104
|
this._edgeStore.addEdge(edge);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core/graph/EdgeEmitter.js","sources":["../../../src/core/graph/EdgeEmitter.ts"],"sourcesContent":["/**\n * EdgeEmitter — emits DAG edges for thoughts based on their metadata.\n *\n * Stateless helper extracted from HistoryManager. Holds a reference to an\n * optional `IEdgeStore` and a feature flag (`dagEdges`) gating writes.\n *\n * @module EdgeEmitter\n */\n\nimport type { IEdgeStore } from '../../contracts/interfaces.js';\nimport { getErrorMessage } from '../../errors.js';\nimport type { Logger } from '../../logger/StructuredLogger.js';\nimport { NullLogger } from '../../logger/NullLogger.js';\nimport type { ThoughtData } from '../thought.js';\nimport type { ResolvedThoughtReferences, ThoughtAdmissionContext } from '../IHistoryManager.js';\nimport { resolvedVerificationTarget } from '../evaluator/VerificationLinks.js';\nimport type { Edge, EdgeKind } from './Edge.js';\nimport {\n\tgenerateEdgeId,\n\ttype BranchId,\n\ttype SessionId,\n\ttype ThoughtId,\n} from '../../contracts/ids.js';\n\n/** Minimal session view needed for edge emission. */\nexport interface EdgeEmissionSession {\n\tthought_history: ThoughtData[];\n\tbranches: Record<BranchId, ThoughtData[]>;\n}\n\n/** Configuration options for EdgeEmitter. */\nexport interface EdgeEmitterConfig {\n\tedgeStore?: IEdgeStore;\n\tdagEdges: boolean;\n\tlogger?: Logger;\n}\n\n/**\n * Emits DAG edges for thought relationships when an `IEdgeStore` is configured\n * and the `dagEdges` feature flag is enabled. No-ops otherwise.\n */\nexport class EdgeEmitter {\n\tprivate readonly _edgeStore?: IEdgeStore;\n\tprivate readonly _dagEdges: boolean;\n\tprivate readonly _logger: Logger;\n\n\tconstructor(config: EdgeEmitterConfig) {\n\t\tthis._edgeStore = config.edgeStore;\n\t\tthis._dagEdges = config.dagEdges;\n\t\tthis._logger = config.logger ?? new NullLogger();\n\t}\n\n\t/** Returns true when edge emission is active (store + flag both set). */\n\tpublic isEnabled(): boolean {\n\t\treturn this._edgeStore !== undefined && this._dagEdges;\n\t}\n\n\t/**\n\t * Emits DAG edges for a thought based on its metadata fields.\n\t *\n\t * Edge kinds (in priority order):\n\t * - branch: branch_from_thought + branch_id → parent.id → current.id\n\t * - merge: merge_from_thoughts → source.id → current.id (per source)\n\t * - verifies: verification_target + thought_type=verification → current.id → target.id\n\t * - critiques: verification_target + thought_type=critique → current.id → target.id\n\t * - derives_from: synthesis_sources → source.id → current.id (per source)\n\t * - revises: revises_thought → current.id → target.id\n\t * - tool_invocation: stable admission source id → current.id\n\t * - sequence: default chronological link from previous thought (if none of the above)\n\t * @returns True only when at least one edge was added to the store\n\t */\n\tpublic emitEdgesForThought(\n\t\tsession: EdgeEmissionSession,\n\t\tthought: ThoughtData,\n\t\tcontext?: ThoughtAdmissionContext\n\t): boolean {\n\t\tif (!this._edgeStore || !this._dagEdges) return false;\n\t\tif (!thought.id) return false;\n\n\t\tconst sessionId = thought.session_id;\n\t\tconst references = context?.resolvedReferences ?? {};\n\t\tconst hasRelationalIntent =\n\t\t\t(thought.branch_from_thought !== undefined && thought.branch_id !== undefined) ||\n\t\t\t(thought.merge_from_thoughts?.length ?? 0) > 0 ||\n\t\t\t(thought.verification_target !== undefined &&\n\t\t\t\t(thought.thought_type === 'verification' || thought.thought_type === 'critique')) ||\n\t\t\t(thought.synthesis_sources?.length ?? 0) > 0 ||\n\t\t\tthought.revises_thought !== undefined ||\n\t\t\t(thought.thought_type === 'tool_observation' &&\n\t\t\t\tcontext?.toolInvocationSourceThoughtId !== undefined);\n\t\tconst emittedRelational = [\n\t\t\tthis._emitBranchEdge(thought, references, sessionId),\n\t\t\tthis._emitMergeEdges(thought, references, sessionId),\n\t\t\tthis._emitVerificationEdge(thought, references, sessionId),\n\t\t\tthis._emitCritiqueEdge(thought, references, sessionId),\n\t\t\tthis._emitSynthesisEdges(thought, references, sessionId),\n\t\t\tthis._emitRevisionEdge(thought, references, sessionId),\n\t\t\tthis._emitToolInvocationEdge(thought, sessionId, context),\n\t\t].some((emitted) => emitted);\n\n\t\tif (emittedRelational || hasRelationalIntent) return emittedRelational;\n\t\treturn this._emitSequenceEdge(session, thought, sessionId);\n\t}\n\n\tprivate _emitBranchEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.branch_from_thought === undefined || !thought.branch_id) return false;\n\n\t\treturn this._addEdgeIfValid(references.branchFromThoughtId, thought.id, 'branch', sessionId);\n\t}\n\n\tprivate _emitMergeEdges(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tlet emitted = false;\n\t\tfor (const srcId of references.mergeFromThoughtIds ?? []) {\n\t\t\temitted = this._addEdgeIfValid(srcId, thought.id, 'merge', sessionId) || emitted;\n\t\t}\n\t\treturn emitted;\n\t}\n\n\tprivate _emitVerificationEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.verification_target === undefined || thought.thought_type !== 'verification')\n\t\t\treturn false;\n\n\t\treturn this._addEdgeIfValid(\n\t\t\tthought.id,\n\t\t\tresolvedVerificationTarget(thought, references),\n\t\t\t'verifies',\n\t\t\tsessionId\n\t\t);\n\t}\n\n\tprivate _emitCritiqueEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.verification_target === undefined || thought.thought_type !== 'critique')\n\t\t\treturn false;\n\n\t\treturn this._addEdgeIfValid(\n\t\t\tthought.id,\n\t\t\treferences.verificationTargetThoughtId,\n\t\t\t'critiques',\n\t\t\tsessionId\n\t\t);\n\t}\n\n\tprivate _emitSynthesisEdges(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tlet emitted = false;\n\t\tfor (const srcId of references.synthesisSourceThoughtIds ?? []) {\n\t\t\temitted = this._addEdgeIfValid(srcId, thought.id, 'derives_from', sessionId) || emitted;\n\t\t}\n\t\treturn emitted;\n\t}\n\n\tprivate _emitRevisionEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.revises_thought === undefined) return false;\n\n\t\treturn this._addEdgeIfValid(thought.id, references.revisesThoughtId, 'revises', sessionId);\n\t}\n\n\tprivate _emitToolInvocationEdge(\n\t\tthought: ThoughtData,\n\t\tsessionId: SessionId,\n\t\tcontext?: ThoughtAdmissionContext\n\t): boolean {\n\t\tif (\n\t\t\tthought.thought_type !== 'tool_observation' ||\n\t\t\tcontext?.toolInvocationSourceThoughtId === undefined\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst metadata = thought.tool_name !== undefined ? { tool_name: thought.tool_name } : undefined;\n\t\treturn this._addEdgeIfValid(\n\t\t\tcontext.toolInvocationSourceThoughtId,\n\t\t\tthought.id,\n\t\t\t'tool_invocation',\n\t\t\tsessionId,\n\t\t\tmetadata\n\t\t);\n\t}\n\n\tprivate _emitSequenceEdge(\n\t\tsession: EdgeEmissionSession,\n\t\tthought: ThoughtData,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tconst history = session.thought_history;\n\t\tif (history.length < 2) return false;\n\n\t\tconst prev = history[history.length - 2];\n\t\tif (!prev?.id) return false;\n\n\t\treturn this._addEdgeIfValid(prev.id, thought.id, 'sequence', sessionId);\n\t}\n\n\t/**\n\t * Adds an edge to the edge store if both endpoints are non-empty strings.\n\t * Returns true if added, false if skipped (missing endpoint).\n\t * Failures (e.g. self-edge) are caught and logged.\n\t */\n\tprivate _addEdgeIfValid(\n\t\tfrom: ThoughtId | undefined,\n\t\tto: ThoughtId | undefined,\n\t\tkind: EdgeKind,\n\t\tsessionId: SessionId,\n\t\tmetadata?: Record<string, unknown>\n\t): boolean {\n\t\tif (!from || !to) {\n\t\t\tthis._logger.debug('Skipping edge: unresolved endpoint', {\n\t\t\t\tkind,\n\t\t\t\tfrom: from ?? null,\n\t\t\t\tto: to ?? null,\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t\tconst edge: Edge = {\n\t\t\tid: generateEdgeId(),\n\t\t\tfrom: from as Edge['from'],\n\t\t\tto: to as Edge['to'],\n\t\t\tkind,\n\t\t\tsessionId: sessionId as Edge['sessionId'],\n\t\t\tcreatedAt: Date.now(),\n\t\t\t...(metadata !== undefined ? { metadata } : {}),\n\t\t};\n\t\tif (!this._edgeStore) {\n\t\t\tthis._logger.warn('EdgeStore not available; skipping edge', { kind });\n\t\t\treturn false;\n\t\t}\n\t\tconst duplicate = this._edgeStore\n\t\t\t.edgesForSession(sessionId)\n\t\t\t.some((existing) => existing.kind === kind && existing.from === from && existing.to === to);\n\t\tif (duplicate) return false;\n\t\ttry {\n\t\t\tthis._edgeStore.addEdge(edge);\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tthis._logger.info('Failed to add DAG edge', {\n\t\t\t\tkind,\n\t\t\t\terror: getErrorMessage(err),\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"],"names":["EdgeEmitter","config","NullLogger","undefined","session","thought","context","sessionId","references","hasRelationalIntent","emittedRelational","emitted","srcId","resolvedVerificationTarget","metadata","history","prev","from","to","kind","edge","generateEdgeId","Date","duplicate","existing","err","getErrorMessage"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core/graph/EdgeEmitter.js","sources":["../../../src/core/graph/EdgeEmitter.ts"],"sourcesContent":["/**\n * EdgeEmitter — emits DAG edges for thoughts based on their metadata.\n *\n * Stateless helper extracted from HistoryManager. Holds a reference to an\n * optional `IEdgeStore` and a feature flag (`dagEdges`) gating writes.\n *\n * @module EdgeEmitter\n */\n\nimport type { IEdgeStore } from '../../contracts/interfaces.js';\nimport { getErrorMessage } from '../../errors.js';\nimport type { Logger } from '../../logger/StructuredLogger.js';\nimport { NullLogger } from '../../logger/NullLogger.js';\nimport type { ThoughtData } from '../thought.js';\nimport type { ResolvedThoughtReferences, ThoughtAdmissionContext } from '../IHistoryManager.js';\nimport { resolvedVerificationTarget } from '../evaluator/VerificationLinks.js';\nimport type { Edge, EdgeKind } from './Edge.js';\nimport {\n\tgenerateEdgeId,\n\ttype BranchId,\n\ttype SessionId,\n\ttype ThoughtId,\n} from '../../contracts/ids.js';\n\n/** Minimal session view needed for edge emission. */\nexport interface EdgeEmissionSession {\n\tthought_history: ThoughtData[];\n\tbranches: Record<BranchId, ThoughtData[]>;\n}\n\n/** Configuration options for EdgeEmitter. */\nexport interface EdgeEmitterConfig {\n\tedgeStore?: IEdgeStore;\n\tdagEdges: boolean;\n\tlogger?: Logger;\n}\n\n/**\n * Emits DAG edges for thought relationships when an `IEdgeStore` is configured\n * and the `dagEdges` feature flag is enabled. No-ops otherwise.\n */\nexport class EdgeEmitter {\n\tprivate readonly _edgeStore?: IEdgeStore;\n\tprivate readonly _dagEdges: boolean;\n\tprivate readonly _logger: Logger;\n\n\tconstructor(config: EdgeEmitterConfig) {\n\t\tthis._edgeStore = config.edgeStore;\n\t\tthis._dagEdges = config.dagEdges;\n\t\tthis._logger = config.logger ?? new NullLogger();\n\t}\n\n\t/** Returns true when edge emission is active (store + flag both set). */\n\tpublic isEnabled(): boolean {\n\t\treturn this._edgeStore !== undefined && this._dagEdges;\n\t}\n\n\t/**\n\t * Emits DAG edges for a thought based on its metadata fields.\n\t *\n\t * Edge kinds (in priority order):\n\t * - branch: branch_from_thought + branch_id → parent.id → current.id\n\t * - merge: merge_from_thoughts → source.id → current.id (per source)\n\t * - verifies: verification_target + thought_type=verification → current.id → target.id\n\t * - critiques: verification_target + thought_type=critique → current.id → target.id\n\t * - derives_from: synthesis_sources → source.id → current.id (per source)\n\t * - revises: revises_thought → current.id → target.id\n\t * - tool_invocation: stable admission source id → current.id\n\t * - sequence: default chronological link from previous thought (if none of the above)\n\t * @returns True only when at least one edge was added to the store\n\t */\n\tpublic emitEdgesForThought(\n\t\tsession: EdgeEmissionSession,\n\t\tthought: ThoughtData,\n\t\tcontext?: ThoughtAdmissionContext\n\t): boolean {\n\t\tif (!this._edgeStore || !this._dagEdges) return false;\n\t\tif (!thought.id) return false;\n\n\t\tconst sessionId = thought.session_id;\n\t\tconst references = context?.resolvedReferences ?? {};\n\t\tconst hasRelationalIntent =\n\t\t\t(thought.branch_from_thought !== undefined && thought.branch_id !== undefined) ||\n\t\t\t(thought.merge_from_thoughts?.length ?? 0) > 0 ||\n\t\t\t(thought.verification_target !== undefined &&\n\t\t\t\t(thought.thought_type === 'verification' || thought.thought_type === 'critique')) ||\n\t\t\t(thought.synthesis_sources?.length ?? 0) > 0 ||\n\t\t\tthought.revises_thought !== undefined ||\n\t\t\t(thought.thought_type === 'tool_observation' &&\n\t\t\t\tcontext?.toolInvocationSourceThoughtId !== undefined);\n\t\tconst emittedRelational = [\n\t\t\tthis._emitBranchEdge(thought, references, sessionId),\n\t\t\tthis._emitMergeEdges(thought, references, sessionId),\n\t\t\tthis._emitVerificationEdge(thought, references, sessionId),\n\t\t\tthis._emitCritiqueEdge(thought, references, sessionId),\n\t\t\tthis._emitSynthesisEdges(thought, references, sessionId),\n\t\t\tthis._emitRevisionEdge(thought, references, sessionId),\n\t\t\tthis._emitToolInvocationEdge(thought, sessionId, context),\n\t\t].some((emitted) => emitted);\n\n\t\tif (emittedRelational || hasRelationalIntent) return emittedRelational;\n\t\treturn this._emitSequenceEdge(session, thought, sessionId);\n\t}\n\n\tprivate _emitBranchEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.branch_from_thought === undefined || !thought.branch_id) return false;\n\n\t\treturn this._addEdgeIfValid(references.branchFromThoughtId, thought.id, 'branch', sessionId);\n\t}\n\n\tprivate _emitMergeEdges(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tlet emitted = false;\n\t\tfor (const srcId of references.mergeFromThoughtIds ?? []) {\n\t\t\temitted = this._addEdgeIfValid(srcId, thought.id, 'merge', sessionId) || emitted;\n\t\t}\n\t\treturn emitted;\n\t}\n\n\tprivate _emitVerificationEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.verification_target === undefined || thought.thought_type !== 'verification')\n\t\t\treturn false;\n\n\t\treturn this._addEdgeIfValid(\n\t\t\tthought.id,\n\t\t\tresolvedVerificationTarget(thought, references),\n\t\t\t'verifies',\n\t\t\tsessionId\n\t\t);\n\t}\n\n\tprivate _emitCritiqueEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.verification_target === undefined || thought.thought_type !== 'critique')\n\t\t\treturn false;\n\n\t\treturn this._addEdgeIfValid(\n\t\t\tthought.id,\n\t\t\treferences.verificationTargetThoughtId,\n\t\t\t'critiques',\n\t\t\tsessionId\n\t\t);\n\t}\n\n\tprivate _emitSynthesisEdges(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tlet emitted = false;\n\t\tfor (const srcId of references.synthesisSourceThoughtIds ?? []) {\n\t\t\temitted = this._addEdgeIfValid(srcId, thought.id, 'derives_from', sessionId) || emitted;\n\t\t}\n\t\treturn emitted;\n\t}\n\n\tprivate _emitRevisionEdge(\n\t\tthought: ThoughtData,\n\t\treferences: ResolvedThoughtReferences,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tif (thought.revises_thought === undefined) return false;\n\n\t\treturn this._addEdgeIfValid(thought.id, references.revisesThoughtId, 'revises', sessionId);\n\t}\n\n\tprivate _emitToolInvocationEdge(\n\t\tthought: ThoughtData,\n\t\tsessionId: SessionId,\n\t\tcontext?: ThoughtAdmissionContext\n\t): boolean {\n\t\tif (\n\t\t\tthought.thought_type !== 'tool_observation' ||\n\t\t\tcontext?.toolInvocationSourceThoughtId === undefined\n\t\t) {\n\t\t\treturn false;\n\t\t}\n\n\t\tconst metadata = thought.tool_name !== undefined ? { tool_name: thought.tool_name } : undefined;\n\t\treturn this._addEdgeIfValid(\n\t\t\tcontext.toolInvocationSourceThoughtId,\n\t\t\tthought.id,\n\t\t\t'tool_invocation',\n\t\t\tsessionId,\n\t\t\tmetadata\n\t\t);\n\t}\n\n\tprivate _emitSequenceEdge(\n\t\tsession: EdgeEmissionSession,\n\t\tthought: ThoughtData,\n\t\tsessionId: SessionId\n\t): boolean {\n\t\tconst history = session.thought_history;\n\t\tif (history.length < 2) return false;\n\n\t\tconst prev = history[history.length - 2];\n\t\tif (!prev?.id) return false;\n\n\t\treturn this._addEdgeIfValid(prev.id, thought.id, 'sequence', sessionId);\n\t}\n\n\t/**\n\t * Adds an edge to the edge store if both endpoints are non-empty strings.\n\t * Returns true if added, false if skipped (missing endpoint).\n\t * Failures (e.g. self-edge) are caught and logged.\n\t */\n\tprivate _addEdgeIfValid(\n\t\tfrom: ThoughtId | undefined,\n\t\tto: ThoughtId | undefined,\n\t\tkind: EdgeKind,\n\t\tsessionId: SessionId,\n\t\tmetadata?: Record<string, unknown>\n\t): boolean {\n\t\tif (!from || !to) {\n\t\t\tthis._logger.debug('Skipping edge: unresolved endpoint', {\n\t\t\t\tkind,\n\t\t\t\tfrom: from ?? null,\n\t\t\t\tto: to ?? null,\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t\tconst edge: Edge = {\n\t\t\tid: generateEdgeId(),\n\t\t\tfrom: from as Edge['from'],\n\t\t\tto: to as Edge['to'],\n\t\t\tkind,\n\t\t\tsessionId: sessionId as Edge['sessionId'],\n\t\t\tcreatedAt: Date.now(),\n\t\t\t...(metadata !== undefined ? { metadata } : {}),\n\t\t};\n\t\tif (!this._edgeStore) {\n\t\t\tthis._logger.warn('EdgeStore not available; skipping edge', { kind });\n\t\t\treturn false;\n\t\t}\n\t\tconst duplicate = this._edgeStore\n\t\t\t.outgoing(sessionId, from)\n\t\t\t.some((existing) => existing.kind === kind && existing.to === to);\n\t\tif (duplicate) return false;\n\t\ttry {\n\t\t\tthis._edgeStore.addEdge(edge);\n\t\t\treturn true;\n\t\t} catch (err) {\n\t\t\tthis._logger.info('Failed to add DAG edge', {\n\t\t\t\tkind,\n\t\t\t\terror: getErrorMessage(err),\n\t\t\t});\n\t\t\treturn false;\n\t\t}\n\t}\n}\n"],"names":["EdgeEmitter","config","NullLogger","undefined","session","thought","context","sessionId","references","hasRelationalIntent","emittedRelational","emitted","srcId","resolvedVerificationTarget","metadata","history","prev","from","to","kind","edge","generateEdgeId","Date","duplicate","existing","err","getErrorMessage"],"mappings":"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"}
|
|
@@ -14,6 +14,30 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import type { IGraphViewStore } from '../../contracts/interfaces.js';
|
|
16
16
|
import type { SessionId, ThoughtId } from '../../contracts/ids.js';
|
|
17
|
+
import type { EdgeKind } from './Edge.js';
|
|
18
|
+
export interface GraphMetrics {
|
|
19
|
+
/** Distinct visible edge endpoints and explicit nodes. */
|
|
20
|
+
readonly node_count: number;
|
|
21
|
+
readonly edge_count: number;
|
|
22
|
+
/** Nodes with no incoming edge. */
|
|
23
|
+
readonly root_count: number;
|
|
24
|
+
/** Nodes with no outgoing edge. */
|
|
25
|
+
readonly leaf_count: number;
|
|
26
|
+
/** Longest path in edges; zero without edges, null when any cycle exists. */
|
|
27
|
+
readonly longest_path: number | null;
|
|
28
|
+
/** Maximum outgoing edge count; zero without edges. */
|
|
29
|
+
readonly max_out_degree: number;
|
|
30
|
+
/** Maximum incoming edge count; zero without edges. */
|
|
31
|
+
readonly max_in_degree: number;
|
|
32
|
+
/** Counts for all eight kinds, including absent kinds. */
|
|
33
|
+
readonly edge_kind_counts: Readonly<Record<EdgeKind, number>>;
|
|
34
|
+
/** Weakly connected components; zero for an empty graph. */
|
|
35
|
+
readonly component_count: number;
|
|
36
|
+
}
|
|
37
|
+
export interface GraphRelation {
|
|
38
|
+
readonly id: ThoughtId;
|
|
39
|
+
readonly kind: EdgeKind;
|
|
40
|
+
}
|
|
17
41
|
/**
|
|
18
42
|
* Read-only traversal queries over an {@link IEdgeStore}.
|
|
19
43
|
*
|
|
@@ -105,6 +129,7 @@ export declare class GraphView {
|
|
|
105
129
|
* @param sessionId - Session to query
|
|
106
130
|
* @param thoughtId - The thought to traverse from
|
|
107
131
|
* @param maxDepth - Optional maximum traversal depth (1 = direct parents only)
|
|
132
|
+
* @param kinds - Optional edge kinds to follow; empty follows all kinds
|
|
108
133
|
* @returns Ancestor thought ids in BFS order
|
|
109
134
|
*
|
|
110
135
|
* @example
|
|
@@ -112,7 +137,7 @@ export declare class GraphView {
|
|
|
112
137
|
* const parents = view.ancestors('s1', 'thought-id', 1);
|
|
113
138
|
* ```
|
|
114
139
|
*/
|
|
115
|
-
ancestors(sessionId: SessionId, thoughtId: ThoughtId, maxDepth?: number): readonly ThoughtId[];
|
|
140
|
+
ancestors(sessionId: SessionId, thoughtId: ThoughtId, maxDepth?: number, kinds?: readonly EdgeKind[]): readonly ThoughtId[];
|
|
116
141
|
/**
|
|
117
142
|
* Return all descendants of `thoughtId` via outgoing-edge BFS.
|
|
118
143
|
*
|
|
@@ -121,6 +146,7 @@ export declare class GraphView {
|
|
|
121
146
|
* @param sessionId - Session to query
|
|
122
147
|
* @param thoughtId - The thought to traverse from
|
|
123
148
|
* @param maxDepth - Optional maximum traversal depth (1 = direct children only)
|
|
149
|
+
* @param kinds - Optional edge kinds to follow; empty follows all kinds
|
|
124
150
|
* @returns Descendant thought ids in BFS order
|
|
125
151
|
*
|
|
126
152
|
* @example
|
|
@@ -128,7 +154,39 @@ export declare class GraphView {
|
|
|
128
154
|
* const children = view.descendants('s1', 'thought-id');
|
|
129
155
|
* ```
|
|
130
156
|
*/
|
|
131
|
-
descendants(sessionId: SessionId, thoughtId: ThoughtId, maxDepth?: number): readonly ThoughtId[];
|
|
157
|
+
descendants(sessionId: SessionId, thoughtId: ThoughtId, maxDepth?: number, kinds?: readonly EdgeKind[]): readonly ThoughtId[];
|
|
158
|
+
/**
|
|
159
|
+
* Return incoming relations in store order.
|
|
160
|
+
* @param sessionId - Session to query
|
|
161
|
+
* @param thoughtId - Thought whose incoming edges to list
|
|
162
|
+
* @returns Source ids and edge kinds, or an empty array for an unknown node
|
|
163
|
+
*/
|
|
164
|
+
inbound(sessionId: SessionId, thoughtId: ThoughtId): readonly GraphRelation[];
|
|
165
|
+
/**
|
|
166
|
+
* Return outgoing relations in store order.
|
|
167
|
+
* @param sessionId - Session to query
|
|
168
|
+
* @param thoughtId - Thought whose outgoing edges to list
|
|
169
|
+
* @returns Target ids and edge kinds, or an empty array for an unknown node
|
|
170
|
+
*/
|
|
171
|
+
outbound(sessionId: SessionId, thoughtId: ThoughtId): readonly GraphRelation[];
|
|
172
|
+
/**
|
|
173
|
+
* Test reachability by one or more directed edges, including cycles.
|
|
174
|
+
* @param sessionId - Session to query
|
|
175
|
+
* @param from - Starting thought id
|
|
176
|
+
* @param to - Target thought id
|
|
177
|
+
* @param kinds - Optional edge kinds to follow; empty follows all kinds
|
|
178
|
+
* @returns Whether a nonempty directed path reaches the target
|
|
179
|
+
*/
|
|
180
|
+
hasPath(sessionId: SessionId, from: ThoughtId, to: ThoughtId, kinds?: readonly EdgeKind[]): boolean;
|
|
181
|
+
/**
|
|
182
|
+
* Compute structural metrics over every visible node and edge kind.
|
|
183
|
+
* @param sessionId - Session to query
|
|
184
|
+
* @returns Counts and longest path length, with null longest_path on cycles
|
|
185
|
+
*/
|
|
186
|
+
metrics(sessionId: SessionId): GraphMetrics;
|
|
187
|
+
private _edgeKindCounts;
|
|
188
|
+
private _longestPath;
|
|
189
|
+
private _componentCount;
|
|
132
190
|
/**
|
|
133
191
|
* Return all thought ids in the session that have no outgoing edges.
|
|
134
192
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GraphView.d.ts","sourceRoot":"","sources":["../../../src/core/graph/GraphView.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,+BAA+B,CAAC;AACrE,OAAO,KAAK,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;
|
|
1
|
+
{"version":3,"file":"GraphView.d.ts","sourceRoot":"","sources":["../../../src/core/graph/GraphView.ts"],"names":[],"mappings":"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"}
|
|
@@ -54,8 +54,9 @@ class GraphView {
|
|
|
54
54
|
const queue = [
|
|
55
55
|
rootThoughtId
|
|
56
56
|
];
|
|
57
|
-
|
|
58
|
-
|
|
57
|
+
let head = 0;
|
|
58
|
+
while(head < queue.length){
|
|
59
|
+
const current = queue[head++];
|
|
59
60
|
const out = this._store.outgoing(sessionId, current);
|
|
60
61
|
for (const edge of out)if ('branch' === edge.kind) {
|
|
61
62
|
if (!visited.has(edge.to)) {
|
|
@@ -74,8 +75,9 @@ class GraphView {
|
|
|
74
75
|
const queue = [];
|
|
75
76
|
for (const [node, deg] of inDegree)if (0 === deg) queue.push(node);
|
|
76
77
|
const order = [];
|
|
77
|
-
|
|
78
|
-
|
|
78
|
+
let head = 0;
|
|
79
|
+
while(head < queue.length){
|
|
80
|
+
const node = queue[head++];
|
|
79
81
|
order.push(node);
|
|
80
82
|
for (const edge of this._store.outgoing(sessionId, node)){
|
|
81
83
|
const next = (inDegree.get(edge.to) ?? 0) - 1;
|
|
@@ -86,11 +88,122 @@ class GraphView {
|
|
|
86
88
|
if (order.length !== inDegree.size) throw new CycleDetectedError(`Cycle detected in session '${sessionId}': topological sort incomplete (${order.length}/${inDegree.size})`);
|
|
87
89
|
return order;
|
|
88
90
|
}
|
|
89
|
-
ancestors(sessionId, thoughtId, maxDepth) {
|
|
90
|
-
return this._bfsClosure(sessionId, thoughtId, maxDepth, 'incoming');
|
|
91
|
+
ancestors(sessionId, thoughtId, maxDepth, kinds) {
|
|
92
|
+
return this._bfsClosure(sessionId, thoughtId, maxDepth, 'incoming', kinds);
|
|
91
93
|
}
|
|
92
|
-
descendants(sessionId, thoughtId, maxDepth) {
|
|
93
|
-
return this._bfsClosure(sessionId, thoughtId, maxDepth, 'outgoing');
|
|
94
|
+
descendants(sessionId, thoughtId, maxDepth, kinds) {
|
|
95
|
+
return this._bfsClosure(sessionId, thoughtId, maxDepth, 'outgoing', kinds);
|
|
96
|
+
}
|
|
97
|
+
inbound(sessionId, thoughtId) {
|
|
98
|
+
return this._store.incoming(sessionId, thoughtId).map((edge)=>({
|
|
99
|
+
id: edge.from,
|
|
100
|
+
kind: edge.kind
|
|
101
|
+
}));
|
|
102
|
+
}
|
|
103
|
+
outbound(sessionId, thoughtId) {
|
|
104
|
+
return this._store.outgoing(sessionId, thoughtId).map((edge)=>({
|
|
105
|
+
id: edge.to,
|
|
106
|
+
kind: edge.kind
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
hasPath(sessionId, from, to, kinds) {
|
|
110
|
+
const allowed = kinds?.length ? new Set(kinds) : void 0;
|
|
111
|
+
const visited = new Set([
|
|
112
|
+
from
|
|
113
|
+
]);
|
|
114
|
+
const queue = [
|
|
115
|
+
from
|
|
116
|
+
];
|
|
117
|
+
for (const node of queue)for (const edge of this._store.outgoing(sessionId, node))if (void 0 === allowed || allowed.has(edge.kind)) {
|
|
118
|
+
if (edge.to === to) return true;
|
|
119
|
+
if (!visited.has(edge.to)) {
|
|
120
|
+
visited.add(edge.to);
|
|
121
|
+
queue.push(edge.to);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return false;
|
|
125
|
+
}
|
|
126
|
+
metrics(sessionId) {
|
|
127
|
+
const edges = this._store.edgesForSession(sessionId);
|
|
128
|
+
const explicitNodes = this._store.nodesForSession?.(sessionId);
|
|
129
|
+
const { nodes, hasIncoming } = this._collectNodes(edges, explicitNodes);
|
|
130
|
+
const inDegree = this._buildInDegree(edges, explicitNodes);
|
|
131
|
+
const outDegree = new Map();
|
|
132
|
+
let maxIn = 0;
|
|
133
|
+
let maxOut = 0;
|
|
134
|
+
for (const edge of edges){
|
|
135
|
+
const degree = (outDegree.get(edge.from) ?? 0) + 1;
|
|
136
|
+
outDegree.set(edge.from, degree);
|
|
137
|
+
maxOut = Math.max(maxOut, degree);
|
|
138
|
+
}
|
|
139
|
+
for (const degree of inDegree.values())maxIn = Math.max(maxIn, degree);
|
|
140
|
+
return {
|
|
141
|
+
node_count: nodes.size,
|
|
142
|
+
edge_count: edges.length,
|
|
143
|
+
root_count: nodes.size - hasIncoming.size,
|
|
144
|
+
leaf_count: nodes.size - outDegree.size,
|
|
145
|
+
longest_path: this._longestPath(sessionId, inDegree),
|
|
146
|
+
max_out_degree: maxOut,
|
|
147
|
+
max_in_degree: maxIn,
|
|
148
|
+
edge_kind_counts: this._edgeKindCounts(edges),
|
|
149
|
+
component_count: this._componentCount(nodes, edges)
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
_edgeKindCounts(edges) {
|
|
153
|
+
const counts = {
|
|
154
|
+
sequence: 0,
|
|
155
|
+
branch: 0,
|
|
156
|
+
merge: 0,
|
|
157
|
+
verifies: 0,
|
|
158
|
+
critiques: 0,
|
|
159
|
+
derives_from: 0,
|
|
160
|
+
tool_invocation: 0,
|
|
161
|
+
revises: 0
|
|
162
|
+
};
|
|
163
|
+
for (const edge of edges)counts[edge.kind]++;
|
|
164
|
+
return counts;
|
|
165
|
+
}
|
|
166
|
+
_longestPath(sessionId, inDegree) {
|
|
167
|
+
const queue = [];
|
|
168
|
+
const distances = new Map();
|
|
169
|
+
for (const [node, degree] of inDegree)if (0 === degree) queue.push(node);
|
|
170
|
+
let longest = 0;
|
|
171
|
+
let head = 0;
|
|
172
|
+
while(head < queue.length){
|
|
173
|
+
const node = queue[head++];
|
|
174
|
+
const distance = distances.get(node) ?? 0;
|
|
175
|
+
longest = Math.max(longest, distance);
|
|
176
|
+
for (const edge of this._store.outgoing(sessionId, node)){
|
|
177
|
+
distances.set(edge.to, Math.max(distances.get(edge.to) ?? 0, distance + 1));
|
|
178
|
+
const degree = (inDegree.get(edge.to) ?? 0) - 1;
|
|
179
|
+
inDegree.set(edge.to, degree);
|
|
180
|
+
if (0 === degree) queue.push(edge.to);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return head === inDegree.size ? longest : null;
|
|
184
|
+
}
|
|
185
|
+
_componentCount(nodes, edges) {
|
|
186
|
+
const neighbours = new Map();
|
|
187
|
+
for (const node of nodes)neighbours.set(node, []);
|
|
188
|
+
for (const edge of edges){
|
|
189
|
+
neighbours.get(edge.from)?.push(edge.to);
|
|
190
|
+
neighbours.get(edge.to)?.push(edge.from);
|
|
191
|
+
}
|
|
192
|
+
const visited = new Set();
|
|
193
|
+
let count = 0;
|
|
194
|
+
for (const node of nodes){
|
|
195
|
+
if (visited.has(node)) continue;
|
|
196
|
+
count++;
|
|
197
|
+
visited.add(node);
|
|
198
|
+
const queue = [
|
|
199
|
+
node
|
|
200
|
+
];
|
|
201
|
+
for (const current of queue)for (const neighbour of neighbours.get(current) ?? [])if (!visited.has(neighbour)) {
|
|
202
|
+
visited.add(neighbour);
|
|
203
|
+
queue.push(neighbour);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
return count;
|
|
94
207
|
}
|
|
95
208
|
leaves(sessionId) {
|
|
96
209
|
const edges = this._store.edgesForSession(sessionId);
|
|
@@ -130,8 +243,9 @@ class GraphView {
|
|
|
130
243
|
order.push(root);
|
|
131
244
|
queue.push(root);
|
|
132
245
|
}
|
|
133
|
-
|
|
134
|
-
|
|
246
|
+
let head = 0;
|
|
247
|
+
while(head < queue.length){
|
|
248
|
+
const node = queue[head++];
|
|
135
249
|
for (const edge of this._store.outgoing(sessionId, node))if (!visited.has(edge.to)) {
|
|
136
250
|
visited.add(edge.to);
|
|
137
251
|
order.push(edge.to);
|
|
@@ -149,7 +263,8 @@ class GraphView {
|
|
|
149
263
|
}
|
|
150
264
|
return inDegree;
|
|
151
265
|
}
|
|
152
|
-
_bfsClosure(sessionId, startId, maxDepth, direction) {
|
|
266
|
+
_bfsClosure(sessionId, startId, maxDepth, direction, kinds) {
|
|
267
|
+
const allowed = kinds?.length ? new Set(kinds) : void 0;
|
|
153
268
|
const cap = maxDepth ?? 1 / 0;
|
|
154
269
|
const visited = new Set([
|
|
155
270
|
startId
|
|
@@ -164,6 +279,7 @@ class GraphView {
|
|
|
164
279
|
for (const node of frontier){
|
|
165
280
|
const edges = 'outgoing' === direction ? this._store.outgoing(sessionId, node) : this._store.incoming(sessionId, node);
|
|
166
281
|
for (const edge of edges){
|
|
282
|
+
if (void 0 !== allowed && !allowed.has(edge.kind)) continue;
|
|
167
283
|
const neighbour = 'outgoing' === direction ? edge.to : edge.from;
|
|
168
284
|
if (!visited.has(neighbour)) {
|
|
169
285
|
visited.add(neighbour);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"core/graph/GraphView.js","sources":["../../../src/core/graph/GraphView.ts"],"sourcesContent":["/**\n * GraphView — read-only graph traversal queries over an {@link IEdgeStore}.\n *\n * Provides BFS / topological / ancestor / descendant queries over the\n * thought DAG. Queries return thought ids or graph distances,\n * never `ThoughtData` objects — the view is intentionally decoupled from\n * `HistoryManager` so it can be reused by strategies that work purely in\n * terms of graph structure.\n *\n * Construction is cheap (stores a reference to the underlying store).\n * Each query reads the latest store state — no caching, no snapshots.\n *\n * @module core/graph/GraphView\n */\n\nimport type { IGraphViewStore } from '../../contracts/interfaces.js';\nimport type { SessionId, ThoughtId } from '../../contracts/ids.js';\nimport { CycleDetectedError } from '../../errors.js';\nimport type { Edge } from './Edge.js';\n\n/**\n * Read-only traversal queries over an {@link IEdgeStore}.\n *\n * @example\n * ```typescript\n * const store = new EdgeStore();\n * // ... populate store ...\n * const view = new GraphView(store);\n * const order = view.topological('s1');\n * ```\n */\nexport class GraphView {\n\tprivate readonly _store: IGraphViewStore;\n\n\t/**\n\t * Create a new GraphView backed by the given edge store.\n\t *\n\t * @param store - The edge store to query (held by reference, read-only access)\n\t *\n\t * @example\n\t * ```typescript\n\t * const view = new GraphView(edgeStore);\n\t * ```\n\t */\n\tconstructor(store: IGraphViewStore) {\n\t\tthis._store = store;\n\t}\n\n\t/**\n\t * Return all thought ids in the session, ordered by BFS from roots.\n\t *\n\t * Roots are nodes with no incoming edges. Within a layer, neighbours\n\t * are visited in `outgoing` order (which the underlying store keeps\n\t * sorted by `createdAt` ascending).\n\t *\n\t * @param sessionId - Session to query\n\t * @returns Thought ids in chronological BFS order\n\t *\n\t * @example\n\t * ```typescript\n\t * const ids = view.chronological('s1');\n\t * ```\n\t */\n\tchronological(sessionId: SessionId): readonly ThoughtId[] {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst { nodes, hasIncoming } = this._collectNodes(\n\t\t\tedges,\n\t\t\tthis._store.nodesForSession?.(sessionId)\n\t\t);\n\t\tif (nodes.size === 0) return [];\n\t\tconst roots = this._findRoots(nodes, hasIncoming, edges);\n\t\treturn this._bfsFromRoots(sessionId, roots) as readonly ThoughtId[];\n\t}\n\n\t/**\n\t * Return the shortest directed distance from any retained root to a thought.\n\t *\n\t * Nodes are derived from edges unless an immutable snapshot supplies original\n\t * endpoint nodes. Thoughts never referenced by an audit edge remain invisible.\n\t * Every edge kind participates in the traversal.\n\t *\n\t * @param sessionId - Session to query\n\t * @param thoughtId - Thought whose root distance to resolve\n\t * @returns Zero for a root, or `undefined` when no root can reach the thought\n\t */\n\tdepthFromRoots(sessionId: SessionId, thoughtId: ThoughtId): number | undefined {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst nodes = new Set<ThoughtId>(this._store.nodesForSession?.(sessionId) ?? []);\n\t\tconst hasIncoming = new Set<ThoughtId>();\n\t\tconst outgoing = new Map<ThoughtId, ThoughtId[]>();\n\t\tfor (const edge of edges) {\n\t\t\tnodes.add(edge.from);\n\t\t\tnodes.add(edge.to);\n\t\t\thasIncoming.add(edge.to);\n\t\t\tconst children = outgoing.get(edge.from);\n\t\t\tif (children === undefined) outgoing.set(edge.from, [edge.to]);\n\t\t\telse children.push(edge.to);\n\t\t}\n\t\tif (!nodes.has(thoughtId)) return undefined;\n\n\t\tlet frontier: ThoughtId[] = [];\n\t\tfor (const node of nodes) {\n\t\t\tif (!hasIncoming.has(node)) frontier.push(node);\n\t\t}\n\t\tconst visited = new Set<ThoughtId>(frontier);\n\t\tlet depth = 0;\n\t\twhile (frontier.length > 0) {\n\t\t\tconst next: ThoughtId[] = [];\n\t\t\tfor (const node of frontier) {\n\t\t\t\tif (node === thoughtId) return depth;\n\t\t\t\tfor (const child of outgoing.get(node) ?? []) {\n\t\t\t\t\tif (visited.has(child)) continue;\n\t\t\t\t\tvisited.add(child);\n\t\t\t\t\tnext.push(child);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfrontier = next;\n\t\t\tdepth++;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Return all thought ids reachable from `rootThoughtId` via `'branch'`\n\t * edges, including the root itself.\n\t *\n\t * Non-branch edges are ignored. Cycles (if any) do not cause infinite\n\t * loops because each node is visited at most once.\n\t *\n\t * @param sessionId - Session to query\n\t * @param rootThoughtId - The starting thought id (typically the branch root)\n\t * @returns Thought ids forming the branch (includes root)\n\t *\n\t * @example\n\t * ```typescript\n\t * const branchIds = view.branchThoughts('s1', 'thought-root');\n\t * ```\n\t */\n\tbranchThoughts(sessionId: SessionId, rootThoughtId: ThoughtId): readonly ThoughtId[] {\n\t\tconst visited = new Set<string>([rootThoughtId]);\n\t\tconst order: string[] = [rootThoughtId];\n\t\tconst queue: string[] = [rootThoughtId];\n\t\twhile (queue.length > 0) {\n\t\t\tconst current = queue.shift()!;\n\t\t\tconst out = this._store.outgoing(sessionId, current as ThoughtId);\n\t\t\tfor (const edge of out) {\n\t\t\t\tif (edge.kind !== 'branch') continue;\n\t\t\t\tif (visited.has(edge.to)) continue;\n\t\t\t\tvisited.add(edge.to);\n\t\t\t\torder.push(edge.to);\n\t\t\t\tqueue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\treturn order as ThoughtId[];\n\t}\n\n\t/**\n\t * Return a topological ordering of all thought ids in the session\n\t * using Kahn's algorithm.\n\t *\n\t * @param sessionId - Session to query\n\t * @returns Thought ids in topological order\n\t * @throws {CycleDetectedError} If the graph contains a cycle\n\t *\n\t * @example\n\t * ```typescript\n\t * const order = view.topological('s1');\n\t * ```\n\t */\n\ttopological(sessionId: SessionId): readonly ThoughtId[] {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst inDegree = this._buildInDegree(edges, this._store.nodesForSession?.(sessionId));\n\t\tif (inDegree.size === 0) return [];\n\t\tconst queue: string[] = [];\n\t\tfor (const [node, deg] of inDegree) {\n\t\t\tif (deg === 0) queue.push(node);\n\t\t}\n\t\tconst order: string[] = [];\n\t\twhile (queue.length > 0) {\n\t\t\tconst node = queue.shift()!;\n\t\t\torder.push(node);\n\t\t\tfor (const edge of this._store.outgoing(sessionId, node as ThoughtId)) {\n\t\t\t\tconst next = (inDegree.get(edge.to) ?? 0) - 1;\n\t\t\t\tinDegree.set(edge.to, next);\n\t\t\t\tif (next === 0) queue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\tif (order.length !== inDegree.size) {\n\t\t\tthrow new CycleDetectedError(\n\t\t\t\t`Cycle detected in session '${sessionId}': topological sort incomplete (${order.length}/${inDegree.size})`\n\t\t\t);\n\t\t}\n\t\treturn order as ThoughtId[];\n\t}\n\n\t/**\n\t * Return all ancestors of `thoughtId` via incoming-edge BFS.\n\t *\n\t * The starting node is NOT included. Cycles do not cause infinite loops.\n\t *\n\t * @param sessionId - Session to query\n\t * @param thoughtId - The thought to traverse from\n\t * @param maxDepth - Optional maximum traversal depth (1 = direct parents only)\n\t * @returns Ancestor thought ids in BFS order\n\t *\n\t * @example\n\t * ```typescript\n\t * const parents = view.ancestors('s1', 'thought-id', 1);\n\t * ```\n\t */\n\tancestors(sessionId: SessionId, thoughtId: ThoughtId, maxDepth?: number): readonly ThoughtId[] {\n\t\treturn this._bfsClosure(sessionId, thoughtId, maxDepth, 'incoming') as readonly ThoughtId[];\n\t}\n\n\t/**\n\t * Return all descendants of `thoughtId` via outgoing-edge BFS.\n\t *\n\t * The starting node is NOT included. Cycles do not cause infinite loops.\n\t *\n\t * @param sessionId - Session to query\n\t * @param thoughtId - The thought to traverse from\n\t * @param maxDepth - Optional maximum traversal depth (1 = direct children only)\n\t * @returns Descendant thought ids in BFS order\n\t *\n\t * @example\n\t * ```typescript\n\t * const children = view.descendants('s1', 'thought-id');\n\t * ```\n\t */\n\tdescendants(sessionId: SessionId, thoughtId: ThoughtId, maxDepth?: number): readonly ThoughtId[] {\n\t\treturn this._bfsClosure(sessionId, thoughtId, maxDepth, 'outgoing') as readonly ThoughtId[];\n\t}\n\n\t/**\n\t * Return all thought ids in the session that have no outgoing edges.\n\t *\n\t * @param sessionId - Session to query\n\t * @returns Thought ids that are graph leaves\n\t *\n\t * @example\n\t * ```typescript\n\t * const tips = view.leaves('s1');\n\t * ```\n\t */\n\tleaves(sessionId: SessionId): readonly ThoughtId[] {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst { nodes } = this._collectNodes(edges, this._store.nodesForSession?.(sessionId));\n\t\tif (nodes.size === 0) return [];\n\t\tconst result: string[] = [];\n\t\tfor (const node of nodes) {\n\t\t\tif (this._store.outgoing(sessionId, node as ThoughtId).length === 0) {\n\t\t\t\tresult.push(node);\n\t\t\t}\n\t\t}\n\t\treturn result as ThoughtId[];\n\t}\n\n\t/**\n\t * Collect every node id referenced by the edges and a set of nodes\n\t * that have at least one incoming edge.\n\t */\n\tprivate _collectNodes(\n\t\tedges: readonly Edge[],\n\t\texplicitNodes: readonly ThoughtId[] = []\n\t): {\n\t\tnodes: Set<string>;\n\t\thasIncoming: Set<string>;\n\t} {\n\t\tconst nodes = new Set<string>(explicitNodes);\n\t\tconst hasIncoming = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tnodes.add(edge.from);\n\t\t\tnodes.add(edge.to);\n\t\t\thasIncoming.add(edge.to);\n\t\t}\n\t\treturn { nodes, hasIncoming };\n\t}\n\n\t/**\n\t * Find roots (no incoming) ordered by the earliest outgoing edge\n\t * `createdAt`. Nodes with no outgoing edges fall back to the order\n\t * they appear among edges (already sorted by `createdAt`).\n\t */\n\tprivate _findRoots(\n\t\tnodes: Set<string>,\n\t\thasIncoming: Set<string>,\n\t\tedges: readonly Edge[]\n\t): string[] {\n\t\tconst earliest = new Map<string, number>();\n\t\tfor (const edge of edges) {\n\t\t\tif (!earliest.has(edge.from)) {\n\t\t\t\tearliest.set(edge.from, edge.createdAt);\n\t\t\t}\n\t\t}\n\t\tconst roots: string[] = [];\n\t\tfor (const node of nodes) {\n\t\t\tif (!hasIncoming.has(node)) roots.push(node);\n\t\t}\n\t\troots.sort((a, b) => (earliest.get(a) ?? Infinity) - (earliest.get(b) ?? Infinity));\n\t\treturn roots;\n\t}\n\n\t/**\n\t * BFS from a list of root ids, visiting each node at most once.\n\t */\n\tprivate _bfsFromRoots(sessionId: SessionId, roots: readonly string[]): readonly string[] {\n\t\tconst visited = new Set<string>();\n\t\tconst order: string[] = [];\n\t\tconst queue: string[] = [];\n\t\tfor (const root of roots) {\n\t\t\tif (visited.has(root)) continue;\n\t\t\tvisited.add(root);\n\t\t\torder.push(root);\n\t\t\tqueue.push(root);\n\t\t}\n\t\twhile (queue.length > 0) {\n\t\t\tconst node = queue.shift()!;\n\t\t\tfor (const edge of this._store.outgoing(sessionId, node as ThoughtId)) {\n\t\t\t\tif (visited.has(edge.to)) continue;\n\t\t\t\tvisited.add(edge.to);\n\t\t\t\torder.push(edge.to);\n\t\t\t\tqueue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\treturn order;\n\t}\n\n\t/**\n\t * Build an in-degree map covering every node referenced by the edges.\n\t */\n\tprivate _buildInDegree(\n\t\tedges: readonly Edge[],\n\t\texplicitNodes: readonly ThoughtId[] = []\n\t): Map<string, number> {\n\t\tconst inDegree = new Map<string, number>();\n\t\tfor (const node of explicitNodes) inDegree.set(node, 0);\n\t\tfor (const edge of edges) {\n\t\t\tif (!inDegree.has(edge.from)) inDegree.set(edge.from, 0);\n\t\t\tinDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);\n\t\t}\n\t\treturn inDegree;\n\t}\n\n\t/**\n\t * Generic BFS closure in either direction. Excludes the start node\n\t * from the result and respects an optional depth cap.\n\t */\n\tprivate _bfsClosure(\n\t\tsessionId: SessionId,\n\t\tstartId: string,\n\t\tmaxDepth: number | undefined,\n\t\tdirection: 'incoming' | 'outgoing'\n\t): readonly string[] {\n\t\tconst cap = maxDepth ?? Number.POSITIVE_INFINITY;\n\t\tconst visited = new Set<string>([startId]);\n\t\tconst order: string[] = [];\n\t\tlet frontier: string[] = [startId];\n\t\tlet depth = 0;\n\t\twhile (frontier.length > 0 && depth < cap) {\n\t\t\tconst next: string[] = [];\n\t\t\tfor (const node of frontier) {\n\t\t\t\tconst edges =\n\t\t\t\t\tdirection === 'outgoing'\n\t\t\t\t\t\t? this._store.outgoing(sessionId, node as ThoughtId)\n\t\t\t\t\t\t: this._store.incoming(sessionId, node as ThoughtId);\n\t\t\t\tfor (const edge of edges) {\n\t\t\t\t\tconst neighbour = direction === 'outgoing' ? edge.to : edge.from;\n\t\t\t\t\tif (visited.has(neighbour)) continue;\n\t\t\t\t\tvisited.add(neighbour);\n\t\t\t\t\torder.push(neighbour);\n\t\t\t\t\tnext.push(neighbour);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfrontier = next;\n\t\t\tdepth++;\n\t\t}\n\t\treturn order;\n\t}\n}\n"],"names":["GraphView","store","sessionId","edges","nodes","hasIncoming","roots","thoughtId","Set","outgoing","Map","edge","children","undefined","frontier","node","visited","depth","next","child","rootThoughtId","order","queue","current","out","inDegree","deg","CycleDetectedError","maxDepth","result","explicitNodes","earliest","a","b","Infinity","root","startId","direction","cap","Number","neighbour"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"core/graph/GraphView.js","sources":["../../../src/core/graph/GraphView.ts"],"sourcesContent":["/**\n * GraphView — read-only graph traversal queries over an {@link IEdgeStore}.\n *\n * Provides BFS / topological / ancestor / descendant queries over the\n * thought DAG. Queries return thought ids or graph distances,\n * never `ThoughtData` objects — the view is intentionally decoupled from\n * `HistoryManager` so it can be reused by strategies that work purely in\n * terms of graph structure.\n *\n * Construction is cheap (stores a reference to the underlying store).\n * Each query reads the latest store state — no caching, no snapshots.\n *\n * @module core/graph/GraphView\n */\n\nimport type { IGraphViewStore } from '../../contracts/interfaces.js';\nimport type { SessionId, ThoughtId } from '../../contracts/ids.js';\nimport { CycleDetectedError } from '../../errors.js';\nimport type { Edge, EdgeKind } from './Edge.js';\n\nexport interface GraphMetrics {\n\t/** Distinct visible edge endpoints and explicit nodes. */\n\treadonly node_count: number;\n\treadonly edge_count: number;\n\t/** Nodes with no incoming edge. */\n\treadonly root_count: number;\n\t/** Nodes with no outgoing edge. */\n\treadonly leaf_count: number;\n\t/** Longest path in edges; zero without edges, null when any cycle exists. */\n\treadonly longest_path: number | null;\n\t/** Maximum outgoing edge count; zero without edges. */\n\treadonly max_out_degree: number;\n\t/** Maximum incoming edge count; zero without edges. */\n\treadonly max_in_degree: number;\n\t/** Counts for all eight kinds, including absent kinds. */\n\treadonly edge_kind_counts: Readonly<Record<EdgeKind, number>>;\n\t/** Weakly connected components; zero for an empty graph. */\n\treadonly component_count: number;\n}\n\nexport interface GraphRelation {\n\treadonly id: ThoughtId;\n\treadonly kind: EdgeKind;\n}\n\n/**\n * Read-only traversal queries over an {@link IEdgeStore}.\n *\n * @example\n * ```typescript\n * const store = new EdgeStore();\n * // ... populate store ...\n * const view = new GraphView(store);\n * const order = view.topological('s1');\n * ```\n */\nexport class GraphView {\n\tprivate readonly _store: IGraphViewStore;\n\n\t/**\n\t * Create a new GraphView backed by the given edge store.\n\t *\n\t * @param store - The edge store to query (held by reference, read-only access)\n\t *\n\t * @example\n\t * ```typescript\n\t * const view = new GraphView(edgeStore);\n\t * ```\n\t */\n\tconstructor(store: IGraphViewStore) {\n\t\tthis._store = store;\n\t}\n\n\t/**\n\t * Return all thought ids in the session, ordered by BFS from roots.\n\t *\n\t * Roots are nodes with no incoming edges. Within a layer, neighbours\n\t * are visited in `outgoing` order (which the underlying store keeps\n\t * sorted by `createdAt` ascending).\n\t *\n\t * @param sessionId - Session to query\n\t * @returns Thought ids in chronological BFS order\n\t *\n\t * @example\n\t * ```typescript\n\t * const ids = view.chronological('s1');\n\t * ```\n\t */\n\tchronological(sessionId: SessionId): readonly ThoughtId[] {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst { nodes, hasIncoming } = this._collectNodes(\n\t\t\tedges,\n\t\t\tthis._store.nodesForSession?.(sessionId)\n\t\t);\n\t\tif (nodes.size === 0) return [];\n\t\tconst roots = this._findRoots(nodes, hasIncoming, edges);\n\t\treturn this._bfsFromRoots(sessionId, roots) as readonly ThoughtId[];\n\t}\n\n\t/**\n\t * Return the shortest directed distance from any retained root to a thought.\n\t *\n\t * Nodes are derived from edges unless an immutable snapshot supplies original\n\t * endpoint nodes. Thoughts never referenced by an audit edge remain invisible.\n\t * Every edge kind participates in the traversal.\n\t *\n\t * @param sessionId - Session to query\n\t * @param thoughtId - Thought whose root distance to resolve\n\t * @returns Zero for a root, or `undefined` when no root can reach the thought\n\t */\n\tdepthFromRoots(sessionId: SessionId, thoughtId: ThoughtId): number | undefined {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst nodes = new Set<ThoughtId>(this._store.nodesForSession?.(sessionId) ?? []);\n\t\tconst hasIncoming = new Set<ThoughtId>();\n\t\tconst outgoing = new Map<ThoughtId, ThoughtId[]>();\n\t\tfor (const edge of edges) {\n\t\t\tnodes.add(edge.from);\n\t\t\tnodes.add(edge.to);\n\t\t\thasIncoming.add(edge.to);\n\t\t\tconst children = outgoing.get(edge.from);\n\t\t\tif (children === undefined) outgoing.set(edge.from, [edge.to]);\n\t\t\telse children.push(edge.to);\n\t\t}\n\t\tif (!nodes.has(thoughtId)) return undefined;\n\n\t\tlet frontier: ThoughtId[] = [];\n\t\tfor (const node of nodes) {\n\t\t\tif (!hasIncoming.has(node)) frontier.push(node);\n\t\t}\n\t\tconst visited = new Set<ThoughtId>(frontier);\n\t\tlet depth = 0;\n\t\twhile (frontier.length > 0) {\n\t\t\tconst next: ThoughtId[] = [];\n\t\t\tfor (const node of frontier) {\n\t\t\t\tif (node === thoughtId) return depth;\n\t\t\t\tfor (const child of outgoing.get(node) ?? []) {\n\t\t\t\t\tif (visited.has(child)) continue;\n\t\t\t\t\tvisited.add(child);\n\t\t\t\t\tnext.push(child);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfrontier = next;\n\t\t\tdepth++;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\t/**\n\t * Return all thought ids reachable from `rootThoughtId` via `'branch'`\n\t * edges, including the root itself.\n\t *\n\t * Non-branch edges are ignored. Cycles (if any) do not cause infinite\n\t * loops because each node is visited at most once.\n\t *\n\t * @param sessionId - Session to query\n\t * @param rootThoughtId - The starting thought id (typically the branch root)\n\t * @returns Thought ids forming the branch (includes root)\n\t *\n\t * @example\n\t * ```typescript\n\t * const branchIds = view.branchThoughts('s1', 'thought-root');\n\t * ```\n\t */\n\tbranchThoughts(sessionId: SessionId, rootThoughtId: ThoughtId): readonly ThoughtId[] {\n\t\tconst visited = new Set<string>([rootThoughtId]);\n\t\tconst order: string[] = [rootThoughtId];\n\t\tconst queue: string[] = [rootThoughtId];\n\t\tlet head = 0;\n\t\twhile (head < queue.length) {\n\t\t\tconst current = queue[head++]!;\n\t\t\tconst out = this._store.outgoing(sessionId, current as ThoughtId);\n\t\t\tfor (const edge of out) {\n\t\t\t\tif (edge.kind !== 'branch') continue;\n\t\t\t\tif (visited.has(edge.to)) continue;\n\t\t\t\tvisited.add(edge.to);\n\t\t\t\torder.push(edge.to);\n\t\t\t\tqueue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\treturn order as ThoughtId[];\n\t}\n\n\t/**\n\t * Return a topological ordering of all thought ids in the session\n\t * using Kahn's algorithm.\n\t *\n\t * @param sessionId - Session to query\n\t * @returns Thought ids in topological order\n\t * @throws {CycleDetectedError} If the graph contains a cycle\n\t *\n\t * @example\n\t * ```typescript\n\t * const order = view.topological('s1');\n\t * ```\n\t */\n\ttopological(sessionId: SessionId): readonly ThoughtId[] {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst inDegree = this._buildInDegree(edges, this._store.nodesForSession?.(sessionId));\n\t\tif (inDegree.size === 0) return [];\n\t\tconst queue: string[] = [];\n\t\tfor (const [node, deg] of inDegree) {\n\t\t\tif (deg === 0) queue.push(node);\n\t\t}\n\t\tconst order: string[] = [];\n\t\tlet head = 0;\n\t\twhile (head < queue.length) {\n\t\t\tconst node = queue[head++]!;\n\t\t\torder.push(node);\n\t\t\tfor (const edge of this._store.outgoing(sessionId, node as ThoughtId)) {\n\t\t\t\tconst next = (inDegree.get(edge.to) ?? 0) - 1;\n\t\t\t\tinDegree.set(edge.to, next);\n\t\t\t\tif (next === 0) queue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\tif (order.length !== inDegree.size) {\n\t\t\tthrow new CycleDetectedError(\n\t\t\t\t`Cycle detected in session '${sessionId}': topological sort incomplete (${order.length}/${inDegree.size})`\n\t\t\t);\n\t\t}\n\t\treturn order as ThoughtId[];\n\t}\n\n\t/**\n\t * Return all ancestors of `thoughtId` via incoming-edge BFS.\n\t *\n\t * The starting node is NOT included. Cycles do not cause infinite loops.\n\t *\n\t * @param sessionId - Session to query\n\t * @param thoughtId - The thought to traverse from\n\t * @param maxDepth - Optional maximum traversal depth (1 = direct parents only)\n\t * @param kinds - Optional edge kinds to follow; empty follows all kinds\n\t * @returns Ancestor thought ids in BFS order\n\t *\n\t * @example\n\t * ```typescript\n\t * const parents = view.ancestors('s1', 'thought-id', 1);\n\t * ```\n\t */\n\tancestors(\n\t\tsessionId: SessionId,\n\t\tthoughtId: ThoughtId,\n\t\tmaxDepth?: number,\n\t\tkinds?: readonly EdgeKind[]\n\t): readonly ThoughtId[] {\n\t\treturn this._bfsClosure(\n\t\t\tsessionId,\n\t\t\tthoughtId,\n\t\t\tmaxDepth,\n\t\t\t'incoming',\n\t\t\tkinds\n\t\t) as readonly ThoughtId[];\n\t}\n\n\t/**\n\t * Return all descendants of `thoughtId` via outgoing-edge BFS.\n\t *\n\t * The starting node is NOT included. Cycles do not cause infinite loops.\n\t *\n\t * @param sessionId - Session to query\n\t * @param thoughtId - The thought to traverse from\n\t * @param maxDepth - Optional maximum traversal depth (1 = direct children only)\n\t * @param kinds - Optional edge kinds to follow; empty follows all kinds\n\t * @returns Descendant thought ids in BFS order\n\t *\n\t * @example\n\t * ```typescript\n\t * const children = view.descendants('s1', 'thought-id');\n\t * ```\n\t */\n\tdescendants(\n\t\tsessionId: SessionId,\n\t\tthoughtId: ThoughtId,\n\t\tmaxDepth?: number,\n\t\tkinds?: readonly EdgeKind[]\n\t): readonly ThoughtId[] {\n\t\treturn this._bfsClosure(\n\t\t\tsessionId,\n\t\t\tthoughtId,\n\t\t\tmaxDepth,\n\t\t\t'outgoing',\n\t\t\tkinds\n\t\t) as readonly ThoughtId[];\n\t}\n\n\t/**\n\t * Return incoming relations in store order.\n\t * @param sessionId - Session to query\n\t * @param thoughtId - Thought whose incoming edges to list\n\t * @returns Source ids and edge kinds, or an empty array for an unknown node\n\t */\n\tinbound(sessionId: SessionId, thoughtId: ThoughtId): readonly GraphRelation[] {\n\t\treturn this._store.incoming(sessionId, thoughtId).map((edge) => ({\n\t\t\tid: edge.from,\n\t\t\tkind: edge.kind,\n\t\t}));\n\t}\n\n\t/**\n\t * Return outgoing relations in store order.\n\t * @param sessionId - Session to query\n\t * @param thoughtId - Thought whose outgoing edges to list\n\t * @returns Target ids and edge kinds, or an empty array for an unknown node\n\t */\n\toutbound(sessionId: SessionId, thoughtId: ThoughtId): readonly GraphRelation[] {\n\t\treturn this._store.outgoing(sessionId, thoughtId).map((edge) => ({\n\t\t\tid: edge.to,\n\t\t\tkind: edge.kind,\n\t\t}));\n\t}\n\n\t/**\n\t * Test reachability by one or more directed edges, including cycles.\n\t * @param sessionId - Session to query\n\t * @param from - Starting thought id\n\t * @param to - Target thought id\n\t * @param kinds - Optional edge kinds to follow; empty follows all kinds\n\t * @returns Whether a nonempty directed path reaches the target\n\t */\n\thasPath(\n\t\tsessionId: SessionId,\n\t\tfrom: ThoughtId,\n\t\tto: ThoughtId,\n\t\tkinds?: readonly EdgeKind[]\n\t): boolean {\n\t\tconst allowed = kinds?.length ? new Set(kinds) : undefined;\n\t\tconst visited = new Set<ThoughtId>([from]);\n\t\tconst queue: ThoughtId[] = [from];\n\t\tfor (const node of queue) {\n\t\t\tfor (const edge of this._store.outgoing(sessionId, node)) {\n\t\t\t\tif (allowed !== undefined && !allowed.has(edge.kind)) continue;\n\t\t\t\tif (edge.to === to) return true;\n\t\t\t\tif (visited.has(edge.to)) continue;\n\t\t\t\tvisited.add(edge.to);\n\t\t\t\tqueue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\treturn false;\n\t}\n\n\t/**\n\t * Compute structural metrics over every visible node and edge kind.\n\t * @param sessionId - Session to query\n\t * @returns Counts and longest path length, with null longest_path on cycles\n\t */\n\tmetrics(sessionId: SessionId): GraphMetrics {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst explicitNodes = this._store.nodesForSession?.(sessionId);\n\t\tconst { nodes, hasIncoming } = this._collectNodes(edges, explicitNodes);\n\t\tconst inDegree = this._buildInDegree(edges, explicitNodes);\n\t\tconst outDegree = new Map<string, number>();\n\t\tlet maxIn = 0;\n\t\tlet maxOut = 0;\n\t\tfor (const edge of edges) {\n\t\t\tconst degree = (outDegree.get(edge.from) ?? 0) + 1;\n\t\t\toutDegree.set(edge.from, degree);\n\t\t\tmaxOut = Math.max(maxOut, degree);\n\t\t}\n\t\tfor (const degree of inDegree.values()) maxIn = Math.max(maxIn, degree);\n\t\treturn {\n\t\t\tnode_count: nodes.size,\n\t\t\tedge_count: edges.length,\n\t\t\troot_count: nodes.size - hasIncoming.size,\n\t\t\tleaf_count: nodes.size - outDegree.size,\n\t\t\tlongest_path: this._longestPath(sessionId, inDegree),\n\t\t\tmax_out_degree: maxOut,\n\t\t\tmax_in_degree: maxIn,\n\t\t\tedge_kind_counts: this._edgeKindCounts(edges),\n\t\t\tcomponent_count: this._componentCount(nodes, edges),\n\t\t};\n\t}\n\n\tprivate _edgeKindCounts(edges: readonly Edge[]): Record<EdgeKind, number> {\n\t\tconst counts: Record<EdgeKind, number> = {\n\t\t\tsequence: 0,\n\t\t\tbranch: 0,\n\t\t\tmerge: 0,\n\t\t\tverifies: 0,\n\t\t\tcritiques: 0,\n\t\t\tderives_from: 0,\n\t\t\ttool_invocation: 0,\n\t\t\trevises: 0,\n\t\t};\n\t\tfor (const edge of edges) counts[edge.kind]++;\n\t\treturn counts;\n\t}\n\n\tprivate _longestPath(sessionId: SessionId, inDegree: Map<string, number>): number | null {\n\t\tconst queue: string[] = [];\n\t\tconst distances = new Map<string, number>();\n\t\tfor (const [node, degree] of inDegree) {\n\t\t\tif (degree === 0) queue.push(node);\n\t\t}\n\t\tlet longest = 0;\n\t\tlet head = 0;\n\t\twhile (head < queue.length) {\n\t\t\tconst node = queue[head++]!;\n\t\t\tconst distance = distances.get(node) ?? 0;\n\t\t\tlongest = Math.max(longest, distance);\n\t\t\tfor (const edge of this._store.outgoing(sessionId, node as ThoughtId)) {\n\t\t\t\tdistances.set(edge.to, Math.max(distances.get(edge.to) ?? 0, distance + 1));\n\t\t\t\tconst degree = (inDegree.get(edge.to) ?? 0) - 1;\n\t\t\t\tinDegree.set(edge.to, degree);\n\t\t\t\tif (degree === 0) queue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\treturn head === inDegree.size ? longest : null;\n\t}\n\n\tprivate _componentCount(nodes: ReadonlySet<string>, edges: readonly Edge[]): number {\n\t\tconst neighbours = new Map<string, string[]>();\n\t\tfor (const node of nodes) neighbours.set(node, []);\n\t\tfor (const edge of edges) {\n\t\t\tneighbours.get(edge.from)?.push(edge.to);\n\t\t\tneighbours.get(edge.to)?.push(edge.from);\n\t\t}\n\t\tconst visited = new Set<string>();\n\t\tlet count = 0;\n\t\tfor (const node of nodes) {\n\t\t\tif (visited.has(node)) continue;\n\t\t\tcount++;\n\t\t\tvisited.add(node);\n\t\t\tconst queue = [node];\n\t\t\tfor (const current of queue) {\n\t\t\t\tfor (const neighbour of neighbours.get(current) ?? []) {\n\t\t\t\t\tif (visited.has(neighbour)) continue;\n\t\t\t\t\tvisited.add(neighbour);\n\t\t\t\t\tqueue.push(neighbour);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn count;\n\t}\n\n\t/**\n\t * Return all thought ids in the session that have no outgoing edges.\n\t *\n\t * @param sessionId - Session to query\n\t * @returns Thought ids that are graph leaves\n\t *\n\t * @example\n\t * ```typescript\n\t * const tips = view.leaves('s1');\n\t * ```\n\t */\n\tleaves(sessionId: SessionId): readonly ThoughtId[] {\n\t\tconst edges = this._store.edgesForSession(sessionId);\n\t\tconst { nodes } = this._collectNodes(edges, this._store.nodesForSession?.(sessionId));\n\t\tif (nodes.size === 0) return [];\n\t\tconst result: string[] = [];\n\t\tfor (const node of nodes) {\n\t\t\tif (this._store.outgoing(sessionId, node as ThoughtId).length === 0) {\n\t\t\t\tresult.push(node);\n\t\t\t}\n\t\t}\n\t\treturn result as ThoughtId[];\n\t}\n\n\t/**\n\t * Collect every node id referenced by the edges and a set of nodes\n\t * that have at least one incoming edge.\n\t */\n\tprivate _collectNodes(\n\t\tedges: readonly Edge[],\n\t\texplicitNodes: readonly ThoughtId[] = []\n\t): {\n\t\tnodes: Set<string>;\n\t\thasIncoming: Set<string>;\n\t} {\n\t\tconst nodes = new Set<string>(explicitNodes);\n\t\tconst hasIncoming = new Set<string>();\n\t\tfor (const edge of edges) {\n\t\t\tnodes.add(edge.from);\n\t\t\tnodes.add(edge.to);\n\t\t\thasIncoming.add(edge.to);\n\t\t}\n\t\treturn { nodes, hasIncoming };\n\t}\n\n\t/**\n\t * Find roots (no incoming) ordered by the earliest outgoing edge\n\t * `createdAt`. Nodes with no outgoing edges fall back to the order\n\t * they appear among edges (already sorted by `createdAt`).\n\t */\n\tprivate _findRoots(\n\t\tnodes: Set<string>,\n\t\thasIncoming: Set<string>,\n\t\tedges: readonly Edge[]\n\t): string[] {\n\t\tconst earliest = new Map<string, number>();\n\t\tfor (const edge of edges) {\n\t\t\tif (!earliest.has(edge.from)) {\n\t\t\t\tearliest.set(edge.from, edge.createdAt);\n\t\t\t}\n\t\t}\n\t\tconst roots: string[] = [];\n\t\tfor (const node of nodes) {\n\t\t\tif (!hasIncoming.has(node)) roots.push(node);\n\t\t}\n\t\troots.sort((a, b) => (earliest.get(a) ?? Infinity) - (earliest.get(b) ?? Infinity));\n\t\treturn roots;\n\t}\n\n\t/**\n\t * BFS from a list of root ids, visiting each node at most once.\n\t */\n\tprivate _bfsFromRoots(sessionId: SessionId, roots: readonly string[]): readonly string[] {\n\t\tconst visited = new Set<string>();\n\t\tconst order: string[] = [];\n\t\tconst queue: string[] = [];\n\t\tfor (const root of roots) {\n\t\t\tif (visited.has(root)) continue;\n\t\t\tvisited.add(root);\n\t\t\torder.push(root);\n\t\t\tqueue.push(root);\n\t\t}\n\t\tlet head = 0;\n\t\twhile (head < queue.length) {\n\t\t\tconst node = queue[head++]!;\n\t\t\tfor (const edge of this._store.outgoing(sessionId, node as ThoughtId)) {\n\t\t\t\tif (visited.has(edge.to)) continue;\n\t\t\t\tvisited.add(edge.to);\n\t\t\t\torder.push(edge.to);\n\t\t\t\tqueue.push(edge.to);\n\t\t\t}\n\t\t}\n\t\treturn order;\n\t}\n\n\t/**\n\t * Build an in-degree map covering every node referenced by the edges.\n\t */\n\tprivate _buildInDegree(\n\t\tedges: readonly Edge[],\n\t\texplicitNodes: readonly ThoughtId[] = []\n\t): Map<string, number> {\n\t\tconst inDegree = new Map<string, number>();\n\t\tfor (const node of explicitNodes) inDegree.set(node, 0);\n\t\tfor (const edge of edges) {\n\t\t\tif (!inDegree.has(edge.from)) inDegree.set(edge.from, 0);\n\t\t\tinDegree.set(edge.to, (inDegree.get(edge.to) ?? 0) + 1);\n\t\t}\n\t\treturn inDegree;\n\t}\n\n\t/**\n\t * Generic BFS closure in either direction. Excludes the start node\n\t * from the result and respects an optional depth cap.\n\t */\n\tprivate _bfsClosure(\n\t\tsessionId: SessionId,\n\t\tstartId: string,\n\t\tmaxDepth: number | undefined,\n\t\tdirection: 'incoming' | 'outgoing',\n\t\tkinds?: readonly EdgeKind[]\n\t): readonly string[] {\n\t\tconst allowed = kinds?.length ? new Set(kinds) : undefined;\n\t\tconst cap = maxDepth ?? Number.POSITIVE_INFINITY;\n\t\tconst visited = new Set<string>([startId]);\n\t\tconst order: string[] = [];\n\t\tlet frontier: string[] = [startId];\n\t\tlet depth = 0;\n\t\twhile (frontier.length > 0 && depth < cap) {\n\t\t\tconst next: string[] = [];\n\t\t\tfor (const node of frontier) {\n\t\t\t\tconst edges =\n\t\t\t\t\tdirection === 'outgoing'\n\t\t\t\t\t\t? this._store.outgoing(sessionId, node as ThoughtId)\n\t\t\t\t\t\t: this._store.incoming(sessionId, node as ThoughtId);\n\t\t\t\tfor (const edge of edges) {\n\t\t\t\t\tif (allowed !== undefined && !allowed.has(edge.kind)) continue;\n\t\t\t\t\tconst neighbour = direction === 'outgoing' ? edge.to : edge.from;\n\t\t\t\t\tif (visited.has(neighbour)) continue;\n\t\t\t\t\tvisited.add(neighbour);\n\t\t\t\t\torder.push(neighbour);\n\t\t\t\t\tnext.push(neighbour);\n\t\t\t\t}\n\t\t\t}\n\t\t\tfrontier = next;\n\t\t\tdepth++;\n\t\t}\n\t\treturn order;\n\t}\n}\n"],"names":["GraphView","store","sessionId","edges","nodes","hasIncoming","roots","thoughtId","Set","outgoing","Map","edge","children","undefined","frontier","node","visited","depth","next","child","rootThoughtId","order","queue","head","current","out","inDegree","deg","CycleDetectedError","maxDepth","kinds","from","to","allowed","explicitNodes","outDegree","maxIn","maxOut","degree","Math","counts","distances","longest","distance","neighbours","count","neighbour","result","earliest","a","b","Infinity","root","startId","direction","cap","Number"],"mappings":"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"}
|