@polycode-projects/the-mechanical-code-talker 2.7.14 → 2.7.16

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@polycode-projects/the-mechanical-code-talker",
3
- "version": "2.7.14",
3
+ "version": "2.7.16",
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.",
@@ -780,6 +780,15 @@ async function runWorldCommand(cmd, { world, memoryDir, env, graph, cache }) {
780
780
  // the same with or without it, and the fold IS the now.
781
781
  const WORLD_WHERE_RE = /^where(?:'s|\s+is|\s+are)\s+(?:the\s+|a\s+|an\s+)?(.+?)(?:\s+now)?[?.!\s]*$/i;
782
782
 
783
+ // A mid-game "is X open/closed" aside. mgx:is-open is a datatype fact
784
+ // ("true"/"false"), so the generic ask engine's adjective/property reader —
785
+ // which looks for a CLASS/adjective membership fact, not a literal value —
786
+ // has nothing to match and answers "I don't have a fact saying X", reading
787
+ // as an epistemic gap even though the negation is fully known (the taught
788
+ // effect wrote the opposite value). This aside reads state.openness directly,
789
+ // the same fold containerStatusPhrase already reads for "look"/"examine".
790
+ const WORLD_IS_OPEN_RE = /^is\s+(?:the\s+|a\s+|an\s+)?(.+?)\s+(open|closed|shut)[?.!\s]*$/i;
791
+
783
792
  /** A locative aside about a placed world thing, answered from the SAME @turnN
784
793
  * fold every other world reader uses — never the raw base rows, whose
785
794
  * superseded placements would answer where things stood at load time. Null
@@ -823,6 +832,28 @@ async function worldWhereAnswer(line, { memoryDir }) {
823
832
  );
824
833
  }
825
834
 
835
+ /** A mid-game "is X open/closed" aside, read from state.openness — the same
836
+ * fold "look"/"examine" already phrase via containerStatusPhrase. Null when
837
+ * the named thing has no openness fact at all (never a container, or one
838
+ * never opened/closed this session), so an ordinary ask keeps its lane. */
839
+ async function worldOpennessAnswer(line, { memoryDir }) {
840
+ const m = String(line).match(WORLD_IS_OPEN_RE);
841
+ if (!m) return null;
842
+ const thing = normFactTerm(m[1]);
843
+ const askedOpen = /^open$/i.test(m[2]);
844
+ let rows;
845
+ try { rows = readFactRows(await loadMemory(memoryDir)); } catch { return null; }
846
+ const state = foldWorldState(rows);
847
+ const openness = state.openness.get(thing);
848
+ if (!openness) return null;
849
+ const matches = askedOpen ? openness.open : !openness.open;
850
+ return answer(
851
+ `${matches ? "yes" : "no"} — the ${thing} is ${openness.open ? "open" : "closed"}.`,
852
+ `ADVENTURE — is-open-aside: ${thing}'s current openness from the world fold (as of turn ${openness.turn})`,
853
+ { goal: `check whether the ${thing} is ${m[2].toLowerCase()}` },
854
+ );
855
+ }
856
+
826
857
  async function inventoryAnswer({ memoryDir, graph }) {
827
858
  const memory = await loadMemory(memoryDir);
828
859
  const rows = readFactRows(memory);
@@ -945,5 +976,7 @@ export async function adventureTurn(line, { planHolder, memoryDir, sessionId = "
945
976
  }
946
977
  const whereAside = await worldWhereAnswer(line, { memoryDir });
947
978
  if (whereAside) return whereAside;
979
+ const opennessAside = await worldOpennessAnswer(line, { memoryDir });
980
+ if (opennessAside) return opennessAside;
948
981
  return null; // a mid-game aside — the ordinary lanes answer, world untouched
949
982
  }
@@ -651,6 +651,18 @@ export async function runSpiderFlyTick(memoryDir, opts = {}) {
651
651
  }
652
652
 
653
653
  const ecology = runEcologyPass({ state, postMovePlacements, postMoveMassByFly, postMoveMassBySpider, turn: k });
654
+ // A fly eaten or starved THIS tick was already assigned a pre-ecology
655
+ // goal/position above (movement runs before the ecology pass resolves
656
+ // eating) — left as-is, the same response would both announce "fly-5 was
657
+ // eaten" and still list fly-5's stale "trapped, can't move" goal one clause
658
+ // later, as if it were still on the board. Drop it from `agents` (its own
659
+ // goal is moot) and let the eating spider's line say what actually
660
+ // happened instead of the now-false "co-located with fly-5 in the web".
661
+ for (const { fly, spider } of ecology.events.eaten) {
662
+ delete agents[fly];
663
+ if (agents[spider]) agents[spider].goal = `just ate ${fly} in the web.`;
664
+ }
665
+ for (const flyId of ecology.events.starved) delete agents[flyId];
654
666
  const writes = [...movementWrites, ...ecology.writes];
655
667
  const provenance = `${worldProvenanceTag(WORLD_NAME)}:turn${k}`;
656
668
  await appendFacts(memoryDir, writes.map((f) => ({ ...f, provenance })));