@theseus-whale/dsh-continuity 0.0.0-stage → 4.12.2

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 (258) hide show
  1. package/INSTALL.md +25 -0
  2. package/PANEL.md +92 -0
  3. package/README.md +286 -2
  4. package/RELEASE-NOTES.md +732 -0
  5. package/bin/bc-acceptance-full.mjs +91 -0
  6. package/bin/bc-acceptance-tight.mjs +66 -0
  7. package/bin/bc-acceptance.mjs +22 -0
  8. package/bin/bc-add-keywords.mjs +21 -0
  9. package/bin/bc-demo.mjs +47 -0
  10. package/bin/bc-expand-hint.mjs +179 -0
  11. package/bin/bc-ingest-index.mjs +35 -0
  12. package/bin/bc-ingest.mjs +31 -0
  13. package/bin/bc-label-demo.mjs +18 -0
  14. package/bin/bc-relabel.mjs +150 -0
  15. package/bin/bc-sessionize.mjs +59 -0
  16. package/bin/coref_pipeline.py +47 -0
  17. package/bin/coref_service.py +57 -0
  18. package/bin/debug_ckip.py +16 -0
  19. package/bin/debug_ner_zh.py +18 -0
  20. package/bin/doctor.mjs +275 -0
  21. package/bin/en_ner_error_audit.py +33 -0
  22. package/bin/eval-label-rules.mjs +342 -0
  23. package/bin/export-turns.py +68 -0
  24. package/bin/extract-user-paragraph.mjs +14 -0
  25. package/bin/extraction-composite-smoke.mjs +28 -0
  26. package/bin/extraction-latency.mjs +58 -0
  27. package/bin/extraction-user-smoke.mjs +28 -0
  28. package/bin/extraction_service.py +164 -0
  29. package/bin/facet-active-experiment.mjs +110 -0
  30. package/bin/facet-covid.mjs +8 -0
  31. package/bin/facet-curve.mjs +27 -0
  32. package/bin/facet-embedding-train.mjs +20 -0
  33. package/bin/facet-embedding-validate.mjs +83 -0
  34. package/bin/facet-eval-roleplay.mjs +42 -0
  35. package/bin/facet-eval.mjs +38 -0
  36. package/bin/facet-fold.mjs +31 -0
  37. package/bin/facet-load.mjs +16 -0
  38. package/bin/facet-mlp-scan.mjs +45 -0
  39. package/bin/facet-mvp.mjs +34 -0
  40. package/bin/facet-online-experiments.mjs +78 -0
  41. package/bin/facet-online-race.mjs +99 -0
  42. package/bin/facet-online-validate.mjs +51 -0
  43. package/bin/facet-panel.mjs +22 -0
  44. package/bin/facet-passage.mjs +41 -0
  45. package/bin/facet-random-validate.mjs +48 -0
  46. package/bin/facet-roleplay.mjs +33 -0
  47. package/bin/facet-scan.mjs +56 -0
  48. package/bin/facet-segment.mjs +34 -0
  49. package/bin/facet-train.mjs +19 -0
  50. package/bin/facet-validate.mjs +103 -0
  51. package/bin/facet-zeroshot.mjs +14 -0
  52. package/bin/install-templates.mjs +72 -0
  53. package/bin/memory-bench.mjs +53 -0
  54. package/bin/memory-build-summary.mjs +43 -0
  55. package/bin/memory-build-vector.mjs +31 -0
  56. package/bin/memory-build.mjs +41 -0
  57. package/bin/memory-calibration.mjs +70 -0
  58. package/bin/memory-closed-loop.mjs +119 -0
  59. package/bin/memory-dry-run.mjs +70 -0
  60. package/bin/memory-eval.mjs +104 -0
  61. package/bin/memory-event-alias-test.mjs +89 -0
  62. package/bin/memory-extraction-bench.mjs +58 -0
  63. package/bin/memory-load-gate-replay.mjs +137 -0
  64. package/bin/memory-loop-test.mjs +90 -0
  65. package/bin/memory-pre-turn-harness.mjs +63 -0
  66. package/bin/memory-query.mjs +39 -0
  67. package/bin/memory-replay-hints.mjs +84 -0
  68. package/bin/memory-scale.mjs +58 -0
  69. package/bin/memory-scenario-test.mjs +146 -0
  70. package/bin/memory-user-loop.mjs +95 -0
  71. package/bin/ner_bench_tiny_zh.py +59 -0
  72. package/bin/ner_bench_zh.py +60 -0
  73. package/bin/ner_conll_en.py +139 -0
  74. package/bin/ner_direct_en.py +123 -0
  75. package/bin/ner_direct_zh.py +100 -0
  76. package/bin/ner_pipeline.py +208 -0
  77. package/bin/noise-selfcheck.mjs +50 -0
  78. package/bin/pack-integrity.mjs +47 -0
  79. package/bin/panel-check.mjs +57 -0
  80. package/bin/plugin-integrity.mjs +74 -0
  81. package/bin/preview-live.mjs +13 -0
  82. package/bin/public-install-check.mjs +222 -0
  83. package/bin/relabel-apply.mjs +272 -0
  84. package/bin/relabel-dry-run.mjs +381 -0
  85. package/bin/relik_pipeline.py +39 -0
  86. package/bin/relik_service.py +52 -0
  87. package/bin/srl_pipeline.py +55 -0
  88. package/bin/srl_service.py +68 -0
  89. package/bin/summary-index.mjs +151 -0
  90. package/bin/summary-query.mjs +79 -0
  91. package/bin/vec-check.mjs +27 -0
  92. package/bin/zh_ner_error_audit.py +36 -0
  93. package/bin/zh_ner_slot_eval.py +38 -0
  94. package/client.js +760 -0
  95. package/cordis.patch.yml +378 -0
  96. package/docs/API-NOTES.md +1743 -0
  97. package/docs/ARCHITECTURE.md +355 -0
  98. package/docs/CACHE.md +253 -0
  99. package/docs/CONDUCT.md +30 -0
  100. package/docs/CREATOR-HANDOFF.md +102 -0
  101. package/docs/EXPERIMENT.md +136 -0
  102. package/docs/FACET-MVP-REPORT.md +473 -0
  103. package/docs/GUIDE-0.4.66.md +195 -0
  104. package/docs/GUIDE.md +127 -0
  105. package/docs/MECHANISM.md +243 -0
  106. package/docs/NEW-INSTANCE-HANDOFF.md +219 -0
  107. package/docs/README.md +37 -0
  108. package/docs/SESSION-LAYOUT.md +151 -0
  109. package/docs/history-map.md +249 -0
  110. package/docs/relabel-migration-wiring.md +366 -0
  111. package/guides/00-READ-FIRST.md +206 -0
  112. package/guides/01-INSTALL.md +207 -0
  113. package/guides/02-TABLES.md +225 -0
  114. package/guides/03-AUDIT.md +201 -0
  115. package/guides/04-REBUILD.md +306 -0
  116. package/guides/05-FIRST-RUN.md +191 -0
  117. package/guides/06-EMBEDDING.md +146 -0
  118. package/guides/07-PTC-AND-ASSETS.md +221 -0
  119. package/icon.svg +7 -0
  120. package/locale/en.json +6 -0
  121. package/locale/zh.json +6 -0
  122. package/models/README.md +13 -0
  123. package/models/Xenova/bge-small-zh-v1.5/config.json +31 -0
  124. package/models/Xenova/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  125. package/models/Xenova/bge-small-zh-v1.5/tokenizer.json +21278 -0
  126. package/models/Xenova/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  127. package/package.json +139 -4
  128. package/src/asset-tool.mjs +237 -0
  129. package/src/assets.mjs +244 -0
  130. package/src/backfill.mjs +74 -0
  131. package/src/backup.mjs +80 -0
  132. package/src/deadend.mjs +95 -0
  133. package/src/deleted-store.mjs +35 -0
  134. package/src/engine.mjs +3831 -0
  135. package/src/facets/classifier.mjs +146 -0
  136. package/src/facets/embedding-service.mjs +53 -0
  137. package/src/facets/embedding-worker.mjs +27 -0
  138. package/src/facets/embedding.mjs +163 -0
  139. package/src/facets/extract.mjs +80 -0
  140. package/src/facets/fold-plan.mjs +84 -0
  141. package/src/facets/hybrid.mjs +29 -0
  142. package/src/facets/index.mjs +113 -0
  143. package/src/facets/linear-probe.mjs +43 -0
  144. package/src/facets/load.mjs +37 -0
  145. package/src/facets/mlp-probe.mjs +54 -0
  146. package/src/facets/mlp-service.mjs +53 -0
  147. package/src/facets/mlp-worker.mjs +26 -0
  148. package/src/facets/online-knn.mjs +58 -0
  149. package/src/facets/online-models.mjs +227 -0
  150. package/src/facets/panel-state.mjs +23 -0
  151. package/src/facets/panel.mjs +73 -0
  152. package/src/facets/random.mjs +26 -0
  153. package/src/facets/scanner.mjs +35 -0
  154. package/src/facets/schema.mjs +75 -0
  155. package/src/facets/seed-examples.mjs +58 -0
  156. package/src/facets/sensitivity.mjs +98 -0
  157. package/src/facets/service.mjs +53 -0
  158. package/src/facets/store.mjs +39 -0
  159. package/src/facets/worker.mjs +23 -0
  160. package/src/folds-store.mjs +109 -0
  161. package/src/groups.mjs +167 -0
  162. package/src/handoff-tool.mjs +51 -0
  163. package/src/handoff.mjs +127 -0
  164. package/src/index.mjs +649 -0
  165. package/src/inject-policy.mjs +17 -0
  166. package/src/learning.mjs +88 -0
  167. package/src/memory/alias-proposal.mjs +40 -0
  168. package/src/memory/bc-audit.mjs +245 -0
  169. package/src/memory/bc-index-updater.mjs +404 -0
  170. package/src/memory/bc-index.mjs +48 -0
  171. package/src/memory/bc-label.mjs +220 -0
  172. package/src/memory/bc-panel-state.mjs +107 -0
  173. package/src/memory/bc-pre-step.mjs +463 -0
  174. package/src/memory/bc-recall.mjs +470 -0
  175. package/src/memory/bc-shift.mjs +123 -0
  176. package/src/memory/bc-store.mjs +41 -0
  177. package/src/memory/bm25.mjs +112 -0
  178. package/src/memory/calibration.mjs +54 -0
  179. package/src/memory/chatter.mjs +29 -0
  180. package/src/memory/cmap-file.mjs +107 -0
  181. package/src/memory/concepts-file.mjs +41 -0
  182. package/src/memory/coref-slots.mjs +40 -0
  183. package/src/memory/event-alias-ledger.mjs +87 -0
  184. package/src/memory/event-log.mjs +95 -0
  185. package/src/memory/event-model.mjs +132 -0
  186. package/src/memory/event-segmenter.mjs +36 -0
  187. package/src/memory/event-summary-tree.mjs +122 -0
  188. package/src/memory/extraction-adapter.mjs +76 -0
  189. package/src/memory/extraction-composite.mjs +75 -0
  190. package/src/memory/extraction-core.mjs +76 -0
  191. package/src/memory/extraction-pre-step.mjs +161 -0
  192. package/src/memory/extraction-provider.mjs +300 -0
  193. package/src/memory/extraction-score.mjs +36 -0
  194. package/src/memory/facet-stabilizer.mjs +59 -0
  195. package/src/memory/history-map.mjs +139 -0
  196. package/src/memory/language-router.mjs +30 -0
  197. package/src/memory/load-gate.mjs +161 -0
  198. package/src/memory/mechanical-note.mjs +104 -0
  199. package/src/memory/onboarding.mjs +163 -0
  200. package/src/memory/persona.mjs +78 -0
  201. package/src/memory/planner.mjs +249 -0
  202. package/src/memory/pre-turn.mjs +80 -0
  203. package/src/memory/query-aliases.mjs +39 -0
  204. package/src/memory/recall-cue.mjs +43 -0
  205. package/src/memory/recall.mjs +65 -0
  206. package/src/memory/referent-candidates.mjs +55 -0
  207. package/src/memory/referent-ledger.mjs +86 -0
  208. package/src/memory/relabel-migrate.mjs +376 -0
  209. package/src/memory/relik-adapter.mjs +25 -0
  210. package/src/memory/special-keywords.mjs +60 -0
  211. package/src/memory/structured-adapters.mjs +75 -0
  212. package/src/memory/summary-index.mjs +335 -0
  213. package/src/memory/summary-tree.mjs +110 -0
  214. package/src/memory/term-extractor.mjs +44 -0
  215. package/src/memory/tokenize.mjs +9 -0
  216. package/src/memory/vector-index.mjs +54 -0
  217. package/src/memory/window-summary.mjs +133 -0
  218. package/src/mood.mjs +133 -0
  219. package/src/peer-tool.mjs +63 -0
  220. package/src/peer.mjs +144 -0
  221. package/src/policy.mjs +170 -0
  222. package/src/preflight.mjs +113 -0
  223. package/src/provenance.mjs +94 -0
  224. package/src/range.mjs +75 -0
  225. package/src/recall-exact-tool.mjs +267 -0
  226. package/src/recall-tool.mjs +521 -0
  227. package/src/recheck.mjs +99 -0
  228. package/src/save-start.mjs +29 -0
  229. package/src/session-guard.mjs +140 -0
  230. package/src/session-log-tool.mjs +322 -0
  231. package/src/session-log.mjs +606 -0
  232. package/src/skills.mjs +111 -0
  233. package/src/split-range.mjs +115 -0
  234. package/src/state.mjs +198 -0
  235. package/src/t0.mjs +149 -0
  236. package/src/tag-confirm.mjs +78 -0
  237. package/src/tag-detect.mjs +42 -0
  238. package/src/tag-maintenance.mjs +285 -0
  239. package/src/tag-reject.mjs +73 -0
  240. package/src/tags-store.mjs +94 -0
  241. package/src/tags.mjs +692 -0
  242. package/src/threads-backend.mjs +26 -0
  243. package/src/threads-core.mjs +1887 -0
  244. package/src/threads-store.mjs +207 -0
  245. package/src/threads-tool.mjs +522 -0
  246. package/src/threads.mjs +929 -0
  247. package/src/tool-preview.mjs +427 -0
  248. package/src/topic.mjs +63 -0
  249. package/src/training.mjs +75 -0
  250. package/src/turn-tag.mjs +62 -0
  251. package/src/user-turn.mjs +39 -0
  252. package/templates/cmap.md +11 -0
  253. package/templates/concepts.md +12 -0
  254. package/templates/habits.md +40 -0
  255. package/templates/keywords.md +6 -0
  256. package/templates/rebuild.md +134 -0
  257. package/templates/rules-safe.md +184 -0
  258. package/templates/rules.md +177 -0
@@ -0,0 +1,164 @@
1
+ #!/usr/bin/env python
2
+ # -*- coding: utf-8 -*-
3
+ """One-shot extraction service: stdin {text}, stdout {ok, lang, entities, slots}."""
4
+ import json
5
+ import os
6
+ import sys
7
+
8
+ import torch
9
+ from transformers import AutoModelForTokenClassification, AutoTokenizer
10
+
11
+ sys.stdout.reconfigure(encoding="utf-8")
12
+ sys.stderr.reconfigure(encoding="utf-8")
13
+
14
+ ZH_MODEL = os.environ.get("ZH_MODEL_DIR", "uer/roberta-base-finetuned-cluener2020-chinese")
15
+ EN_MODEL = os.environ.get("EN_MODEL_DIR", "elastic/distilbert-base-uncased-finetuned-conll03-english")
16
+ ZH_SLOT = {
17
+ "name": "characters", "address": "location", "scene": "location",
18
+ "company": "objects", "organization": "objects", "government": "objects",
19
+ "book": "objects", "movie": "objects", "game": "objects",
20
+ }
21
+ EN_SLOT = {"PER": "characters", "LOC": "location", "ORG": "objects", "MISC": "objects"}
22
+
23
+ torch.set_num_threads(1)
24
+ zh_tok = AutoTokenizer.from_pretrained(ZH_MODEL)
25
+ zh_model = AutoModelForTokenClassification.from_pretrained(ZH_MODEL, local_files_only=True, use_safetensors=False)
26
+ zh_model.eval()
27
+ en_tok = AutoTokenizer.from_pretrained(EN_MODEL)
28
+ en_model = AutoModelForTokenClassification.from_pretrained(EN_MODEL, local_files_only=True, use_safetensors=False)
29
+ en_model.eval()
30
+
31
+
32
+ def route(text):
33
+ cjk = sum(1 for c in text if "\u4e00" <= c <= "\u9fff")
34
+ latin = sum(1 for c in text if c.isascii() and c.isalpha())
35
+ return "en" if cjk == 0 and latin > 0 else "zh"
36
+
37
+
38
+ def infer(tok, model, text, mode):
39
+ enc = tok(text, return_tensors="pt", truncation=True, max_length=256, return_offsets_mapping=True)
40
+ offsets = enc.pop("offset_mapping")[0].tolist()
41
+ with torch.no_grad():
42
+ ids = model(**enc).logits[0].argmax(-1).tolist()
43
+ if os.environ.get("DEBUG_EXTRACTION"):
44
+ global DEBUG_LAST
45
+ DEBUG_LAST = {
46
+ "tokens": tok.convert_ids_to_tokens(enc["input_ids"][0])[:12],
47
+ "pred": [model.config.id2label[int(i)] for i in ids[:12]],
48
+ }
49
+ entities = []
50
+ current = None
51
+ for idx, label_id in enumerate(ids):
52
+ label = model.config.id2label[int(label_id)]
53
+ start, end = offsets[idx]
54
+ if start == end:
55
+ continue
56
+ if label == "O":
57
+ if current:
58
+ entities.append(current)
59
+ current = None
60
+ continue
61
+ prefix, _, kind = label.partition("-")
62
+ if mode == "zh":
63
+ if prefix == "S":
64
+ if current:
65
+ entities.append(current)
66
+ entities.append({"text": text[start:end], "label": kind, "start": start, "end": end})
67
+ current = None
68
+ elif prefix == "B":
69
+ if current:
70
+ entities.append(current)
71
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
72
+ elif prefix in ("I", "E") and current:
73
+ current["text"] += text[start:end]
74
+ current["end"] = end
75
+ if prefix == "E":
76
+ entities.append(current)
77
+ current = None
78
+ else:
79
+ if prefix == "B":
80
+ if current:
81
+ entities.append(current)
82
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
83
+ elif prefix == "I" and current and current["label"] == kind:
84
+ current["text"] += text[start:end]
85
+ current["end"] = end
86
+ else:
87
+ if current:
88
+ entities.append(current)
89
+ current = {"text": text[start:end], "label": kind, "start": start, "end": end}
90
+ if current:
91
+ entities.append(current)
92
+ if mode == "en":
93
+ merged = []
94
+ for entity in entities:
95
+ last = merged[-1] if merged else None
96
+ if last and last["label"] == entity["label"] and entity["start"] - last["end"] <= 1:
97
+ last["text"] += entity["text"]
98
+ last["end"] = entity["end"]
99
+ else:
100
+ merged.append(entity)
101
+ entities = merged
102
+ return entities
103
+
104
+
105
+ def extract(text):
106
+ lang = route(text)
107
+ zh_entities = infer(zh_tok, zh_model, text, "zh")
108
+ en_entities = infer(en_tok, en_model, text, "en")
109
+ if os.environ.get("DEBUG_EXTRACTION"):
110
+ print("DEBUG", repr(text[:20]), zh_model.config.id2label, zh_entities[:3], file=sys.stderr)
111
+ combined = []
112
+ seen = set()
113
+ for entity in en_entities:
114
+ if any(c.isascii() and c.isalpha() for c in entity["text"]):
115
+ key = (entity["start"], entity["end"], entity["text"])
116
+ if key not in seen:
117
+ seen.add(key)
118
+ combined.append(entity)
119
+ for entity in zh_entities:
120
+ if not any(c.isascii() and c.isalpha() for c in entity["text"]):
121
+ key = (entity["start"], entity["end"], entity["text"])
122
+ if key not in seen:
123
+ seen.add(key)
124
+ combined.append(entity)
125
+ combined.sort(key=lambda item: (item["start"], item["end"]))
126
+ slot_map = {**ZH_SLOT, **EN_SLOT}
127
+ slots = {"characters": [], "location": None, "objects": []}
128
+ for entity in combined:
129
+ slot = slot_map.get(entity["label"])
130
+ if slot == "characters":
131
+ slots["characters"].append(entity["text"])
132
+ elif slot == "location" and slots["location"] is None:
133
+ slots["location"] = entity["text"]
134
+ elif slot == "objects":
135
+ slots["objects"].append(entity["text"])
136
+ slots["characters"] = list(dict.fromkeys(slots["characters"]))
137
+ slots["objects"] = list(dict.fromkeys(slots["objects"]))
138
+ slots["objects"] = [item for item in slots["objects"] if item not in slots["characters"]]
139
+ debug = None
140
+ if os.environ.get("DEBUG_EXTRACTION"):
141
+ debug = {"text": text[:40], "zh_entities": zh_entities[:5], "labels": [zh_model.config.id2label[i] for i in range(4)], "last": DEBUG_LAST}
142
+ return {"lang": lang, "entities": combined, "slots": slots, "debug": debug}
143
+
144
+
145
+ def handle(request):
146
+ if request.get("health"):
147
+ return {"ok": True, "health": "ready", "langs": ["zh", "en"]}
148
+ try:
149
+ return {"ok": True, **extract(str(request.get("text", "")))}
150
+ except Exception as error:
151
+ return {"ok": False, "reason": type(error).__name__, "message": str(error)[:300]}
152
+
153
+
154
+ if "--serve" in sys.argv:
155
+ for raw in sys.stdin.buffer:
156
+ line = raw.decode("utf-8", "replace").strip()
157
+ if not line:
158
+ continue
159
+ request = json.loads(line)
160
+ result = {"id": request.get("id"), **handle(request)}
161
+ print(json.dumps(result, ensure_ascii=False), flush=True)
162
+ else:
163
+ request = json.loads(sys.stdin.buffer.read().decode("utf-8"))
164
+ print(json.dumps(handle(request), ensure_ascii=False))
@@ -0,0 +1,110 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Active-learning label-efficiency experiment.
4
+ *
5
+ * Select 10 labels from a train pool (no test leakage), compare random vs
6
+ * uncertainty selection, then evaluate the untouched test pool.
7
+ */
8
+ import fs from 'node:fs';
9
+ import { embedTexts } from '../src/facets/embedding.mjs';
10
+ import { OnlineFacetKnn } from '../src/facets/online-knn.mjs';
11
+ import { detectRandom } from '../src/facets/random.mjs';
12
+ import { SLOTS, normalizeFacets } from '../src/facets/schema.mjs';
13
+
14
+ const args = process.argv.slice(2);
15
+ const argOf = (name) => {
16
+ const i = args.indexOf(name);
17
+ return i >= 0 ? args[i + 1] : undefined;
18
+ };
19
+ const input = argOf('--input');
20
+ if (!input) {
21
+ console.error('usage: node bin/facet-active-experiment.mjs --input early50-labeled.jsonl');
22
+ process.exit(2);
23
+ }
24
+ const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
25
+ const vectors = await embedTexts(rows.map((row) => row.text));
26
+
27
+ const groups = new Map();
28
+ for (const row of rows) {
29
+ const activity = normalizeFacets(row.facets).activity ?? '其他';
30
+ if (!groups.has(activity)) groups.set(activity, []);
31
+ groups.get(activity).push(row);
32
+ }
33
+ const train = [];
34
+ const test = [];
35
+ for (const list of groups.values()) {
36
+ list.sort((a, b) => a.turn - b.turn);
37
+ list.forEach((row, index) => (index % 2 === 0 ? train : test).push(row));
38
+ }
39
+ const vectorOf = new Map(rows.map((row, index) => [row.turn, vectors[index]]));
40
+
41
+ function makeLearner(selected) {
42
+ const learner = new OnlineFacetKnn({ k: 5, minSimilarity: 0.15, minVote: 0.5 });
43
+ for (const row of selected) learner.add(vectorOf.get(row.turn), row.facets);
44
+ return learner;
45
+ }
46
+
47
+ function evaluate(selected) {
48
+ const learner = makeLearner(selected);
49
+ const report = Object.fromEntries(SLOTS.map((slot) => [slot, { total: 0, covered: 0, correct: 0 }]));
50
+ for (const row of test) {
51
+ const truth = normalizeFacets(row.facets ?? {});
52
+ const predicted = learner.predict(vectorOf.get(row.turn));
53
+ predicted.facets.random = detectRandom(row.text).value;
54
+ for (const slot of SLOTS) {
55
+ if (truth[slot] === undefined) continue;
56
+ report[slot].total += 1;
57
+ if (predicted.facets[slot] === undefined) continue;
58
+ report[slot].covered += 1;
59
+ if (predicted.facets[slot] === truth[slot]) report[slot].correct += 1;
60
+ }
61
+ }
62
+ for (const slot of SLOTS) {
63
+ const row = report[slot];
64
+ row.coverage = row.total === 0 ? 0 : row.covered / row.total;
65
+ row.accuracy = row.covered === 0 ? 0 : row.correct / row.covered;
66
+ }
67
+ return report;
68
+ }
69
+
70
+ function uncertainty(learner, vector) {
71
+ const predicted = learner.predict(vector);
72
+ const slots = ['activity', 'topic'];
73
+ let sum = 0;
74
+ for (const slot of slots) {
75
+ const confidence = predicted.confidence?.[slot];
76
+ sum += confidence === undefined ? 1 : 1 - confidence;
77
+ }
78
+ return sum / slots.length;
79
+ }
80
+
81
+ function activeSelect(seedCount, budget) {
82
+ const selected = [...train.slice(0, seedCount)];
83
+ const pool = train.slice(seedCount);
84
+ while (selected.length < budget && pool.length > 0) {
85
+ const learner = makeLearner(selected);
86
+ let bestIndex = 0;
87
+ let best = -1;
88
+ pool.forEach((row, index) => {
89
+ const score = uncertainty(learner, vectorOf.get(row.turn));
90
+ if (score > best) { best = score; bestIndex = index; }
91
+ });
92
+ selected.push(pool.splice(bestIndex, 1)[0]);
93
+ }
94
+ return selected;
95
+ }
96
+
97
+ function randomSelect(seedCount, budget) {
98
+ return train.slice(0, Math.min(budget, train.length));
99
+ }
100
+
101
+ const result = {
102
+ full: evaluate(train),
103
+ random10: evaluate(randomSelect(2, 10)),
104
+ active10: evaluate(activeSelect(2, 10)),
105
+ random20: evaluate(randomSelect(2, 20)),
106
+ active20: evaluate(activeSelect(2, 20)),
107
+ };
108
+ fs.writeFileSync(`${input}.active-experiment.json`, `${JSON.stringify(result, null, 2)}\n`, 'utf8');
109
+ const summary = Object.fromEntries(Object.entries(result).map(([name, report]) => [name, Object.fromEntries(Object.entries(report).map(([slot, row]) => [slot, `${(row.accuracy * 100).toFixed(1)}% @ ${(row.coverage * 100).toFixed(1)}%`]))]));
110
+ console.log(JSON.stringify(summary, null, 2));
@@ -0,0 +1,8 @@
1
+ import fs from 'node:fs';
2
+ import { deserializePrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
3
+ import { extractFacets } from '../src/facets/extract.mjs';
4
+
5
+ const text = '我昨天看了COVID-19的起源报告,病毒可能来自实验室泄漏,也提到自然宿主和冷链传播。';
6
+ const prototypes = deserializePrototypes(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-prototypes.json', 'utf8')));
7
+ const result = await predictEmbedding(prototypes, text);
8
+ console.log(JSON.stringify({ text, rule: extractFacets(text), embedding: result }, null, 2));
@@ -0,0 +1,27 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Facet sensitivity CLI: rows with facets -> weighted temporal curve.
4
+ */
5
+ import fs from 'node:fs';
6
+ import { buildFacetIndex } from '../src/facets/index.mjs';
7
+ import { sensitivitySeries } from '../src/facets/sensitivity.mjs';
8
+ import { aliasFacets } from '../src/facets/schema.mjs';
9
+
10
+ const args = process.argv.slice(2);
11
+ const argOf = (name) => {
12
+ const i = args.indexOf(name);
13
+ return i >= 0 ? args[i + 1] : undefined;
14
+ };
15
+ const input = argOf('--input');
16
+ const query = argOf('--query') ?? '跑团';
17
+ if (!input) {
18
+ console.error('usage: node bin/facet-curve.mjs --input rows.jsonl --query 跑团 [--out series.json]');
19
+ process.exit(2);
20
+ }
21
+ const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
22
+ const index = buildFacetIndex(rows);
23
+ const series = sensitivitySeries(index.entries, aliasFacets(query), { idf: index.idf });
24
+ const out = argOf('--out');
25
+ const text = JSON.stringify({ query, facets: aliasFacets(query), series }, null, 2);
26
+ if (out) fs.writeFileSync(out, `${text}\n`, 'utf8');
27
+ else console.log(text);
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { buildEmbeddingPrototypes, serializePrototypes } from '../src/facets/embedding.mjs';
4
+
5
+ const args = process.argv.slice(2);
6
+ const argOf = (name) => {
7
+ const i = args.indexOf(name);
8
+ return i >= 0 ? args[i + 1] : undefined;
9
+ };
10
+ const input = argOf('--input');
11
+ const out = argOf('--out') ?? 'facet-prototypes.json';
12
+ const model = argOf('--model') ?? 'Xenova/bge-small-zh-v1.5';
13
+ if (!input) {
14
+ console.error('usage: node bin/facet-embedding-train.mjs --input rows.jsonl --out facet-prototypes.json');
15
+ process.exit(2);
16
+ }
17
+ const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
18
+ const prototypes = await buildEmbeddingPrototypes(rows, model);
19
+ fs.writeFileSync(out, `${JSON.stringify(serializePrototypes(prototypes))}\n`, 'utf8');
20
+ console.log(JSON.stringify({ input: rows.length, out, model }, null, 2));
@@ -0,0 +1,83 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Embedding holdout validation.
4
+ *
5
+ * node bin/facet-embedding-validate.mjs --input rows.jsonl
6
+ *
7
+ * Requires @huggingface/transformers and a first-run model download (local
8
+ * cache afterwards). No text leaves the machine after the model is cached.
9
+ */
10
+ import fs from 'node:fs';
11
+ import { buildEmbeddingPrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
12
+ import { trainFacetClassifier } from '../src/facets/classifier.mjs';
13
+ import { hybridExtractor } from '../src/facets/hybrid.mjs';
14
+ import { SLOTS, normalizeFacets } from '../src/facets/schema.mjs';
15
+
16
+ const args = process.argv.slice(2);
17
+ const argOf = (name) => {
18
+ const i = args.indexOf(name);
19
+ return i >= 0 ? args[i + 1] : undefined;
20
+ };
21
+ const input = argOf('--input');
22
+ const model = argOf('--model') ?? 'Xenova/bge-small-zh-v1.5';
23
+ if (!input) {
24
+ console.error('usage: node bin/facet-embedding-validate.mjs --input rows.jsonl [--model ...]');
25
+ process.exit(2);
26
+ }
27
+ const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
28
+
29
+ const groups = new Map();
30
+ for (const row of rows) {
31
+ const activity = normalizeFacets(row.facets ?? {}).activity ?? '其他';
32
+ if (!groups.has(activity)) groups.set(activity, []);
33
+ groups.get(activity).push(row);
34
+ }
35
+ const train = [];
36
+ const test = [];
37
+ for (const list of groups.values()) {
38
+ list.sort((a, b) => a.turn - b.turn);
39
+ list.forEach((row, index) => (index % 2 === 0 ? train : test).push(row));
40
+ }
41
+
42
+ const prototypes = await buildEmbeddingPrototypes(train, model);
43
+ const fallback = hybridExtractor(trainFacetClassifier(train));
44
+
45
+ function emptyReport() {
46
+ return Object.fromEntries(SLOTS.map((slot) => [slot, { total: 0, covered: 0, correct: 0 }]));
47
+ }
48
+ const embedReport = emptyReport();
49
+ const mergedReport = emptyReport();
50
+ for (const row of test) {
51
+ const truth = normalizeFacets(row.facets ?? {});
52
+ const embedded = await predictEmbedding(prototypes, row.text, model);
53
+ const merged = fallback(row.text);
54
+ for (const slot of SLOTS) {
55
+ if (truth[slot] === undefined) continue;
56
+ embedReport[slot].total += 1;
57
+ mergedReport[slot].total += 1;
58
+ if (embedded.facets[slot] !== undefined) {
59
+ embedReport[slot].covered += 1;
60
+ if (embedded.facets[slot] === truth[slot]) embedReport[slot].correct += 1;
61
+ }
62
+ const predicted = embedded.facets[slot] ?? merged.facets[slot];
63
+ if (predicted !== undefined) {
64
+ mergedReport[slot].covered += 1;
65
+ if (predicted === truth[slot]) mergedReport[slot].correct += 1;
66
+ }
67
+ }
68
+ }
69
+
70
+ function finish(report) {
71
+ return Object.fromEntries(Object.entries(report).map(([slot, value]) => [slot, {
72
+ ...value,
73
+ coverage: value.total === 0 ? 0 : value.covered / value.total,
74
+ accuracy: value.covered === 0 ? 0 : value.correct / value.covered,
75
+ }]));
76
+ }
77
+
78
+ console.log(JSON.stringify({
79
+ model,
80
+ split: { train: train.length, test: test.length },
81
+ embeddingOnly: finish(embedReport),
82
+ embeddingWithNBFallback: finish(mergedReport),
83
+ }, null, 2));
@@ -0,0 +1,42 @@
1
+ import fs from 'node:fs';
2
+ import { extractFacets } from '../src/facets/extract.mjs';
3
+ import { deserializeClassifier, predictFacets } from '../src/facets/classifier.mjs';
4
+ import { deserializePrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
5
+
6
+ const rows = fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/online400/continuity-1500.jsonl', 'utf8')
7
+ .split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line))
8
+ .filter((row) => row.facets?.activity === '跑团');
9
+ const nb = deserializeClassifier(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-model.json', 'utf8')));
10
+ const prototypes = deserializePrototypes(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-prototypes.json', 'utf8')));
11
+ const slots = ['activity', 'interaction', 'random', 'participants', 'medium', 'topic'];
12
+ const report = { total: rows.length, methods: {} };
13
+ const examples = [];
14
+ for (const [name, method] of [['rule', (text) => extractFacets(text)], ['nb', (text) => predictFacets(nb, text)], ['embedding', async (text) => predictEmbedding(prototypes, text)]]) {
15
+ const stats = Object.fromEntries(slots.map((slot) => [slot, { total: 0, covered: 0, correct: 0 }]));
16
+ let exact = 0;
17
+ for (const row of rows) {
18
+ const prediction = await method(row.text);
19
+ let all = true;
20
+ for (const slot of slots) {
21
+ const truth = row.facets?.[slot];
22
+ if (truth === undefined) { all = false; continue; }
23
+ stats[slot].total += 1;
24
+ const predicted = prediction.facets?.[slot];
25
+ if (predicted === undefined) { all = false; continue; }
26
+ stats[slot].covered += 1;
27
+ if (predicted === truth) stats[slot].correct += 1;
28
+ else all = false;
29
+ }
30
+ if (all) exact += 1;
31
+ if (name === 'embedding' && examples.length < 8) examples.push({ turn: row.turn, truth: row.facets, predicted: prediction.facets, text: String(row.text).replace(/\s+/g, ' ').slice(0, 180) });
32
+ }
33
+ for (const slot of slots) {
34
+ const s = stats[slot];
35
+ s.coverage = s.total === 0 ? 0 : Number((s.covered / s.total).toFixed(3));
36
+ s.accuracy = s.covered === 0 ? 0 : Number((s.correct / s.covered).toFixed(3));
37
+ }
38
+ report.methods[name] = { slots: stats, exactFull: Number((exact / rows.length).toFixed(3)) };
39
+ }
40
+ report.examples = examples;
41
+ fs.writeFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a/FACET-EVAL-ROLEPLAY.json', JSON.stringify(report, null, 2));
42
+ console.log(JSON.stringify(report, null, 2));
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs';
2
+ import { extractFacets } from '../src/facets/extract.mjs';
3
+ import { deserializeClassifier, predictFacets } from '../src/facets/classifier.mjs';
4
+ import { deserializePrototypes, predictEmbedding } from '../src/facets/embedding.mjs';
5
+
6
+ const rows = fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/online400/continuity-1500.jsonl', 'utf8')
7
+ .split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line)).filter((row) => row.facets && row.text);
8
+ const nb = deserializeClassifier(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-model.json', 'utf8')));
9
+ const prototypes = deserializePrototypes(JSON.parse(fs.readFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/last100/facet-prototypes.json', 'utf8')));
10
+ const slots = ['activity', 'interaction', 'random', 'participants', 'medium', 'topic'];
11
+ const report = { total: rows.length, methods: {} };
12
+ for (const [name, method] of [['rule', (text) => extractFacets(text)], ['nb', (text) => predictFacets(nb, text)], ['embedding', async (text) => predictEmbedding(prototypes, text)]]) {
13
+ const stats = Object.fromEntries(slots.map((slot) => [slot, { total: 0, covered: 0, correct: 0 }]));
14
+ let exact = 0;
15
+ for (const row of rows) {
16
+ const prediction = await method(row.text);
17
+ let all = true;
18
+ for (const slot of slots) {
19
+ const truth = row.facets?.[slot];
20
+ if (truth === undefined) { all = false; continue; }
21
+ stats[slot].total += 1;
22
+ const predicted = prediction.facets?.[slot];
23
+ if (predicted === undefined) { all = false; continue; }
24
+ stats[slot].covered += 1;
25
+ if (predicted === truth) stats[slot].correct += 1;
26
+ else all = false;
27
+ }
28
+ if (all) exact += 1;
29
+ }
30
+ for (const slot of slots) {
31
+ const s = stats[slot];
32
+ s.coverage = s.total === 0 ? 0 : Number((s.covered / s.total).toFixed(3));
33
+ s.accuracy = s.covered === 0 ? 0 : Number((s.correct / s.covered).toFixed(3));
34
+ }
35
+ report.methods[name] = { slots: stats, exactFull: Number((exact / rows.length).toFixed(3)) };
36
+ }
37
+ fs.writeFileSync('C:/Users/35794/Documents/DeepSeek\'s Sandbox/coopanion-continuity/backups/continuity-tags-20261031/durable-memory/build-a/FACET-EVAL-1462.json', JSON.stringify(report, null, 2));
38
+ console.log(JSON.stringify(report, null, 2));
@@ -0,0 +1,31 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { buildFacetIndex } from '../src/facets/index.mjs';
4
+ import { aliasFacets } from '../src/facets/schema.mjs';
5
+
6
+ const args = process.argv.slice(2);
7
+ const argOf = (name) => {
8
+ const i = args.indexOf(name);
9
+ return i >= 0 ? args[i + 1] : undefined;
10
+ };
11
+ const input = argOf('--input');
12
+ const out = argOf('--out');
13
+ const query = argOf('--query') ?? '跑团';
14
+ if (!input || !out) {
15
+ console.error('usage: node bin/facet-fold.mjs --input rows.jsonl --out facet-fold.json --query 跑团');
16
+ process.exit(2);
17
+ }
18
+ const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
19
+ const index = buildFacetIndex(rows);
20
+ const payload = {
21
+ query: aliasFacets(query),
22
+ idf: [...index.idf.entries()],
23
+ entries: index.entries.map((entry) => ({
24
+ turn: entry.turn,
25
+ seqStart: entry.seqStart,
26
+ seqEnd: entry.seqEnd,
27
+ facets: entry.facets,
28
+ })),
29
+ };
30
+ fs.writeFileSync(out, `${JSON.stringify(payload, null, 2)}\n`, 'utf8');
31
+ console.log(JSON.stringify({ out, query, entries: payload.entries.length }, null, 2));
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ import { loadFacetQuery } from '../src/facets/load.mjs';
3
+
4
+ const args = process.argv.slice(2);
5
+ const argOf = (name) => {
6
+ const i = args.indexOf(name);
7
+ return i >= 0 ? args[i + 1] : undefined;
8
+ };
9
+ const query = argOf('--query') ?? '跑团';
10
+ const indexFile = argOf('--index');
11
+ if (!indexFile) {
12
+ console.error('usage: node bin/facet-load.mjs --index facet-index.json --query 跑团 [--top 20]');
13
+ process.exit(2);
14
+ }
15
+ const topK = Number(argOf('--top') ?? 20);
16
+ console.log(JSON.stringify(loadFacetQuery(query, { indexFile, topK }), null, 2));
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import { deserializeMlp, predictMlp } from '../src/facets/mlp-probe.mjs';
4
+ import { embedTexts } from '../src/facets/embedding.mjs';
5
+ import { detectRandom } from '../src/facets/random.mjs';
6
+ import { buildFacetIndex, queryFacets, indexStats } from '../src/facets/index.mjs';
7
+ import { saveIndex } from '../src/facets/store.mjs';
8
+
9
+ const args = process.argv.slice(2);
10
+ const argOf = (name) => {
11
+ const i = args.indexOf(name);
12
+ return i >= 0 ? args[i + 1] : undefined;
13
+ };
14
+ const input = argOf('--input');
15
+ const modelPath = argOf('--model');
16
+ const dir = argOf('--dir') ?? '.facet-mlp';
17
+ const query = argOf('--query') ?? '跑团';
18
+ if (!input || !modelPath) {
19
+ console.error('usage: node bin/facet-mlp-scan.mjs --input rows.jsonl --model MLP-MODEL.json --dir .facet --query 跑团');
20
+ process.exit(2);
21
+ }
22
+ const rows = fs.readFileSync(input, 'utf8').split(/\r?\n/).filter(Boolean).map((line) => JSON.parse(line));
23
+ const model = deserializeMlp(JSON.parse(fs.readFileSync(modelPath, 'utf8')));
24
+ const entries = [];
25
+ for (const row of rows) {
26
+ const [vector] = await embedTexts([row.text]);
27
+ const result = predictMlp(model, vector);
28
+ result.facets.random = detectRandom(row.text).value;
29
+ if (result.facets.participants === undefined) result.facets.participants = '协作';
30
+ entries.push({
31
+ turn: row.turn,
32
+ seqStart: row.seqStart,
33
+ seqEnd: row.seqEnd,
34
+ facets: result.facets,
35
+ confidence: result.confidence,
36
+ });
37
+ }
38
+ const index = buildFacetIndex(entries);
39
+ fs.mkdirSync(dir, { recursive: true });
40
+ saveIndex(`${dir}/facet-index.json`, index);
41
+ const result = queryFacets(index, query, { topK: rows.length });
42
+ console.log(JSON.stringify({
43
+ stats: indexStats(index),
44
+ query: { name: result.query, facets: result.facets, matches: result.matches.length, ranges: result.ranges },
45
+ }, null, 2));
@@ -0,0 +1,34 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Facet MVP CLI.
4
+ *
5
+ * Input JSONL rows: { turn, seqStart, seqEnd, text? , facets? }
6
+ * Builds a facet index, prints stats, and runs one alias query.
7
+ */
8
+ import fs from 'node:fs';
9
+ import { buildFacetIndex, indexStats, queryFacets } from '../src/facets/index.mjs';
10
+
11
+ const args = process.argv.slice(2);
12
+ const argOf = (name) => {
13
+ const index = args.indexOf(name);
14
+ return index >= 0 ? args[index + 1] : undefined;
15
+ };
16
+ const input = argOf('--input');
17
+ const query = argOf('--query') ?? '跑团';
18
+ const topK = Number(argOf('--top')) || 20;
19
+ if (!input) {
20
+ console.error('usage: node bin/facet-mvp.mjs --input rows.jsonl --query 跑团 [--top 20]');
21
+ process.exit(2);
22
+ }
23
+ const rows = fs.readFileSync(input, 'utf8')
24
+ .split(/\r?\n/).filter(Boolean)
25
+ .map((line) => JSON.parse(line));
26
+ const index = buildFacetIndex(rows);
27
+ const result = queryFacets(index, query, { topK });
28
+ console.log(JSON.stringify({
29
+ stats: indexStats(index),
30
+ query: { name: result.query, facets: result.facets },
31
+ matchCount: result.matches.length,
32
+ ranges: result.ranges,
33
+ top: result.matches.slice(0, Math.min(10, topK)),
34
+ }, null, 2));