@polycode-projects/the-mechanical-code-talker 2.3.0 → 2.5.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.
- package/README.md +127 -28
- package/ROADMAP.md +3 -2
- package/bin/tmct.mjs +17 -90
- package/corpus/LICENSES.json +19 -4
- package/corpus/README.md +48 -0
- package/corpus/generated/README.md +24 -9
- package/corpus/generated/ace-surface-variants.jsonl +4 -1
- package/corpus/generated/manifest.json +4 -4
- package/corpus/prose/manifest.json +512 -0
- package/corpus/prose/sqlite/LICENSE-NOTICE +53 -0
- package/corpus/prose/sqlite/arch.txt +213 -0
- package/corpus/prose/sqlite/atomiccommit.txt +1117 -0
- package/corpus/prose/sqlite/faq.txt +473 -0
- package/corpus/prose/sqlite/fileformat.txt +1589 -0
- package/corpus/prose/sqlite/lang_createtable.txt +1339 -0
- package/corpus/prose/sqlite/lang_insert.txt +580 -0
- package/corpus/prose/sqlite/lang_select.txt +3293 -0
- package/corpus/prose/sqlite/optoverview.txt +908 -0
- package/corpus/prose/sqlite/queryplanner.txt +447 -0
- package/corpus/prose/sqlite/transactional.txt +41 -0
- package/corpus/prose/sqlite/wal.txt +567 -0
- package/corpus/prose/sqlite/whentouse.txt +300 -0
- package/corpus/prose/wikipedia/Apple.txt +4 -0
- package/corpus/prose/wikipedia/Attempto_Controlled_English.txt +169 -0
- package/corpus/prose/wikipedia/Automated_planning_and_scheduling.txt +67 -0
- package/corpus/prose/wikipedia/Bee.txt +7 -0
- package/corpus/prose/wikipedia/Bird.txt +8 -0
- package/corpus/prose/wikipedia/Bone.txt +4 -0
- package/corpus/prose/wikipedia/Book.txt +7 -0
- package/corpus/prose/wikipedia/Bread.txt +6 -0
- package/corpus/prose/wikipedia/Butterfly.txt +6 -0
- package/corpus/prose/wikipedia/Car.txt +1 -0
- package/corpus/prose/wikipedia/Cat.txt +1 -0
- package/corpus/prose/wikipedia/Child.txt +3 -0
- package/corpus/prose/wikipedia/City.txt +2 -0
- package/corpus/prose/wikipedia/Clock.txt +2 -0
- package/corpus/prose/wikipedia/Cooking.txt +1 -0
- package/corpus/prose/wikipedia/Description_logic.txt +660 -0
- package/corpus/prose/wikipedia/Doctor.txt +6 -0
- package/corpus/prose/wikipedia/Dog.txt +4 -0
- package/corpus/prose/wikipedia/Eagle.txt +4 -0
- package/corpus/prose/wikipedia/Emotion.txt +9 -0
- package/corpus/prose/wikipedia/Eye.txt +5 -0
- package/corpus/prose/wikipedia/Family.txt +3 -0
- package/corpus/prose/wikipedia/Farm.txt +4 -0
- package/corpus/prose/wikipedia/Fear.txt +4 -0
- package/corpus/prose/wikipedia/First-order_logic.txt +1518 -0
- package/corpus/prose/wikipedia/Fish.txt +10 -0
- package/corpus/prose/wikipedia/Flower.txt +3 -0
- package/corpus/prose/wikipedia/Food.txt +10 -0
- package/corpus/prose/wikipedia/Grass.txt +9 -0
- package/corpus/prose/wikipedia/Hand.txt +2 -0
- package/corpus/prose/wikipedia/Happiness.txt +3 -0
- package/corpus/prose/wikipedia/Heart.txt +4 -0
- package/corpus/prose/wikipedia/Horse.txt +4 -0
- package/corpus/prose/wikipedia/House.txt +6 -0
- package/corpus/prose/wikipedia/Human.txt +4 -0
- package/corpus/prose/wikipedia/Insect.txt +6 -0
- package/corpus/prose/wikipedia/Interactive_fiction.txt +112 -0
- package/corpus/prose/wikipedia/Knowledge.txt +5 -0
- package/corpus/prose/wikipedia/Knowledge_representation_and_reasoning.txt +87 -0
- package/corpus/prose/wikipedia/LICENSE-NOTICE +94 -0
- package/corpus/prose/wikipedia/Language.txt +10 -0
- package/corpus/prose/wikipedia/Learning.txt +4 -0
- package/corpus/prose/wikipedia/Mammal.txt +3 -0
- package/corpus/prose/wikipedia/Memory.txt +5 -0
- package/corpus/prose/wikipedia/Milk.txt +1 -0
- package/corpus/prose/wikipedia/Mountain.txt +1 -0
- package/corpus/prose/wikipedia/Natural_language_processing.txt +211 -0
- package/corpus/prose/wikipedia/Ostrich.txt +2 -0
- package/corpus/prose/wikipedia/Owl.txt +2 -0
- package/corpus/prose/wikipedia/Penguin.txt +2 -0
- package/corpus/prose/wikipedia/Plant.txt +5 -0
- package/corpus/prose/wikipedia/Rain.txt +1 -0
- package/corpus/prose/wikipedia/Resource_Description_Framework.txt +184 -0
- package/corpus/prose/wikipedia/River.txt +1 -0
- package/corpus/prose/wikipedia/School.txt +8 -0
- package/corpus/prose/wikipedia/Sea.txt +1 -0
- package/corpus/prose/wikipedia/Semantic_Web.txt +114 -0
- package/corpus/prose/wikipedia/Semantic_reasoner.txt +29 -0
- package/corpus/prose/wikipedia/Snow.txt +5 -0
- package/corpus/prose/wikipedia/Sun.txt +5 -0
- package/corpus/prose/wikipedia/Teacher.txt +4 -0
- package/corpus/prose/wikipedia/Team.txt +3 -0
- package/corpus/prose/wikipedia/Text-based_game.txt +17 -0
- package/corpus/prose/wikipedia/Tool.txt +4 -0
- package/corpus/prose/wikipedia/Tree.txt +7 -0
- package/corpus/prose/wikipedia/Weather.txt +4 -0
- package/corpus/prose/wikipedia/Web_Ontology_Language.txt +133 -0
- package/corpus/prose/wikipedia/Wind.txt +8 -0
- package/corpus/prose/wikipedia/Writing.txt +5 -0
- package/corpus/seon/README.md +1 -0
- package/corpus/tier2/generate.mjs +18 -18
- package/corpus/tier2/manifest.json +3 -3
- package/data/games/hanoi-3.txt +8 -2
- package/package.json +24 -5
- package/src/adapters/corpus-lanes.mjs +13 -0
- package/src/adapters/graph-build.mjs +5 -7
- package/src/adapters/import-closure.mjs +28 -0
- package/src/adapters/memory/blocks.mjs +5 -4
- package/src/adapters/memory/core.mjs +78 -5
- package/src/adapters/memory/shacl.mjs +12 -0
- package/src/adapters/providers/graph-service.mjs +12 -5
- package/src/adapters/tracked-files.mjs +17 -0
- package/src/domain/ask-vocab.mjs +2 -0
- package/src/domain/ask.mjs +225 -13
- package/src/domain/cli-verbs.mjs +201 -0
- package/src/domain/codegraph.mjs +142 -56
- package/src/domain/completions/graph-adapter.mjs +1 -1
- package/src/domain/completions/group.mjs +3 -17
- package/src/domain/completions/infer.mjs +4 -13
- package/src/domain/completions/rank.mjs +6 -19
- package/src/domain/grammar/lexicon-core.json +1 -1
- package/src/domain/hash.mjs +36 -13
- package/src/domain/interpret/fuzzy.mjs +7 -2
- package/src/domain/interpret/normalize.mjs +9 -0
- package/src/domain/interpret/strategies/keywords.mjs +19 -9
- package/src/domain/memory/capability.mjs +22 -3
- package/src/domain/memory/touched-facts.mjs +17 -0
- package/src/domain/module-paths.mjs +9 -0
- package/src/domain/persona/tiers.mjs +1 -1
- package/src/domain/planning.mjs +37 -0
- package/src/domain/prose.mjs +10 -2
- package/src/domain/relative-specifiers.mjs +12 -0
- package/src/domain/router/registry.mjs +3 -2
- package/src/domain/router/results.mjs +5 -18
- package/src/domain/seeded-random.mjs +33 -0
- package/src/domain/syllogise.mjs +10 -7
- package/src/domain/text-stats.mjs +31 -0
- package/src/services/chat.mjs +720 -182
- package/src/services/extract-facts.mjs +155 -0
- package/src/services/import-file.mjs +2 -2
- package/src/services/ledger-viz.mjs +6 -1
- package/src/services/sentences.mjs +26 -0
- package/src/surfaces/web/memory-ask-browser.bundle.js +11390 -360
- package/src/tools/graph-load.mjs +7 -1
- package/src/tools/readme-docs.mjs +113 -0
- package/src/tools/schema-docs.mjs +2 -2
- package/corpus/namenet/generate.mjs +0 -309
- package/corpus/wordnet/generate.mjs +0 -332
- package/src/adapters/prose-tokens.mjs +0 -98
- package/src/adapters/wordnet-source.mjs +0 -70
- package/src/domain/corpus-matrix.mjs +0 -87
- package/src/domain/inflect.mjs +0 -67
- package/src/domain/licences.mjs +0 -68
- package/src/domain/markdown-links.mjs +0 -55
- package/src/domain/persona/codegen.mjs +0 -123
- package/src/domain/publish-gate.mjs +0 -41
- package/src/domain/schemaorg/turtle.mjs +0 -25
- package/src/domain/semcor/parse.mjs +0 -87
- package/src/domain/version-stamp.mjs +0 -36
- package/src/domain/wordnet/yaml.mjs +0 -133
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
{
|
|
62
62
|
"id": "human",
|
|
63
63
|
"kind": "domain",
|
|
64
|
-
"description": "The default human-world persona
|
|
64
|
+
"description": "The default human-world persona: everyday people, places, objects, nature, time/events, body/food and mind vocabulary, hand-curated from Open English WordNet (CC-BY-4.0) and bridged to Schema.org's (Apache-2.0) top-level classes — replaces the code-domain SEON+ConceptNet default.",
|
|
65
65
|
"source": {
|
|
66
66
|
"kind": "curated",
|
|
67
67
|
"tool": "corpus/tier2/generate.mjs"
|
|
@@ -75,7 +75,7 @@
|
|
|
75
75
|
{
|
|
76
76
|
"id": "human-medium",
|
|
77
77
|
"kind": "domain",
|
|
78
|
-
"description": "The Medium tier of the default human-world persona
|
|
78
|
+
"description": "The Medium tier of the default human-world persona: incremental facts beyond Small only — activated alongside \"human\" via --persona-size medium, never active by default.",
|
|
79
79
|
"source": {
|
|
80
80
|
"kind": "curated",
|
|
81
81
|
"tool": "corpus/tier2/generate.mjs"
|
|
@@ -89,7 +89,7 @@
|
|
|
89
89
|
{
|
|
90
90
|
"id": "human-large",
|
|
91
91
|
"kind": "domain",
|
|
92
|
-
"description": "The Large tier of the default human-world persona
|
|
92
|
+
"description": "The Large tier of the default human-world persona: incremental facts beyond Medium only — activated alongside \"human\" via --persona-size large, never active by default.",
|
|
93
93
|
"source": {
|
|
94
94
|
"kind": "curated",
|
|
95
95
|
"tool": "corpus/tier2/generate.mjs"
|
package/data/games/hanoi-3.txt
CHANGED
|
@@ -5,8 +5,14 @@
|
|
|
5
5
|
# the goal is that every disk rests on peg-c. solve it.
|
|
6
6
|
#
|
|
7
7
|
# Variations, each stretching a different direction:
|
|
8
|
-
# scale — first teach: "disk-4 is a disk.
|
|
9
|
-
#
|
|
8
|
+
# scale — first teach: "disk-4 is a disk."
|
|
9
|
+
# then: "disk-1 is smaller than disk-4. disk-2 is smaller than disk-4.
|
|
10
|
+
# disk-3 is smaller than disk-4."
|
|
11
|
+
# then: "disk-1 rests on disk-2. disk-2 rests on disk-3. disk-3 rests on
|
|
12
|
+
# disk-4. disk-4 rests on peg-a." — same goal. 15 moves (2^4 - 1).
|
|
13
|
+
# Every smaller-than pair is taught: the relation is stored as given and
|
|
14
|
+
# is not chased transitively, so disk-3 < disk-4 alone leaves disk-1 and
|
|
15
|
+
# disk-2 with no legal move onto disk-4 and no plan exists.
|
|
10
16
|
# any start — disk-1 rests on peg-b. disk-2 rests on peg-c. disk-3 rests on peg-a.
|
|
11
17
|
# the goal is that every disk rests on peg-c. solve it.
|
|
12
18
|
# other goal — same start, "the goal is that every disk rests on peg-b. solve it."
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@polycode-projects/the-mechanical-code-talker",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.5.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"description": "The Mechanical Code Talker (tmct) — a tolerant, offline, $0 chat surface that guides you toward precision queries about a software repository. ELIZA/PARRY-style but domain-obsessed with code. No model calls; no codebase index of its own.",
|
|
@@ -51,10 +51,23 @@
|
|
|
51
51
|
"files": [
|
|
52
52
|
"bin/",
|
|
53
53
|
"src/",
|
|
54
|
+
"!src/adapters/pii-scan.mjs",
|
|
55
|
+
"!src/domain/corpus-matrix.mjs",
|
|
56
|
+
"!src/domain/inflect.mjs",
|
|
57
|
+
"!src/domain/licences.mjs",
|
|
58
|
+
"!src/domain/markdown-links.mjs",
|
|
59
|
+
"!src/domain/pack-manifest.mjs",
|
|
60
|
+
"!src/domain/persona/codegen.mjs",
|
|
61
|
+
"!src/domain/pii-rules.mjs",
|
|
62
|
+
"!src/domain/publish-gate.mjs",
|
|
63
|
+
"!src/domain/schemaorg/turtle.mjs",
|
|
64
|
+
"!src/domain/version-stamp.mjs",
|
|
54
65
|
"README.md",
|
|
55
66
|
"ROADMAP.md",
|
|
56
67
|
"LICENSE",
|
|
57
68
|
"corpus/",
|
|
69
|
+
"!corpus/wordnet/generate.mjs",
|
|
70
|
+
"!corpus/namenet/generate.mjs",
|
|
58
71
|
"data/"
|
|
59
72
|
],
|
|
60
73
|
"publishConfig": {
|
|
@@ -69,7 +82,9 @@
|
|
|
69
82
|
},
|
|
70
83
|
"scripts": {
|
|
71
84
|
"help": "node bin/tmct.mjs --help",
|
|
72
|
-
"test": "node --test \"test/**/*.test.mjs\"",
|
|
85
|
+
"test": "node --test --test-concurrency=8 \"test/**/*.test.mjs\"",
|
|
86
|
+
"test:smoke": "node --test test/smoke/*.test.mjs",
|
|
87
|
+
"test:fast": "node --test --test-concurrency=8 test/smoke/*.test.mjs test/fast/*.test.mjs test/tools/ask.test.mjs test/tools/server.test.mjs test/estate/import-layers.test.mjs",
|
|
73
88
|
"test:e2e": "node --test \"e2e/**/*.test.mjs\"",
|
|
74
89
|
"e2e:browsers": "playwright install chromium",
|
|
75
90
|
"check:links": "node scripts/check-links.mjs",
|
|
@@ -78,8 +93,9 @@
|
|
|
78
93
|
"check:licences": "node scripts/check-licences.mjs",
|
|
79
94
|
"check:publint": "npx --no-install publint",
|
|
80
95
|
"check:tool-docs": "node scripts/generate-tool-docs.mjs --check",
|
|
96
|
+
"check:budgets": "node scripts/check-tier-budgets.mjs",
|
|
81
97
|
"check:publish": "node scripts/check-publish.mjs",
|
|
82
|
-
"check:all": "npm run check:links && npm run check:pii && npm run check:licences && npm run check:publint && npm run check:tool-docs",
|
|
98
|
+
"check:all": "npm run check:links && npm run check:pii && npm run check:licences && npm run check:publint && npm run check:tool-docs && npm run check:budgets",
|
|
83
99
|
"smoke:deploy": "node scripts/post-deploy-smoke.mjs",
|
|
84
100
|
"chat": "node bin/tmct.mjs",
|
|
85
101
|
"chat:repo": "node bin/tmct.mjs chat --repo",
|
|
@@ -122,13 +138,16 @@
|
|
|
122
138
|
"build:demo-memory": "node scripts/build-demo-memory.mjs",
|
|
123
139
|
"gen:tool-docs": "node scripts/generate-tool-docs.mjs",
|
|
124
140
|
"gen:collisions": "node scripts/generate-real-word-collisions.mjs",
|
|
141
|
+
"gen:prose-corpus": "node scripts/fetch-prose-corpus.mjs",
|
|
125
142
|
"gen:variants": "node scripts/generate-template-variants.mjs",
|
|
126
|
-
"extract
|
|
143
|
+
"extract": "node bin/tmct.mjs extract",
|
|
144
|
+
"extract:facts": "node bin/tmct.mjs extract"
|
|
127
145
|
},
|
|
128
146
|
"devDependencies": {
|
|
129
147
|
"esbuild": "^0.28.1",
|
|
130
148
|
"ink-testing-library": "^4.0.0",
|
|
131
149
|
"playwright": "1.61.1",
|
|
132
|
-
"publint": "0.3.21"
|
|
150
|
+
"publint": "0.3.21",
|
|
151
|
+
"yaml": "2.9.0"
|
|
133
152
|
}
|
|
134
153
|
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Discover corpus lanes on disk: every .jsonl under `dir` (recursively),
|
|
2
|
+
// returned as lane names — the path relative to dir, without the .jsonl suffix
|
|
3
|
+
// — sorted. Sharded lane families live one directory down, e.g.
|
|
4
|
+
// games/openers.jsonl -> "games/openers".
|
|
5
|
+
import { readdirSync } from "node:fs";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
|
|
8
|
+
export function corpusLanes(dir) {
|
|
9
|
+
return readdirSync(dir, { withFileTypes: true, recursive: true })
|
|
10
|
+
.filter((entry) => entry.isFile() && entry.name.endsWith(".jsonl"))
|
|
11
|
+
.map((entry) => relative(dir, join(entry.parentPath, entry.name)).slice(0, -".jsonl".length))
|
|
12
|
+
.sort();
|
|
13
|
+
}
|
|
@@ -15,12 +15,10 @@
|
|
|
15
15
|
// mgx:touchesSymbol Commit -> CodeEntity (commit changed-line-range x symbol span)
|
|
16
16
|
// mgx:callsSymbol Function/Method -> Function/Class (symbol-granular, unambiguous)
|
|
17
17
|
// seon:containsCodeEntity Class -> Method/Attribute (class membership)
|
|
18
|
-
//
|
|
18
|
+
// seon:hasSuperType Class -> Class (inheritance)
|
|
19
19
|
|
|
20
|
-
import { attachProseTokens, buildProseIndex } from "
|
|
21
|
-
|
|
22
|
-
const isTestPath = (p) =>
|
|
23
|
-
p.startsWith("tests/") || /(^|\/)tests?\//.test(p) || /(^|\/)test_[^/]*\.py$/.test(p) || /\.tests(\.|$)/.test(p);
|
|
20
|
+
import { attachProseTokens, buildProseIndex } from "../domain/prose.mjs";
|
|
21
|
+
import { isTestPath } from "../domain/module-paths.mjs";
|
|
24
22
|
|
|
25
23
|
const lastIdent = (name) => {
|
|
26
24
|
const m = String(name).match(/([A-Za-z_][A-Za-z0-9_]*)\s*$/);
|
|
@@ -145,7 +143,7 @@ export function buildEntities(modules, commits, { generatedAt = "", symbolHistor
|
|
|
145
143
|
if (d.raises?.length) attrs.push({ prop: "seon:throwsException", key: "raises", value: list(d.raises) });
|
|
146
144
|
if (d.catches?.length) attrs.push({ prop: "seon:catchesException", key: "catches", value: list(d.catches) });
|
|
147
145
|
if (d.self_fields?.length) attrs.push({ prop: "seon:accessesField", key: "self_fields", value: list(d.self_fields) });
|
|
148
|
-
if (d.subkind) attrs.push({ prop: "
|
|
146
|
+
if (d.subkind) attrs.push({ prop: "mgx:subKind", key: "subkind", value: String(d.subkind) });
|
|
149
147
|
if (d.is_static) attrs.push({ prop: "seon:isStatic", key: "isStatic", value: "true" });
|
|
150
148
|
if (d.is_abstract) attrs.push({ prop: "seon:isAbstract", key: "isAbstract", value: "true" });
|
|
151
149
|
if (d.is_constant) attrs.push({ prop: "seon:isConstant", key: "isConstant", value: "true" });
|
|
@@ -392,7 +390,7 @@ export function buildEntities(modules, commits, { generatedAt = "", symbolHistor
|
|
|
392
390
|
{ prop: "seon:isStatic", note: "@staticmethod/@classmethod" },
|
|
393
391
|
{ prop: "seon:isAbstract", note: "@abstractmethod/@abstractproperty" },
|
|
394
392
|
{ prop: "seon:isConstant", note: "ALL_CAPS module global" },
|
|
395
|
-
{ prop: "
|
|
393
|
+
{ prop: "mgx:subKind", note: "type flavour on a Class define when not a plain class (interface/enum/struct/record); kind stays class; owned — SEON has no subKind property" },
|
|
396
394
|
{ prop: "seon:hasAccessModifier", note: "visibility from leading underscore (private/protected); public omitted" },
|
|
397
395
|
{ prop: "seon:hasDoc", note: "first docstring line (capped) — one-line purpose without the body" },
|
|
398
396
|
],
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Every file under `root` that `entry`'s relative imports reach, walked from
|
|
2
|
+
// source at read time and returned as paths relative to root, sorted.
|
|
3
|
+
//
|
|
4
|
+
// `seeds` names extra entry points to fold into the same closure — modules that
|
|
5
|
+
// arrive through a computed specifier a static read cannot follow (a lemma/POS
|
|
6
|
+
// tier loaded by a variable), so they and everything they reach still get
|
|
7
|
+
// walked.
|
|
8
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
9
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
10
|
+
|
|
11
|
+
import { relativeSpecifiers } from "../domain/relative-specifiers.mjs";
|
|
12
|
+
|
|
13
|
+
export function importClosure(entry, { root, seeds = [] } = {}) {
|
|
14
|
+
const reached = new Set();
|
|
15
|
+
const visit = (file) => {
|
|
16
|
+
const rel = relative(root, file);
|
|
17
|
+
if (reached.has(rel) || !existsSync(file)) return;
|
|
18
|
+
reached.add(rel);
|
|
19
|
+
if (file.endsWith(".json")) return;
|
|
20
|
+
const source = readFileSync(file, "utf8");
|
|
21
|
+
for (const specifier of relativeSpecifiers(source)) {
|
|
22
|
+
visit(resolve(dirname(file), specifier));
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
visit(join(root, entry));
|
|
26
|
+
for (const file of seeds) visit(join(root, file));
|
|
27
|
+
return [...reached].sort();
|
|
28
|
+
}
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
|
|
7
7
|
import { mkdir, readFile, readdir, rename, rm, writeFile } from "node:fs/promises";
|
|
8
8
|
import { join } from "node:path";
|
|
9
|
-
import { splitIdentifierWords, tokenizeProse } from "
|
|
9
|
+
import { splitIdentifierWords, tokenizeProse } from "../../domain/prose.mjs";
|
|
10
|
+
import { idfWeight } from "../../domain/text-stats.mjs";
|
|
10
11
|
import { SOURCE_PRIOR } from "../../domain/memory/trust.mjs";
|
|
11
12
|
|
|
12
13
|
// A block inherits its Source's trust (operator 1.0, corpus 0.7); retrieval
|
|
@@ -182,12 +183,12 @@ export async function retrieveBlocks(dir, query, k = 3) {
|
|
|
182
183
|
if (!qTokens.length) return [];
|
|
183
184
|
|
|
184
185
|
const sets = entries.map(([, b]) => new Set(b.tokens || []));
|
|
185
|
-
// idf per query token, computed once
|
|
186
|
-
//
|
|
186
|
+
// idf per query token, computed once over the block set (~0 for ubiquitous
|
|
187
|
+
// tokens, large for rare ones) — the same weighting codegraph.mjs locate uses.
|
|
187
188
|
const idf = new Map(qTokens.map((t) => {
|
|
188
189
|
let df = 0;
|
|
189
190
|
for (const s of sets) if (s.has(t)) df += 1;
|
|
190
|
-
return [t,
|
|
191
|
+
return [t, idfWeight(N, df)];
|
|
191
192
|
}));
|
|
192
193
|
const scored = [];
|
|
193
194
|
for (let i = 0; i < N; i += 1) {
|
|
@@ -4,10 +4,17 @@
|
|
|
4
4
|
// appendFact), and Sessions are all typed twice — payload `class` and an
|
|
5
5
|
// `rdf:type` attribute. Every append is crash-safe and idempotent (utterance
|
|
6
6
|
// ids are deterministic, fact ids hash the triple).
|
|
7
|
+
//
|
|
8
|
+
// A Fact is textbook RDF reification: `rdf:type rdf:Statement` plus
|
|
9
|
+
// rdf:subject/predicate/object, which carries the per-triple provenance and
|
|
10
|
+
// polarity stamps a bare triple has no room for. This is the RDF 1.1
|
|
11
|
+
// reification vocabulary, which RDF 1.2 reclassifies as legacy (steering new
|
|
12
|
+
// systems toward triple terms) but does not deprecate. See
|
|
13
|
+
// docs/references/schemas/rdf-reification-and-rdf-star.md.
|
|
7
14
|
|
|
8
15
|
import { mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
|
|
9
16
|
import { dirname, join } from "node:path";
|
|
10
|
-
import { proseTokensFor, buildProseIndex } from "
|
|
17
|
+
import { proseTokensFor, buildProseIndex } from "../../domain/prose.mjs";
|
|
11
18
|
import { fnv1aHex, normText, normFactTerm, normFactPredicate, factIdFor, factIdForTriple } from "../../domain/hash.mjs";
|
|
12
19
|
|
|
13
20
|
// Fact identity (normalization + id derivation) lives in hash.mjs — the one
|
|
@@ -80,7 +87,7 @@ const MEMORY_VOCABULARY = [
|
|
|
80
87
|
{ prop: "mgx:ruleBaseCase", note: "recursive only: the base-case relation name (hop zero)" },
|
|
81
88
|
{ prop: "mgx:ruleRecStep", note: "recursive only: the self-referential recursive-step relation name" },
|
|
82
89
|
{ prop: CREATED_AT_PROP, note: "when an individual was FIRST written, ISO-8601 (first-write-wins on upsert); the audit 'when', the recency input to trust, the novelty signal" },
|
|
83
|
-
{ prop: UPDATED_AT_PROP, note: "when an individual's OWN attributes were last mutated in place (upsertSession, recomputeFactTrust, recomputeSourceReliability) — most individuals never carry
|
|
90
|
+
{ prop: UPDATED_AT_PROP, note: "an audit / last-modified stamp: when an individual's OWN attributes were last mutated in place (upsertSession, recomputeFactTrust, recomputeSourceReliability) — most individuals never carry it at all. NOT transaction time: a mutable stamp cannot record when the previous version stopped being current, so tmct is not bitemporal" },
|
|
84
91
|
{ prop: DERIVED_FROM_PROP, predicate: "derivedFrom", note: "umbrella: a Fact derived from a Source (or another Fact). ext ref prov:wasDerivedFrom (UNVERIFIED-pending-web-check)" },
|
|
85
92
|
{ prop: STATED_BY_PROP, predicate: "statedBy", note: "subPropertyOf derivedFrom: a Source directly asserts this Fact (one edge per independent source — replaces the factProvenance union)" },
|
|
86
93
|
{ prop: CANONICALISED_FROM_PROP, predicate: "canonicalisedFrom", note: "subPropertyOf derivedFrom: a canonical Fact cleaned from a raw Block/Source, never replacing it" },
|
|
@@ -519,8 +526,8 @@ export async function snapshotMemory(dir, { retentionVersions } = {}) {
|
|
|
519
526
|
* append creates the file). The result is a raw entities payload;
|
|
520
527
|
* parseEntities() loads it. */
|
|
521
528
|
export async function loadMemory(dir) {
|
|
522
|
-
if (isMemoryHandle(dir)) return dir.payload;
|
|
523
|
-
if (isSqliteHandle(dir)) return readSqlitePayload(dir);
|
|
529
|
+
if (isMemoryHandle(dir)) return migrateLegacyFactIds(dir.payload);
|
|
530
|
+
if (isSqliteHandle(dir)) return migrateLegacyFactIds(readSqlitePayload(dir));
|
|
524
531
|
let text;
|
|
525
532
|
try {
|
|
526
533
|
text = await readFile(memoryGraphFile(dir), "utf8");
|
|
@@ -528,7 +535,50 @@ export async function loadMemory(dir) {
|
|
|
528
535
|
if (e?.code === "ENOENT") return emptyMemory();
|
|
529
536
|
throw e;
|
|
530
537
|
}
|
|
531
|
-
return JSON.parse(text);
|
|
538
|
+
return migrateLegacyFactIds(JSON.parse(text));
|
|
539
|
+
}
|
|
540
|
+
|
|
541
|
+
// A Fact id written before factIdFor widened to 64 bits — `fact:` + exactly 8
|
|
542
|
+
// hex. A current id is 16 hex, so this anchored test never matches one, and a
|
|
543
|
+
// migrated store pays only string checks with no rehash on load.
|
|
544
|
+
const LEGACY_FACT_ID_RE = /^fact:[0-9a-f]{8}$/;
|
|
545
|
+
|
|
546
|
+
/** Bring a store written under the old 32-bit fact id up to the current
|
|
547
|
+
* factIdFor id, in place, on load. Every Fact carries its own (s, p, o) in
|
|
548
|
+
* rdf:subject/predicate/object, so the current id recomputes from the payload
|
|
549
|
+
* with no external key. Rewrites the Fact ids and everything that points at
|
|
550
|
+
* them by id: statedBy/derivedFrom (and every other) edge endpoint, a rule's
|
|
551
|
+
* mgx:factJustification premise-id list, and derived_from links. Idempotent —
|
|
552
|
+
* a Fact already on a wide id is skipped by LEGACY_FACT_ID_RE, so a
|
|
553
|
+
* fully-migrated store makes no change and does no work beyond the shape test.
|
|
554
|
+
* This is what lets a pre-widening store keep resolving its facts: a lookup by
|
|
555
|
+
* the current id finds the Fact because load moved it onto that id. */
|
|
556
|
+
function migrateLegacyFactIds(payload) {
|
|
557
|
+
if (!Array.isArray(payload?.individuals)) return payload;
|
|
558
|
+
const remap = new Map(); // old fact id -> current fact id
|
|
559
|
+
for (const ind of payload.individuals) {
|
|
560
|
+
if (ind?.class !== FACT_CLASS || !LEGACY_FACT_ID_RE.test(ind.id || "")) continue;
|
|
561
|
+
const get = (k) => (ind.attributes || []).find((a) => a?.key === k)?.value || "";
|
|
562
|
+
const currentId = factIdFor(get("subject"), get("predicate"), get("object"));
|
|
563
|
+
if (currentId === ind.id) continue;
|
|
564
|
+
remap.set(ind.id, currentId);
|
|
565
|
+
ind.id = currentId;
|
|
566
|
+
}
|
|
567
|
+
if (!remap.size) return payload;
|
|
568
|
+
const remapId = (id) => remap.get(id) || id;
|
|
569
|
+
for (const group of payload.objectProperties || []) {
|
|
570
|
+
for (const e of group.examples || []) {
|
|
571
|
+
if (!e) continue;
|
|
572
|
+
if (remap.has(e.subject)) e.subject = remap.get(e.subject);
|
|
573
|
+
if (remap.has(e.object)) e.object = remap.get(e.object);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
for (const ind of payload.individuals) {
|
|
577
|
+
if (Array.isArray(ind?.derived_from) && ind.derived_from.length) ind.derived_from = ind.derived_from.map(remapId);
|
|
578
|
+
const just = (ind?.attributes || []).find((a) => a?.prop === "mgx:factJustification");
|
|
579
|
+
if (just?.value) just.value = just.value.split(" ").filter(Boolean).map(remapId).join(" ");
|
|
580
|
+
}
|
|
581
|
+
return payload;
|
|
532
582
|
}
|
|
533
583
|
|
|
534
584
|
/** Persist a mutated payload back to `dir`: an atomic file write (Backend A),
|
|
@@ -627,6 +677,29 @@ function sourceIdFor(desc) {
|
|
|
627
677
|
|
|
628
678
|
const sourceLabel = (id) => String(id).replace(/^src:/, "");
|
|
629
679
|
|
|
680
|
+
// The read-side of the Source split ontology/tmct-core.ttl declares. tmct:Source
|
|
681
|
+
// is one flat class over a sourceType key whose values fall into all three of
|
|
682
|
+
// PROV's disjoint top classes; this maps each stored sourceType to its read-side
|
|
683
|
+
// subclass and PROV top class. Nothing on disk changes — sourceType is already
|
|
684
|
+
// stored on every Source — so this is derivation, not migration.
|
|
685
|
+
const PROV_CLASS_BY_SOURCE_TYPE = Object.freeze({
|
|
686
|
+
operator: { subClass: "tmct:AgentSource", prov: "prov:Agent" },
|
|
687
|
+
teach: { subClass: "tmct:AgentSource", prov: "prov:Agent" },
|
|
688
|
+
provider: { subClass: "tmct:AgentSource", prov: "prov:Agent" },
|
|
689
|
+
corpus: { subClass: "tmct:DocumentSource", prov: "prov:Entity" },
|
|
690
|
+
corpusWeak: { subClass: "tmct:DocumentSource", prov: "prov:Entity" },
|
|
691
|
+
web: { subClass: "tmct:DocumentSource", prov: "prov:Entity" },
|
|
692
|
+
extracted: { subClass: "tmct:DocumentSource", prov: "prov:Entity" },
|
|
693
|
+
entailed: { subClass: "tmct:ActivitySource", prov: "prov:Activity" },
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
/** The read-side PROV subclass and top class a Source's mgx:sourceType maps to,
|
|
697
|
+
* or null for an unrecognised type (never force-fit). Mirrors the Source-split
|
|
698
|
+
* subclasses of tmct:Source in ontology/tmct-core.ttl. */
|
|
699
|
+
export function provSourceClassFor(sourceType) {
|
|
700
|
+
return PROV_CLASS_BY_SOURCE_TYPE[sourceType] || null;
|
|
701
|
+
}
|
|
702
|
+
|
|
630
703
|
/** Upsert a Source individual (deterministic id → idempotent, edges never
|
|
631
704
|
* dangle). createdAt is first-write-wins; a recovered @<ts> (desc.createdAt)
|
|
632
705
|
* seeds it when present. Returns the Source id, or null for an unknown kind. */
|
|
@@ -31,6 +31,12 @@ const RULE_SLOT_PROPS = {
|
|
|
31
31
|
],
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
+
// A term that runs past a sentence terminator into the next sentence
|
|
35
|
+
// ("disk-2. disk-2 rests on disk-3") is a whole un-split line captured as one
|
|
36
|
+
// term, not a term. Requires the word character after the terminator, so an
|
|
37
|
+
// abbreviation or a version ("v1.2", "core.mjs") stays a legal term.
|
|
38
|
+
const SPANS_A_SENTENCE_BOUNDARY_RE = /[.!?]\s+\w/;
|
|
39
|
+
|
|
34
40
|
function attrValue(ind, prop) {
|
|
35
41
|
const a = (ind?.attributes || []).find((x) => x?.prop === prop);
|
|
36
42
|
return a ? String(a.value ?? "") : undefined;
|
|
@@ -54,6 +60,12 @@ function checkFact(ind, violations) {
|
|
|
54
60
|
for (const prop of ["rdf:subject", "rdf:predicate", "rdf:object"]) {
|
|
55
61
|
if (!nonEmpty(attrValue(ind, prop))) violations.push(`a Fact needs a non-empty ${prop}`);
|
|
56
62
|
}
|
|
63
|
+
for (const prop of ["rdf:subject", "rdf:object"]) {
|
|
64
|
+
const term = attrValue(ind, prop);
|
|
65
|
+
if (term !== undefined && SPANS_A_SENTENCE_BOUNDARY_RE.test(term)) {
|
|
66
|
+
violations.push(`a Fact's ${prop} must be a single term, not text spanning a sentence boundary (got ${JSON.stringify(term)})`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
57
69
|
const prov = attrValue(ind, "mgx:factProvenance");
|
|
58
70
|
if (prov !== undefined && !nonEmpty(prov)) violations.push("mgx:factProvenance, when present, must be non-empty");
|
|
59
71
|
}
|
|
@@ -20,6 +20,7 @@ import {
|
|
|
20
20
|
impactClosure,
|
|
21
21
|
scoreSymbolsRanked,
|
|
22
22
|
searchModulesRanked,
|
|
23
|
+
compileNameFilter,
|
|
23
24
|
SEARCH_LIMIT,
|
|
24
25
|
contextPlan,
|
|
25
26
|
sizeBundle,
|
|
@@ -383,14 +384,20 @@ export function createGraphService(graph, { sourceAccess = false, repoRoot = nul
|
|
|
383
384
|
const k = String(kind || "").trim().toLowerCase();
|
|
384
385
|
const nm = String(name || "").trim();
|
|
385
386
|
const dec = String(decorator || "").trim().toLowerCase();
|
|
386
|
-
let nameRe = null;
|
|
387
|
-
if (nm) {
|
|
388
|
-
try { nameRe = new RegExp(nm, "i"); } catch { nameRe = null; }
|
|
389
|
-
}
|
|
390
387
|
let rankedInds; // Individual[], highest-ranked first
|
|
391
388
|
if (k && k !== "module") {
|
|
389
|
+
let nameRe = null;
|
|
390
|
+
if (nm) {
|
|
391
|
+
const compiled = compileNameFilter(nm);
|
|
392
|
+
if (compiled.error) return miss(MISS_REASONS.UNRESOLVED_TERM, { term: nm, detail: compiled.error });
|
|
393
|
+
nameRe = compiled;
|
|
394
|
+
}
|
|
392
395
|
const tokens = rawQuery.toLowerCase().split(/[^a-z0-9_]+/).filter(Boolean);
|
|
393
|
-
|
|
396
|
+
try {
|
|
397
|
+
rankedInds = scoreSymbolsRanked(graph, tokens, { kind: k, decFilter: dec, nameRe }).map((s) => s.ind);
|
|
398
|
+
} catch (e) {
|
|
399
|
+
return miss(MISS_REASONS.UNRESOLVED_TERM, { term: nm, detail: String(e?.message || e) });
|
|
400
|
+
}
|
|
394
401
|
} else {
|
|
395
402
|
// label -> individual, scoped to this call: maps searchModulesRanked's path labels
|
|
396
403
|
// back to real Individuals.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Enumerate the repo's git-tracked files, optionally filtered by a git
|
|
2
|
+
// pathspec, via `git ls-files -z`. Node builtins + git only, no bare specifier:
|
|
3
|
+
// the pii and links CI jobs import this and run without npm ci.
|
|
4
|
+
|
|
5
|
+
import { execFileSync } from "node:child_process";
|
|
6
|
+
import { dirname, join } from "node:path";
|
|
7
|
+
import { fileURLToPath } from "node:url";
|
|
8
|
+
|
|
9
|
+
const REPO_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
10
|
+
|
|
11
|
+
export function trackedFiles(pattern) {
|
|
12
|
+
const args = ["ls-files", "-z"];
|
|
13
|
+
if (pattern) args.push(pattern);
|
|
14
|
+
return execFileSync("git", args, { cwd: REPO_ROOT, encoding: "utf8", maxBuffer: 64 * 1024 * 1024 })
|
|
15
|
+
.split("\0")
|
|
16
|
+
.filter(Boolean);
|
|
17
|
+
}
|
package/src/domain/ask-vocab.mjs
CHANGED
|
@@ -593,6 +593,8 @@ export const CASCADE_NOISE = Object.freeze([
|
|
|
593
593
|
"the", "a", "an", "some", "other", "about",
|
|
594
594
|
"please", "pls", "plz", "kindly", "just", "simply", "maybe", "perhaps",
|
|
595
595
|
"thanks", "thank", "ta", "cheers",
|
|
596
|
+
"too", "also", "well", "either",
|
|
597
|
+
"sorry", "oops", "actually", "mean", "meant", "rather",
|
|
596
598
|
"hi", "hello", "hey", "yo", "hiya", "howdy", "ok", "okay",
|
|
597
599
|
"matey", "mate", "buddy", "pal", "dude", "man", "bro", "bru", "fam",
|
|
598
600
|
"friend", "sir", "maam", "folks", "guys", "everyone", "dear",
|