@avantf/dsh-mem 0.6.4 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +19 -10
  2. package/lib/client.js +1665 -3466
  3. package/lib/dsh-build.json +6 -5
  4. package/lib/engine/graph-recall/in-memory.d.ts +93 -0
  5. package/lib/engine/graph-recall/index.d.ts +22 -0
  6. package/lib/engine/graph-recall/traverse.d.ts +119 -0
  7. package/lib/engine/graph-recall/types.d.ts +188 -0
  8. package/lib/engine/mem/config/config_files.d.ts +1 -1
  9. package/lib/engine/mem/config/loader.d.ts +2 -3
  10. package/lib/engine/mem/config/paths.d.ts +2 -13
  11. package/lib/engine/mem/db/dao/contradictions.d.ts +4 -22
  12. package/lib/engine/mem/db/dao/entities.d.ts +80 -7
  13. package/lib/engine/mem/db/dao/fact_sources.d.ts +3 -13
  14. package/lib/engine/mem/db/dao/facts.d.ts +311 -56
  15. package/lib/engine/mem/db/dao/triples.d.ts +1 -26
  16. package/lib/engine/mem/db/graph.d.ts +249 -0
  17. package/lib/engine/mem/db/journal_notes.d.ts +35 -0
  18. package/lib/engine/mem/db/migrations.d.ts +9 -0
  19. package/lib/engine/mem/db/port.d.ts +22 -3
  20. package/lib/engine/mem/db/schema.d.ts +119 -0
  21. package/lib/engine/mem/db/tokenizer.d.ts +9 -0
  22. package/lib/engine/mem/dbdrivers/fake/driver.d.ts +75 -0
  23. package/lib/engine/mem/dbdrivers/fake/index.d.ts +7 -0
  24. package/lib/engine/mem/dbdrivers/fake/surface.d.ts +34 -0
  25. package/lib/engine/mem/dbdrivers/fake/transport.d.ts +61 -0
  26. package/lib/engine/mem/dbdrivers/sqlite/connection.d.ts +14 -0
  27. package/lib/engine/mem/dbdrivers/sqlite/contradictions.d.ts +35 -0
  28. package/lib/engine/mem/dbdrivers/sqlite/dao/index.d.ts +16 -0
  29. package/lib/engine/mem/dbdrivers/sqlite/driver.d.ts +54 -0
  30. package/lib/engine/mem/dbdrivers/sqlite/facts.d.ts +117 -0
  31. package/lib/engine/mem/dbdrivers/sqlite/index.d.ts +6 -0
  32. package/lib/engine/mem/dbdrivers/sqlite/migrations.d.ts +15 -0
  33. package/lib/engine/mem/dbdrivers/sqlite/relations.d.ts +62 -0
  34. package/lib/engine/mem/dbdrivers/sqlite/search.d.ts +56 -0
  35. package/lib/engine/mem/dbdrivers/sqlite/sources.d.ts +37 -0
  36. package/lib/engine/mem/defaults.d.ts +3 -4
  37. package/lib/engine/mem/dispatch.d.ts +7 -32
  38. package/lib/engine/mem/entities/extract.d.ts +44 -47
  39. package/lib/engine/mem/eval/derived_runner.d.ts +6 -1
  40. package/lib/engine/mem/eval/relation_gold.d.ts +251 -0
  41. package/lib/engine/mem/eval/write_metrics.d.ts +1 -1
  42. package/lib/engine/mem/facts_io.d.ts +328 -0
  43. package/lib/engine/mem/index.d.ts +10 -2
  44. package/lib/engine/mem/interfaces/capabilities.d.ts +30 -0
  45. package/lib/engine/mem/interfaces/common.d.ts +11 -0
  46. package/lib/engine/mem/interfaces/errors.d.ts +65 -0
  47. package/lib/engine/mem/interfaces/index.d.ts +18 -0
  48. package/lib/engine/mem/interfaces/migrations.d.ts +21 -0
  49. package/lib/engine/mem/interfaces/repositories.d.ts +471 -0
  50. package/lib/engine/mem/interfaces/search_index.d.ts +113 -0
  51. package/lib/engine/mem/interfaces/storage_backend.d.ts +84 -0
  52. package/lib/engine/mem/interfaces/storage_driver.d.ts +43 -0
  53. package/lib/engine/mem/interfaces/version.d.ts +34 -0
  54. package/lib/engine/mem/lifecycle/contradiction.d.ts +2 -2
  55. package/lib/engine/mem/maintenance_paths.d.ts +41 -0
  56. package/lib/engine/mem/modelBootstrap.d.ts +1 -1
  57. package/lib/engine/mem/relations/confidence.d.ts +39 -0
  58. package/lib/engine/mem/relations/extract.d.ts +128 -0
  59. package/lib/engine/mem/relations/index.d.ts +32 -0
  60. package/lib/engine/mem/relations/name-quality.d.ts +78 -0
  61. package/lib/engine/mem/relations/normalize.d.ts +275 -0
  62. package/lib/engine/mem/relations/patterns.d.ts +67 -0
  63. package/lib/engine/mem/relations/statement.d.ts +88 -0
  64. package/lib/engine/mem/relations/types.d.ts +79 -0
  65. package/lib/engine/mem/runtime.d.ts +117 -90
  66. package/lib/engine/mem/storage/create.d.ts +42 -0
  67. package/lib/engine/mem/storage/errors.d.ts +65 -0
  68. package/lib/engine/mem/storage/index.d.ts +15 -0
  69. package/lib/engine/mem/storage/ports.d.ts +472 -0
  70. package/lib/engine/mem/storage/registry.d.ts +37 -0
  71. package/lib/engine/mem/storage/sqlite.d.ts +28 -0
  72. package/lib/engine/mem/store/common.d.ts +1 -1
  73. package/lib/engine/mem/store/entity_leg.d.ts +59 -2
  74. package/lib/engine/mem/store/entity_sweep.d.ts +4 -3
  75. package/lib/engine/mem/store/explicit_relations.d.ts +52 -0
  76. package/lib/engine/mem/store/facts_restore.d.ts +124 -0
  77. package/lib/engine/mem/store/floors.d.ts +4 -1
  78. package/lib/engine/mem/store/graph_reader.d.ts +146 -0
  79. package/lib/engine/mem/store/hybrid.d.ts +50 -13
  80. package/lib/engine/mem/store/kinds.d.ts +47 -0
  81. package/lib/engine/mem/store/legs.d.ts +24 -29
  82. package/lib/engine/mem/store/lexical.d.ts +36 -45
  83. package/lib/engine/mem/store/memory.d.ts +486 -82
  84. package/lib/engine/mem/store/recall_policy.d.ts +49 -0
  85. package/lib/engine/mem/store/relation_backfill.d.ts +210 -0
  86. package/lib/engine/mem/store/relation_planner.d.ts +191 -0
  87. package/lib/engine/mem/store/secret_guard.d.ts +0 -1
  88. package/lib/engine/mem/store/self_query.d.ts +4 -2
  89. package/lib/engine/mem/store/time_filter.d.ts +121 -0
  90. package/lib/engine/mem/store/titles.d.ts +52 -0
  91. package/lib/engine/mem/store/vector_repair.d.ts +44 -54
  92. package/lib/engine/mem-contract/config.d.ts +44 -38
  93. package/lib/engine/mem-contract/text.d.ts +1 -1
  94. package/lib/engine/mem-contract/tools.d.ts +184 -117
  95. package/lib/engine/mem-contract/types.d.ts +364 -288
  96. package/lib/index.js +13898 -11500
  97. package/lib/interface-version.json +1 -1
  98. package/lib/types/client/index.d.ts +2 -2
  99. package/lib/types/client/index.d.ts.map +1 -1
  100. package/lib/types/client/paging.d.ts +3 -12
  101. package/lib/types/client/paging.d.ts.map +1 -1
  102. package/lib/types/client/summarize.d.ts.map +1 -1
  103. package/lib/types/command.d.ts +61 -0
  104. package/lib/types/command.d.ts.map +1 -0
  105. package/lib/types/envinit-bootstrap.d.ts +1 -1
  106. package/lib/types/envinit.d.ts +2 -6
  107. package/lib/types/envinit.d.ts.map +1 -1
  108. package/lib/types/hints.d.ts +28 -24
  109. package/lib/types/hints.d.ts.map +1 -1
  110. package/lib/types/index.d.ts +21 -40
  111. package/lib/types/index.d.ts.map +1 -1
  112. package/lib/types/inject.d.ts +3 -2
  113. package/lib/types/inject.d.ts.map +1 -1
  114. package/lib/types/interface_gate.d.ts +9 -9
  115. package/lib/types/prompt.d.ts +17 -41
  116. package/lib/types/prompt.d.ts.map +1 -1
  117. package/lib/types/provision.d.ts +3 -0
  118. package/lib/types/provision.d.ts.map +1 -1
  119. package/lib/types/remote.d.ts +23 -9
  120. package/lib/types/remote.d.ts.map +1 -1
  121. package/lib/types/render.d.ts +3 -3
  122. package/lib/types/tool_schema.d.ts +4 -0
  123. package/lib/types/tool_schema.d.ts.map +1 -1
  124. package/lib/types/vectorMigration.d.ts +5 -7
  125. package/lib/types/vectorMigration.d.ts.map +1 -1
  126. package/package.json +16 -15
  127. package/lib/engine/mem/config/domains.d.ts +0 -20
  128. package/lib/engine/mem/db/dao/chunks.d.ts +0 -231
  129. package/lib/engine/mem/db/dao/documents.d.ts +0 -51
  130. package/lib/engine/mem/db/hash.d.ts +0 -2
  131. package/lib/engine/mem/db/knowledge.d.ts +0 -17
  132. package/lib/engine/mem/eval/index.d.ts +0 -4
  133. package/lib/engine/mem/git.d.ts +0 -73
  134. package/lib/engine/mem/router.d.ts +0 -51
  135. package/lib/engine/mem/store/doc_files.d.ts +0 -125
  136. package/lib/engine/mem/store/document_text.d.ts +0 -47
  137. package/lib/engine/mem/store/ingest_guard.d.ts +0 -25
  138. package/lib/engine/mem/store/knowledge.d.ts +0 -598
  139. package/lib/engine/mem/store/source_picker.d.ts +0 -43
  140. package/lib/engine/mem-retrieval/adapters/local_reranker.d.ts +0 -52
  141. package/lib/engine/mem-retrieval/adapters/none_reranker.d.ts +0 -10
  142. package/lib/engine/mem-retrieval/rerank.d.ts +0 -13
  143. package/lib/types/client/domains.d.ts +0 -53
  144. package/lib/types/client/domains.d.ts.map +0 -1
  145. package/lib/types/client/ingest.d.ts +0 -24
  146. package/lib/types/client/ingest.d.ts.map +0 -1
  147. package/lib/types/open.d.ts +0 -77
  148. package/lib/types/open.d.ts.map +0 -1
  149. package/lib/types/reconcile.d.ts +0 -64
  150. package/lib/types/reconcile.d.ts.map +0 -1
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The storage DRIVER contract (docs/DB_DRIVER_LAYOUT.md §二/§八).
3
+ *
4
+ * "Adding a storage" = writing one driver directory that implements this interface, not editing a
5
+ * store. A driver:
6
+ *
7
+ * - declares the `name` its config section is chosen by (`storage.driver`, or the DB-1 alias
8
+ * `storage.backend`);
9
+ * - declares the CONTRACT MAJOR it was written against (`contractVersion`): the host refuses a
10
+ * mismatch at registration AND at open (never a silent fallback);
11
+ * - declares what it can do ({@link StorageCapabilities}), so optional behaviour is a flag rather
12
+ * than a probe that fails at query time;
13
+ * - parses ITS OWN config section (`storage.sqlite`, `storage.fake`, `storage.postgres`), owns its
14
+ * connection, schema/migrations, transactions and error mapping, and returns a
15
+ * {@link StorageBackend}.
16
+ *
17
+ * `open` is async for every driver, including the in-process one: the host must not have two opening
18
+ * paths, and a remote driver's open is I/O by nature.
19
+ *
20
+ * @module interfaces/storage_driver
21
+ */
22
+ import type { StorageCapabilities } from './capabilities.js';
23
+ import type { StorageBackend } from './storage_backend.js';
24
+ /**
25
+ * What the host hands a driver at open.
26
+ *
27
+ * Deliberately tiny: a driver owns its connection and its configuration. The one thing the host has
28
+ * to pass is a runtime knob that is NOT the driver's business (the contradiction threshold is a
29
+ * domain policy the detector is built with, and the detector lives above the storage layer).
30
+ */
31
+ export interface DriverDeps {
32
+ /** `lifecycle.contradiction_threshold` — supplied to the contradiction façade at construction. */
33
+ readonly contradictionThreshold?: number;
34
+ }
35
+ export interface StorageDriver<Config = unknown> {
36
+ /** The name `storage.driver` selects (also the config section key). */
37
+ readonly name: string;
38
+ /** The interface ABI this driver was written against (`interfaces/version.ts`). */
39
+ readonly contractVersion: number;
40
+ readonly capabilities: StorageCapabilities;
41
+ /** Open (or create) the store and return its backend. Rejecting = the host fails to mount. */
42
+ open(config: Config, deps: DriverDeps): Promise<StorageBackend>;
43
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * The storage HAL's CONTRACT VERSION (docs/DB_DRIVER_LAYOUT.md §八).
3
+ *
4
+ * `contractVersion` is the interface ABI, NOT the package version. It exists because a driver and
5
+ * the host that opens it are independent upgrade units: without a number, a driver built for a
6
+ * different shape of `StorageBackend` would load, open and then misbehave silently.
7
+ *
8
+ * Rules (the doc's §8.2, restated where the code enforces them):
9
+ *
10
+ * - major = a breaking interface change (a method, a parameter or a return's meaning changed);
11
+ * - minor = an ADDITIVE change (an optional capability flag, an optional field): old drivers keep
12
+ * loading because they simply do not declare it;
13
+ * - a driver must implement the major it declares — no "partial implementation";
14
+ * - the host verifies the number at REGISTRATION and at OPEN, and refuses with
15
+ * {@link UnsupportedStorageContractError}. It never falls back to another driver.
16
+ *
17
+ * Bump {@link STORAGE_CONTRACT_VERSION} only with the interface change, and add the previous major to
18
+ * {@link SUPPORTED_STORAGE_CONTRACT_VERSIONS} when an adapter for it is actually written.
19
+ *
20
+ * @module interfaces/version
21
+ */
22
+ /** The contract major this build implements (`storage/ports` shapes, async boundary). */
23
+ export declare const STORAGE_CONTRACT_VERSION = 2;
24
+ /** The contract majors this host can open. A driver outside this list is refused at registration. */
25
+ export declare const SUPPORTED_STORAGE_CONTRACT_VERSIONS: readonly [2];
26
+ /** The error a driver/host mismatch raises. Explicit refusal, never a silent fallback. */
27
+ export declare class UnsupportedStorageContractError extends Error {
28
+ readonly driver: string;
29
+ readonly contractVersion: number;
30
+ readonly supported: readonly number[];
31
+ constructor(driver: string, contractVersion: number, supported?: readonly number[]);
32
+ }
33
+ /** Whether this host can open a driver that declares `version`. */
34
+ export declare function supportsStorageContract(version: number): boolean;
@@ -1,4 +1,4 @@
1
- import { MAX_REPORTED_CONFLICTS, type ContradictionRecord } from '../../mem-contract/index.js';
1
+ import { MAX_REPORTED_CONFLICTS, type ContradictionRecord, type MemoryKind } from '../../mem-contract/index.js';
2
2
  import type { Db } from '../db/conn.js';
3
3
  import { type ConflictPair, type StoredConflict } from '../db/dao/contradictions.js';
4
4
  export { polarityLookupSql, sameSubjPredLookupSql } from '../db/dao/triples.js';
@@ -97,7 +97,7 @@ export declare class ContradictDetector {
97
97
  /** Record a verdict on a conflict WITHOUT touching the facts (see `resolveContradiction`). */
98
98
  resolve(id: number, resolution: 'true_positive' | 'false_positive', loserFactId?: number): void;
99
99
  /** List conflicts for the admin surface (`resolved` 0 = open). */
100
- list(resolved?: number, category?: string, threshold?: number, limit?: number): ContradictionRecord[];
100
+ list(resolved?: number, kind?: MemoryKind, threshold?: number, limit?: number): ContradictionRecord[];
101
101
  /** Drain the pending set: the incremental sweep (`admin contradict_check`). */
102
102
  /**
103
103
  * Check a LIST of facts — the store's durable drain supplies it (`contradict_check`).
@@ -0,0 +1,41 @@
1
+ import type { FactsExportCodec } from './facts_io.js';
2
+ /** Hard cap on a file a maintenance action will read (import / merge / restore_snapshot). */
3
+ export declare const MAINTENANCE_MAX_FILE_BYTES: number;
4
+ /** Where an export lands when the caller does not name a file. */
5
+ export declare const MAINTENANCE_BACKUP_DIRNAME = "backups";
6
+ /**
7
+ * `<data home>/backups/memory-YYYYMMDD-HHMMSS.<ext>` — never inside `memory/`.
8
+ *
9
+ * Brotli is the default because it is the smallest (measured: 70.6 KiB vs 92.3 gzip vs 300.0 plain
10
+ * for 233 facts). A caller that passes another codec keeps the file name truthful by changing the
11
+ * suffix along with the bytes.
12
+ */
13
+ export declare function defaultFactsExportPath(home: string, now?: Date, codec?: FactsExportCodec): string;
14
+ /** A resolved export destination, plus whether this call will overwrite an existing file. */
15
+ export interface MaintenanceExportTarget {
16
+ file: string;
17
+ existed: boolean;
18
+ }
19
+ /**
20
+ * Resolve an export destination inside the data root.
21
+ *
22
+ * The TARGET may not exist yet (that is the normal case), so containment is established on the
23
+ * nearest existing ancestor: every component below it is one this call will create, and a
24
+ * not-yet-created component cannot be a symlink. When the target DOES exist, its realpath is
25
+ * checked — a symlink pointing outside is refused rather than followed.
26
+ */
27
+ export declare function resolveMaintenanceExportTarget(home: string, input?: string): MaintenanceExportTarget;
28
+ /**
29
+ * Resolve an INPUT file (import / merge / restore_snapshot) inside the data root.
30
+ *
31
+ * The file must exist, be a regular file, and be no larger than {@link MAINTENANCE_MAX_FILE_BYTES}.
32
+ * A symlink to a regular file INSIDE the root is usable; a symlink that escapes it is refused before
33
+ * the size or existence of the outside target is examined.
34
+ */
35
+ export declare function resolveMaintenanceInputFile(home: string, input: string): string;
36
+ /**
37
+ * Write a file atomically: a sibling temp file, then one `rename` (same directory, so the rename
38
+ * cannot cross a filesystem). An overwrite is therefore either the old bytes or the new bytes, never
39
+ * a truncated export. The temp file is removed when anything fails.
40
+ */
41
+ export declare function writeFileAtomic(path: string, bytes: Buffer | string): void;
@@ -122,7 +122,7 @@ export interface ProvisionSweepResult {
122
122
  * It replaced a one-shot model warm-up that sat beside a separate tools sweep, which meant two
123
123
  * initialization paths, two logging vocabularies, and no single answer to "is this host fully
124
124
  * provisioned?". Callers (the DSH plugin mount, the MCP entry) now make one call, and the per-artifact
125
- * result lines in the host log come from the same mechanism that installs pandoc.
125
+ * result lines in the host log come from the same mechanism that provisions each artifact.
126
126
  *
127
127
  * The FIRST step is the dsh compatibility gate: an incompatible host means NOTHING is resolved,
128
128
  * installed or warmed — preparing an environment for an API we cannot drive is wasted mission.
@@ -0,0 +1,39 @@
1
+ import type { NormalizedRelation } from './normalize.js';
2
+ import { type RelationEvaluation } from './types.js';
3
+ /**
4
+ * Edges below this confidence are NOT written (§4.3: "低置信度不写 edges,或写但 planner 不默认
5
+ * 使用" — this store takes the first option, because an unqueryable edge is still a lie a later
6
+ * reader can see).
7
+ *
8
+ * The number is a POLICY, kept in one place so a gold set (R6) can move it:
9
+ *
10
+ * - a triple whose predicate is CURATED (a described relation — see `PredicateRef.curated` in
11
+ * `normalize.ts`; every seeded family is, and so is one an operator registered with a
12
+ * description) scores `min(1, base + 0.25)` → 0.75 at the extractor's 0.5, well above the line;
13
+ * - an undescribed predicate scores `base × 0.6` → 0.3, below it, so the relation stays a
14
+ * legacy triple + content hit and never enters the graph. That is the §R3 stance: a wrong edge
15
+ * is worse than a missing one, and "计划/听说/可能" style verbs must not become facts.
16
+ * - a high-confidence non-heuristic triple (an `llm` row at 0.9 → 0.54) still qualifies, because
17
+ * the confidence came from the producer rather than from our vocabulary.
18
+ */
19
+ export declare const EDGE_MIN_CONFIDENCE = 0.5;
20
+ /** An explicit relation's confidence: the caller asserted it, so there is no score to compute. */
21
+ export declare const EXPLICIT_EDGE_CONFIDENCE = 1;
22
+ /**
23
+ * The confidence an edge carries, as a pure function of the producer's score and one bit of
24
+ * vocabulary knowledge.
25
+ */
26
+ export declare function edgeConfidence(tripleConfidence: number | null | undefined, curatedPredicate: boolean): number;
27
+ /**
28
+ * Decide whether one normalized relation may become an edge — and with what confidence.
29
+ *
30
+ * The result is a decision, not a mutation: the caller (`db/graph.ts#resolveForEdge`) keeps its own
31
+ * drop accounting (it adds the post-resolution `self-relation` case, which needs ids), and a
32
+ * refused relation is reported with the reason so a coverage report can say WHERE the relation was
33
+ * lost.
34
+ *
35
+ * `content` is the fact's text; it is what the statement test reads. A caller without text (a dry
36
+ * run over an `.svo` triple) gets `''`, which can only refuse an `svo` triple — the honest answer,
37
+ * since nothing is known to have stated it.
38
+ */
39
+ export declare function evaluateRelation(relation: NormalizedRelation, content: string): RelationEvaluation;
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Relation CANDIDATE extraction — the two producers that read a fact's text, and the seam a third
3
+ * producer would implement (docs/RELATIONAL_RECALL.md §4.3/R3, §十七).
4
+ *
5
+ * WHAT A CANDIDATE IS. `(subj, pred, obj)` plus the producer's confidence and provenance. A
6
+ * candidate is NOT an edge: nothing here resolves names, applies the gates or touches a database.
7
+ * The pipeline is
8
+ *
9
+ * ```text
10
+ * extractRelationCandidates(text, tokens?) ← THIS module (SVO heuristic + explicit patterns)
11
+ * normalizeRelation(db, candidate) ← normalize.ts (aliases, predicate vocabulary)
12
+ * evaluateRelation(normalized, content) ← confidence.ts (name / statement / confidence gates)
13
+ * writeEdgesForFact(db, factId, candidates) ← db/graph.ts (ids, direction, rows)
14
+ * ```
15
+ *
16
+ * THREE READINGS, ONE ENTRY POINT. The POS/SVO heuristic, the curated vocabulary's surface matching
17
+ * (`主管`/`隶属于`/`汇报`/`部署在` are nouns to the tagger, relations to the store) and the explicit
18
+ * patterns are three readings of the same text; they must not disagree about what counts as a
19
+ * candidate, so one function returns all of them (the write path, `update`, and the entity sweep all
20
+ * call {@link relationCandidatesFromText}). The pattern half needs the RAW TEXT, which is why the
21
+ * entry point takes both the content and its tokens — and why the token-based half is additionally
22
+ * filtered by `relations/statement.ts#isContiguousTriple`: its arguments come from proximity, so a
23
+ * merge that crossed a clause boundary must not reach `triples`.
24
+ *
25
+ * THE TOKENS COME FROM `entities/extract.ts`. Tagging is the expensive step (~1.1 ms per 1000
26
+ * characters) and the entity half needs it too, so the write path tags ONCE and hands the same
27
+ * array to both — `relationCandidatesFromText` is the sync form for exactly that caller.
28
+ * `extractRelationCandidates` is the standalone form: omit `tokens` and it tags; pass `null` to say
29
+ * "the tagger is unavailable" (patterns still run, the token-based halves produce nothing).
30
+ *
31
+ * @module relations/extract
32
+ */
33
+ import { type PosToken } from '../entities/extract.js';
34
+ import { type RelationCandidate } from './types.js';
35
+ /**
36
+ * Predicates that are a verb in form only — the tokenizer's reading of them carries no relation.
37
+ *
38
+ * `属于` used to sit here while `relations/normalize.ts#PREDICATE_VOCABULARY` curates the 属于 family
39
+ * (`归属/隶属于/隶属`), so the canonical form was unreachable and only an alias could produce the
40
+ * `属于` edge (`项目归属平台组` worked, `项目属于平台组` produced nothing) — the 2026-10-25 review's
41
+ * §2.1. The invariant that prevents a repeat is `STOP_PREDICATES ∩ curated surfaces = ∅`, asserted
42
+ * in `test/relation_extraction.spec.ts`: a word this list refuses may not be a word the vocabulary
43
+ * promises to extract.
44
+ *
45
+ * EXPORTED for that test. It is a closed list of closed-class verbs (`是`, `有`, `可以`, `成为`…),
46
+ * not a tuning knob.
47
+ */
48
+ export declare const STOP_PREDICATES: Set<string>;
49
+ /** A query's triple shape: an interrogative slot is a wildcard (`undefined`). */
50
+ export interface TriplePattern {
51
+ subj?: string;
52
+ pred?: string;
53
+ obj?: string;
54
+ }
55
+ /**
56
+ * The POS/SVO half: triples from ALREADY-TAGGED tokens (`null` = tagger unavailable → no triples).
57
+ *
58
+ * The triples are stamped `source: 'svo'` (Phase 5b): they are the product of a PROXIMITY heuristic,
59
+ * so the graph holds them to the "was this actually stated?" test in `relations/statement.ts`.
60
+ *
61
+ * STRICT AND WEAK ARE MERGED, not fallbacks (2026-10-25 review §六). The old rule ran the weak
62
+ * reading (`vn`/preposition) only when the strict verbs produced NOTHING, so one strict candidate —
63
+ * which the gates might refuse a moment later — suppressed a weak candidate that would have become a
64
+ * real edge. Both readings now contribute, deduplicated, and the gates decide.
65
+ */
66
+ export declare function svoCandidatesFromTokens(rawTokens: PosToken[] | null): RelationCandidate[];
67
+ /**
68
+ * The curated vocabulary's reading of a tagged text: one candidate per surface OCCURRENCE, with the
69
+ * same left/right argument merge the SVO half uses.
70
+ *
71
+ * A surface matches only as a whole TOKEN RUN (`工作于` = `工作` + `于`), which is the boundary rule:
72
+ * a tokenizer boundary is a word boundary, so `工作区` (`工作` + `区`) can never match `工作` and the
73
+ * `区` cannot be read as an object either. `pred` is the SURFACE as written (`汇报`, `部署在`), not
74
+ * the canonical name: `relations/normalize.ts` maps it, and `relations/statement.ts` has to test the
75
+ * word the fact actually contains.
76
+ *
77
+ * Stamped `source: 'svo'` on purpose. A curated word is evidence that a RELATION was written, not
78
+ * evidence of who did what to whom — the arguments still come from proximity, so the statement gate
79
+ * must run. (`relations/patterns.ts` is the half that carries a syntactic cue; those triples are
80
+ * `source: 'pattern'`.)
81
+ */
82
+ export declare function vocabularyCandidatesFromTokens(rawTokens: PosToken[] | null): RelationCandidate[];
83
+ /**
84
+ * EVERYTHING the write path turns into `triples` + `edges`: the two token-based producers above plus
85
+ * the explicit patterns of `relations/patterns.ts`, deduplicated.
86
+ *
87
+ * The token-based half is filtered by {@link isContiguousTriple} first: a candidate whose three slots
88
+ * are not one unbroken run of the fact text came out of a merge that crossed a clause boundary, and
89
+ * it must not reach `triples` at all (the review's §五 `张伟阿里巴巴 — 工作 — 报告`). Pattern triples
90
+ * are exempt: their predicate is the CANONICAL name and their evidence is the cue, so the text does
91
+ * not contain the three slots literally — `relations/patterns.ts` enforces its own boundaries.
92
+ *
93
+ * Sync on purpose: the caller already tagged the text for the entity half, and re-tagging here would
94
+ * pay the dictionary cost twice for one write (see the module note).
95
+ */
96
+ export declare function relationCandidatesFromText(text: string, tokens: PosToken[] | null): RelationCandidate[];
97
+ /**
98
+ * The module's candidate-extraction API: every relation this text yields, with one tagging pass.
99
+ *
100
+ * `tokens` is the SAME tagging pass a caller may already have (see `entities/extract.ts#tagText`);
101
+ * omit it to tag here, or pass `null` when the tagger is unavailable. The resolved value is
102
+ * candidates only — the caller still has to normalize and evaluate them before anything is stored.
103
+ */
104
+ export declare function extractRelationCandidates(text: string, tokens?: PosToken[] | null): Promise<RelationCandidate[]>;
105
+ /**
106
+ * A relation PRODUCER — the seam for anything that is not the string extractor (a later LLM pass,
107
+ * the legacy backfill, a test fixture).
108
+ *
109
+ * `extract` may only produce CANDIDATES: it cannot normalize, evaluate or write anything, so
110
+ * implementing this interface is not a way around the name/statement/confidence gates. `id` is what
111
+ * a coverage report can attribute a candidate to.
112
+ */
113
+ export interface RelationExtractor {
114
+ readonly id: string;
115
+ extract(text: string, tokens?: PosToken[] | null): Promise<RelationCandidate[]>;
116
+ }
117
+ /** The engine's own producer: the POS/SVO heuristic plus the explicit patterns. */
118
+ export declare const ruleBasedRelationExtractor: RelationExtractor;
119
+ /**
120
+ * Parse a natural-language question into a direction-aware TriplePattern where
121
+ * interrogative slots are wildcards (`undefined`).
122
+ * `parse_query_pattern`: predicates anchor to the interrogative, and no literal
123
+ * interrogative word fills a slot.
124
+ *
125
+ * 「李娜管理谁」 → { subj: '李娜', pred: '管理' }
126
+ * 「谁管理李娜」 → { pred: '管理', obj: '李娜' }
127
+ */
128
+ export declare function parseQueryPattern(query: string): Promise<TriplePattern>;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The relation module's front door.
3
+ *
4
+ * WHAT BELONGS HERE. One relation edge is decided by four steps, and this module owns the first
5
+ * three; the fourth (rows, ids, direction) is storage and lives in `db/graph.ts`:
6
+ *
7
+ * ```text
8
+ * extractRelationCandidates(text, tokens?) candidates from the text — no database
9
+ * normalizeRelation(db, candidate) alias/name + predicate vocabulary
10
+ * evaluateRelation(normalized, content) name / statement / confidence gates
11
+ * writeEdgesForFact(db, factId, candidates) ids, direction, rows, extraction_version
12
+ * ```
13
+ *
14
+ * Nothing else in the engine needs to know which rule produced a candidate: `db/graph.ts` reads the
15
+ * policy from here, `store/memory.ts` reads the extractor from here, and the caller-relation
16
+ * validator (`store/explicit_relations.ts`) reads the same name gate every extracted edge passes.
17
+ * A producer that is not the string extractor implements {@link RelationExtractor} and gets the same
18
+ * gates for free.
19
+ *
20
+ * The module is importable BY PATH (`relations/extract.js`, …) like every other internal module;
21
+ * the names below are the ones the rest of the engine (and a future LLM/backfill producer) should
22
+ * use, so a rule exists in exactly one place.
23
+ *
24
+ * @module relations
25
+ */
26
+ export { STOP_PREDICATES, extractRelationCandidates, relationCandidatesFromText, svoCandidatesFromTokens, vocabularyCandidatesFromTokens, ruleBasedRelationExtractor, parseQueryPattern, type RelationExtractor, type TriplePattern, } from './extract.js';
27
+ export { ensureRelationalVocabulary, isSymmetricPredicate, normalizeRelation, parseAliasPairs, registerEntityAlias, registerEntityAliasesFromText, resolveEntity, resolvePredicate, stripAliasClause, PREDICATE_VOCABULARY, type AliasPair, type AliasRegistration, type AliasSkipReason, type EntityRef, type NormalizedRelation, type PredicateDirection, type PredicateRef, type PredicateSeed, type ResolveOptions, type ResolveVia, type VocabularySeedReport, } from './normalize.js';
28
+ export { EDGE_MIN_CONFIDENCE, EXPLICIT_EDGE_CONFIDENCE, edgeConfidence, evaluateRelation, } from './confidence.js';
29
+ export { GRAPH_NAME_MAX_CHARS, GRAPH_NAME_MAX_CJK_CHARS, graphNameIssue, isGraphName, stripParentheticalSuffix, type GraphNameIssue, } from './name-quality.js';
30
+ export { CLAUSE_BOUNDARY_CHARS, isContiguousTriple, isStatedRelation } from './statement.js';
31
+ export { PATTERN_TRIPLE_CONFIDENCE, patternTriples, patternTriplesWithReport, type PatternYield, } from './patterns.js';
32
+ export { EXPLICIT_EDGE_SOURCE, PATTERN_SOURCE, SVO_SOURCE, type RelationCandidate, type RelationDropReason, type RelationEvaluation, type RelationTripleInput, } from './types.js';
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Is a string a plausible NAME for a graph node? — the deterministic quality gate of Phase 5b
3
+ * (docs/RELATIONAL_RECALL.md §4.3/R3, §十七).
4
+ *
5
+ * WHY THIS EXISTS. The POS/SVO heuristic (`relations/extract.ts`) merges the noun-ish tokens on each
6
+ * side of a verb, and on real prose (long engineering notes) that rule happily produces slots like
7
+ * `)。**`, `,仓库…` or `包与CLI/MCP)。`. Phase 5a measured the result on a live snapshot: 69 of the
8
+ * 87 entity names in the active graph contained punctuation, and every edge v1 accepted was a clause
9
+ * fragment rather than a relation. A node whose name is a sentence fragment cannot be asked about,
10
+ * so it is worse than a missing node: it makes `relationCoverage` look healthy while the graph
11
+ * answers nothing (§R3's "错误边比缺失边更危险").
12
+ *
13
+ * The gate is a SHAPE test, not a semantics test. It cannot tell `阿里巴巴` (a company) from
14
+ * `发布节奏` (a compound noun) — that needs the statement test in `relations/statement.ts`. What it
15
+ * does guarantee is that every name the graph stores is a bounded, punctuation-free token, which is
16
+ * the precondition for the statement test to mean anything.
17
+ *
18
+ * Rules, each derived from the live failure samples (2026-10-25):
19
+ *
20
+ * - **identifier form is accepted as-is.** `@avantf/dsh-mem`, `v0.1.5`, `104.16.0.34`,
21
+ * `packages/core` are names in this store's corpus, and their punctuation (`.` `/` `-` `+` `@`
22
+ * `:`) is part of the name. They are matched by {@link IDENTIFIER_NAME}, so the punctuation rules
23
+ * below cannot reject them (the length bounds still apply).
24
+ * - **markup / quotes / brackets are refused.** A name that came out of Markdown emphasis, a code
25
+ * span, a quotation or a parenthesis is a fragment of formatting, not a name.
26
+ * - **sentence punctuation is refused.** Chinese commas/periods inside a slot mean the merge ran
27
+ * across a clause boundary.
28
+ * - **whitespace is refused.** The extractor joins tokens without spaces, so a space inside a
29
+ * slot means two tokens the tokenizer did not consider adjacent. (A multi-word ASCII name is the
30
+ * one case this loses; the live corpus has none, and accepting spaces would readmit clauses.)
31
+ * - **the name must carry a letter or digit.** Pure symbols (`——`, `**`, `()`) are not nodes.
32
+ * - **length is bounded**: {@link GRAPH_NAME_MAX_CHARS} overall, {@link GRAPH_NAME_MAX_CJK_CHARS}
33
+ * for a name with CJK in it. Chinese has no spaces, so a long CJK run is a clause; the shorter
34
+ * bound is what rejects it. Latin identifiers keep the longer one.
35
+ * - **a clause particle at the start is refused** (`的了着而并且或把被让`). A slot that OPENS with one
36
+ * starts mid-clause (`的那个工作区` passed for `当前会话所在的那个工作区`). The rule is deliberately
37
+ * starts-only: Chinese compounds routinely END in characters from that set, so a tail rule would
38
+ * reject `知识入库的规则`, `参与` and `行为` — measured against the live graph, the tail rule cost
39
+ * more real names than fragments.
40
+ *
41
+ * The gate is used by the EDGE writer (`db/graph.ts#resolveForEdge`) and by the caller-relation
42
+ * validator (`store/explicit_relations.ts`). `entities`/`fact_entities` keep the extractor's raw
43
+ * names: content recall searches what was written, and narrowing that is a different (and riskier)
44
+ * change.
45
+ *
46
+ * @module relations/name-quality
47
+ */
48
+ /** Longest name the graph accepts, in characters (a package path or a version fits; a clause does not). */
49
+ export declare const GRAPH_NAME_MAX_CHARS = 40;
50
+ /**
51
+ * Longest name the graph accepts when it contains CJK, in characters.
52
+ *
53
+ * Chinese needs no spaces, so this is the only length signal that separates a name (`阿里巴巴`,
54
+ * `支付网关`) from a clause (`用户可以立即重发一个新版本`). 20 leaves room for names like
55
+ * `知识入库的规则` while rejecting sentence-shaped runs.
56
+ */
57
+ export declare const GRAPH_NAME_MAX_CJK_CHARS = 20;
58
+ /** Why a string cannot be a graph node name. */
59
+ export type GraphNameIssue = 'blank' | 'too-long' | 'markup' | 'punctuation' | 'whitespace' | 'no-content' | 'long-clause' | 'function-word';
60
+ /**
61
+ * Classify `name` against the graph's name rules — `undefined` means "usable as a node".
62
+ *
63
+ * Pure and total: the edge writer calls it for both slots of every triple, so it must not throw
64
+ * on any input (the extractor can hand it anything the tokenizer produced).
65
+ */
66
+ export declare function graphNameIssue(name: string): GraphNameIssue | undefined;
67
+ /** Whether {@link graphNameIssue} accepts `name` (the boolean form callers usually want). */
68
+ export declare function isGraphName(name: string): boolean;
69
+ /**
70
+ * Drop a parenthetical suffix when what remains is still a name: `阿里巴巴(阿里)` → `阿里巴巴`.
71
+ *
72
+ * The extractor merges brackets into the slot (`阿里巴巴(阿里)` is one argument), and the graph's
73
+ * own alias rule already strips a PARENTHESISED ALIAS STATEMENT (`(又名 老张)`). A bare parenthesis
74
+ * — the common "full name (short name)" form — is not an alias statement and has no canonical
75
+ * side, so the graph uses the head. Only a TRAILING group is dropped, and only when the head is a
76
+ * usable name; anything else is returned unchanged so a caller never receives an invented name.
77
+ */
78
+ export declare function stripParentheticalSuffix(name: string): string;