zam-core 0.9.2 → 0.9.3

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/dist/index.d.ts CHANGED
@@ -1479,6 +1479,9 @@ declare function cascadeBlock(db: Database, userId: string, tokenSlug: string):
1479
1479
  * If a blocked card has no prerequisites at all, it is unblocked immediately
1480
1480
  * (it was likely blocked in error or its prerequisites were removed).
1481
1481
  *
1482
+ * Unblocking cascades: when unblocking a card satisfies the last unmet
1483
+ * prerequisite of another blocked card, that card unblocks in the same call.
1484
+ *
1482
1485
  * @param db - Database connection
1483
1486
  * @param userId - The user whose blocked cards to check
1484
1487
  * @returns List of cards that were unblocked
package/dist/index.js CHANGED
@@ -1705,6 +1705,18 @@ async function getTokenBySlug(db, slug) {
1705
1705
  parseTokenFallback(token);
1706
1706
  return token;
1707
1707
  }
1708
+ async function getTokensBySlugs(db, slugs) {
1709
+ const tokens = /* @__PURE__ */ new Map();
1710
+ const unique = [...new Set(slugs)];
1711
+ if (unique.length === 0) return tokens;
1712
+ const placeholders = unique.map(() => "?").join(",");
1713
+ const rows = await db.prepare(`SELECT * FROM tokens WHERE slug IN (${placeholders})`).all(...unique);
1714
+ for (const token of rows) {
1715
+ parseTokenFallback(token);
1716
+ tokens.set(token.slug, token);
1717
+ }
1718
+ return tokens;
1719
+ }
1708
1720
  async function getTokenById(db, id) {
1709
1721
  const token = await db.prepare("SELECT * FROM tokens WHERE id = ?").get(id);
1710
1722
  parseTokenFallback(token);
@@ -3769,25 +3781,29 @@ async function cascadeBlock(db, userId, tokenSlug) {
3769
3781
  };
3770
3782
  }
3771
3783
  async function unblockReady(db, userId) {
3772
- const blockedCards = await db.prepare(
3773
- `SELECT c.token_id, t.slug, t.concept
3774
- FROM cards c
3775
- JOIN tokens t ON t.id = c.token_id
3776
- WHERE c.user_id = ? AND c.blocked = 1`
3777
- ).all(userId);
3778
3784
  const unblocked = [];
3779
- for (const card of blockedCards) {
3780
- const totalPrereqs = await db.prepare("SELECT COUNT(*) as n FROM prerequisites WHERE token_id = ?").get(card.token_id);
3781
- const metPrereqs = await db.prepare(
3782
- `SELECT COUNT(*) as n FROM cards c
3783
- JOIN prerequisites p ON p.requires_id = c.token_id
3784
- WHERE p.token_id = ? AND c.user_id = ? AND c.reps >= 1 AND c.blocked = 0`
3785
- ).get(card.token_id, userId);
3786
- if (totalPrereqs.n === 0 || metPrereqs.n === totalPrereqs.n) {
3787
- const now = (/* @__PURE__ */ new Date()).toISOString();
3788
- await db.prepare(
3789
- "UPDATE cards SET blocked = 0, due_at = ? WHERE token_id = ? AND user_id = ?"
3790
- ).run(now, card.token_id, userId);
3785
+ for (; ; ) {
3786
+ const readyCards = await db.prepare(
3787
+ `SELECT c.token_id, t.slug, t.concept
3788
+ FROM cards c
3789
+ JOIN tokens t ON t.id = c.token_id
3790
+ WHERE c.user_id = ? AND c.blocked = 1
3791
+ AND (SELECT COUNT(*) FROM prerequisites p
3792
+ WHERE p.token_id = c.token_id) =
3793
+ (SELECT COUNT(*) FROM prerequisites p
3794
+ JOIN cards pc ON pc.token_id = p.requires_id
3795
+ AND pc.user_id = c.user_id
3796
+ WHERE p.token_id = c.token_id
3797
+ AND pc.reps >= 1 AND pc.blocked = 0)`
3798
+ ).all(userId);
3799
+ if (readyCards.length === 0) break;
3800
+ const now = (/* @__PURE__ */ new Date()).toISOString();
3801
+ const placeholders = readyCards.map(() => "?").join(",");
3802
+ await db.prepare(
3803
+ `UPDATE cards SET blocked = 0, due_at = ?
3804
+ WHERE user_id = ? AND token_id IN (${placeholders})`
3805
+ ).run(now, userId, ...readyCards.map((card) => card.token_id));
3806
+ for (const card of readyCards) {
3791
3807
  unblocked.push({ slug: card.slug, concept: card.concept });
3792
3808
  }
3793
3809
  }
@@ -3949,10 +3965,14 @@ async function prepareSessionSynthesis(db, input) {
3949
3965
  await buildSkillPatterns(db),
3950
3966
  input.explicitPatterns ?? []
3951
3967
  );
3968
+ const patternTokens = await getTokensBySlugs(
3969
+ db,
3970
+ patterns.map((pattern) => pattern.slug)
3971
+ );
3952
3972
  const validPatterns = [];
3953
3973
  const tokens = /* @__PURE__ */ new Map();
3954
3974
  for (const pattern of patterns) {
3955
- const token = await getTokenBySlug(db, pattern.slug);
3975
+ const token = patternTokens.get(pattern.slug);
3956
3976
  if (!token || token.deprecated_at) continue;
3957
3977
  validPatterns.push(pattern);
3958
3978
  tokens.set(pattern.slug, token);
@@ -3965,13 +3985,13 @@ async function prepareSessionSynthesis(db, input) {
3965
3985
  const minConfidence = input.minConfidence ?? "medium";
3966
3986
  if (session.execution_context === "ui") {
3967
3987
  const reports = readUiObservationLog(input.sessionId);
3968
- for (const report of reports) {
3969
- for (const candidate of report.candidateTokens) {
3970
- if (tokens.has(candidate.slug)) continue;
3971
- const token = await getTokenBySlug(db, candidate.slug);
3972
- if (token && !token.deprecated_at) {
3973
- tokens.set(candidate.slug, token);
3974
- }
3988
+ const candidateTokens = await getTokensBySlugs(
3989
+ db,
3990
+ reports.flatMap((report) => report.candidateTokens).map((candidate) => candidate.slug).filter((slug) => !tokens.has(slug))
3991
+ );
3992
+ for (const [slug, token] of candidateTokens) {
3993
+ if (!token.deprecated_at) {
3994
+ tokens.set(slug, token);
3975
3995
  }
3976
3996
  }
3977
3997
  const { candidates: candidates2, skippedLowConfidence: skippedLowConfidence2 } = buildUiSynthesisCandidates(