@iworkforces/tracelattice 2.3.0 → 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.
Files changed (40) hide show
  1. package/README.md +1 -1
  2. package/dist/cli.js +4 -2
  3. package/dist/contracts/reasoning-types.d.ts +3 -3
  4. package/dist/contracts/reasoning-types.d.ts.map +1 -1
  5. package/dist/core/ThoughtEvaluator.d.ts +9 -2
  6. package/dist/core/ThoughtEvaluator.d.ts.map +1 -1
  7. package/dist/core/ThoughtEvaluator.js +9 -2
  8. package/dist/core/ThoughtEvaluator.js.map +1 -1
  9. package/dist/core/ThoughtProcessor.d.ts +1 -0
  10. package/dist/core/ThoughtProcessor.d.ts.map +1 -1
  11. package/dist/core/ThoughtProcessor.js +42 -16
  12. package/dist/core/ThoughtProcessor.js.map +1 -1
  13. package/dist/core/evaluator/GraphPatterns.d.ts +8 -0
  14. package/dist/core/evaluator/GraphPatterns.d.ts.map +1 -0
  15. package/dist/core/evaluator/GraphPatterns.js +88 -0
  16. package/dist/core/evaluator/GraphPatterns.js.map +1 -0
  17. package/dist/core/evaluator/GraphSignalComputer.d.ts +12 -0
  18. package/dist/core/evaluator/GraphSignalComputer.d.ts.map +1 -0
  19. package/dist/core/evaluator/GraphSignalComputer.js +17 -0
  20. package/dist/core/evaluator/GraphSignalComputer.js.map +1 -0
  21. package/dist/core/evaluator/PatternDetector.d.ts +4 -1
  22. package/dist/core/evaluator/PatternDetector.d.ts.map +1 -1
  23. package/dist/core/evaluator/PatternDetector.js +8 -2
  24. package/dist/core/evaluator/PatternDetector.js.map +1 -1
  25. package/dist/core/graph/EdgeEmitter.js +1 -1
  26. package/dist/core/graph/EdgeEmitter.js.map +1 -1
  27. package/dist/core/graph/GraphView.d.ts +60 -2
  28. package/dist/core/graph/GraphView.d.ts.map +1 -1
  29. package/dist/core/graph/GraphView.js +127 -11
  30. package/dist/core/graph/GraphView.js.map +1 -1
  31. package/dist/core/reasoning/GraphContext.d.ts +25 -0
  32. package/dist/core/reasoning/GraphContext.d.ts.map +1 -0
  33. package/dist/core/reasoning/GraphContext.js +32 -0
  34. package/dist/core/reasoning/GraphContext.js.map +1 -0
  35. package/dist/core/reasoning.d.ts +10 -0
  36. package/dist/core/reasoning.d.ts.map +1 -1
  37. package/dist/schema.d.ts.map +1 -1
  38. package/dist/schema.js +4 -2
  39. package/dist/schema.js.map +1 -1
  40. package/package.json +6 -6
@@ -0,0 +1,88 @@
1
+ function detectRefutedHypothesisDependency(links, activeGraph) {
2
+ if (void 0 === activeGraph) return [];
3
+ const { view, sessionId } = activeGraph;
4
+ const thoughtsById = new Map(links.thoughts.flatMap((thought)=>thought.id ? [
5
+ [
6
+ thought.id,
7
+ thought
8
+ ]
9
+ ] : []));
10
+ const latest = new Map();
11
+ for (const thought of links.thoughts){
12
+ if ('verification' !== thought.thought_type || void 0 === thought.id || void 0 === thought.verification_result) continue;
13
+ const target = links.targetFor(thought.id);
14
+ if (void 0 !== target) latest.set(target, thought);
15
+ }
16
+ const signals = [];
17
+ for (const [id, verifier] of latest){
18
+ if (0 !== verifier.verification_result) continue;
19
+ const hypothesis = thoughtsById.get(id);
20
+ if (void 0 === hypothesis) continue;
21
+ const dependents = view.descendants(sessionId, id, void 0, [
22
+ 'derives_from',
23
+ 'merge',
24
+ 'branch'
25
+ ]).filter((dep)=>!view.inbound(sessionId, dep).some((r)=>'revises' === r.kind)).flatMap((dep)=>{
26
+ const thought = thoughtsById.get(dep);
27
+ return void 0 === thought ? [] : [
28
+ thought.thought_number
29
+ ];
30
+ });
31
+ if (0 === dependents.length) continue;
32
+ const numbers = [
33
+ hypothesis.thought_number,
34
+ ...dependents
35
+ ];
36
+ signals.push({
37
+ pattern: 'refuted_hypothesis_dependency',
38
+ severity: 'warning',
39
+ message: `Thought(s) ${dependents.join(', ')} build on hypothesis at thought ${hypothesis.thought_number}, which was refuted at thought ${verifier.thought_number} - revise or backtrack them`,
40
+ thought_range: [
41
+ Math.min(...numbers),
42
+ Math.max(...numbers)
43
+ ]
44
+ });
45
+ }
46
+ return signals;
47
+ }
48
+ function detectUnaddressedCritique(links, activeGraph) {
49
+ if (void 0 === activeGraph) return [];
50
+ const { view, sessionId } = activeGraph;
51
+ const positions = new Map(links.thoughts.flatMap((thought, index)=>thought.id ? [
52
+ [
53
+ thought.id,
54
+ index
55
+ ]
56
+ ] : []));
57
+ const thoughtsById = new Map(links.thoughts.flatMap((thought)=>thought.id ? [
58
+ [
59
+ thought.id,
60
+ thought
61
+ ]
62
+ ] : []));
63
+ const signals = [];
64
+ for (const [index, critique] of links.thoughts.entries())if ('critique' === critique.thought_type && void 0 !== critique.id && !(links.thoughts.length - index - 1 < 3)) for (const relation of view.outbound(sessionId, critique.id)){
65
+ if ('critiques' !== relation.kind) continue;
66
+ const target = thoughtsById.get(relation.id);
67
+ if (void 0 === target) continue;
68
+ const addressed = view.inbound(sessionId, relation.id).some((r)=>'revises' === r.kind || 'verifies' === r.kind && (positions.get(r.id) ?? -1) > index);
69
+ if (addressed) continue;
70
+ const numbers = [
71
+ critique.thought_number,
72
+ target.thought_number
73
+ ];
74
+ signals.push({
75
+ pattern: 'unaddressed_critique',
76
+ severity: 'warning',
77
+ message: `Critique at thought ${critique.thought_number} of thought ${target.thought_number} has not been addressed by a revision or verification within 3 thoughts`,
78
+ thought_range: [
79
+ Math.min(...numbers),
80
+ Math.max(...numbers)
81
+ ]
82
+ });
83
+ }
84
+ return signals;
85
+ }
86
+ export { detectRefutedHypothesisDependency, detectUnaddressedCritique };
87
+
88
+ //# sourceMappingURL=GraphPatterns.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core/evaluator/GraphPatterns.js","sources":["../../../src/core/evaluator/GraphPatterns.ts"],"sourcesContent":["import type { ThoughtId } from '../../contracts/ids.js';\nimport type { PatternSignal } from '../reasoning.js';\nimport type { ThoughtData } from '../thought.js';\nimport type { ActiveGraphContext } from './GraphSignalComputer.js';\nimport type { VerificationLinks } from './VerificationLinks.js';\n\n/** Warn about active dependencies on the latest refuted hypothesis outcomes. */\nexport function detectRefutedHypothesisDependency(\n\tlinks: VerificationLinks,\n\tactiveGraph?: ActiveGraphContext\n): PatternSignal[] {\n\tif (activeGraph === undefined) return [];\n\tconst { view, sessionId } = activeGraph;\n\tconst thoughtsById = new Map(\n\t\tlinks.thoughts.flatMap((thought) => (thought.id ? [[thought.id, thought] as const] : []))\n\t);\n\tconst latest = new Map<ThoughtId, ThoughtData>();\n\tfor (const thought of links.thoughts) {\n\t\tif (\n\t\t\tthought.thought_type !== 'verification' ||\n\t\t\tthought.id === undefined ||\n\t\t\tthought.verification_result === undefined\n\t\t)\n\t\t\tcontinue;\n\t\tconst target = links.targetFor(thought.id);\n\t\tif (target !== undefined) latest.set(target, thought);\n\t}\n\tconst signals: PatternSignal[] = [];\n\tfor (const [id, verifier] of latest) {\n\t\tif (verifier.verification_result !== 0) continue;\n\t\tconst hypothesis = thoughtsById.get(id);\n\t\tif (hypothesis === undefined) continue;\n\t\tconst dependents = view\n\t\t\t.descendants(sessionId, id, undefined, ['derives_from', 'merge', 'branch'])\n\t\t\t.filter((dep) => !view.inbound(sessionId, dep).some((r) => r.kind === 'revises'))\n\t\t\t.flatMap((dep) => {\n\t\t\t\tconst thought = thoughtsById.get(dep);\n\t\t\t\treturn thought === undefined ? [] : [thought.thought_number];\n\t\t\t});\n\t\tif (dependents.length === 0) continue;\n\t\tconst numbers = [hypothesis.thought_number, ...dependents];\n\t\tsignals.push({\n\t\t\tpattern: 'refuted_hypothesis_dependency',\n\t\t\tseverity: 'warning',\n\t\t\tmessage: `Thought(s) ${dependents.join(', ')} build on hypothesis at thought ${hypothesis.thought_number}, which was refuted at thought ${verifier.thought_number} - revise or backtrack them`,\n\t\t\tthought_range: [Math.min(...numbers), Math.max(...numbers)],\n\t\t});\n\t}\n\treturn signals;\n}\n\n/** Warn after three active thoughts when a critique target remains unaddressed. */\nexport function detectUnaddressedCritique(\n\tlinks: VerificationLinks,\n\tactiveGraph?: ActiveGraphContext\n): PatternSignal[] {\n\tif (activeGraph === undefined) return [];\n\tconst { view, sessionId } = activeGraph;\n\tconst positions = new Map(\n\t\tlinks.thoughts.flatMap((thought, index) => (thought.id ? [[thought.id, index] as const] : []))\n\t);\n\tconst thoughtsById = new Map(\n\t\tlinks.thoughts.flatMap((thought) => (thought.id ? [[thought.id, thought] as const] : []))\n\t);\n\tconst signals: PatternSignal[] = [];\n\tfor (const [index, critique] of links.thoughts.entries()) {\n\t\tif (\n\t\t\tcritique.thought_type !== 'critique' ||\n\t\t\tcritique.id === undefined ||\n\t\t\tlinks.thoughts.length - index - 1 < 3\n\t\t)\n\t\t\tcontinue;\n\t\tfor (const relation of view.outbound(sessionId, critique.id)) {\n\t\t\tif (relation.kind !== 'critiques') continue;\n\t\t\tconst target = thoughtsById.get(relation.id);\n\t\t\tif (target === undefined) continue;\n\t\t\tconst addressed = view\n\t\t\t\t.inbound(sessionId, relation.id)\n\t\t\t\t.some(\n\t\t\t\t\t(r) =>\n\t\t\t\t\t\tr.kind === 'revises' || (r.kind === 'verifies' && (positions.get(r.id) ?? -1) > index)\n\t\t\t\t);\n\t\t\tif (addressed) continue;\n\t\t\tconst numbers = [critique.thought_number, target.thought_number];\n\t\t\tsignals.push({\n\t\t\t\tpattern: 'unaddressed_critique',\n\t\t\t\tseverity: 'warning',\n\t\t\t\tmessage: `Critique at thought ${critique.thought_number} of thought ${target.thought_number} has not been addressed by a revision or verification within 3 thoughts`,\n\t\t\t\tthought_range: [Math.min(...numbers), Math.max(...numbers)],\n\t\t\t});\n\t\t}\n\t}\n\treturn signals;\n}\n"],"names":["detectRefutedHypothesisDependency","links","activeGraph","undefined","view","sessionId","thoughtsById","Map","thought","latest","target","signals","id","verifier","hypothesis","dependents","dep","r","numbers","Math","detectUnaddressedCritique","positions","index","critique","relation","addressed"],"mappings":"AAOO,SAASA,kCACfC,KAAwB,EACxBC,WAAgC;IAEhC,IAAIA,AAAgBC,WAAhBD,aAA2B,OAAO,EAAE;IACxC,MAAM,EAAEE,IAAI,EAAEC,SAAS,EAAE,GAAGH;IAC5B,MAAMI,eAAe,IAAIC,IACxBN,MAAM,QAAQ,CAAC,OAAO,CAAC,CAACO,UAAaA,QAAQ,EAAE,GAAG;YAAC;gBAACA,QAAQ,EAAE;gBAAEA;aAAQ;SAAU,GAAG,EAAE;IAExF,MAAMC,SAAS,IAAIF;IACnB,KAAK,MAAMC,WAAWP,MAAM,QAAQ,CAAE;QACrC,IACCO,AAAyB,mBAAzBA,QAAQ,YAAY,IACpBA,AAAeL,WAAfK,QAAQ,EAAE,IACVA,AAAgCL,WAAhCK,QAAQ,mBAAmB,EAE3B;QACD,MAAME,SAAST,MAAM,SAAS,CAACO,QAAQ,EAAE;QACzC,IAAIE,AAAWP,WAAXO,QAAsBD,OAAO,GAAG,CAACC,QAAQF;IAC9C;IACA,MAAMG,UAA2B,EAAE;IACnC,KAAK,MAAM,CAACC,IAAIC,SAAS,IAAIJ,OAAQ;QACpC,IAAII,AAAiC,MAAjCA,SAAS,mBAAmB,EAAQ;QACxC,MAAMC,aAAaR,aAAa,GAAG,CAACM;QACpC,IAAIE,AAAeX,WAAfW,YAA0B;QAC9B,MAAMC,aAAaX,KACjB,WAAW,CAACC,WAAWO,IAAIT,QAAW;YAAC;YAAgB;YAAS;SAAS,EACzE,MAAM,CAAC,CAACa,MAAQ,CAACZ,KAAK,OAAO,CAACC,WAAWW,KAAK,IAAI,CAAC,CAACC,IAAMA,AAAW,cAAXA,EAAE,IAAI,GAChE,OAAO,CAAC,CAACD;YACT,MAAMR,UAAUF,aAAa,GAAG,CAACU;YACjC,OAAOR,AAAYL,WAAZK,UAAwB,EAAE,GAAG;gBAACA,QAAQ,cAAc;aAAC;QAC7D;QACD,IAAIO,AAAsB,MAAtBA,WAAW,MAAM,EAAQ;QAC7B,MAAMG,UAAU;YAACJ,WAAW,cAAc;eAAKC;SAAW;QAC1DJ,QAAQ,IAAI,CAAC;YACZ,SAAS;YACT,UAAU;YACV,SAAS,CAAC,WAAW,EAAEI,WAAW,IAAI,CAAC,MAAM,gCAAgC,EAAED,WAAW,cAAc,CAAC,+BAA+B,EAAED,SAAS,cAAc,CAAC,2BAA2B,CAAC;YAC9L,eAAe;gBAACM,KAAK,GAAG,IAAID;gBAAUC,KAAK,GAAG,IAAID;aAAS;QAC5D;IACD;IACA,OAAOP;AACR;AAGO,SAASS,0BACfnB,KAAwB,EACxBC,WAAgC;IAEhC,IAAIA,AAAgBC,WAAhBD,aAA2B,OAAO,EAAE;IACxC,MAAM,EAAEE,IAAI,EAAEC,SAAS,EAAE,GAAGH;IAC5B,MAAMmB,YAAY,IAAId,IACrBN,MAAM,QAAQ,CAAC,OAAO,CAAC,CAACO,SAASc,QAAWd,QAAQ,EAAE,GAAG;YAAC;gBAACA,QAAQ,EAAE;gBAAEc;aAAM;SAAU,GAAG,EAAE;IAE7F,MAAMhB,eAAe,IAAIC,IACxBN,MAAM,QAAQ,CAAC,OAAO,CAAC,CAACO,UAAaA,QAAQ,EAAE,GAAG;YAAC;gBAACA,QAAQ,EAAE;gBAAEA;aAAQ;SAAU,GAAG,EAAE;IAExF,MAAMG,UAA2B,EAAE;IACnC,KAAK,MAAM,CAACW,OAAOC,SAAS,IAAItB,MAAM,QAAQ,CAAC,OAAO,GACrD,IACCsB,AAA0B,eAA1BA,SAAS,YAAY,IACrBA,AAAgBpB,WAAhBoB,SAAS,EAAE,KACXtB,CAAAA,MAAM,QAAQ,CAAC,MAAM,GAAGqB,QAAQ,IAAI,IAGrC,KAAK,MAAME,YAAYpB,KAAK,QAAQ,CAACC,WAAWkB,SAAS,EAAE,EAAG;QAC7D,IAAIC,AAAkB,gBAAlBA,SAAS,IAAI,EAAkB;QACnC,MAAMd,SAASJ,aAAa,GAAG,CAACkB,SAAS,EAAE;QAC3C,IAAId,AAAWP,WAAXO,QAAsB;QAC1B,MAAMe,YAAYrB,KAChB,OAAO,CAACC,WAAWmB,SAAS,EAAE,EAC9B,IAAI,CACJ,CAACP,IACAA,AAAW,cAAXA,EAAE,IAAI,IAAmBA,AAAW,eAAXA,EAAE,IAAI,IAAoBI,AAAAA,CAAAA,UAAU,GAAG,CAACJ,EAAE,EAAE,KAAK,EAAC,IAAKK;QAEnF,IAAIG,WAAW;QACf,MAAMP,UAAU;YAACK,SAAS,cAAc;YAAEb,OAAO,cAAc;SAAC;QAChEC,QAAQ,IAAI,CAAC;YACZ,SAAS;YACT,UAAU;YACV,SAAS,CAAC,oBAAoB,EAAEY,SAAS,cAAc,CAAC,YAAY,EAAEb,OAAO,cAAc,CAAC,uEAAuE,CAAC;YACpK,eAAe;gBAACS,KAAK,GAAG,IAAID;gBAAUC,KAAK,GAAG,IAAID;aAAS;QAC5D;IACD;IAED,OAAOP;AACR"}
@@ -0,0 +1,12 @@
1
+ import type { SessionId } from '../../contracts/ids.js';
2
+ import type { GraphView } from '../graph/GraphView.js';
3
+ import type { GraphSignals } from '../reasoning.js';
4
+ export interface ActiveGraphContext {
5
+ readonly sessionId: SessionId;
6
+ readonly view: GraphView;
7
+ }
8
+ /** Stateless, additive graph analytics; does not affect structural scoring. */
9
+ export declare class GraphSignalComputer {
10
+ compute(ctx: ActiveGraphContext): GraphSignals | undefined;
11
+ }
12
+ //# sourceMappingURL=GraphSignalComputer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"GraphSignalComputer.d.ts","sourceRoot":"","sources":["../../../src/core/evaluator/GraphSignalComputer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC;AACxD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AACvD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEpD,MAAM,WAAW,kBAAkB;IAClC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAC9B,QAAQ,CAAC,IAAI,EAAE,SAAS,CAAC;CACzB;AAED,+EAA+E;AAC/E,qBAAa,mBAAmB;IACxB,OAAO,CAAC,GAAG,EAAE,kBAAkB,GAAG,YAAY,GAAG,SAAS;CAajE"}
@@ -0,0 +1,17 @@
1
+ class GraphSignalComputer {
2
+ compute(ctx) {
3
+ const metrics = ctx.view.metrics(ctx.sessionId);
4
+ if (0 === metrics.edge_count) return;
5
+ return {
6
+ ...metrics,
7
+ branching_factor: roundToPrecision(metrics.edge_count / (metrics.node_count - metrics.leaf_count)),
8
+ relational_density: roundToPrecision((metrics.edge_count - metrics.edge_kind_counts.sequence) / metrics.edge_count)
9
+ };
10
+ }
11
+ }
12
+ function roundToPrecision(value) {
13
+ return Math.round(1e10 * value) / 1e10;
14
+ }
15
+ export { GraphSignalComputer };
16
+
17
+ //# sourceMappingURL=GraphSignalComputer.js.map
@@ -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;;;;;;;;;;;;;;;;;;;;;GAqBG;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;AAEhE;;;;;;;;;;;;;;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,GACtB,aAAa,EAAE;IAalB,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
+ {"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.edgesForSession(sessionId).some((existing)=>existing.kind === kind && existing.from === from && existing.to === to);
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;AAInE;;;;;;;;;;GAUG;AACH,qBAAa,SAAS;IACrB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAkB;IAEzC;;;;;;;;;OASG;gBACS,KAAK,EAAE,eAAe;IAIlC;;;;;;;;;;;;;;OAcG;IACH,aAAa,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE;IAWzD;;;;;;;;;;OAUG;IACH,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,GAAG,MAAM,GAAG,SAAS;IAqC9E;;;;;;;;;;;;;;;OAeG;IACH,cAAc,CAAC,SAAS,EAAE,SAAS,EAAE,aAAa,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE;IAkBpF;;;;;;;;;;;;OAYG;IACH,WAAW,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE;IA0BvD;;;;;;;;;;;;;;OAcG;IACH,SAAS,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE;IAI9F;;;;;;;;;;;;;;OAcG;IACH,WAAW,CAAC,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,SAAS,SAAS,EAAE;IAIhG;;;;;;;;;;OAUG;IACH,MAAM,CAAC,SAAS,EAAE,SAAS,GAAG,SAAS,SAAS,EAAE;IAalD;;;OAGG;IACH,OAAO,CAAC,aAAa;IAiBrB;;;;OAIG;IACH,OAAO,CAAC,UAAU;IAmBlB;;OAEG;IACH,OAAO,CAAC,aAAa;IAsBrB;;OAEG;IACH,OAAO,CAAC,cAAc;IAatB;;;OAGG;IACH,OAAO,CAAC,WAAW;CA+BnB"}
1
+ {"version":3,"file":"GraphView.d.ts","sourceRoot":"","sources":["../../../src/core/graph/GraphView.ts"],"names":[],"mappings":"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"}