zam-core 0.7.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agent/skills/zam/SKILL.md +12 -0
- package/.agents/skills/zam/SKILL.md +12 -0
- package/.claude/skills/zam/SKILL.md +12 -0
- package/dist/cli/index.js +230 -6
- package/dist/cli/index.js.map +1 -1
- package/dist/index.d.ts +41 -2
- package/dist/index.js +70 -3
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
|
@@ -147,6 +147,12 @@ zam token find --query "<keywords>"
|
|
|
147
147
|
```
|
|
148
148
|
Only register genuinely new concepts. Reuse existing slugs where the concept matches. Note that `zam token find` matches paraphrases semantically.
|
|
149
149
|
|
|
150
|
+
After deduplication and before registering a new token, check for existing semantically related tokens that could be prerequisites ("foundations"):
|
|
151
|
+
```bash
|
|
152
|
+
echo '{"concept":"<concept>","question":"<question>","domain":"<domain>"}' | zam bridge suggest-foundations
|
|
153
|
+
```
|
|
154
|
+
Present non-flagged suggestions to the user ("Related existing concept X — link it as a foundation?"); on approval link via the existing prereq path after registering.
|
|
155
|
+
|
|
150
156
|
**Register tokens and prerequisites:**
|
|
151
157
|
|
|
152
158
|
As the frontier model, YOU author both the concept and the recall question. The
|
|
@@ -371,6 +377,12 @@ A token should be decomposed when ALL of the following hold:
|
|
|
371
377
|
After a rating of 1, pause. Do not just re-ask the same question or move on.
|
|
372
378
|
Ask yourself: **"What would the user have needed to know to answer this?"**
|
|
373
379
|
|
|
380
|
+
Always check for existing related foundation tokens first:
|
|
381
|
+
```bash
|
|
382
|
+
echo '{"slug":"<failed-token-slug>"}' | zam bridge suggest-foundations
|
|
383
|
+
```
|
|
384
|
+
Offer existing tokens returned as suggestions to the user first (linking them feeds the existing `confirmFoundations` path with `exists: true` and `slug`). Only generate NEW foundation proposals via the LLM for gaps that the suggestions do not cover.
|
|
385
|
+
|
|
374
386
|
| Symptom | Missing foundation | Create Bloom 1-2 token for |
|
|
375
387
|
|---------|-------------------|---------------------------|
|
|
376
388
|
| Couldn't name key terms | Factual recall | Definitions, terminology |
|
|
@@ -178,6 +178,12 @@ zam token find --query "<keywords>"
|
|
|
178
178
|
```
|
|
179
179
|
Only register genuinely new concepts. Reuse existing slugs where the concept matches. Note that `zam token find` matches paraphrases semantically.
|
|
180
180
|
|
|
181
|
+
After deduplication and before registering a new token, check for existing semantically related tokens that could be prerequisites ("foundations"):
|
|
182
|
+
```bash
|
|
183
|
+
echo '{"concept":"<concept>","question":"<question>","domain":"<domain>"}' | zam bridge suggest-foundations
|
|
184
|
+
```
|
|
185
|
+
Present non-flagged suggestions to the user ("Related existing concept X — link it as a foundation?"); on approval link via the existing prereq path after registering.
|
|
186
|
+
|
|
181
187
|
**Register tokens and prerequisites:**
|
|
182
188
|
|
|
183
189
|
As the frontier model, author both the concept and the recall question. The
|
|
@@ -467,6 +473,12 @@ A token should be decomposed when ALL of the following hold:
|
|
|
467
473
|
After a rating of 1, pause. Do not just re-ask the same question or move on.
|
|
468
474
|
Ask yourself: **"What would the user have needed to know to answer this?"**
|
|
469
475
|
|
|
476
|
+
Always check for existing related foundation tokens first:
|
|
477
|
+
```bash
|
|
478
|
+
echo '{"slug":"<failed-token-slug>"}' | zam bridge suggest-foundations
|
|
479
|
+
```
|
|
480
|
+
Offer existing tokens returned as suggestions to the user first (linking them feeds the existing `confirmFoundations` path with `exists: true` and `slug`). Only generate NEW foundation proposals via the LLM for gaps that the suggestions do not cover.
|
|
481
|
+
|
|
470
482
|
| Symptom | Missing foundation | Create Bloom 1-2 token for |
|
|
471
483
|
|---------|-------------------|---------------------------|
|
|
472
484
|
| Couldn't name key terms | Factual recall | Definitions, terminology |
|
|
@@ -147,6 +147,12 @@ zam token find --query "<keywords>"
|
|
|
147
147
|
```
|
|
148
148
|
Only register genuinely new concepts. Reuse existing slugs where the concept matches. Note that `zam token find` matches paraphrases semantically.
|
|
149
149
|
|
|
150
|
+
After deduplication and before registering a new token, check for existing semantically related tokens that could be prerequisites ("foundations"):
|
|
151
|
+
```bash
|
|
152
|
+
echo '{"concept":"<concept>","question":"<question>","domain":"<domain>"}' | zam bridge suggest-foundations
|
|
153
|
+
```
|
|
154
|
+
Present non-flagged suggestions to the user ("Related existing concept X — link it as a foundation?"); on approval link via the existing prereq path after registering.
|
|
155
|
+
|
|
150
156
|
**Register tokens and prerequisites:**
|
|
151
157
|
|
|
152
158
|
As the frontier model, YOU author both the concept and the recall question. The
|
|
@@ -371,6 +377,12 @@ A token should be decomposed when ALL of the following hold:
|
|
|
371
377
|
After a rating of 1, pause. Do not just re-ask the same question or move on.
|
|
372
378
|
Ask yourself: **"What would the user have needed to know to answer this?"**
|
|
373
379
|
|
|
380
|
+
Always check for existing related foundation tokens first:
|
|
381
|
+
```bash
|
|
382
|
+
echo '{"slug":"<failed-token-slug>"}' | zam bridge suggest-foundations
|
|
383
|
+
```
|
|
384
|
+
Offer existing tokens returned as suggestions to the user first (linking them feeds the existing `confirmFoundations` path with `exists: true` and `slug`). Only generate NEW foundation proposals via the LLM for gaps that the suggestions do not cover.
|
|
385
|
+
|
|
374
386
|
| Symptom | Missing foundation | Create Bloom 1-2 token for |
|
|
375
387
|
|---------|-------------------|---------------------------|
|
|
376
388
|
| Couldn't name key terms | Factual recall | Definitions, terminology |
|
package/dist/cli/index.js
CHANGED
|
@@ -2219,9 +2219,9 @@ async function buildAncestorMap(db) {
|
|
|
2219
2219
|
}
|
|
2220
2220
|
return map;
|
|
2221
2221
|
}
|
|
2222
|
-
async function wouldCreateCycle(db, tokenId, requiresId) {
|
|
2222
|
+
async function wouldCreateCycle(db, tokenId, requiresId, ancestors) {
|
|
2223
2223
|
if (tokenId === requiresId) return true;
|
|
2224
|
-
const
|
|
2224
|
+
const map = ancestors ?? await buildAncestorMap(db);
|
|
2225
2225
|
const visited = /* @__PURE__ */ new Set();
|
|
2226
2226
|
const queue = [requiresId];
|
|
2227
2227
|
while (queue.length > 0) {
|
|
@@ -2229,7 +2229,7 @@ async function wouldCreateCycle(db, tokenId, requiresId) {
|
|
|
2229
2229
|
if (current === tokenId) return true;
|
|
2230
2230
|
if (visited.has(current)) continue;
|
|
2231
2231
|
visited.add(current);
|
|
2232
|
-
const parents =
|
|
2232
|
+
const parents = map.get(current);
|
|
2233
2233
|
if (parents) {
|
|
2234
2234
|
for (const parent of parents) {
|
|
2235
2235
|
if (!visited.has(parent)) queue.push(parent);
|
|
@@ -4789,6 +4789,71 @@ async function searchTokensHybrid(db, query, opts) {
|
|
|
4789
4789
|
return results.slice(0, limit);
|
|
4790
4790
|
}
|
|
4791
4791
|
|
|
4792
|
+
// src/kernel/search/suggestions.ts
|
|
4793
|
+
async function suggestFoundations(db, opts) {
|
|
4794
|
+
const minSimilarity = opts.minSimilarity ?? 0.45;
|
|
4795
|
+
const maxSimilarity = opts.maxSimilarity ?? 0.85;
|
|
4796
|
+
const limit = opts.limit ?? 5;
|
|
4797
|
+
const targetBloomLevel = opts.targetBloomLevel ?? 5;
|
|
4798
|
+
if (minSimilarity >= maxSimilarity) {
|
|
4799
|
+
return [];
|
|
4800
|
+
}
|
|
4801
|
+
const embedded = await listEmbeddedTokens(db, opts.model);
|
|
4802
|
+
if (embedded.length === 0) {
|
|
4803
|
+
return [];
|
|
4804
|
+
}
|
|
4805
|
+
const queryVec = Float32Array.from(opts.queryEmbedding);
|
|
4806
|
+
const candidates = [];
|
|
4807
|
+
for (const row of embedded) {
|
|
4808
|
+
if (opts.targetTokenId && row.token.id === opts.targetTokenId) {
|
|
4809
|
+
continue;
|
|
4810
|
+
}
|
|
4811
|
+
const similarity = cosineSimilarity(queryVec, row.embedding);
|
|
4812
|
+
if (similarity >= minSimilarity && similarity < maxSimilarity) {
|
|
4813
|
+
candidates.push({ token: row.token, similarity });
|
|
4814
|
+
}
|
|
4815
|
+
}
|
|
4816
|
+
candidates.sort((a, b) => {
|
|
4817
|
+
if (Math.abs(a.similarity - b.similarity) > 1e-9) {
|
|
4818
|
+
return b.similarity - a.similarity;
|
|
4819
|
+
}
|
|
4820
|
+
return a.token.slug.localeCompare(b.token.slug);
|
|
4821
|
+
});
|
|
4822
|
+
const topCandidates = candidates.slice(0, limit);
|
|
4823
|
+
const prereqIds = /* @__PURE__ */ new Set();
|
|
4824
|
+
let ancestors;
|
|
4825
|
+
if (opts.targetTokenId) {
|
|
4826
|
+
const prereqs = await getPrerequisites(db, opts.targetTokenId);
|
|
4827
|
+
for (const p of prereqs) {
|
|
4828
|
+
prereqIds.add(p.requires_id);
|
|
4829
|
+
}
|
|
4830
|
+
ancestors = await buildAncestorMap(db);
|
|
4831
|
+
}
|
|
4832
|
+
const results = [];
|
|
4833
|
+
for (const cand of topCandidates) {
|
|
4834
|
+
let alreadyPrerequisite = false;
|
|
4835
|
+
let wouldCreateCycleFlag = false;
|
|
4836
|
+
const bloomAboveTarget = cand.token.bloom_level > targetBloomLevel;
|
|
4837
|
+
if (opts.targetTokenId) {
|
|
4838
|
+
alreadyPrerequisite = prereqIds.has(cand.token.id);
|
|
4839
|
+
wouldCreateCycleFlag = await wouldCreateCycle(
|
|
4840
|
+
db,
|
|
4841
|
+
opts.targetTokenId,
|
|
4842
|
+
cand.token.id,
|
|
4843
|
+
ancestors
|
|
4844
|
+
);
|
|
4845
|
+
}
|
|
4846
|
+
results.push({
|
|
4847
|
+
token: cand.token,
|
|
4848
|
+
similarity: cand.similarity,
|
|
4849
|
+
alreadyPrerequisite,
|
|
4850
|
+
wouldCreateCycle: wouldCreateCycleFlag,
|
|
4851
|
+
bloomAboveTarget
|
|
4852
|
+
});
|
|
4853
|
+
}
|
|
4854
|
+
return results;
|
|
4855
|
+
}
|
|
4856
|
+
|
|
4792
4857
|
// src/kernel/system/hooks.ts
|
|
4793
4858
|
import {
|
|
4794
4859
|
appendFileSync as appendFileSync3,
|
|
@@ -10559,9 +10624,7 @@ async function findPossibleDuplicates(db, candidate, embed = embedQuery) {
|
|
|
10559
10624
|
);
|
|
10560
10625
|
}
|
|
10561
10626
|
}
|
|
10562
|
-
const
|
|
10563
|
-
const parsed = thresholdStr ? Number.parseFloat(thresholdStr) : Number.NaN;
|
|
10564
|
-
const threshold = Number.isFinite(parsed) && parsed > 0 && parsed <= 1 ? parsed : 0.85;
|
|
10627
|
+
const threshold = await resolveDedupThreshold(db);
|
|
10565
10628
|
const hits = await searchTokensHybrid(db, queryText, {
|
|
10566
10629
|
queryEmbedding: q2.vector,
|
|
10567
10630
|
model: q2.model,
|
|
@@ -10580,6 +10643,16 @@ async function findPossibleDuplicates(db, candidate, embed = embedQuery) {
|
|
|
10580
10643
|
}
|
|
10581
10644
|
return results;
|
|
10582
10645
|
}
|
|
10646
|
+
async function resolveDedupThreshold(db) {
|
|
10647
|
+
const thresholdStr = await getSetting(db, "search.dedup_threshold");
|
|
10648
|
+
const parsed = thresholdStr ? Number.parseFloat(thresholdStr) : Number.NaN;
|
|
10649
|
+
return Number.isFinite(parsed) && parsed > 0 && parsed <= 1 ? parsed : 0.85;
|
|
10650
|
+
}
|
|
10651
|
+
async function resolveSuggestMinSimilarity(db) {
|
|
10652
|
+
const minStr = await getSetting(db, "search.suggest_min_similarity");
|
|
10653
|
+
const parsed = minStr ? Number.parseFloat(minStr) : Number.NaN;
|
|
10654
|
+
return Number.isFinite(parsed) && parsed > 0 && parsed <= 1 ? parsed : 0.45;
|
|
10655
|
+
}
|
|
10583
10656
|
|
|
10584
10657
|
// src/cli/llm/vision.ts
|
|
10585
10658
|
import { randomBytes } from "crypto";
|
|
@@ -12428,6 +12501,117 @@ bridgeCommand.command("relevant-tokens").description("Find tokens relevant to a
|
|
|
12428
12501
|
});
|
|
12429
12502
|
});
|
|
12430
12503
|
});
|
|
12504
|
+
bridgeCommand.command("suggest-foundations").description("Propose existing tokens as foundation/prerequisite candidates").option("--user <id>", "User ID (default: whoami)").action(async (_opts) => {
|
|
12505
|
+
await withDb2(async (db) => {
|
|
12506
|
+
let raw;
|
|
12507
|
+
if (isServeMode) {
|
|
12508
|
+
raw = serveStdinPayload ?? "";
|
|
12509
|
+
} else {
|
|
12510
|
+
const chunks = [];
|
|
12511
|
+
for await (const chunk of process.stdin) {
|
|
12512
|
+
chunks.push(chunk);
|
|
12513
|
+
}
|
|
12514
|
+
raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
12515
|
+
}
|
|
12516
|
+
if (!raw) {
|
|
12517
|
+
jsonError("No input received on stdin. Pipe JSON.");
|
|
12518
|
+
}
|
|
12519
|
+
let data;
|
|
12520
|
+
try {
|
|
12521
|
+
data = JSON.parse(raw);
|
|
12522
|
+
} catch {
|
|
12523
|
+
jsonError("Invalid JSON input");
|
|
12524
|
+
}
|
|
12525
|
+
let queryText = "";
|
|
12526
|
+
let targetTokenId;
|
|
12527
|
+
let targetBloomLevel;
|
|
12528
|
+
let targetJson = null;
|
|
12529
|
+
if (data?.slug !== void 0) {
|
|
12530
|
+
if (typeof data.slug !== "string" || data.slug.trim() === "") {
|
|
12531
|
+
jsonError("Invalid slug");
|
|
12532
|
+
}
|
|
12533
|
+
const token = await getTokenBySlug(db, data.slug);
|
|
12534
|
+
if (!token) {
|
|
12535
|
+
jsonError(`Token not found: ${data.slug}`);
|
|
12536
|
+
}
|
|
12537
|
+
queryText = embeddingContentForToken(token);
|
|
12538
|
+
targetTokenId = token.id;
|
|
12539
|
+
targetBloomLevel = token.bloom_level;
|
|
12540
|
+
targetJson = { slug: token.slug };
|
|
12541
|
+
} else {
|
|
12542
|
+
if (!data?.concept || typeof data.concept !== "string" || data.concept.trim() === "") {
|
|
12543
|
+
jsonError("JSON must include a non-empty 'slug' or 'concept' field");
|
|
12544
|
+
}
|
|
12545
|
+
queryText = embeddingContentForToken({
|
|
12546
|
+
concept: data.concept,
|
|
12547
|
+
question: typeof data.question === "string" ? data.question : null,
|
|
12548
|
+
domain: typeof data.domain === "string" ? data.domain : ""
|
|
12549
|
+
});
|
|
12550
|
+
if (data.bloom_level !== void 0) {
|
|
12551
|
+
if (typeof data.bloom_level !== "number" || !Number.isInteger(data.bloom_level) || data.bloom_level < 1 || data.bloom_level > 5) {
|
|
12552
|
+
jsonError("bloom_level must be an integer between 1 and 5");
|
|
12553
|
+
}
|
|
12554
|
+
targetBloomLevel = data.bloom_level;
|
|
12555
|
+
}
|
|
12556
|
+
}
|
|
12557
|
+
let limit = data?.limit ?? 5;
|
|
12558
|
+
if (typeof limit !== "number" || limit <= 0 || !Number.isInteger(limit)) {
|
|
12559
|
+
limit = 5;
|
|
12560
|
+
}
|
|
12561
|
+
if (limit > 20) {
|
|
12562
|
+
limit = 20;
|
|
12563
|
+
}
|
|
12564
|
+
const q2 = await embedQuery(db, queryText);
|
|
12565
|
+
if (q2 === null) {
|
|
12566
|
+
jsonOut2({
|
|
12567
|
+
semantic: false,
|
|
12568
|
+
target: targetJson,
|
|
12569
|
+
suggestions: []
|
|
12570
|
+
});
|
|
12571
|
+
return;
|
|
12572
|
+
}
|
|
12573
|
+
try {
|
|
12574
|
+
await ensureTokenEmbeddings(db, {
|
|
12575
|
+
limit: 100,
|
|
12576
|
+
dims: q2.vector.length
|
|
12577
|
+
});
|
|
12578
|
+
} catch {
|
|
12579
|
+
}
|
|
12580
|
+
const maxSimilarity = await resolveDedupThreshold(db);
|
|
12581
|
+
const minSimilarity = await resolveSuggestMinSimilarity(db);
|
|
12582
|
+
if (minSimilarity >= maxSimilarity) {
|
|
12583
|
+
jsonOut2({
|
|
12584
|
+
semantic: true,
|
|
12585
|
+
target: targetJson,
|
|
12586
|
+
suggestions: []
|
|
12587
|
+
});
|
|
12588
|
+
return;
|
|
12589
|
+
}
|
|
12590
|
+
const suggestions = await suggestFoundations(db, {
|
|
12591
|
+
queryEmbedding: q2.vector,
|
|
12592
|
+
model: q2.model,
|
|
12593
|
+
targetTokenId,
|
|
12594
|
+
targetBloomLevel,
|
|
12595
|
+
limit,
|
|
12596
|
+
minSimilarity,
|
|
12597
|
+
maxSimilarity
|
|
12598
|
+
});
|
|
12599
|
+
jsonOut2({
|
|
12600
|
+
semantic: true,
|
|
12601
|
+
target: targetJson,
|
|
12602
|
+
suggestions: suggestions.map((s) => ({
|
|
12603
|
+
slug: s.token.slug,
|
|
12604
|
+
concept: s.token.concept,
|
|
12605
|
+
domain: s.token.domain,
|
|
12606
|
+
bloom_level: s.token.bloom_level,
|
|
12607
|
+
similarity: s.similarity,
|
|
12608
|
+
already_prerequisite: s.alreadyPrerequisite,
|
|
12609
|
+
would_create_cycle: s.wouldCreateCycle,
|
|
12610
|
+
bloom_above_target: s.bloomAboveTarget
|
|
12611
|
+
}))
|
|
12612
|
+
});
|
|
12613
|
+
});
|
|
12614
|
+
});
|
|
12431
12615
|
bridgeCommand.command("discover-skills").description(
|
|
12432
12616
|
"Analyze monitor logs across sessions to discover recurring patterns"
|
|
12433
12617
|
).option(
|
|
@@ -17452,6 +17636,46 @@ WARNING: Possible duplicate tokens found:`);
|
|
|
17452
17636
|
);
|
|
17453
17637
|
}
|
|
17454
17638
|
}
|
|
17639
|
+
try {
|
|
17640
|
+
const queryText = embeddingContentForToken({
|
|
17641
|
+
concept: opts.concept,
|
|
17642
|
+
question: question ?? null,
|
|
17643
|
+
domain: opts.domain ?? ""
|
|
17644
|
+
});
|
|
17645
|
+
const q2 = await embedQuery(db, queryText);
|
|
17646
|
+
if (q2) {
|
|
17647
|
+
const maxSimilarity = await resolveDedupThreshold(db);
|
|
17648
|
+
const minSimilarity = await resolveSuggestMinSimilarity(db);
|
|
17649
|
+
const suggestions = await suggestFoundations(db, {
|
|
17650
|
+
queryEmbedding: q2.vector,
|
|
17651
|
+
model: q2.model,
|
|
17652
|
+
targetTokenId: token.id,
|
|
17653
|
+
targetBloomLevel: Number(opts.bloom),
|
|
17654
|
+
limit: 3,
|
|
17655
|
+
minSimilarity,
|
|
17656
|
+
maxSimilarity
|
|
17657
|
+
});
|
|
17658
|
+
const filtered = suggestions.filter(
|
|
17659
|
+
(s) => !s.wouldCreateCycle && !s.alreadyPrerequisite
|
|
17660
|
+
);
|
|
17661
|
+
if (filtered.length > 0) {
|
|
17662
|
+
console.log(
|
|
17663
|
+
`
|
|
17664
|
+
Related existing tokens as potential foundations:`
|
|
17665
|
+
);
|
|
17666
|
+
for (const s of filtered) {
|
|
17667
|
+
const note = s.bloomAboveTarget ? " (higher bloom than target)" : "";
|
|
17668
|
+
console.log(
|
|
17669
|
+
` - ${s.token.slug} (similarity: ${s.similarity.toFixed(2)})${note}`
|
|
17670
|
+
);
|
|
17671
|
+
}
|
|
17672
|
+
console.log(
|
|
17673
|
+
`Link with: zam token prereq --token <slug> --requires <slug> (see zam token prereq --help)`
|
|
17674
|
+
);
|
|
17675
|
+
}
|
|
17676
|
+
}
|
|
17677
|
+
} catch {
|
|
17678
|
+
}
|
|
17455
17679
|
}
|
|
17456
17680
|
});
|
|
17457
17681
|
});
|