synabun 2.0.10 → 2.2.0
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/CHANGELOG.md +207 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +13 -13
- package/hooks/claude-code/pre-websearch.mjs +12 -1
- package/hooks/claude-code/shared.mjs +5 -0
- package/hooks/claude-code/stop.mjs +1 -1
- package/lib/claude-hooks.js +172 -18
- package/lib/client-config-repair.js +30 -17
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +7 -6
- package/lib/first-launch-protection.js +57 -12
- package/lib/mcp-dist.js +176 -0
- package/lib/release-npm-ci.mjs +138 -0
- package/lib/release-publish.mjs +91 -0
- package/lib/release-scan.mjs +50 -0
- package/lib/server-crash-record.js +123 -0
- package/lib/server-exit.js +318 -0
- package/lib/server-supervisor.js +508 -0
- package/lib/start-launcher.js +37 -4
- package/lib/tools-cli.js +45 -0
- package/lib/update-snapshot.js +302 -0
- package/lib/virustotal.mjs +203 -0
- package/lib/windows-crash-event.js +269 -0
- package/mcp-server/dist/http.d.ts +20 -1
- package/mcp-server/dist/http.d.ts.map +1 -1
- package/mcp-server/dist/http.js +59 -12
- package/mcp-server/dist/http.js.map +1 -1
- package/mcp-server/dist/index.d.ts +33 -27
- package/mcp-server/dist/index.d.ts.map +1 -1
- package/mcp-server/dist/index.js +128 -143
- package/mcp-server/dist/index.js.map +1 -1
- package/mcp-server/dist/preload.js +1 -1
- package/mcp-server/dist/preload.js.map +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts +1 -1
- package/mcp-server/dist/services/budget-judgments.d.ts.map +1 -1
- package/mcp-server/dist/services/budget-judgments.js +5 -3
- package/mcp-server/dist/services/budget-judgments.js.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.d.ts +6 -2
- package/mcp-server/dist/services/codex-config-heal.d.ts.map +1 -1
- package/mcp-server/dist/services/codex-config-heal.js +81 -28
- package/mcp-server/dist/services/codex-config-heal.js.map +1 -1
- package/mcp-server/dist/services/identity.d.ts +2 -2
- package/mcp-server/dist/services/identity.js +2 -2
- package/mcp-server/dist/services/memory-judge-backfill.d.ts +6 -0
- package/mcp-server/dist/services/memory-judge-backfill.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-judge-backfill.js +7 -1
- package/mcp-server/dist/services/memory-judge-backfill.js.map +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts +1 -1
- package/mcp-server/dist/services/memory-sessions.d.ts.map +1 -1
- package/mcp-server/dist/services/memory-sessions.js +28 -2
- package/mcp-server/dist/services/memory-sessions.js.map +1 -1
- package/mcp-server/dist/services/neural-interface.d.ts +18 -11
- package/mcp-server/dist/services/neural-interface.d.ts.map +1 -1
- package/mcp-server/dist/services/neural-interface.js +67 -11
- package/mcp-server/dist/services/neural-interface.js.map +1 -1
- package/mcp-server/dist/services/server-instructions.d.ts +7 -2
- package/mcp-server/dist/services/server-instructions.d.ts.map +1 -1
- package/mcp-server/dist/services/server-instructions.js +22 -63
- package/mcp-server/dist/services/server-instructions.js.map +1 -1
- package/mcp-server/dist/services/tool-catalog.d.ts +120 -0
- package/mcp-server/dist/services/tool-catalog.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-catalog.js +339 -0
- package/mcp-server/dist/services/tool-catalog.js.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts +12 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js +40 -0
- package/mcp-server/dist/services/tool-list-refresh-barrier.js.map +1 -0
- package/mcp-server/dist/services/tool-list.d.ts +108 -0
- package/mcp-server/dist/services/tool-list.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-list.js +334 -0
- package/mcp-server/dist/services/tool-list.js.map +1 -0
- package/mcp-server/dist/services/tool-runtime.d.ts +161 -0
- package/mcp-server/dist/services/tool-runtime.d.ts.map +1 -0
- package/mcp-server/dist/services/tool-runtime.js +400 -0
- package/mcp-server/dist/services/tool-runtime.js.map +1 -0
- package/mcp-server/dist/services/typesafe-config.js +1 -1
- package/mcp-server/dist/services/typesafe-config.js.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.d.ts +26 -2
- package/mcp-server/dist/services/whiteboard-geometry.d.ts.map +1 -1
- package/mcp-server/dist/services/whiteboard-geometry.js +91 -8
- package/mcp-server/dist/services/whiteboard-geometry.js.map +1 -1
- package/mcp-server/dist/tools/agents.d.ts +14 -14
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +29 -19
- package/mcp-server/dist/tools/agents.js.map +1 -1
- package/mcp-server/dist/tools/browser-batch.d.ts +3 -3
- package/mcp-server/dist/tools/choice.d.ts +6 -0
- package/mcp-server/dist/tools/choice.d.ts.map +1 -1
- package/mcp-server/dist/tools/choice.js +18 -13
- package/mcp-server/dist/tools/choice.js.map +1 -1
- package/mcp-server/dist/tools/git-tools.js +2 -2
- package/mcp-server/dist/tools/git-tools.js.map +1 -1
- package/mcp-server/dist/tools/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +1 -1
- package/mcp-server/dist/tools/morelogin.d.ts.map +1 -1
- package/mcp-server/dist/tools/morelogin.js +9 -6
- package/mcp-server/dist/tools/morelogin.js.map +1 -1
- package/mcp-server/dist/tools/skill-tools.d.ts +505 -0
- package/mcp-server/dist/tools/skill-tools.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill-tools.js +642 -0
- package/mcp-server/dist/tools/skill-tools.js.map +1 -0
- package/mcp-server/dist/tools/skill.d.ts +7 -0
- package/mcp-server/dist/tools/skill.d.ts.map +1 -0
- package/mcp-server/dist/tools/skill.js +11 -0
- package/mcp-server/dist/tools/skill.js.map +1 -0
- package/mcp-server/dist/tools/tool-groups.d.ts +68 -0
- package/mcp-server/dist/tools/tool-groups.d.ts.map +1 -0
- package/mcp-server/dist/tools/tool-groups.js +147 -0
- package/mcp-server/dist/tools/tool-groups.js.map +1 -0
- package/mcp-server/dist/tools/whiteboard-format.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-format.js +7 -4
- package/mcp-server/dist/tools/whiteboard-format.js.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.d.ts +64 -13
- package/mcp-server/dist/tools/whiteboard-tools.d.ts.map +1 -1
- package/mcp-server/dist/tools/whiteboard-tools.js +30 -5
- package/mcp-server/dist/tools/whiteboard-tools.js.map +1 -1
- package/mcp-server/package-lock.json +2 -2
- package/mcp-server/package.json +1 -1
- package/mcp-server/src/http.ts +75 -12
- package/mcp-server/src/index.ts +147 -154
- package/mcp-server/src/preload.ts +12 -12
- package/mcp-server/src/services/budget-judgments.ts +5 -3
- package/mcp-server/src/services/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/identity.ts +2 -2
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/memory-sessions.ts +24 -3
- package/mcp-server/src/services/neural-interface.ts +81 -21
- package/mcp-server/src/services/server-instructions.ts +29 -64
- package/mcp-server/src/services/tool-catalog.ts +403 -0
- package/mcp-server/src/services/tool-list-refresh-barrier.ts +33 -0
- package/mcp-server/src/services/tool-list.ts +410 -0
- package/mcp-server/src/services/tool-runtime.ts +457 -0
- package/mcp-server/src/services/typesafe-config.ts +1 -1
- package/mcp-server/src/services/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +29 -20
- package/mcp-server/src/tools/choice.ts +17 -12
- package/mcp-server/src/tools/git-tools.ts +2 -2
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/morelogin.ts +6 -4
- package/mcp-server/src/tools/skill-tools.ts +592 -0
- package/mcp-server/src/tools/skill.ts +13 -0
- package/mcp-server/src/tools/tool-groups.ts +164 -0
- package/mcp-server/src/tools/whiteboard-format.ts +9 -3
- package/mcp-server/src/tools/whiteboard-tools.ts +39 -5
- package/neural-interface/i18n/en.json +3953 -228
- package/neural-interface/i18n/pt-BR.json +3901 -179
- package/neural-interface/lib/agent-sandbox-elevation.js +6 -0
- package/neural-interface/lib/antigravity/acp-client-host.js +554 -0
- package/neural-interface/lib/antigravity/acp-connection.js +299 -0
- package/neural-interface/lib/antigravity/bypass.js +100 -0
- package/neural-interface/lib/antigravity/catalog.js +273 -0
- package/neural-interface/lib/antigravity/home.js +260 -0
- package/neural-interface/lib/antigravity/migrate-gemini.js +139 -0
- package/neural-interface/lib/antigravity/panel-bridge.js +1058 -0
- package/neural-interface/lib/antigravity/policy-hook.mjs +78 -0
- package/neural-interface/lib/antigravity/policy.js +74 -0
- package/neural-interface/lib/antigravity/tool-call.js +178 -0
- package/neural-interface/lib/antigravity/transcripts.js +252 -0
- package/neural-interface/lib/antigravity/usage.js +43 -0
- package/neural-interface/lib/assistant-api.js +128 -28
- package/neural-interface/lib/assistant-brains/antigravity.js +423 -0
- package/neural-interface/lib/assistant-brains/claude.js +4 -3
- package/neural-interface/lib/assistant-brains/codex-browser-policy-hook.mjs +12 -2
- package/neural-interface/lib/assistant-brains/codex-route-gate-hook.mjs +32 -9
- package/neural-interface/lib/assistant-brains/codex.js +9 -5
- package/neural-interface/lib/assistant-brains/opencode.js +120 -54
- package/neural-interface/lib/assistant-brains/opencode2-browser-policy/index.js +28 -13
- package/neural-interface/lib/assistant-brains/opencode2-route-gate/index.js +59 -19
- package/neural-interface/lib/assistant-budget-planner.js +7 -3
- package/neural-interface/lib/assistant-budget-split.js +42 -15
- package/neural-interface/lib/assistant-catalog.js +81 -15
- package/neural-interface/lib/assistant-config.js +6 -3
- package/neural-interface/lib/assistant-dispatch.js +1073 -200
- package/neural-interface/lib/assistant-envelope.js +156 -2
- package/neural-interface/lib/assistant-evidence.js +80 -0
- package/neural-interface/lib/assistant-hidden-models.js +7 -5
- package/neural-interface/lib/assistant-persona.js +93 -19
- package/neural-interface/lib/assistant-plan-permissions.js +25 -9
- package/neural-interface/lib/assistant-route-gate-codex.js +3 -0
- package/neural-interface/lib/assistant-route-gate.js +6 -4
- package/neural-interface/lib/assistant-router.js +245 -38
- package/neural-interface/lib/assistant-runtime.js +214 -30
- package/neural-interface/lib/assistant-task-prompt.js +87 -21
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/assistant-usage.js +29 -0
- package/neural-interface/lib/backup-service.js +626 -4
- package/neural-interface/lib/backup-zip-reader.js +10 -5
- package/neural-interface/lib/backup-zip-writer.js +257 -0
- package/neural-interface/lib/browser-execution.js +10 -2
- package/neural-interface/lib/browser-guard-cdp.js +84 -0
- package/neural-interface/lib/browser-network-guard.js +476 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +198 -38
- package/neural-interface/lib/claude-agent-bridge.js +11 -2
- package/neural-interface/lib/claude-history-cache.js +210 -0
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-model-catalog.js +5 -1
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install-api.js +1 -1
- package/neural-interface/lib/cli-install.js +163 -154
- package/neural-interface/lib/cli-resolver.js +13 -3
- package/neural-interface/lib/cli-update-planner.js +52 -41
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-rollout-reader.js +52 -0
- package/neural-interface/lib/codex-worker-app-server.js +191 -0
- package/neural-interface/lib/confined-fs.js +30 -3
- package/neural-interface/lib/crash-notice-api.js +98 -0
- package/neural-interface/lib/editor-completion-opencode.js +68 -68
- package/neural-interface/lib/editor-git-api.js +3 -20
- package/neural-interface/lib/exec-loop-command.js +13 -10
- package/neural-interface/lib/file-explorer-api.js +74 -40
- package/neural-interface/lib/file-explorer-watch.js +9 -3
- package/neural-interface/lib/git-bash/install.js +52 -6
- package/neural-interface/lib/git-bash/manager.js +59 -17
- package/neural-interface/lib/git-bash/pins.js +12 -0
- package/neural-interface/lib/git-read.js +2 -2
- package/neural-interface/lib/git-status-runner.js +390 -0
- package/neural-interface/lib/git-status-watchdog.js +40 -0
- package/neural-interface/lib/greeting/budgets.js +1 -0
- package/neural-interface/lib/greeting/engine.js +2 -2
- package/neural-interface/lib/greeting/sources.js +10 -12
- package/neural-interface/lib/greeting/writer.js +1 -1
- package/neural-interface/lib/http-guards.js +8 -2
- package/neural-interface/lib/install-details.js +4 -1
- package/neural-interface/lib/mcp-agent-servers.js +67 -0
- package/neural-interface/lib/mcp-client-config.js +35 -33
- package/neural-interface/lib/mcp-external-servers.js +332 -0
- package/neural-interface/lib/mcp-installer.js +2 -1
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/memory-session-capture.js +17 -3
- package/neural-interface/lib/morelogin.js +177 -25
- package/neural-interface/lib/native-loop-providers.js +965 -66
- package/neural-interface/lib/native-loop-runtime.js +290 -42
- package/neural-interface/lib/opencode-config.js +557 -0
- package/neural-interface/lib/opencode-db-schema.js +17 -34
- package/neural-interface/lib/opencode-history.js +48 -218
- package/neural-interface/lib/opencode-mcp-ready.js +108 -0
- package/neural-interface/lib/opencode-native-boundary.js +53 -0
- package/neural-interface/lib/opencode-protocol.js +249 -112
- package/neural-interface/lib/opencode-runtime-config.js +32 -22
- package/neural-interface/lib/opencode-serve-records.js +39 -21
- package/neural-interface/lib/opencode-v2-client.js +48 -153
- package/neural-interface/lib/opencode-v2-ws-requests.js +36 -210
- package/neural-interface/lib/opencode-worker-data.js +27 -0
- package/neural-interface/lib/opencode2-client.js +73 -37
- package/neural-interface/lib/opencode2-proxy.js +23 -63
- package/neural-interface/lib/opencode2-translate.js +51 -2
- package/neural-interface/lib/owner-authority.js +249 -0
- package/neural-interface/lib/page-session.js +275 -0
- package/neural-interface/lib/path-confine.js +12 -9
- package/neural-interface/lib/peer-identity.js +793 -0
- package/neural-interface/lib/pty-host/core.js +28 -1
- package/neural-interface/lib/pty-host/lifetime.js +12 -0
- package/neural-interface/lib/pty-host/manager.js +12 -1
- package/neural-interface/lib/remote-policy.js +1 -1
- package/neural-interface/lib/request-gate.js +20 -2
- package/neural-interface/lib/rulesets/api.js +7 -6
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +180 -36
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/rulesets/render.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +81 -31
- package/neural-interface/lib/session-title-generator.js +58 -36
- package/neural-interface/lib/skills/api-v2.js +495 -0
- package/neural-interface/lib/skills/bundle.js +418 -0
- package/neural-interface/lib/skills/catalog.js +298 -0
- package/neural-interface/lib/skills/creation-api.js +191 -0
- package/neural-interface/lib/skills/creation-brief.js +247 -0
- package/neural-interface/lib/skills/creation-sources.js +285 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +569 -0
- package/neural-interface/lib/skills/digest.js +119 -0
- package/neural-interface/lib/skills/discover.js +111 -0
- package/neural-interface/lib/skills/draft-confinement.js +216 -0
- package/neural-interface/lib/skills/eval-confinement.js +399 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +135 -0
- package/neural-interface/lib/skills/evals-api.js +212 -0
- package/neural-interface/lib/skills/evals.js +1573 -0
- package/neural-interface/lib/skills/export.js +203 -0
- package/neural-interface/lib/skills/frontmatter.js +64 -0
- package/neural-interface/lib/skills/health.js +75 -0
- package/neural-interface/lib/skills/json-edit.js +165 -0
- package/neural-interface/lib/skills/known-bridge-digests.json +441 -0
- package/neural-interface/lib/skills/known-bridges.js +96 -0
- package/neural-interface/lib/skills/library.js +823 -0
- package/neural-interface/lib/skills/local-store.js +21 -0
- package/neural-interface/lib/skills/migration.js +433 -0
- package/neural-interface/lib/skills/operations.js +45 -0
- package/neural-interface/lib/skills/ownership.js +427 -0
- package/neural-interface/lib/skills/packages.js +88 -0
- package/neural-interface/lib/skills/reviews.js +212 -0
- package/neural-interface/lib/skills/revision.js +80 -0
- package/neural-interface/lib/skills/roots.js +269 -0
- package/neural-interface/lib/skills/runtime-checks.js +358 -0
- package/neural-interface/lib/skills/scan-assets.js +252 -0
- package/neural-interface/lib/skills/scan-context.js +26 -0
- package/neural-interface/lib/skills/scan-shell.js +324 -0
- package/neural-interface/lib/skills/scan.js +364 -0
- package/neural-interface/lib/skills/scanners.js +54 -0
- package/neural-interface/lib/skills/service.js +530 -0
- package/neural-interface/lib/skills/telemetry.js +119 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/synabun-version.js +1 -1
- package/neural-interface/lib/system-activity.js +1 -1
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/brain.js +7 -3
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/whatsapp/cards.js +22 -9
- package/neural-interface/lib/whatsapp/config.js +1 -1
- package/neural-interface/lib/worker-confinement.js +1268 -0
- package/neural-interface/lib/worker-guard-setting.js +56 -0
- package/neural-interface/lib/worker-mcp-channel.js +175 -0
- package/neural-interface/lib/worker-question.js +74 -0
- package/neural-interface/lib/worker-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +238 -0
- package/neural-interface/lib/worker-sandbox.js +329 -0
- package/neural-interface/package-lock.json +34 -12
- package/neural-interface/package.json +3 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +3 -3
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +125 -31
- package/neural-interface/public/shared/agy/agy-bypass.js +47 -0
- package/neural-interface/public/shared/agy/agy-client.js +266 -0
- package/neural-interface/public/shared/agy/agy-commands.js +120 -0
- package/neural-interface/public/shared/agy/agy-copy.js +326 -0
- package/neural-interface/public/shared/agy/agy-diff.js +161 -0
- package/neural-interface/public/shared/agy/agy-events.js +469 -0
- package/neural-interface/public/shared/agy/agy-greeting.js +26 -0
- package/neural-interface/public/shared/agy/agy-options.js +232 -0
- package/neural-interface/public/shared/agy/agy-panel.js +2543 -0
- package/neural-interface/public/shared/agy/agy-render.js +392 -0
- package/neural-interface/public/shared/agy/agy-setup.js +149 -0
- package/neural-interface/public/shared/agy/agy-styles.js +604 -0
- package/neural-interface/public/shared/agy/agy-tabs-logic.js +181 -0
- package/neural-interface/public/shared/api.js +1527 -1336
- package/neural-interface/public/shared/app-frame.js +192 -0
- package/neural-interface/public/shared/assistant/asst-agents-dock.js +36 -6
- package/neural-interface/public/shared/assistant/asst-api.js +1 -1
- package/neural-interface/public/shared/assistant/asst-ask-styles.js +270 -0
- package/neural-interface/public/shared/assistant/asst-ask-tail.js +118 -0
- package/neural-interface/public/shared/assistant/asst-ask.js +397 -0
- package/neural-interface/public/shared/assistant/asst-brain-picker.js +105 -44
- package/neural-interface/public/shared/assistant/asst-budget.js +5 -4
- package/neural-interface/public/shared/assistant/asst-control.js +205 -26
- package/neural-interface/public/shared/assistant/asst-followups-card.js +383 -0
- package/neural-interface/public/shared/assistant/asst-followups.js +417 -0
- package/neural-interface/public/shared/assistant/asst-hosts.js +16 -7
- package/neural-interface/public/shared/assistant/asst-limit-dock.js +45 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +327 -0
- package/neural-interface/public/shared/assistant/asst-limit.js +168 -0
- package/neural-interface/public/shared/assistant/asst-mascot.js +49 -12
- package/neural-interface/public/shared/assistant/asst-menu.js +7 -6
- package/neural-interface/public/shared/assistant/asst-panel.js +151 -19
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +445 -82
- package/neural-interface/public/shared/assistant/asst-route.js +304 -53
- package/neural-interface/public/shared/assistant/asst-routes-editor.js +9 -8
- package/neural-interface/public/shared/assistant/asst-scenes.js +56 -1
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +117 -10
- package/neural-interface/public/shared/assistant/asst-slash.js +17 -9
- package/neural-interface/public/shared/assistant/asst-state.js +87 -24
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +93 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +190 -23
- package/neural-interface/public/shared/assistant/asst-usage.js +2 -1
- package/neural-interface/public/shared/automation-context.js +5 -2
- package/neural-interface/public/shared/cc-dropdown.js +89 -19
- package/neural-interface/public/shared/cdx/cdx-context-menu.js +10 -9
- package/neural-interface/public/shared/cdx/cdx-copy.js +16 -0
- package/neural-interface/public/shared/cdx/cdx-output-model.js +22 -1
- package/neural-interface/public/shared/cdx/cdx-output.js +34 -10
- package/neural-interface/public/shared/cdx/cdx-panel.js +59 -151
- package/neural-interface/public/shared/cdx/cdx-preview.js +12 -0
- package/neural-interface/public/shared/cdx/cdx-protocol.js +39 -11
- package/neural-interface/public/shared/cdx/cdx-render.js +57 -38
- package/neural-interface/public/shared/cdx/cdx-requests.js +14 -4
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +15 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +127 -44
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-controller.js +218 -0
- package/neural-interface/public/shared/cli-install-view.js +11 -12
- package/neural-interface/public/shared/cli-status.js +11 -10
- package/neural-interface/public/shared/constants.js +5 -4
- package/neural-interface/public/shared/context-menu-areas.js +34 -0
- package/neural-interface/public/shared/context-menu-logic.js +243 -0
- package/neural-interface/public/shared/context-menu-model.js +367 -0
- package/neural-interface/public/shared/context-menu.js +372 -0
- package/neural-interface/public/shared/cp/cp-context-menu.js +10 -9
- package/neural-interface/public/shared/cp/cp-restore.js +162 -0
- package/neural-interface/public/shared/cp/cp-tool-cards.js +10 -3
- package/neural-interface/public/shared/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-bridge.js +17 -0
- package/neural-interface/public/shared/desktop-titlebar-view.js +12 -0
- package/neural-interface/public/shared/desktop-titlebar.js +13 -4
- package/neural-interface/public/shared/desktop-update-view.js +21 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/explorer/fx-api.js +4 -4
- package/neural-interface/public/shared/explorer/fx-links.js +3 -3
- package/neural-interface/public/shared/explorer/fx-logic.js +1 -1
- package/neural-interface/public/shared/explorer/fx-main.js +48 -16
- package/neural-interface/public/shared/explorer/fx-tree.js +1 -0
- package/neural-interface/public/shared/greeting/greeting-card.js +2 -2
- package/neural-interface/public/shared/greeting/greeting-view.js +1 -0
- package/neural-interface/public/shared/html-shell.js +30 -15
- package/neural-interface/public/shared/i18n.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-approvals.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-binding.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-changes-logic.js +3 -101
- package/neural-interface/public/shared/ocp-v2/ocp-v2-childpanel.js +2 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-compact-button.js +1 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-composer-logic.js +2 -6
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-menu.js +11 -11
- package/neural-interface/public/shared/ocp-v2/ocp-v2-context-model.js +0 -3
- package/neural-interface/public/shared/ocp-v2/ocp-v2-events.js +8 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-manager.js +7 -7
- package/neural-interface/public/shared/ocp-v2/ocp-v2-outcome-notifications.js +16 -0
- package/neural-interface/public/shared/ocp-v2/ocp-v2-panel.js +74 -259
- package/neural-interface/public/shared/ocp-v2/ocp-v2-plan.js +1 -2
- package/neural-interface/public/shared/ocp-v2/ocp-v2-projectbar.js +95 -202
- package/neural-interface/public/shared/ocp-v2/ocp-v2-rehydrate.js +13 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-render.js +13 -22
- package/neural-interface/public/shared/ocp-v2/ocp-v2-send.js +4 -4
- package/neural-interface/public/shared/ocp-v2/ocp-v2-session-actions.js +8 -61
- package/neural-interface/public/shared/ocp-v2/ocp-v2-sessions-logic.js +2 -91
- package/neural-interface/public/shared/ocp-v2/ocp-v2-slash-hints.js +16 -15
- package/neural-interface/public/shared/ocp-v2/ocp-v2-state.js +8 -21
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status-logic.js +1 -99
- package/neural-interface/public/shared/ocp-v2/ocp-v2-status.js +5 -35
- package/neural-interface/public/shared/ocp-v2/ocp-v2-styles.js +17 -21
- package/neural-interface/public/shared/ocp-v2/{ocp-v2-profile-inheritance.js → ocp-v2-tools-inheritance.js} +16 -16
- package/neural-interface/public/shared/ocp-v2/ocp-v2-ws.js +60 -50
- package/neural-interface/public/shared/page-session.js +96 -0
- package/neural-interface/public/shared/provider-icons.js +4 -2
- package/neural-interface/public/shared/registry.js +5 -3
- package/neural-interface/public/shared/retired-2d.js +3 -0
- package/neural-interface/public/shared/schedules-view.js +195 -61
- package/neural-interface/public/shared/settings/settings-claude-hooks.js +135 -0
- package/neural-interface/public/shared/settings/settings-data.js +3 -1
- package/neural-interface/public/shared/settings/settings-ia.js +6 -6
- package/neural-interface/public/shared/settings/settings-opencode.js +89 -0
- package/neural-interface/public/shared/settings/settings-rules.js +2 -2
- package/neural-interface/public/shared/settings/settings-skill-folders-pane.js +265 -0
- package/neural-interface/public/shared/settings/settings-skill-folders.js +130 -0
- package/neural-interface/public/shared/settings/settings-worker-guard.js +72 -0
- package/neural-interface/public/shared/showcase-privacy.js +6 -1
- package/neural-interface/public/shared/sidepanel-session-status.js +3 -3
- package/neural-interface/public/shared/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +222 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +133 -0
- package/neural-interface/public/shared/skills/ss-doc.js +173 -0
- package/neural-interface/public/shared/skills/ss-eval-adapters.js +269 -0
- package/neural-interface/public/shared/skills/ss-eval-grade.js +359 -0
- package/neural-interface/public/shared/skills/ss-eval-optimise.js +153 -0
- package/neural-interface/public/shared/skills/ss-eval-plan.js +315 -0
- package/neural-interface/public/shared/skills/ss-evals.js +417 -0
- package/neural-interface/public/shared/skills/ss-form.js +159 -0
- package/neural-interface/public/shared/skills/ss-frontmatter.js +376 -0
- package/neural-interface/public/shared/skills/ss-ids.js +32 -0
- package/neural-interface/public/shared/skills/ss-lint.js +149 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +513 -0
- package/neural-interface/public/shared/skills/ss-resources.js +165 -0
- package/neural-interface/public/shared/skills/ss-review-view.js +190 -0
- package/neural-interface/public/shared/skills/ss-tabs.js +79 -0
- package/neural-interface/public/shared/skills/ss-unicode.js +135 -0
- package/neural-interface/public/shared/skills/studio/sx-actions.js +222 -0
- package/neural-interface/public/shared/skills/studio/sx-cm.js +101 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai-view.js +749 -0
- package/neural-interface/public/shared/skills/studio/sx-create-ai.js +237 -0
- package/neural-interface/public/shared/skills/studio/sx-create-view.js +254 -0
- package/neural-interface/public/shared/skills/studio/sx-create.js +126 -0
- package/neural-interface/public/shared/skills/studio/sx-diff-view.js +43 -0
- package/neural-interface/public/shared/skills/studio/sx-diff.js +133 -0
- package/neural-interface/public/shared/skills/studio/sx-docs.js +480 -0
- package/neural-interface/public/shared/skills/studio/sx-dom.js +230 -0
- package/neural-interface/public/shared/skills/studio/sx-edit-view.js +848 -0
- package/neural-interface/public/shared/skills/studio/sx-editor-model.js +193 -0
- package/neural-interface/public/shared/skills/studio/sx-errors.js +22 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes-view.js +359 -0
- package/neural-interface/public/shared/skills/studio/sx-fixes.js +327 -0
- package/neural-interface/public/shared/skills/studio/sx-geometry.js +82 -0
- package/neural-interface/public/shared/skills/studio/sx-import.js +262 -0
- package/neural-interface/public/shared/skills/studio/sx-inspector.js +308 -0
- package/neural-interface/public/shared/skills/studio/sx-install-flow.js +282 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +313 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +748 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +479 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +690 -0
- package/neural-interface/public/shared/skills/studio/sx-operations-view.js +98 -0
- package/neural-interface/public/shared/skills/studio/sx-operations.js +11 -0
- package/neural-interface/public/shared/skills/studio/sx-plan.js +158 -0
- package/neural-interface/public/shared/skills/studio/sx-publish-view.js +621 -0
- package/neural-interface/public/shared/skills/studio/sx-review-view.js +369 -0
- package/neural-interface/public/shared/skills/studio/sx-review.js +148 -0
- package/neural-interface/public/shared/skills/studio/sx-scan.js +35 -0
- package/neural-interface/public/shared/skills/studio/sx-snippets.js +96 -0
- package/neural-interface/public/shared/skills/studio/sx-subject.js +249 -0
- package/neural-interface/public/shared/skills/studio/sx-test-view.js +994 -0
- package/neural-interface/public/shared/skills/studio/sx-test.js +328 -0
- package/neural-interface/public/shared/skills/studio/sx-versions-view.js +241 -0
- package/neural-interface/public/shared/skills/studio/sx-workspace-view.js +426 -0
- package/neural-interface/public/shared/state.js +1 -1
- package/neural-interface/public/shared/streaming-markdown.js +58 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1822 -1756
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- package/neural-interface/public/shared/text-cache.js +29 -0
- package/neural-interface/public/shared/tool-list-picker.js +55 -0
- package/neural-interface/public/shared/tool-list-storage.js +24 -0
- package/neural-interface/public/shared/tool-list-view.js +112 -0
- package/neural-interface/public/shared/topright-overflow-logic.js +54 -0
- package/neural-interface/public/shared/tour/tour-actor.js +268 -0
- package/neural-interface/public/shared/tour/tour-beats.js +336 -0
- package/neural-interface/public/shared/tour/tour-engine.js +884 -0
- package/neural-interface/public/shared/tour/tour-ink.js +390 -0
- package/neural-interface/public/shared/tour/tour-stage.js +440 -0
- package/neural-interface/public/shared/tour/tour-state.js +40 -0
- package/neural-interface/public/shared/tour/tour-tips.js +99 -0
- package/neural-interface/public/shared/tour/tour.css +144 -0
- package/neural-interface/public/shared/ui-antigravity-panel.js +19 -0
- package/neural-interface/public/shared/ui-assistant.js +2 -0
- package/neural-interface/public/shared/ui-automation-studio.js +111 -140
- package/neural-interface/public/shared/ui-claude-panel.js +376 -82
- package/neural-interface/public/shared/ui-cli-banner.js +23 -167
- package/neural-interface/public/shared/ui-command-runner.js +6 -3
- package/neural-interface/public/shared/ui-context-menu.js +295 -0
- package/neural-interface/public/shared/ui-cost-widget.js +57 -59
- package/neural-interface/public/shared/ui-crash-notice.js +187 -0
- package/neural-interface/public/shared/ui-detail.js +15 -3
- package/neural-interface/public/shared/ui-file-explorer.js +48 -6
- package/neural-interface/public/shared/ui-file-links.js +2 -2
- package/neural-interface/public/shared/ui-games.js +2 -2
- package/neural-interface/public/shared/ui-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-judgments.js +1 -1
- package/neural-interface/public/shared/ui-keybinds.js +33 -2
- package/neural-interface/public/shared/ui-link.js +3 -3
- package/neural-interface/public/shared/ui-menubar.js +10 -20
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +11 -1
- package/neural-interface/public/shared/ui-navbar.js +49 -24
- package/neural-interface/public/shared/ui-notifications.js +1 -0
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-resume.js +9 -7
- package/neural-interface/public/shared/ui-schedules-studio.js +546 -431
- package/neural-interface/public/shared/ui-scroll-follow.js +16 -5
- package/neural-interface/public/shared/ui-sessions.js +27 -15
- package/neural-interface/public/shared/ui-settings.js +683 -526
- package/neural-interface/public/shared/ui-sidepanel-session-entry.js +9 -2
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +4 -4
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-tray.js +48 -7
- package/neural-interface/public/shared/ui-sidepanel-windows.js +18 -13
- package/neural-interface/public/shared/ui-skills.js +33 -2830
- package/neural-interface/public/shared/ui-styleguide.js +5 -2
- package/neural-interface/public/shared/ui-sync.js +15 -5
- package/neural-interface/public/shared/ui-terminal.js +270 -190
- package/neural-interface/public/shared/ui-tool-updates.js +6 -4
- package/neural-interface/public/shared/ui-tooltip.js +81 -51
- package/neural-interface/public/shared/ui-topright-overflow.js +233 -0
- package/neural-interface/public/shared/ui-trash.js +6 -4
- package/neural-interface/public/shared/ui-tutorial-layout.js +3 -117
- package/neural-interface/public/shared/ui-tutorial-preview.js +103 -0
- package/neural-interface/public/shared/ui-tutorial-steps.js +3 -65
- package/neural-interface/public/shared/ui-tutorial.css +47 -100
- package/neural-interface/public/shared/ui-tutorial.js +285 -509
- package/neural-interface/public/shared/ui-update.js +38 -2
- package/neural-interface/public/shared/ui-whiteboard.js +312 -59
- package/neural-interface/public/shared/ui-worker-end.js +5 -1
- package/neural-interface/public/shared/ui-workspaces.js +3 -2
- package/neural-interface/public/shared/view-variant.js +66 -0
- package/neural-interface/public/shared/whatsapp/wa-view.js +3 -3
- package/neural-interface/public/shared/whiteboard-list.js +172 -0
- package/neural-interface/public/shared/worker-end.js +10 -3
- package/neural-interface/public/shared/worker-questions.js +34 -0
- package/neural-interface/public/shared/worker-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +2 -2
- package/neural-interface/public/variant/2d/background.js +2 -1
- package/neural-interface/public/variant/2d/context-menu.js +8 -2
- package/neural-interface/public/variant/2d/graph.js +42 -20
- package/neural-interface/public/variant/2d/hulls.js +14 -9
- package/neural-interface/public/variant/2d/lasso.js +11 -7
- package/neural-interface/public/variant/2d/main.js +32 -1
- package/neural-interface/public/variant/2d/minimap.js +11 -13
- package/neural-interface/public/variant/3d/main.js +69 -6
- package/neural-interface/public/variant/3d/map-controls.js +24 -9
- package/neural-interface/public/vendor/caveat/OFL.txt +93 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-400-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat-latin-700-normal.woff2 +0 -0
- package/neural-interface/public/vendor/caveat/caveat.css +17 -0
- package/neural-interface/public/vendor/caveat/manifest.json +12 -0
- package/neural-interface/public/vendor/yaml/LICENSE +13 -0
- package/neural-interface/public/vendor/yaml/manifest.json +4 -0
- package/neural-interface/public/vendor/yaml/yaml-2.9.1.min.js +137 -0
- package/neural-interface/scripts/build-vendor.mjs +32 -0
- package/neural-interface/server.js +4553 -2855
- package/neural-interface/templates/rulesets/README.md +1 -1
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +23 -5
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +34 -12
- package/package.json +17 -3
- package/postinstall.js +9 -4
- package/preuninstall.js +1 -1
- package/setup.js +97 -322
- package/skills/skill-forge/SKILL.md +177 -0
- package/skills/skill-forge/evals/evals.json +54 -0
- package/skills/skill-forge/references/package.md +105 -0
- package/skills/skill-forge/references/review-and-recovery.md +63 -0
- package/skills/skill-forge/references/runtime-profiles.md +20 -0
- package/skills/skill-forge/references/sources-and-capabilities.md +51 -0
- package/skills/synabun/SKILL.md +10 -7
- package/skills/synabun/modules/audit.md +1 -5
- package/skills/synabun/modules/changelog.md +1 -5
- package/skills/synabun/modules/idea.md +1 -1
- package/skills/synabun/modules/memorize.md +6 -9
- package/skills/synabun/modules/schedule.md +6 -6
- package/mcp-server/dist/services/profiles.d.ts +0 -120
- package/mcp-server/dist/services/profiles.d.ts.map +0 -1
- package/mcp-server/dist/services/profiles.js +0 -387
- package/mcp-server/dist/services/profiles.js.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts +0 -97
- package/mcp-server/dist/tools/leonardo-browser-tools.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo-browser-tools.js +0 -228
- package/mcp-server/dist/tools/leonardo-browser-tools.js.map +0 -1
- package/mcp-server/dist/tools/leonardo.d.ts +0 -13
- package/mcp-server/dist/tools/leonardo.d.ts.map +0 -1
- package/mcp-server/dist/tools/leonardo.js +0 -21
- package/mcp-server/dist/tools/leonardo.js.map +0 -1
- package/mcp-server/dist/tools/profile.d.ts +0 -17
- package/mcp-server/dist/tools/profile.d.ts.map +0 -1
- package/mcp-server/dist/tools/profile.js +0 -114
- package/mcp-server/dist/tools/profile.js.map +0 -1
- package/mcp-server/src/services/profiles.ts +0 -428
- package/mcp-server/src/tools/leonardo-browser-tools.ts +0 -291
- package/mcp-server/src/tools/leonardo.ts +0 -30
- package/mcp-server/src/tools/profile.ts +0 -122
- package/neural-interface/lib/assistant-brains/opencode-browser-policy.js +0 -47
- package/neural-interface/lib/assistant-brains/opencode-route-gate.js +0 -62
- package/neural-interface/lib/mcp-profile-servers.js +0 -120
- package/neural-interface/public/shared/ui-tutorial-draw.js +0 -311
- package/skills/leonardo/SKILL.md +0 -168
- package/skills/leonardo/icon.svg +0 -1
- package/skills/leonardo/modules/browser-guide.md +0 -378
- package/skills/leonardo/modules/image-prompter.md +0 -283
- package/skills/leonardo/modules/model-advisor.md +0 -100
- package/skills/leonardo/modules/prompt-library.md +0 -110
- package/skills/leonardo/modules/style-guide.md +0 -186
- package/skills/leonardo/modules/video-prompter.md +0 -317
- /package/neural-interface/templates/rulesets/{gemini.md → antigravity.md} +0 -0
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun — a skill's test prompts: evals/evals.json
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// What AI drafting (P3) writes next to a skill and what evaluation (P4) runs: task cases (a request, what a good
|
|
6
|
+
// answer holds, the checks it must pass) and trigger cases (a request that should or should not pick the skill).
|
|
7
|
+
// The task cases keep the Agent Skills evaluation guide's shape (`evals[]` with id, prompt, expected_output, files,
|
|
8
|
+
// assertions) and the trigger cases the skill-creator's (`query`, `should_trigger`), so either tool can read them;
|
|
9
|
+
// SynaBun adds a `group` and a `split` to each case. The upstream pitfalls are checked here, not left to the
|
|
10
|
+
// writer (03a §3.2, master plan §4):
|
|
11
|
+
// - duplicate prompts: the same request written twice (even with other spacing or capitals) is one case, and in
|
|
12
|
+
// two splits it would leak the answer;
|
|
13
|
+
// - holdout reuse: a group (the variants of one request) lives in one split only, and a `test` case is held
|
|
14
|
+
// out: its text never appears in the skill itself;
|
|
15
|
+
// - characters counted as tokens: a token count written here could only be an estimate, so none is kept.
|
|
16
|
+
// Counts below the default recipe (3 task cases, 3 positive and 2 near-miss triggers) are warnings, not errors.
|
|
17
|
+
// An assertion is a text a grader checks against the answer, or an object `{ id?, text, check?, kind?, required? }`:
|
|
18
|
+
// a `check` is decided by code, never by a model (ASSERTION_CHECKS: a word or a pattern in the answer or an output
|
|
19
|
+
// file, a file that exists, a JSON file that parses, a line limit), and `kind: "preference"` marks a check of the
|
|
20
|
+
// way the user wants it done (the report measures their fidelity apart). A task case may ask for a blind comparison
|
|
21
|
+
// of the answers with and without the skill (`compare: true`) and say it needs a shell (`shell: true`: it runs only
|
|
22
|
+
// where the OS sandbox holds one). P4 runs the cases (lib/skills/evals.js); readSuite() is what it reads.
|
|
23
|
+
// No DOM and no imports: the page and the server read the same file the same way.
|
|
24
|
+
|
|
25
|
+
export const EVALS_PATH = 'evals/evals.json';
|
|
26
|
+
export const EVALS_SCHEMA = 'synabun-evals/1';
|
|
27
|
+
export const EVAL_SPLITS = Object.freeze(['train', 'validation', 'test']);
|
|
28
|
+
export const EVALS_LIMITS = Object.freeze({ maxBytes: 256 * 1024, maxCases: 200, promptChars: 4000, queryChars: 2000, expectedChars: 4000, assertions: 20, assertionChars: 500, files: 20 });
|
|
29
|
+
/** The default recipe (master plan §4): what a substantive skill starts with. Fewer is a warning. */
|
|
30
|
+
export const EVALS_RECIPE = Object.freeze({ tasks: 3, positive: 3, nearMiss: 2 });
|
|
31
|
+
/** What a deterministic check is: decided by code over the answer (or an output file), never by a model. */
|
|
32
|
+
export const ASSERTION_CHECKS = Object.freeze(['contains', 'not_contains', 'regex', 'file_exists', 'json_valid', 'max_lines']);
|
|
33
|
+
/** What an assertion is about: the outcome (the default), or the way the user wants it done (preference fidelity). */
|
|
34
|
+
export const ASSERTION_KINDS = Object.freeze(['outcome', 'preference']);
|
|
35
|
+
export const CHECK_LIMITS = Object.freeze({ valueChars: 500, patternChars: 300, maxLines: 100_000 });
|
|
36
|
+
|
|
37
|
+
const ID_RE = /^[a-z0-9][a-z0-9._-]{0,63}$/;
|
|
38
|
+
const isRelativeFile = (f) => typeof f === 'string' && !!f && f.length <= 300 && !f.startsWith('/') && !/^[A-Za-z]:/.test(f) && !f.split('/').includes('..') && !f.includes('\\') && !/[\u0000-\u001f]/.test(f);
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Folders a runtime or a tool reads its own settings, skills, hooks, plugins or code from, wherever they sit in a
|
|
42
|
+
* case's test folder (Claude Code `.claude`, `.claude-plugin`; Codex `.codex`, `.agents`; OpenCode `.opencode`;
|
|
43
|
+
* `.config`; a version-control folder, whose configuration names programs git and the runtimes run). A test file is
|
|
44
|
+
* never copied into one: a fixture `.claude/settings.json` would be a hook the runtime runs before any check.
|
|
45
|
+
*/
|
|
46
|
+
export const EVAL_CONFIG_DIRS = Object.freeze({
|
|
47
|
+
'.claude': 'Claude Code', '.claude-plugin': 'Claude Code', '.agents': 'Codex and OpenCode', '.codex': 'Codex', '.opencode': 'OpenCode',
|
|
48
|
+
'.config': 'programs', '.git': 'git (and every runtime that runs it)', '.hg': 'Mercurial (and the runtimes that run it)', '.svn': 'Subversion',
|
|
49
|
+
'.jj': 'Jujutsu', '.sl': 'Sapling', '.bzr': 'Bazaar', _darcs: 'Darcs', '.pijul': 'Pijul',
|
|
50
|
+
});
|
|
51
|
+
/** Files at the top of a test folder a runtime or a tool loads as its own settings (MCP servers, plugins, an environment). */
|
|
52
|
+
export const EVAL_CONFIG_FILES = Object.freeze({
|
|
53
|
+
'.mcp.json': 'Claude Code', '.lsp.json': 'Claude Code', 'opencode.json': 'OpenCode', 'opencode.jsonc': 'OpenCode', '.opencode.json': 'OpenCode',
|
|
54
|
+
'.opencode.jsonc': 'OpenCode', '.envrc': 'direnv', '.fslckout': 'Fossil', _fossil_: 'Fossil',
|
|
55
|
+
});
|
|
56
|
+
/**
|
|
57
|
+
* Files at the top of a test folder a runtime reads as instructions for the whole run, before the request: Claude Code
|
|
58
|
+
* CLAUDE.md and CLAUDE.local.md (with its project and local settings, which an evaluation turns off; AGENTS.md is in its
|
|
59
|
+
* list of memory files too), Codex AGENTS.md and AGENTS.override.md (always), OpenCode AGENTS.md (with its project
|
|
60
|
+
* configuration, which an evaluation turns off).
|
|
61
|
+
* As a test file it would be a second prompt in both arms, and a different one per runtime: refused at the top. A copy
|
|
62
|
+
* deeper in a folder (project/AGENTS.md) is a test file like any other: docs/skills-studio.md says what each runtime
|
|
63
|
+
* does with one.
|
|
64
|
+
*/
|
|
65
|
+
export const EVAL_INSTRUCTION_FILES = Object.freeze({
|
|
66
|
+
'claude.md': 'Claude Code', 'claude.local.md': 'Claude Code', 'agents.md': 'Codex, OpenCode and Claude Code', 'agents.override.md': 'Codex',
|
|
67
|
+
});
|
|
68
|
+
// Characters a file system may drop or fold when it compares names (HFS+/APFS ignorable code points, NTFS trailing dots
|
|
69
|
+
// and spaces): ".git", ".GIT" and ".git." are all ".git" there.
|
|
70
|
+
const IGNORABLE_RE = /[͏ᅟᅠ឴឵᠋-᠏---ㅤ︀-️ᅠ]/gu;
|
|
71
|
+
/** A path segment as a file system may read it: case, compatibility forms, ignorable characters and trailing dots aside. */
|
|
72
|
+
export function fixtureSegmentName(segment) {
|
|
73
|
+
return String(segment ?? '').normalize('NFKC').replace(IGNORABLE_RE, '').replace(/[ .]+$/u, '').toLowerCase();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Why a test file (a case's `files` entry, which is also where it lands in the test folder) cannot be copied there,
|
|
78
|
+
* or null: what evalConfigPathProblem() refuses, or one of EVAL_INSTRUCTION_FILES at ANY depth (code
|
|
79
|
+
* `fixture-instructions`). A runtime reads such a file as instructions for the whole run, not as test data: at the top
|
|
80
|
+
* of the folder, and — the owner's decision — a nested one too, because OpenCode 2.1.4 loads a nested AGENTS.md as
|
|
81
|
+
* instructions once a file beside it is read, and Codex's prompt tells the model to follow one. → { code, name, who,
|
|
82
|
+
* message }
|
|
83
|
+
*/
|
|
84
|
+
export function evalFixturePathProblem(path) {
|
|
85
|
+
const config = evalConfigPathProblem(path);
|
|
86
|
+
if (config) return config;
|
|
87
|
+
const parts = String(path ?? '').split('/').filter((p) => p !== '' && p !== '.');
|
|
88
|
+
const last = parts.length ? parts[parts.length - 1] : '';
|
|
89
|
+
const name = fixtureSegmentName(last);
|
|
90
|
+
if (name && Object.prototype.hasOwnProperty.call(EVAL_INSTRUCTION_FILES, name)) {
|
|
91
|
+
const who = EVAL_INSTRUCTION_FILES[name];
|
|
92
|
+
const where = parts.length === 1
|
|
93
|
+
? `"${path}" is ${last} at the top of the test folder, which ${who} reads as instructions for the whole run`
|
|
94
|
+
: `"${path}" is ${last}, an instruction file inside the test folder, which ${who} reads as instructions for the whole run — at the top, and a nested one too once a runtime reads a file beside it`;
|
|
95
|
+
return { code: 'fixture-instructions', name, who, message: `${where}. As a test file it would steer the run, differently per runtime, so it is never copied there. Rename it — a runtime reads a file with this name as instructions, not as test data.` };
|
|
96
|
+
}
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Why a place of a test folder holds a runtime's own settings or code, or null: inside a folder of EVAL_CONFIG_DIRS at
|
|
102
|
+
* any depth, one of EVAL_CONFIG_FILES at the top, or a name another file system reads as another file's (a Windows
|
|
103
|
+
* short name such as CLAUDE~1, a ":" stream). What a run may not write either (lib/skills/eval-confinement.js); an
|
|
104
|
+
* instruction file (EVAL_INSTRUCTION_FILES) is a file a run may write. → { code, name, who, message }
|
|
105
|
+
*/
|
|
106
|
+
export function evalConfigPathProblem(path) {
|
|
107
|
+
const parts = String(path ?? '').split('/').filter((p) => p !== '' && p !== '.');
|
|
108
|
+
for (let i = 0; i < parts.length; i++) {
|
|
109
|
+
const raw = parts[i];
|
|
110
|
+
if (raw.includes(':') || /~\d/.test(raw)) {
|
|
111
|
+
return { code: 'fixture-alias', name: raw, who: null, message: `"${path}" has a name another file system can read as another file's ("${raw}": a short name like CLAUDE~1, or a ":" stream). Give it a plain name.` };
|
|
112
|
+
}
|
|
113
|
+
const name = fixtureSegmentName(raw);
|
|
114
|
+
const isLast = i === parts.length - 1;
|
|
115
|
+
if (!isLast && Object.prototype.hasOwnProperty.call(EVAL_CONFIG_DIRS, name)) {
|
|
116
|
+
const who = EVAL_CONFIG_DIRS[name];
|
|
117
|
+
return { code: 'fixture-config', name, who, message: `"${path}" goes into ${name}/, a folder ${who} reads its own settings or code from: a test file is never copied there, so it cannot change how a runtime runs. Keep it under evals/files/ instead.` };
|
|
118
|
+
}
|
|
119
|
+
if (isLast && Object.prototype.hasOwnProperty.call(EVAL_CONFIG_DIRS, name)) {
|
|
120
|
+
const who = EVAL_CONFIG_DIRS[name];
|
|
121
|
+
return { code: 'fixture-config', name, who, message: `"${path}" is named ${name}, which ${who} reads as its own settings or code: a test file is never copied there under that name. Keep it under evals/files/ instead.` };
|
|
122
|
+
}
|
|
123
|
+
if (i === 0 && isLast && Object.prototype.hasOwnProperty.call(EVAL_CONFIG_FILES, name)) {
|
|
124
|
+
const who = EVAL_CONFIG_FILES[name];
|
|
125
|
+
return { code: 'fixture-config', name, who, message: `"${path}" is ${name} at the top of the test folder, which ${who} loads as its own settings: a test file is never copied there. Keep it under evals/files/ instead.` };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* Why a regular expression of a check cannot be run, or null. A pattern runs on what a model wrote, so one that can
|
|
133
|
+
* take exponential time on a crafted answer (a quantified group that holds a quantifier or an alternation, a
|
|
134
|
+
* backreference) is refused; the server runs the rest with a time limit too.
|
|
135
|
+
*/
|
|
136
|
+
export function regexProblem(source, flags = '') {
|
|
137
|
+
if (typeof source !== 'string' || !source) return 'empty';
|
|
138
|
+
if (source.length > CHECK_LIMITS.patternChars) return 'too-long';
|
|
139
|
+
if (typeof flags !== 'string' || !/^[ims]{0,3}$/.test(flags) || new Set(flags).size !== flags.length) return 'flags';
|
|
140
|
+
try { new RegExp(source, flags); } catch { return 'invalid'; }
|
|
141
|
+
if (/\\[1-9]|\\k</.test(source)) return 'backreference';
|
|
142
|
+
return repeatsARepeat(source) ? 'nested-quantifier' : null;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Whether a pattern repeats a group that itself repeats or chooses (`(a+)+`, `((a+))*`, `(a|aa){2,}`): the shapes
|
|
147
|
+
* that backtrack exponentially. A group is risky when it holds a repeated atom, an alternation or a risky group; a
|
|
148
|
+
* risky group followed by `*`, `+` or `{n,}` / `{n,m}` (m > 1) is refused. `?` and `{0,1}` repeat nothing.
|
|
149
|
+
*/
|
|
150
|
+
function repeatsARepeat(source) {
|
|
151
|
+
const stack = [{ risky: false }];
|
|
152
|
+
let inClass = false;
|
|
153
|
+
const repeatAt = (j) => {
|
|
154
|
+
const c = source[j];
|
|
155
|
+
if (c === '*' || c === '+') return 1;
|
|
156
|
+
if (c !== '{') return 0;
|
|
157
|
+
const m = /^\{(\d+)(,(\d*))?\}/.exec(source.slice(j));
|
|
158
|
+
if (!m) return 0;
|
|
159
|
+
const max = m[2] === undefined ? Number(m[1]) : (m[3] === '' ? Infinity : Number(m[3]));
|
|
160
|
+
return max > 1 ? m[0].length : -m[0].length; // negative: a bounded once-at-most count, skipped as no repeat
|
|
161
|
+
};
|
|
162
|
+
for (let i = 0; i < source.length; i++) {
|
|
163
|
+
const c = source[i];
|
|
164
|
+
if (c === '\\') { i++; const r = repeatAt(i + 1); if (r > 0) stack[stack.length - 1].risky = true; continue; }
|
|
165
|
+
if (inClass) { if (c === ']') { inClass = false; if (repeatAt(i + 1) > 0) stack[stack.length - 1].risky = true; } continue; }
|
|
166
|
+
if (c === '[') { inClass = true; continue; }
|
|
167
|
+
if (c === '(') { stack.push({ risky: false }); continue; }
|
|
168
|
+
if (c === '|') { if (stack.length > 1) stack[stack.length - 1].risky = true; continue; }
|
|
169
|
+
if (c === ')') {
|
|
170
|
+
const group = stack.length > 1 ? stack.pop() : { risky: false };
|
|
171
|
+
const r = repeatAt(i + 1);
|
|
172
|
+
if (r > 0 && group.risky) return true;
|
|
173
|
+
if (r > 0 || group.risky) stack[stack.length - 1].risky = true;
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
if (c === '*' || c === '+' || c === '?' || c === '{') continue; // a quantifier: its atom was looked at already
|
|
177
|
+
if (repeatAt(i + 1) > 0) stack[stack.length - 1].risky = true;
|
|
178
|
+
}
|
|
179
|
+
return false;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* Why a `check` of an assertion cannot be used, or null. `{ type, value?, in?, ignoreCase?, flags? }`: `in` is
|
|
184
|
+
* "answer" (the default: the final reply) or a relative path of a file the run wrote.
|
|
185
|
+
*/
|
|
186
|
+
export function checkProblem(check) {
|
|
187
|
+
if (!isObject(check)) return 'A check is an object: { "type": …, "value": … }.';
|
|
188
|
+
if (!ASSERTION_CHECKS.includes(check.type)) return `A check's "type" is one of ${ASSERTION_CHECKS.join(', ')}.`;
|
|
189
|
+
if (own(check, 'in') && check.in !== 'answer' && !isRelativeFile(check.in)) return 'A check\'s "in" is "answer" or a relative path of a file the run writes.';
|
|
190
|
+
if (own(check, 'ignoreCase') && typeof check.ignoreCase !== 'boolean') return '"ignoreCase" is true or false.';
|
|
191
|
+
if (['contains', 'not_contains'].includes(check.type)) {
|
|
192
|
+
if (typeof check.value !== 'string' || !check.value.trim()) return `A ${check.type} check has a non-empty "value".`;
|
|
193
|
+
if (check.value.length > CHECK_LIMITS.valueChars) return `A check's "value" is at most ${CHECK_LIMITS.valueChars} characters.`;
|
|
194
|
+
}
|
|
195
|
+
if (check.type === 'regex') {
|
|
196
|
+
const why = regexProblem(check.value, own(check, 'flags') ? check.flags : '');
|
|
197
|
+
if (why) return { empty: 'A regex check has a pattern in "value".', 'too-long': `A pattern is at most ${CHECK_LIMITS.patternChars} characters.`, flags: '"flags" holds only i, m and s.', invalid: 'The pattern is not a valid regular expression.', backreference: 'A pattern with a backreference is refused (it can take very long on a crafted answer).', 'nested-quantifier': 'A pattern with a repeated group that repeats inside (like (a+)+ or (a|b)*) is refused: it can take very long on a crafted answer.' }[why];
|
|
198
|
+
}
|
|
199
|
+
if (['file_exists', 'json_valid'].includes(check.type) && !isRelativeFile(check.path ?? check.in)) return `A ${check.type} check names the file in "path" (a relative path the run writes).`;
|
|
200
|
+
if (check.type === 'max_lines' && !(Number.isInteger(check.value) && check.value >= 1 && check.value <= CHECK_LIMITS.maxLines)) return `A max_lines check's "value" is a whole number from 1 to ${CHECK_LIMITS.maxLines}.`;
|
|
201
|
+
return null;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** A text as a duplicate check compares it: case, spacing and punctuation aside. */
|
|
205
|
+
export function normalizedCaseText(text) {
|
|
206
|
+
return String(text ?? '').normalize('NFKC').toLowerCase().replace(/[\p{P}\p{S}]+/gu, ' ').replace(/\s+/g, ' ').trim();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const isObject = (v) => v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
210
|
+
const own = (o, k) => Object.prototype.hasOwnProperty.call(o, k);
|
|
211
|
+
|
|
212
|
+
/** Every key path of a JSON value whose key names tokens (token counts here would be estimates). */
|
|
213
|
+
function tokenKeys(value, path = '', out = []) {
|
|
214
|
+
if (out.length >= 10) return out;
|
|
215
|
+
if (Array.isArray(value)) value.forEach((v, i) => tokenKeys(v, `${path}[${i}]`, out));
|
|
216
|
+
else if (isObject(value)) {
|
|
217
|
+
for (const k of Object.keys(value)) {
|
|
218
|
+
const here = path ? `${path}.${k}` : k;
|
|
219
|
+
if (/tokens?/i.test(k)) out.push(here);
|
|
220
|
+
tokenKeys(value[k], here, out);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
return out;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Check a skill's evals/evals.json.
|
|
228
|
+
* @param {string|object} input the file's text, or its parsed value
|
|
229
|
+
* @param {object} [o]
|
|
230
|
+
* @param {string} [o.skillName] the skill's name (`skill_name` should match it)
|
|
231
|
+
* @param {string[]} [o.skillTexts] the skill's own instructions (SKILL.md, references): a held-out case's text must
|
|
232
|
+
* not be in them
|
|
233
|
+
* @param {string[]} [o.packageFiles] the paths of the package: a case's `files` must name files of it
|
|
234
|
+
* @param {string} [o.description] the skill's description: a near-miss that shares no word with it is a warning
|
|
235
|
+
* @returns {{ ok: boolean, errors: {code, path, message}[], warnings: {code, path, message}[], summary: object|null }}
|
|
236
|
+
*/
|
|
237
|
+
export function validateEvals(input, { skillName = '', skillTexts = [], packageFiles = null, description = '' } = {}) {
|
|
238
|
+
const errors = [];
|
|
239
|
+
const warnings = [];
|
|
240
|
+
const err = (code, path, message, extra = null) => errors.push({ code, path, message, ...(extra || {}) });
|
|
241
|
+
const warn = (code, path, message) => warnings.push({ code, path, message });
|
|
242
|
+
let value = input;
|
|
243
|
+
if (typeof input === 'string') {
|
|
244
|
+
if (input.length > EVALS_LIMITS.maxBytes) { err('too-large', '', `evals.json is larger than ${Math.round(EVALS_LIMITS.maxBytes / 1024)} KB.`); return { ok: false, errors, warnings, summary: null }; }
|
|
245
|
+
try { value = JSON.parse(input.replace(/^/, '')); } catch (e) { err('json', '', `evals.json is not valid JSON (${e?.message || e}).`); return { ok: false, errors, warnings, summary: null }; }
|
|
246
|
+
}
|
|
247
|
+
if (!isObject(value)) { err('shape', '', 'evals.json holds one object with "evals" and "triggers".'); return { ok: false, errors, warnings, summary: null }; }
|
|
248
|
+
if (!own(value, 'schema')) warn('schema-missing', 'schema', `No "schema": "${EVALS_SCHEMA}".`);
|
|
249
|
+
else if (value.schema !== EVALS_SCHEMA) err('schema', 'schema', `"schema" is "${String(value.schema).slice(0, 60)}"; this file format is "${EVALS_SCHEMA}".`);
|
|
250
|
+
if (typeof value.skill_name !== 'string' || !value.skill_name.trim()) warn('skill-name-missing', 'skill_name', 'No "skill_name".');
|
|
251
|
+
else if (skillName && value.skill_name !== skillName) warn('skill-name-mismatch', 'skill_name', `"skill_name" is "${value.skill_name.slice(0, 64)}", the skill is "${skillName}".`);
|
|
252
|
+
|
|
253
|
+
const evals = own(value, 'evals') ? value.evals : [];
|
|
254
|
+
const triggers = own(value, 'triggers') ? value.triggers : [];
|
|
255
|
+
if (!Array.isArray(evals)) err('shape', 'evals', '"evals" is a list of task cases.');
|
|
256
|
+
if (!Array.isArray(triggers)) err('shape', 'triggers', '"triggers" is a list of trigger cases.');
|
|
257
|
+
const taskList = Array.isArray(evals) ? evals : [];
|
|
258
|
+
const triggerList = Array.isArray(triggers) ? triggers : [];
|
|
259
|
+
if (taskList.length + triggerList.length > EVALS_LIMITS.maxCases) err('too-many', '', `${taskList.length + triggerList.length} cases; at most ${EVALS_LIMITS.maxCases}.`);
|
|
260
|
+
|
|
261
|
+
const ids = new Map(); // id → path
|
|
262
|
+
const texts = new Map(); // normalized text → { path, split }
|
|
263
|
+
const groups = new Map(); // group → Set(split)
|
|
264
|
+
const splits = { train: 0, validation: 0, test: 0 };
|
|
265
|
+
const testTexts = [];
|
|
266
|
+
let positive = 0; let nearMiss = 0; let tasks = 0;
|
|
267
|
+
const files = Array.isArray(packageFiles) ? new Set(packageFiles) : null;
|
|
268
|
+
|
|
269
|
+
const common = (c, path) => {
|
|
270
|
+
if (typeof c.id !== 'string' || !ID_RE.test(c.id)) err('id', `${path}.id`, 'Each case has an "id": lowercase letters, digits, ".", "_" or "-", at most 64 characters.');
|
|
271
|
+
else if (ids.has(c.id)) err('duplicate-id', `${path}.id`, `The id "${c.id}" is used twice (also ${ids.get(c.id)}).`);
|
|
272
|
+
else ids.set(c.id, path);
|
|
273
|
+
const split = own(c, 'split') ? c.split : null;
|
|
274
|
+
if (!EVAL_SPLITS.includes(split)) err('split', `${path}.split`, `"split" is one of ${EVAL_SPLITS.join(', ')}.`);
|
|
275
|
+
else splits[split] += 1;
|
|
276
|
+
const group = typeof c.group === 'string' && c.group.trim() ? c.group.trim() : (typeof c.id === 'string' ? c.id : null);
|
|
277
|
+
if (own(c, 'group') && !(typeof c.group === 'string' && ID_RE.test(c.group))) err('group', `${path}.group`, '"group" names the request this case is a variant of: a lowercase slug.');
|
|
278
|
+
if (group && EVAL_SPLITS.includes(split)) {
|
|
279
|
+
if (!groups.has(group)) groups.set(group, new Set());
|
|
280
|
+
groups.get(group).add(split);
|
|
281
|
+
}
|
|
282
|
+
return { split };
|
|
283
|
+
};
|
|
284
|
+
const text = (c, key, max, path, split) => {
|
|
285
|
+
const v = c[key];
|
|
286
|
+
if (typeof v !== 'string' || !v.trim()) { err('empty', `${path}.${key}`, `"${key}" is a non-empty text.`); return; }
|
|
287
|
+
if (v.length > max) err('too-long', `${path}.${key}`, `"${key}" is longer than ${max} characters.`);
|
|
288
|
+
const n = normalizedCaseText(v);
|
|
289
|
+
const prior = texts.get(n);
|
|
290
|
+
if (prior) {
|
|
291
|
+
if (prior.split !== split) err('duplicate-across-splits', `${path}.${key}`, `The same request is also ${prior.path} (split ${prior.split}): a request in two splits leaks the held-out answer.`);
|
|
292
|
+
else err('duplicate-prompt', `${path}.${key}`, `The same request is also ${prior.path}: write each request once.`);
|
|
293
|
+
} else texts.set(n, { path, split });
|
|
294
|
+
if (split === 'test') testTexts.push({ text: v, path: `${path}.${key}` });
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
taskList.forEach((c, i) => {
|
|
298
|
+
const path = `evals[${i}]`;
|
|
299
|
+
if (!isObject(c)) { err('shape', path, 'A task case is an object.'); return; }
|
|
300
|
+
tasks += 1;
|
|
301
|
+
const { split } = common(c, path);
|
|
302
|
+
text(c, 'prompt', EVALS_LIMITS.promptChars, path, split);
|
|
303
|
+
if (!own(c, 'expected_output')) warn('expected-missing', `${path}.expected_output`, 'Say what a good answer holds ("expected_output").');
|
|
304
|
+
else if (typeof c.expected_output !== 'string') err('expected', `${path}.expected_output`, '"expected_output" is text.');
|
|
305
|
+
else if (c.expected_output.length > EVALS_LIMITS.expectedChars) err('too-long', `${path}.expected_output`, `"expected_output" is longer than ${EVALS_LIMITS.expectedChars} characters.`);
|
|
306
|
+
// A held-out case's answer is sealed with its prompt: the skill must not hold it either.
|
|
307
|
+
if (split === 'test' && typeof c.expected_output === 'string') testTexts.push({ text: c.expected_output, path: `${path}.expected_output`, what: 'answer' });
|
|
308
|
+
if (own(c, 'files')) {
|
|
309
|
+
if (!Array.isArray(c.files) || c.files.length > EVALS_LIMITS.files) err('files', `${path}.files`, `"files" is a list of at most ${EVALS_LIMITS.files} paths inside the skill.`);
|
|
310
|
+
else c.files.forEach((f, j) => {
|
|
311
|
+
if (typeof f !== 'string' || !f || f.startsWith('/') || /^[A-Za-z]:/.test(f) || f.split('/').includes('..') || f.includes('\\')) err('file-path', `${path}.files[${j}]`, 'A file is a relative path inside the skill (forward slashes, no "..").');
|
|
312
|
+
else if (evalFixturePathProblem(f)) { const why = evalFixturePathProblem(f); err({ 'fixture-alias': 'file-alias', 'fixture-instructions': 'file-instructions' }[why.code] || 'file-config', `${path}.files[${j}]`, why.message, { file: f, name: why.name, who: why.who }); }
|
|
313
|
+
else if (files && !files.has(f)) err('file-missing', `${path}.files[${j}]`, `"${f}" is not a file of the skill.`);
|
|
314
|
+
});
|
|
315
|
+
}
|
|
316
|
+
const assertions = own(c, 'assertions') ? c.assertions : [];
|
|
317
|
+
if (!Array.isArray(assertions)) err('assertions', `${path}.assertions`, '"assertions" is a list.');
|
|
318
|
+
else {
|
|
319
|
+
if (!assertions.length) warn('no-assertions', `${path}.assertions`, 'A task case without assertions cannot be graded: add what must be true of the answer.');
|
|
320
|
+
if (assertions.length > EVALS_LIMITS.assertions) err('too-many', `${path}.assertions`, `At most ${EVALS_LIMITS.assertions} assertions.`);
|
|
321
|
+
const assertionIds = new Set();
|
|
322
|
+
assertions.forEach((a, j) => {
|
|
323
|
+
const t = typeof a === 'string' ? a : (isObject(a) && typeof a.text === 'string' ? a.text : null);
|
|
324
|
+
if (t === null || !t.trim()) err('assertion', `${path}.assertions[${j}]`, 'An assertion is a non-empty text (or { "text": … }).');
|
|
325
|
+
else if (t.length > EVALS_LIMITS.assertionChars) err('too-long', `${path}.assertions[${j}]`, `An assertion is at most ${EVALS_LIMITS.assertionChars} characters.`);
|
|
326
|
+
if (split === 'test' && typeof t === 'string') testTexts.push({ text: t, path: `${path}.assertions[${j}]`, what: 'check' });
|
|
327
|
+
if (!isObject(a)) return;
|
|
328
|
+
if (own(a, 'id')) {
|
|
329
|
+
if (typeof a.id !== 'string' || !ID_RE.test(a.id)) err('assertion-id', `${path}.assertions[${j}].id`, 'An assertion\'s "id" is a lowercase slug.');
|
|
330
|
+
else if (assertionIds.has(a.id)) err('duplicate-assertion-id', `${path}.assertions[${j}].id`, `The assertion id "${a.id}" is used twice in this case.`);
|
|
331
|
+
else assertionIds.add(a.id);
|
|
332
|
+
}
|
|
333
|
+
if (own(a, 'check')) { const why = checkProblem(a.check); if (why) err('check', `${path}.assertions[${j}].check`, why); }
|
|
334
|
+
if (own(a, 'kind') && !ASSERTION_KINDS.includes(a.kind)) err('assertion-kind', `${path}.assertions[${j}].kind`, `An assertion's "kind" is ${ASSERTION_KINDS.join(' or ')}.`);
|
|
335
|
+
if (own(a, 'required') && typeof a.required !== 'boolean') err('assertion-required', `${path}.assertions[${j}].required`, '"required" is true or false.');
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
for (const key of ['compare', 'shell']) if (own(c, key) && typeof c[key] !== 'boolean') err(key, `${path}.${key}`, `"${key}" is true or false.`);
|
|
339
|
+
});
|
|
340
|
+
triggerList.forEach((c, i) => {
|
|
341
|
+
const path = `triggers[${i}]`;
|
|
342
|
+
if (!isObject(c)) { err('shape', path, 'A trigger case is an object.'); return; }
|
|
343
|
+
const { split } = common(c, path);
|
|
344
|
+
text(c, 'query', EVALS_LIMITS.queryChars, path, split);
|
|
345
|
+
if (typeof c.should_trigger !== 'boolean') err('should-trigger', `${path}.should_trigger`, '"should_trigger" is true or false.');
|
|
346
|
+
else if (c.should_trigger) positive += 1;
|
|
347
|
+
else nearMiss += 1;
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
for (const [group, set] of groups) {
|
|
351
|
+
if (set.size > 1) err('group-split-leak', `group ${group}`, `The group "${group}" is in ${[...set].join(' and ')}: a group lives in one split only, or the held-out cases are seen in training.`);
|
|
352
|
+
}
|
|
353
|
+
// A held-out case's request, expected answer or checks in the skill's own instructions would give the answer away.
|
|
354
|
+
const haystack = normalizedCaseText((Array.isArray(skillTexts) ? skillTexts : []).join('\n'));
|
|
355
|
+
for (const t of testTexts) {
|
|
356
|
+
const needle = normalizedCaseText(t.text);
|
|
357
|
+
if (needle.length < 24 || !haystack.includes(needle)) continue;
|
|
358
|
+
const what = t.what === 'answer' ? 'expected answer' : t.what === 'check' ? 'check (assertion)' : 'request';
|
|
359
|
+
err('test-leak', t.path, `A held-out (test) case's ${what} is written into the skill itself: move it out of SKILL.md and the references, or the case out of the test split.`);
|
|
360
|
+
}
|
|
361
|
+
const tokenPaths = tokenKeys(value);
|
|
362
|
+
if (tokenPaths.length) warn('token-field', tokenPaths[0], `A token count here can only be an estimate (characters are not tokens): leave ${tokenPaths.length === 1 ? 'it' : 'them'} out (${tokenPaths.slice(0, 3).join(', ')}).`);
|
|
363
|
+
|
|
364
|
+
if (tasks < EVALS_RECIPE.tasks) warn('few-tasks', 'evals', `${tasks} task case${tasks === 1 ? '' : 's'}; a substantive skill starts with at least ${EVALS_RECIPE.tasks}.`);
|
|
365
|
+
if (positive < EVALS_RECIPE.positive) warn('few-positive', 'triggers', `${positive} trigger${positive === 1 ? '' : 's'} that should pick the skill; at least ${EVALS_RECIPE.positive}.`);
|
|
366
|
+
if (nearMiss < EVALS_RECIPE.nearMiss) warn('few-near-miss', 'triggers', `${nearMiss} near-miss trigger${nearMiss === 1 ? '' : 's'} (shares words, different intent); at least ${EVALS_RECIPE.nearMiss}.`);
|
|
367
|
+
if (tasks + positive + nearMiss > 0 && splits.test === 0) warn('no-held-out', 'split', 'No case is held out ("test"): nothing can show the skill generalises.');
|
|
368
|
+
// A near-miss shares words with what the skill does and asks for something else: one that shares none with its
|
|
369
|
+
// description is an obviously unrelated request, which tells nothing about where the skill's boundary is.
|
|
370
|
+
if (typeof description === 'string' && description.trim()) {
|
|
371
|
+
const words = contentWords(description);
|
|
372
|
+
triggerList.forEach((c, i) => {
|
|
373
|
+
if (!isObject(c) || c.should_trigger !== false || typeof c.query !== 'string') return;
|
|
374
|
+
if (![...contentWords(c.query)].some((w) => words.has(w))) warn('near-miss-unrelated', `triggers[${i}].query`, 'This near-miss shares no word with the skill\'s description: it tests little. A near-miss uses the same words for another intent.');
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
return {
|
|
379
|
+
ok: errors.length === 0, errors: errors.slice(0, 100), warnings: warnings.slice(0, 100),
|
|
380
|
+
summary: { tasks, triggers: positive + nearMiss, positive, nearMiss, splits, groups: groups.size },
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const STOP_WORDS = new Set('this that with from your what when where which into about them they their there then than have will would could should only also each such some more most other over under after before because these those just very much many does done make made used uses using like into onto when were been being here want need asks asked user users skill skills'.split(' '));
|
|
385
|
+
/** The content words of a text (four letters or more, common words aside), for the near-miss check. */
|
|
386
|
+
function contentWords(text) {
|
|
387
|
+
return new Set(normalizedCaseText(text).split(' ').filter((w) => w.length >= 4 && !STOP_WORDS.has(w)).map((w) => w.replace(/(?:ing|ed|es|s)$/, '')).filter((w) => w.length >= 4));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
/**
|
|
391
|
+
* The suite as an evaluation reads it: validateEvals()'s answer plus `cases`, each case once, normalized:
|
|
392
|
+
* task { kind: 'task', id, group, split, prompt, expected, files, assertions, compare, shell }
|
|
393
|
+
* trigger { kind: 'trigger', id, group, split, query, shouldTrigger }
|
|
394
|
+
* An assertion is `{ id, text, check, kind, required }` (`id` the case's own or a1, a2…, in order; `check` null for
|
|
395
|
+
* one a grader decides). `cases` is empty while the file has errors: a suite with errors does not run.
|
|
396
|
+
*/
|
|
397
|
+
export function readSuite(input, o = {}) {
|
|
398
|
+
const checked = validateEvals(input, o);
|
|
399
|
+
if (!checked.ok) return { ...checked, cases: [] };
|
|
400
|
+
let value = input;
|
|
401
|
+
if (typeof input === 'string') value = JSON.parse(input.replace(/^/, ''));
|
|
402
|
+
const cases = [];
|
|
403
|
+
for (const c of Array.isArray(value.evals) ? value.evals : []) {
|
|
404
|
+
const assertions = (Array.isArray(c.assertions) ? c.assertions : []).map((a, j) => (typeof a === 'string'
|
|
405
|
+
? { id: `a${j + 1}`, text: a.trim(), check: null, kind: 'outcome', required: true }
|
|
406
|
+
: { id: typeof a.id === 'string' ? a.id : `a${j + 1}`, text: a.text.trim(), check: isObject(a.check) ? { ...a.check } : null, kind: a.kind === 'preference' ? 'preference' : 'outcome', required: a.required !== false }));
|
|
407
|
+
cases.push({
|
|
408
|
+
kind: 'task', id: c.id, group: typeof c.group === 'string' && c.group.trim() ? c.group.trim() : c.id, split: c.split,
|
|
409
|
+
prompt: c.prompt, expected: typeof c.expected_output === 'string' ? c.expected_output : '', files: Array.isArray(c.files) ? [...c.files] : [],
|
|
410
|
+
assertions, compare: c.compare === true, shell: c.shell === true,
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
for (const c of Array.isArray(value.triggers) ? value.triggers : []) {
|
|
414
|
+
cases.push({ kind: 'trigger', id: c.id, group: typeof c.group === 'string' && c.group.trim() ? c.group.trim() : c.id, split: c.split, query: c.query, shouldTrigger: c.should_trigger === true });
|
|
415
|
+
}
|
|
416
|
+
return { ...checked, cases };
|
|
417
|
+
}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Skills Studio: the metadata form
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The form beside the editor is a view of the document's frontmatter, never a second copy of it: what each
|
|
6
|
+
// field shows is read from the text (ss-frontmatter.js parse), and a change in a field becomes a change of that
|
|
7
|
+
// one key in the text (patch). A key the form has no field for, a comment, the order and the quoting are
|
|
8
|
+
// never touched, and a field whose value is not of the kind the field writes (a description that is a list, an
|
|
9
|
+
// aliased value) is shown and left alone: "edit it in the text". No DOM and no imports.
|
|
10
|
+
|
|
11
|
+
const NAME = { key: 'name', control: 'text', label: 'Name' };
|
|
12
|
+
const DESCRIPTION = { key: 'description', control: 'textarea', label: 'Description' };
|
|
13
|
+
|
|
14
|
+
export const FORM_FIELDS = Object.freeze({
|
|
15
|
+
skill: [
|
|
16
|
+
NAME, DESCRIPTION,
|
|
17
|
+
{ key: 'argument-hint', control: 'text', label: 'Argument Hint' },
|
|
18
|
+
{ key: 'allowed-tools', control: 'tools', label: 'Allowed Tools' },
|
|
19
|
+
{ key: 'user-invocable', control: 'checkbox', label: 'User-invocable', default: true },
|
|
20
|
+
{ key: 'disable-model-invocation', control: 'checkbox', label: 'Disable model invocation', default: false },
|
|
21
|
+
],
|
|
22
|
+
agent: [
|
|
23
|
+
NAME, DESCRIPTION,
|
|
24
|
+
{ key: 'model', control: 'model', label: 'Model' },
|
|
25
|
+
{ key: 'tools', control: 'list-text', label: 'Tools', hint: '(comma-separated)' },
|
|
26
|
+
{ key: 'maxTurns', control: 'number', label: 'Max Turns' },
|
|
27
|
+
{ key: 'color', control: 'text', label: 'Color' },
|
|
28
|
+
],
|
|
29
|
+
command: [DESCRIPTION],
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
/** The tools the tools picker offers (a value outside it is kept as it is). */
|
|
33
|
+
export const KNOWN_TOOLS = Object.freeze(['Read', 'Write', 'Edit', 'Bash', 'Grep', 'Glob', 'WebFetch', 'WebSearch', 'Task', 'NotebookEdit']);
|
|
34
|
+
/** The models the agent model picker offers; '' is "inherit". A value outside it is offered too, as it is. */
|
|
35
|
+
export const KNOWN_MODELS = Object.freeze(['sonnet', 'haiku', 'opus']);
|
|
36
|
+
|
|
37
|
+
const own = (data, key) => !!data && Object.prototype.hasOwnProperty.call(data, key);
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A value of any kind as text, for a field that only shows it. Never throws: a mapping read from YAML has no
|
|
41
|
+
* prototype (`String()` of it throws), and an alias can make a value hold itself (JSON cannot write that).
|
|
42
|
+
*/
|
|
43
|
+
export function shownText(raw) {
|
|
44
|
+
if (raw === undefined || raw === null) return '';
|
|
45
|
+
if (typeof raw === 'string') return raw;
|
|
46
|
+
if (typeof raw === 'number' || typeof raw === 'boolean' || typeof raw === 'bigint') return String(raw);
|
|
47
|
+
try { return JSON.stringify(raw) ?? ''; } catch { return Array.isArray(raw) ? '(a list)' : '(a mapping)'; }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* What the form shows for a document.
|
|
52
|
+
* @param {'skill'|'command'|'agent'} type
|
|
53
|
+
* @param {object} parsed ss-frontmatter.js parse()
|
|
54
|
+
* @returns {{ editable: boolean, reason: string|null, fields: { key, control, label, hint?, value, present, writable, note }[] }}
|
|
55
|
+
*/
|
|
56
|
+
export function formView(type, parsed) {
|
|
57
|
+
const defs = FORM_FIELDS[type] || [];
|
|
58
|
+
const data = parsed?.data || null;
|
|
59
|
+
const hasErrors = (parsed?.diagnostics || []).some((d) => d.severity === 'error');
|
|
60
|
+
const editable = !!parsed?.editable && !!data && !hasErrors;
|
|
61
|
+
const reason = editable ? null
|
|
62
|
+
: hasErrors ? 'The frontmatter has errors: fix them in the text. The fields are read-only until then.'
|
|
63
|
+
: 'This frontmatter is written in a way the form does not change: edit it in the text.';
|
|
64
|
+
const aliased = new Set((parsed?.keys || []).filter((k) => k.alias).map((k) => k.key));
|
|
65
|
+
const fields = defs.map((def) => {
|
|
66
|
+
const present = own(data, def.key);
|
|
67
|
+
const raw = present ? data[def.key] : undefined;
|
|
68
|
+
let value; let writable = editable && !aliased.has(def.key); let note = null;
|
|
69
|
+
switch (def.control) {
|
|
70
|
+
case 'text':
|
|
71
|
+
case 'textarea':
|
|
72
|
+
case 'model':
|
|
73
|
+
if (raw === undefined || raw === null) value = '';
|
|
74
|
+
else if (typeof raw === 'string') value = def.control === 'textarea' ? raw.replace(/\n+$/, '') : raw;
|
|
75
|
+
else { value = shownText(raw); writable = false; note = 'Not text: edit it in the text.'; }
|
|
76
|
+
break;
|
|
77
|
+
case 'tools':
|
|
78
|
+
if (raw === undefined || raw === null) value = [];
|
|
79
|
+
else if (Array.isArray(raw) && raw.every((t) => typeof t === 'string')) value = raw.slice();
|
|
80
|
+
else if (typeof raw === 'string') value = raw; // "Read Grep" stays one text value
|
|
81
|
+
else { value = []; writable = false; note = 'Not a list of tool names: edit it in the text.'; }
|
|
82
|
+
break;
|
|
83
|
+
case 'list-text':
|
|
84
|
+
if (raw === undefined || raw === null) value = '';
|
|
85
|
+
else if (typeof raw === 'string') value = raw;
|
|
86
|
+
else if (Array.isArray(raw) && raw.every((t) => typeof t === 'string')) value = raw.join(', ');
|
|
87
|
+
else { value = ''; writable = false; note = 'Not a list of tool names: edit it in the text.'; }
|
|
88
|
+
break;
|
|
89
|
+
case 'checkbox':
|
|
90
|
+
if (raw === undefined) value = !!def.default;
|
|
91
|
+
else if (typeof raw === 'boolean') value = raw;
|
|
92
|
+
else { value = !!def.default; writable = false; note = 'Not true or false: edit it in the text.'; }
|
|
93
|
+
break;
|
|
94
|
+
case 'number':
|
|
95
|
+
if (raw === undefined || raw === null) value = '';
|
|
96
|
+
else if (Number.isInteger(raw) && raw >= 0) value = String(raw);
|
|
97
|
+
else { value = shownText(raw); writable = false; note = 'Not a whole number: edit it in the text.'; }
|
|
98
|
+
break;
|
|
99
|
+
default:
|
|
100
|
+
value = shownText(raw); writable = false;
|
|
101
|
+
}
|
|
102
|
+
if (aliased.has(def.key)) note = 'An alias: edit it in the text.';
|
|
103
|
+
return { ...def, value, present, writable, note };
|
|
104
|
+
});
|
|
105
|
+
return { editable, reason, fields };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* What to patch a key with, for what the person put in its field. `undefined` removes the key.
|
|
110
|
+
* @param {object} o
|
|
111
|
+
* @param {object} o.field a field of formView()
|
|
112
|
+
* @param {any} o.input what the control holds now (text, a boolean, a list of tools)
|
|
113
|
+
* @param {any} o.current the key's value in the document now (undefined: not there)
|
|
114
|
+
* @param {boolean} o.savedHasKey the key is in the file as it was last saved (a checkbox put back to its default
|
|
115
|
+
* then writes the default, instead of leaving out a key the file had)
|
|
116
|
+
* @returns {{ value: any } | { invalid: string }}
|
|
117
|
+
*/
|
|
118
|
+
export function fieldPatchValue({ field, input, current, savedHasKey = false }) {
|
|
119
|
+
switch (field.control) {
|
|
120
|
+
case 'text':
|
|
121
|
+
case 'model': {
|
|
122
|
+
const text = String(input ?? '');
|
|
123
|
+
return { value: text.trim() === '' ? undefined : text.trim() };
|
|
124
|
+
}
|
|
125
|
+
case 'textarea': {
|
|
126
|
+
const text = String(input ?? '').replace(/\r\n?/g, '\n');
|
|
127
|
+
if (text.trim() === '') return { value: undefined };
|
|
128
|
+
// A block value keeps the line endings it closed with (a folded `>` description reads "…\n").
|
|
129
|
+
const tail = typeof current === 'string' ? (/\n+$/.exec(current)?.[0] || '') : '';
|
|
130
|
+
return { value: text.replace(/\n+$/, '') + tail };
|
|
131
|
+
}
|
|
132
|
+
case 'tools': {
|
|
133
|
+
if (typeof input === 'string') return { value: input.trim() === '' ? undefined : input.trim() };
|
|
134
|
+
const list = (Array.isArray(input) ? input : []).map((t) => String(t).trim()).filter(Boolean);
|
|
135
|
+
return { value: list.length ? [...new Set(list)] : undefined };
|
|
136
|
+
}
|
|
137
|
+
case 'list-text': {
|
|
138
|
+
const text = String(input ?? '').trim();
|
|
139
|
+
if (!text) return { value: undefined };
|
|
140
|
+
// Written the way the file had it: a list stays a list, text stays text.
|
|
141
|
+
if (Array.isArray(current)) return { value: text.split(',').map((t) => t.trim()).filter(Boolean) };
|
|
142
|
+
return { value: text };
|
|
143
|
+
}
|
|
144
|
+
case 'checkbox': {
|
|
145
|
+
const on = !!input;
|
|
146
|
+
// Back to the default on a file that never named the key: the key goes again, the file is as it was.
|
|
147
|
+
if (on === !!field.default && !savedHasKey) return { value: undefined };
|
|
148
|
+
return { value: on };
|
|
149
|
+
}
|
|
150
|
+
case 'number': {
|
|
151
|
+
const text = String(input ?? '').trim();
|
|
152
|
+
if (!text) return { value: undefined };
|
|
153
|
+
if (!/^\d+$/.test(text)) return { invalid: 'A whole number, 0 or more.' };
|
|
154
|
+
return { value: Number.parseInt(text, 10) };
|
|
155
|
+
}
|
|
156
|
+
default:
|
|
157
|
+
return { invalid: 'This field is not written by the form.' };
|
|
158
|
+
}
|
|
159
|
+
}
|