1 line
10 KiB
JSON
1 line
10 KiB
JSON
{"repo": "arthur0n/xenodot-twin", "files": {"plugin/skills/twin-analyze/SKILL.md": {"376978f54277a88549133bcab36dc28eba223493": {"t": 1785596446.7148604, "n": 9785, "h": "dfa730c97b9416c6", "c": "---\nname: twin-analyze\nagents: [orchestrator]\ndomain: twin\ndescription: >-\n The multi-model analysis seam — pack a twin recording (+ binding map + property sidecar) into one\n deterministic bundle and have a NON-Anthropic model narrate a window of it, back as an advisory\n Markdown report the FRAMEWORK writes (never the worker). Two contracts, not a worker: bundle in\n (tools/analyze/bundle.js), report out (reports/analysis/<date>-<task>.md); the worker is a swappable\n adapter (openai-compatible endpoint, Hermes, or a human pasting the bundle into any chat UI). Use it\n to summarize a day of tag history, narrate anomalies in a recording, or draft an inspection report\n from binding data — advisory only, provider+model always named. This skill is the OPERATOR MANUAL:\n when to run `npm run analyze`, how to configure a worker, and the guardrails. NOT a build/verify gate\n (analysis gates nothing and writes no project files), NOT anomaly DETECTION (v1 narrates the\n deterministic stats block), and NOT for wiring a live source (that is twin-bind-data).\n---\n\n# Twin analyze (recording → bundle → other-model narration → advisory report)\n\nClaude Code stays the build-and-verify system; **other models plug in here for analysis**. The seam is\nthe two contracts — a deterministic **bundle in**, an advisory **report out** — so the worker is\nswappable: an OpenAI-compatible endpoint, a Hermes run, or a human pasting the bundle into any chat UI.\n\n```\nrecording.ndjson ─┐\nbinding_map.json ─┼─▶ bundle.js ─▶ bundle.json ─▶ [worker: other model] ─▶ framework writes ─▶\nduplex_props.json ┘ (data-in) ≤ ~100 KB narration only reports/analysis/<date>-<task>.md\n```\n\nThe worker returns **only a body string**; the framework wraps it in provenance frontmatter (task,\nworker, provider/model, bundle sha256, window) and drops it under `reports/analysis/` — nowhere else.\n\n## When to run it\n\n- Summarize a recorded window of twin telemetry — a day of tag history, a captured incident window.\n- Narrate the movements/excursions/drops the stats block surfaces, in plain language that names the\n bound element (\"the boiler wall\", not \"tag boiler.temp\").\n- Draft an inspection-report skeleton from binding data for a human to finish.\n- To get a bundle you can paste into ANY chat UI by hand — the worker is optional; the contract is the\n product. Build just the bundle with `node tools/analyze/bundle.js … --out bundle.json`.\n\n## When NOT to reach for it\n\n- You want a build/verify gate — analysis is **not** a gate. It writes no project files, moves no\n scene, and `verify_twin.sh` never runs it. It is advisory input, full stop.\n- You need anomaly DETECTION — v1 narrates the deterministic stats block (min/max/mean/stddev,\n seq-gaps, range-crossings, max-step-delta). Fancier detection enters only as more pure functions in\n `tools/analyze/stats.js` with the same exact-expectation tests, never as worker cleverness.\n- You are wiring a live source or authoring a binding map — that is `twin-bind-data`, not this skill.\n- You want to close a loop on plant data — never. A report proposes nothing that acts (guardrail 2).\n\n## The three-task menu\n\n`--task <one of>` selects a reviewed prompt template (`skills/twin-analyze/tasks/*.md`); each states the\nrole, embeds the honesty rules, and defines the report structure. The bundle JSON is appended verbatim.\n\n| Task | What it produces |\n| ------------------- | ------------------------------------------------------------------------------------ |\n| `summarize-window` | A concise, faithful summary of one window: overview, per-tag summary, notable moves. |\n| `narrate-anomalies` | Plain-language narration of the seq-gaps, range-crossings, and largest step-deltas. |\n| `inspection-report` | An inspection-report skeleton from the binding context, for a human to complete. |\n\n## The command\n\n```\nnpm run analyze -- --task <summarize-window|narrate-anomalies|inspection-report> \\\n (--bundle bundle.json | --recording rec.ndjson [--map M] [--sidecar S] \\\n [--from-ms A --to-ms B] [--tags t1,t2] [--points-per-tag N] [--allow-oversize])\n```\n\n- **`--bundle`** — analyze a bundle you already packed (`node tools/analyze/bundle.js …`).\n- **`--recording`** (+ optional `--map`/`--sidecar`/window/tags) — inline-build the bundle through the\n same packager, then analyze it. The size budget (~100 KB, `SIZE_BUDGET_BYTES`) is enforced here too:\n over budget it FAILs with the levers (narrow the window, select `--tags`, lower `--points-per-tag`)\n unless you pass `--allow-oversize` (loud).\n- The report lands in `<project>/reports/analysis/<date>-<task>.md`. Project root resolves from\n `GAME_DIR` → `.xenodot.json` `projectDir` → the default sibling (NOT an argv scan — `--task`'s value\n would be misread as a path).\n\n### From a Hive session (the `mcp__ui__analyze` tool)\n\n`npm run analyze` is **canonical**; the same seam is also reachable from a running session as the\n`mcp__ui__analyze` tool (same shared dispatch core, so the two never drift). Its args mirror the CLI:\n`task` (the whitelist enum), and EITHER `bundle` (a path) OR `recording` (+ optional\n`map`/`sidecar`/`fromMs`/`toMs`/`tags`/`pointsPerTag`/`allowOversize`). It **returns an advisory\nsummary + the report path** — never the raw worker body, and it applies nothing. An unconfigured\nworker is the same graceful no-op message. Prefer the CLI for scripted/batch runs; the tool is for\nnarrating a window from inside the session.\n\nConsent + confinement (accurate statement): an **interactive** session gates each dispatch per call\n(a real model call + a file write — allow/deny in the web UI), but **autonomous / all-policy\nsessions auto-allow it**. That is WHY the tool **confines every model-supplied file path to the\nproject root** (realpath-checked, symlink-safe — out-of-tree paths are refused with a graceful\nmessage): the confinement is the load-bearing control, the per-call gate is defense-in-depth.\nResidual, stated honestly: in an unattended session the worst case is dispatching **in-project**\nfiles the session could already read to the configured worker endpoint — nothing outside the\nproject can reach it. The operator-run CLI keeps absolute paths (a human typed them).\n\n## Configure a worker (quickstart)\n\nThe default worker is **`openai-compatible`** — one `/v1/chat/completions` POST that covers OpenRouter,\nlocal llama.cpp/Ollama, vLLM, and most hosted \"other models\". Configure it in `.xenodot.json` (the file\nis gitignored — the key lives only here or in env):\n\n```json\n{\n \"analysis\": {\n \"worker\": \"openai-compatible\",\n \"apiUrl\": \"https://openrouter.ai/api\",\n \"apiKey\": \"sk-…\",\n \"model\": \"meta-llama/llama-3.1-70b-instruct\"\n }\n}\n```\n\nEnv overrides mirror the Hermes pattern (win over the saved block, no restart):\n`ANALYSIS_WORKER`, `ANALYSIS_API_URL`, `ANALYSIS_API_KEY`, `ANALYSIS_MODEL`. A bare local endpoint needs\nno key (`apiUrl: \"http://localhost:11434\"` for Ollama — the Bearer header is omitted when no key is set).\n\nThe **`hermes`** worker reuses the existing Hermes runs-API bridge and reads its connection from the\n`hermes` config block (not `analysis`) — set `analysis.worker` (or `ANALYSIS_WORKER`) to `hermes` and\nHermes' own provider/model/billing apply. See `HERMES.md`.\n\nProbe before you spend: `npm run analysis:check` confirms the selected worker is configured and the\nendpoint answers (`GET /v1/models` — no model run, no charge), printing a one-line verdict.\n\n## Graceful absence (guardrail 3)\n\nAn **unconfigured** worker is a no-op with a clear message pointing at setup — never a crash, never a\nhalf-written report:\n\n```\nanalyze: worker 'openai-compatible' is not configured — analysis.apiUrl is not set — point it at an\nOpenAI-compatible endpoint (OpenRouter, Ollama, vLLM, …) in .xenodot.json `analysis` block or the\nANALYSIS_API_URL env var.\nNo report was written. Configure the worker (see above), then re-run.\n```\n\nExit is nonzero (the job could not be produced, same precedent as `npm run hermes:check`). The framework\nbehaves identically to before otherwise — analysis is purely additive.\n\n## Guardrails (inherited verbatim from the Hermes acceptance list)\n\n1. Analysis workers never write project files; the framework writes only `reports/analysis/`.\n2. Output is advisory input for humans/gated agents, never an action.\n3. Unconfigured → no-op with a clear message.\n4. Every report names its provider+model and its bundle hash.\n5. No machine-access of any kind on any worker path.\n\nThese are not aspirations — they are enforced: the adapter module carries no `node:fs`/`node:child_process`\n(guardrails 1 + 5, greppable), the report writer whitelists the task before it builds a path under\n`reports/analysis/` and nowhere else (guardrails 1 + 4), and `configured()` returns a reason string rather\nthan throwing (guardrail 3).\n\n## Honesty rules (summary — the templates carry the verbatim text)\n\nEvery task template embeds the standing wording rules; do NOT restate or edit them here. In short: narrate\nthe **data**, never claim a physics simulation/solver/live sensor unless the bundle says so; every cited\nnumber must come from the bundle's `stats`/`series`; name elements from `bindings`, not raw tags; propose\nno action. The full text and the frontmatter contract are in\n[`references/report-format.md`](references/report-format.md); the bundle shape and its determinism/size\nguarantees are in [`references/bundle-schema.md`](references/bundle-schema.md).\n\n## RTK note\n\nPrefix shell commands with `rtk` as usual. `npm run analyze` / `npm run analysis:check` and the bare-node\n`tools/analyze/bundle.js` run without an rtk filter (passthrough). Never reference rtk inside the tool\nsources.\n"}}}} |