Files
pepa-pi-bot/tui/format.test.js
T
mayatnikovandClaude Opus 4.8 cd63edfeaf feat(v0.4.1): make the Settlement Contract honest end-to-end
Patches each link of the closed-loop chain (world-truth -> invariant ->
dispatch -> worldDelta -> milestone) so the contract can no longer advance
on a lie or stall on an unreachable milestone, and surfaces the result on
the monitor.

- Unblock M6_lighting: add gather.coal producer (mirrors gather.stone,
  strict expectGain), register it, add the skill-graph node, and make
  curriculum.shelter.torch two-step (gather coal -> craft torch). craft.torch
  needed coal but no skill produced it, so the contract pinned on M6 forever
  and starved M7/M8/M9. One curriculum edit fixes both rails.
- M8 no longer falsely satisfied: build-shelter records locations.shelter
  only on real progress (setLocation moved below the no_progress guard).
- M2_bed made monotonic: sleepInBed writes locations.bed when the bot places
  its own bed, so M2 stays met after the carried bed item is consumed.
- Observability: new ink-free tui/format.js helper renders per-tick
  state / firing rail / active skill / blocked prerequisite / stall reason
  in the monitor header (fields bot.js already computes but never showed).
- Harden the valid-skill-id guardrail: skillRegistryPrompt preserves the
  "NEVER invent" footer when truncating (a growing registry was dropping it),
  and fix the stale "tech-tree > autonomous" reflex chain in the escalation
  prompt.

Tests: 494/494 passing; new build-shelter / invariants / format suites plus
skill-graph, curriculum, skill-registry cases. lint-patch clean. Deferred
audit findings recorded in plans/v0.4.0-vnext.md (local).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-29 12:33:45 +03:00

68 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Pure formatting helpers for the monitor header. These assemble the per-tick
// "what / why / blocked / stalled" line from snapshot fields the bot already
// broadcasts, so the operator can see why the bot is (not) acting. Tested off
// ink — no React/render involved.
import { test } from "node:test";
import assert from "node:assert/strict";
import { formatStatusSegments, statusText, formatBlockedBy, stateColor } from "./format.js";
test("statusText surfaces state, firing rail, active skill and stall reason", () => {
const txt = statusText({
runtimeState: "idle",
lastReflex: { name: "curriculum" },
activeSkill: "gather.logs",
noProgressReason: "waiting_for_day",
});
assert.match(txt, /idle/);
assert.match(txt, /curriculum/);
assert.match(txt, /gather\.logs/);
assert.match(txt, /waiting_for_day/);
});
test("reflexPaused renders a PAUSED marker", () => {
assert.match(statusText({ runtimeState: "working", reflexPaused: true }), /PAUSED/);
assert.doesNotMatch(statusText({ runtimeState: "working", reflexPaused: false }), /PAUSED/);
});
test("blocked prerequisite is shown by name, never as [object Object]", () => {
const snap = {
runtimeState: "working",
contract: { suggestedSkill: { skillId: "craft.torch", blockedBy: [{ item: "coal", min: 1, have: 0 }] } },
};
const txt = statusText(snap);
assert.match(txt, /craft\.torch/);
assert.match(txt, /coal/);
assert.doesNotMatch(txt, /\[object Object\]/);
});
test("urgent contract reason (food preemption) is surfaced", () => {
const txt = statusText({
runtimeState: "working",
contract: { reason: "urgent:M4_food_security(100) preempts M1_wood_tools" },
});
assert.match(txt, /urgent/);
});
test("formatBlockedBy maps {item,min} and {tool} objects, null when empty", () => {
assert.equal(formatBlockedBy([]), null);
assert.equal(formatBlockedBy(undefined), null);
assert.equal(formatBlockedBy([{ item: "planks", min: 8, have: 0 }]), "planks×8");
assert.equal(formatBlockedBy([{ tool: "pickaxe" }]), "pickaxe");
assert.equal(formatBlockedBy([{ item: "coal", min: 1 }, { item: "stick", min: 1 }]), "coal+stick");
});
test("stateColor maps known states and defaults unknown ones to a visible colour", () => {
assert.equal(stateColor("emergency"), "red");
assert.equal(stateColor("idle"), "gray");
assert.equal(stateColor("planning"), "magenta");
// unknown / future states render in white rather than being blanked out
assert.equal(stateColor("teleporting"), "white");
assert.equal(stateColor(undefined), "gray");
});
test("empty snapshot yields no segments (header row collapses)", () => {
assert.equal(formatStatusSegments({}).length, 0);
});