synabun 2.0.10 → 2.1.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 +86 -0
- package/README.md +25 -2
- package/THIRD-PARTY-LICENSES.md +22 -0
- package/bin/synabun.js +71 -24
- package/docs/whatsapp.md +2 -2
- 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 +17 -8
- package/lib/desktop-app.js +7 -1
- package/lib/external-tools.js +1 -1
- 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 +228 -0
- package/lib/virustotal.mjs +192 -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/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/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/neural-interface.d.ts +16 -10
- 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 +20 -62
- 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/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 +10 -10
- package/mcp-server/dist/tools/agents.d.ts.map +1 -1
- package/mcp-server/dist/tools/agents.js +22 -13
- 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/image-tools.d.ts +1 -1
- package/mcp-server/dist/tools/image-tools.js +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 +639 -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/codex-config-heal.ts +80 -29
- package/mcp-server/src/services/memory-judge-backfill.ts +7 -1
- package/mcp-server/src/services/neural-interface.ts +79 -20
- package/mcp-server/src/services/server-instructions.ts +27 -63
- 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/whiteboard-geometry.ts +94 -9
- package/mcp-server/src/tools/agents.ts +22 -14
- package/mcp-server/src/tools/image-tools.ts +1 -1
- package/mcp-server/src/tools/skill-tools.ts +589 -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 +3463 -200
- package/neural-interface/i18n/pt-BR.json +3411 -151
- package/neural-interface/lib/assistant-api.js +127 -28
- 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-catalog.js +6 -7
- package/neural-interface/lib/assistant-dispatch.js +871 -156
- package/neural-interface/lib/assistant-envelope.js +3 -1
- package/neural-interface/lib/assistant-persona.js +80 -11
- package/neural-interface/lib/assistant-plan-permissions.js +10 -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 +13 -8
- package/neural-interface/lib/assistant-runtime.js +56 -18
- package/neural-interface/lib/assistant-task-prompt.js +78 -14
- package/neural-interface/lib/assistant-tools.js +51 -0
- package/neural-interface/lib/backup-service.js +620 -3
- 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 +83 -0
- package/neural-interface/lib/browser-network-guard.js +284 -0
- package/neural-interface/lib/browser-policy-roots.js +64 -0
- package/neural-interface/lib/browser-tool-policy.js +186 -31
- package/neural-interface/lib/claude-agent-bridge.js +4 -2
- package/neural-interface/lib/claude-hooks-status.js +103 -0
- package/neural-interface/lib/claude-temporary.js +5 -11
- package/neural-interface/lib/cli-install.js +18 -138
- package/neural-interface/lib/cli-resolver.js +4 -1
- package/neural-interface/lib/cli-update-planner.js +32 -31
- package/neural-interface/lib/cloudflared/tunnel.js +42 -2
- package/neural-interface/lib/codex-mcp-approvals.js +11 -0
- package/neural-interface/lib/codex-worker-app-server.js +172 -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/exec-loop-command.js +4 -8
- 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 +0 -29
- package/neural-interface/lib/mcp-external-servers.js +323 -0
- package/neural-interface/lib/{mcp-profile-refresh-coordinator.js → mcp-tools-refresh-coordinator.js} +4 -4
- package/neural-interface/lib/morelogin.js +30 -0
- package/neural-interface/lib/native-loop-providers.js +547 -47
- package/neural-interface/lib/native-loop-runtime.js +268 -41
- 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 +44 -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/peer-identity.js +763 -0
- package/neural-interface/lib/request-gate.js +17 -2
- package/neural-interface/lib/rulesets/api.js +1 -1
- package/neural-interface/lib/rulesets/fs-safe.js +1 -1
- package/neural-interface/lib/rulesets/installer.js +92 -27
- package/neural-interface/lib/rulesets/json-edit.js +1 -1
- package/neural-interface/lib/session-cache-builder.js +17 -30
- 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 +294 -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 +267 -0
- package/neural-interface/lib/skills/creation.js +843 -0
- package/neural-interface/lib/skills/deploy.js +564 -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 +214 -0
- package/neural-interface/lib/skills/eval-confinement.js +397 -0
- package/neural-interface/lib/skills/eval-sandbox.js +280 -0
- package/neural-interface/lib/skills/eval-trace.js +119 -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 +200 -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 +387 -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 +262 -0
- package/neural-interface/lib/skills/runtime-checks.js +355 -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 +522 -0
- package/neural-interface/lib/skills/telemetry.js +113 -0
- package/neural-interface/lib/sqlite-integrity-worker.js +22 -0
- package/neural-interface/lib/tool-lists.js +124 -0
- package/neural-interface/lib/whatsapp/bridge.js +6 -3
- package/neural-interface/lib/worker-confinement.js +1192 -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-relay-ports.js +227 -0
- package/neural-interface/lib/worker-resume.js +229 -0
- package/neural-interface/lib/worker-sandbox.js +299 -0
- package/neural-interface/package-lock.json +18 -12
- package/neural-interface/package.json +2 -2
- package/neural-interface/public/index.html +3 -1
- package/neural-interface/public/index2d.html +19 -11
- package/neural-interface/public/onboarding.html +2 -2
- package/neural-interface/public/shared/account-menu.js +8 -5
- package/neural-interface/public/shared/agent-runtime-options.js +35 -19
- 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 +33 -2
- 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 +49 -35
- package/neural-interface/public/shared/assistant/asst-control.js +157 -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-limit-dock.js +40 -0
- package/neural-interface/public/shared/assistant/asst-limit-wait.js +294 -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 +106 -11
- package/neural-interface/public/shared/assistant/asst-rack-styles.js +66 -49
- package/neural-interface/public/shared/assistant/asst-render.js +401 -77
- package/neural-interface/public/shared/assistant/asst-route.js +83 -12
- package/neural-interface/public/shared/assistant/asst-scenes.js +2 -0
- package/neural-interface/public/shared/assistant/asst-sidepanel.js +67 -4
- package/neural-interface/public/shared/assistant/asst-slash.js +9 -7
- package/neural-interface/public/shared/assistant/asst-state.js +11 -15
- package/neural-interface/public/shared/assistant/asst-stop.js +127 -0
- package/neural-interface/public/shared/assistant/asst-styles.js +59 -21
- package/neural-interface/public/shared/assistant/asst-tool-kinds.js +156 -16
- 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 +8 -7
- package/neural-interface/public/shared/cdx/cdx-panel.js +45 -149
- package/neural-interface/public/shared/cdx/cdx-protocol.js +28 -1
- package/neural-interface/public/shared/cdx/cdx-render.js +47 -29
- package/neural-interface/public/shared/cdx/cdx-requests.js +3 -3
- package/neural-interface/public/shared/cdx/cdx-skill-hints.js +54 -0
- package/neural-interface/public/shared/cdx/cdx-styles.js +11 -3
- package/neural-interface/public/shared/cdx/cdx-tabs.js +52 -39
- package/neural-interface/public/shared/cdx/cdx-ws.js +0 -1
- package/neural-interface/public/shared/cli-install-view.js +4 -8
- package/neural-interface/public/shared/cli-status.js +2 -2
- package/neural-interface/public/shared/constants.js +2 -2
- 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 +366 -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/crash-notice-view.js +168 -0
- package/neural-interface/public/shared/desktop-update-view.js +20 -0
- package/neural-interface/public/shared/editor/editor-panel-logic.js +2 -2
- package/neural-interface/public/shared/html-shell.js +9 -11
- 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 +47 -252
- 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-render.js +6 -21
- 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 +0 -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 +14 -47
- package/neural-interface/public/shared/page-session.js +96 -0
- 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-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/skills/ss-args.js +96 -0
- package/neural-interface/public/shared/skills/ss-creation.js +219 -0
- package/neural-interface/public/shared/skills/ss-dialog.js +130 -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 +141 -0
- package/neural-interface/public/shared/skills/ss-presets.js +117 -0
- package/neural-interface/public/shared/skills/ss-profiles.js +475 -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 +236 -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 +229 -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-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 +279 -0
- package/neural-interface/public/shared/skills/studio/sx-install.js +296 -0
- package/neural-interface/public/shared/skills/studio/sx-library-view.js +639 -0
- package/neural-interface/public/shared/skills/studio/sx-library.js +475 -0
- package/neural-interface/public/shared/skills/studio/sx-main.js +681 -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 +248 -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 +425 -0
- package/neural-interface/public/shared/styleguide/sg-panel.js +4 -2
- package/neural-interface/public/shared/styles.css +1525 -1779
- package/neural-interface/public/shared/synabun-mascot.js +1115 -9
- 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/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-automation-studio.js +91 -128
- package/neural-interface/public/shared/ui-claude-panel.js +77 -5
- 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-image-gallery.js +50 -47
- package/neural-interface/public/shared/ui-keybinds.js +26 -1
- package/neural-interface/public/shared/ui-menubar.js +8 -18
- package/neural-interface/public/shared/ui-native-loop-router-state.js +5 -0
- package/neural-interface/public/shared/ui-native-loop-router.js +7 -1
- package/neural-interface/public/shared/ui-navbar.js +4 -24
- package/neural-interface/public/shared/ui-panels.js +96 -65
- package/neural-interface/public/shared/ui-schedules-studio.js +507 -423
- package/neural-interface/public/shared/ui-sessions.js +5 -3
- package/neural-interface/public/shared/ui-settings.js +594 -462
- package/neural-interface/public/shared/ui-sidepanel-sessions.js +1 -1
- package/neural-interface/public/shared/ui-sidepanel-state.js +23 -12
- package/neural-interface/public/shared/ui-sidepanel-windows.js +16 -11
- 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 +124 -180
- package/neural-interface/public/shared/ui-tool-updates.js +3 -1
- package/neural-interface/public/shared/ui-tooltip.js +10 -5
- 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 +311 -58
- package/neural-interface/public/shared/ui-worker-end.js +4 -0
- 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 +1 -1
- 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-restart.js +12 -0
- package/neural-interface/public/sidepanel-session.html +1 -1
- 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 +3934 -2454
- package/neural-interface/templates/rulesets/core.md +1 -1
- package/neural-interface/templates/rulesets/legacy-hashes.json +18 -0
- package/neural-interface/templates/rulesets/manifest.json +8 -8
- package/npm-shrinkwrap.json +18 -12
- package/package.json +16 -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 +4 -4
- 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
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Skills Studio: the frontmatter of a skill, a command or an agent
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// A skill's metadata is YAML between two `---` lines at the top of its file. It is read here with a real YAML
|
|
6
|
+
// parser (the `yaml` package), and changed one key at a time: an edit replaces the text of that one key and
|
|
7
|
+
// nothing else, so every key the edit does not touch keeps its bytes (order, quoting, folding, comments, keys
|
|
8
|
+
// no form knows). A change is checked by reading the result again before it is handed back: the key holds the
|
|
9
|
+
// new value, every other key what it held, or the change is refused and the text stays as it was.
|
|
10
|
+
//
|
|
11
|
+
// What is not plain YAML is said, never guessed: a syntax error, a key given twice, a root that is not a
|
|
12
|
+
// mapping, an alias, a tag the parser does not resolve. While the YAML has errors nothing is patched: the
|
|
13
|
+
// form waits, the raw text is still editable and saveable.
|
|
14
|
+
//
|
|
15
|
+
// No imports: the YAML library is handed in (the server gives it the `yaml` package, the page the copy in
|
|
16
|
+
// /vendor/yaml/), so `node --test` loads this module as it is.
|
|
17
|
+
|
|
18
|
+
/** A frontmatter longer than this is not parsed (a bound on what one request makes the parser do). */
|
|
19
|
+
export const FRONTMATTER_MAX_CHARS = 128 * 1024;
|
|
20
|
+
/** Aliases a document may expand while it is read (the `yaml` package's guard against alias bombs). */
|
|
21
|
+
export const MAX_ALIAS_COUNT = 100;
|
|
22
|
+
|
|
23
|
+
export const PARSE_OPTIONS = Object.freeze({
|
|
24
|
+
uniqueKeys: true, // a key given twice is an error, not "the last one wins"
|
|
25
|
+
strict: true,
|
|
26
|
+
prettyErrors: true, // line and column on every error
|
|
27
|
+
resolveKnownTags: false, // !!binary, !!set, !!omap… stay unresolved (a warning), never other types
|
|
28
|
+
merge: false, // `<<` is a key like any other
|
|
29
|
+
keepSourceTokens: false,
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const BREAK = /\r\n|\n|\r/g;
|
|
33
|
+
|
|
34
|
+
/** The line ending at `index`: its length (0 at the end of the text). */
|
|
35
|
+
function breakAt(text, index) {
|
|
36
|
+
if (text.charCodeAt(index) === 13) return text.charCodeAt(index + 1) === 10 ? 2 : 1;
|
|
37
|
+
if (text.charCodeAt(index) === 10) return 1;
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The index of the next line break at or after `from` (the text's length when there is none). */
|
|
42
|
+
function nextBreak(text, from) {
|
|
43
|
+
for (let i = from; i < text.length; i++) {
|
|
44
|
+
const c = text.charCodeAt(i);
|
|
45
|
+
if (c === 10 || c === 13) return i;
|
|
46
|
+
}
|
|
47
|
+
return text.length;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 1-based line number of an index in a text. */
|
|
51
|
+
export function lineOfIndex(text, index) {
|
|
52
|
+
let line = 1;
|
|
53
|
+
const s = String(text ?? '');
|
|
54
|
+
BREAK.lastIndex = 0;
|
|
55
|
+
let m;
|
|
56
|
+
while ((m = BREAK.exec(s)) && m.index < index) line++;
|
|
57
|
+
return line;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Where the frontmatter is. It opens with a first line `---` (after a byte-order mark, if any) and closes at the
|
|
62
|
+
* next line that is `---`; trailing blanks on either line are allowed.
|
|
63
|
+
* @returns {{ bom: boolean, hasFrontmatter: boolean, unterminated: boolean, yamlStart: number, yamlEnd: number,
|
|
64
|
+
* bodyStart: number, yaml: string, body: string, startLine: number }} `yaml` is the text between the two lines
|
|
65
|
+
* (with the line ending of its last line), `startLine` the line it starts on (1-based), `bodyStart` where the
|
|
66
|
+
* text after the closing line starts.
|
|
67
|
+
*/
|
|
68
|
+
export function splitFrontmatter(text) {
|
|
69
|
+
const s = String(text ?? '');
|
|
70
|
+
const bom = s.charCodeAt(0) === 0xFEFF;
|
|
71
|
+
const at = bom ? 1 : 0;
|
|
72
|
+
const none = (unterminated = false) => ({
|
|
73
|
+
bom, hasFrontmatter: false, unterminated, yamlStart: at, yamlEnd: at, bodyStart: at, yaml: '', body: s.slice(at), startLine: 1,
|
|
74
|
+
});
|
|
75
|
+
const firstEnd = nextBreak(s, at);
|
|
76
|
+
if (!/^---[ \t]*$/.test(s.slice(at, firstEnd)) || firstEnd >= s.length) return none(false);
|
|
77
|
+
const yamlStart = firstEnd + breakAt(s, firstEnd);
|
|
78
|
+
let line = yamlStart;
|
|
79
|
+
while (line <= s.length) {
|
|
80
|
+
const end = nextBreak(s, line);
|
|
81
|
+
if (/^---[ \t]*$/.test(s.slice(line, end))) {
|
|
82
|
+
const bodyStart = end + breakAt(s, end);
|
|
83
|
+
return {
|
|
84
|
+
bom, hasFrontmatter: true, unterminated: false, yamlStart, yamlEnd: line, bodyStart,
|
|
85
|
+
yaml: s.slice(yamlStart, line), body: s.slice(bodyStart), startLine: 2,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
if (end >= s.length) break;
|
|
89
|
+
line = end + breakAt(s, end);
|
|
90
|
+
}
|
|
91
|
+
return none(true);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** What kind of value a frontmatter key holds, for a form that shows only what it can write back. */
|
|
95
|
+
export function valueKind(value) {
|
|
96
|
+
if (value === undefined) return 'absent';
|
|
97
|
+
if (value === null) return 'null';
|
|
98
|
+
if (typeof value === 'string') return 'string';
|
|
99
|
+
if (typeof value === 'boolean') return 'boolean';
|
|
100
|
+
if (typeof value === 'number') return Number.isFinite(value) ? 'number' : 'other';
|
|
101
|
+
if (Array.isArray(value)) return value.every((v) => typeof v === 'string') ? 'string-list' : 'list';
|
|
102
|
+
return 'other';
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function deepEqual(a, b) {
|
|
106
|
+
if (a === b) return true;
|
|
107
|
+
if (typeof a !== typeof b || a === null || b === null || typeof a !== 'object') return Number.isNaN(a) && Number.isNaN(b);
|
|
108
|
+
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
109
|
+
const ka = Object.keys(a); const kb = Object.keys(b);
|
|
110
|
+
if (ka.length !== kb.length) return false;
|
|
111
|
+
return ka.every((k, i) => k === kb[i] && deepEqual(a[k], b[k]));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** A copy of `data` whose keys are own data properties of objects without a prototype chain to reach. */
|
|
115
|
+
function plainCopy(value, depth = 0) {
|
|
116
|
+
if (depth > 64) return null;
|
|
117
|
+
if (Array.isArray(value)) return value.map((v) => plainCopy(v, depth + 1));
|
|
118
|
+
if (value && typeof value === 'object') {
|
|
119
|
+
if (typeof value.length === 'number' && typeof value.BYTES_PER_ELEMENT === 'number') return null; // typed arrays: not a skill value
|
|
120
|
+
const out = Object.create(null);
|
|
121
|
+
for (const key of Object.keys(value)) out[key] = plainCopy(value[key], depth + 1);
|
|
122
|
+
return out;
|
|
123
|
+
}
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* The frontmatter tools over one YAML library.
|
|
129
|
+
* @param {object} YAML the `yaml` package (parseDocument, Document, isMap, isScalar, isSeq, isCollection, isAlias, visit)
|
|
130
|
+
*/
|
|
131
|
+
export function createFrontmatter(YAML) {
|
|
132
|
+
const diag = (ruleId, severity, message, line = null, column = null) => ({ ruleId, severity, message, line, column });
|
|
133
|
+
|
|
134
|
+
function fromYamlError(err, severity, startLine) {
|
|
135
|
+
const pos = Array.isArray(err?.linePos) ? err.linePos[0] : null;
|
|
136
|
+
const first = String(err?.message || 'YAML error').split('\n')[0].replace(/ at line \d+, column \d+:?$/, '');
|
|
137
|
+
const code = String(err?.code || '').toLowerCase().replace(/_/g, '-');
|
|
138
|
+
const ruleId = code === 'duplicate-key' ? 'yaml-duplicate-key' : (code === 'tag-resolve-failed' ? 'yaml-tag' : 'yaml-syntax');
|
|
139
|
+
return diag(ruleId, severity, first, pos ? pos.line + startLine - 1 : startLine, pos ? pos.col : null);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function parseYaml(yaml) {
|
|
143
|
+
return YAML.parseDocument(yaml, PARSE_OPTIONS);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const keyName = (pair) => (YAML.isScalar(pair?.key) && typeof pair.key.value === 'string' ? pair.key.value : null);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Read a document's frontmatter.
|
|
150
|
+
* @param {string} text the whole file (a byte-order mark and any line ending are fine)
|
|
151
|
+
* @returns {{ hasFrontmatter: boolean, data: object | null, diagnostics: object[], keys: object[],
|
|
152
|
+
* editable: boolean, body: string, bodyLine: number, split: object }} `data` is null when the YAML cannot
|
|
153
|
+
* be read as a mapping; `editable` says whether patch() may change it; `keys` lists the top-level keys in
|
|
154
|
+
* order: `{ key, line, kind, style, flow }`.
|
|
155
|
+
*/
|
|
156
|
+
function parse(text) {
|
|
157
|
+
const s = String(text ?? '');
|
|
158
|
+
const split = splitFrontmatter(s);
|
|
159
|
+
const result = {
|
|
160
|
+
hasFrontmatter: split.hasFrontmatter, data: null, diagnostics: [], keys: [], editable: false,
|
|
161
|
+
body: split.hasFrontmatter ? split.body : s.slice(split.bom ? 1 : 0),
|
|
162
|
+
bodyLine: split.hasFrontmatter ? lineOfIndex(s, split.bodyStart) : 1,
|
|
163
|
+
split,
|
|
164
|
+
};
|
|
165
|
+
if (!split.hasFrontmatter) {
|
|
166
|
+
if (split.unterminated) result.diagnostics.push(diag('yaml-unterminated', 'error', 'The frontmatter opens with --- but no line closes it with ---.', 1));
|
|
167
|
+
result.data = Object.create(null);
|
|
168
|
+
result.editable = !split.unterminated;
|
|
169
|
+
return result;
|
|
170
|
+
}
|
|
171
|
+
if (split.yaml.length > FRONTMATTER_MAX_CHARS) {
|
|
172
|
+
result.diagnostics.push(diag('yaml-too-large', 'error', `The frontmatter is longer than ${Math.round(FRONTMATTER_MAX_CHARS / 1024)} KB and was not read.`, split.startLine));
|
|
173
|
+
return result;
|
|
174
|
+
}
|
|
175
|
+
let doc;
|
|
176
|
+
try { doc = parseYaml(split.yaml); } catch (err) {
|
|
177
|
+
result.diagnostics.push(diag('yaml-syntax', 'error', String(err?.message || 'The frontmatter could not be read.').split('\n')[0], split.startLine));
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
for (const err of doc.errors) result.diagnostics.push(fromYamlError(err, 'error', split.startLine));
|
|
181
|
+
for (const warn of doc.warnings) result.diagnostics.push(fromYamlError(warn, 'warning', split.startLine));
|
|
182
|
+
const root = doc.contents;
|
|
183
|
+
if (root === null || root === undefined) {
|
|
184
|
+
result.data = Object.create(null);
|
|
185
|
+
result.editable = doc.errors.length === 0;
|
|
186
|
+
return result;
|
|
187
|
+
}
|
|
188
|
+
if (!YAML.isMap(root)) {
|
|
189
|
+
result.diagnostics.push(diag('yaml-not-mapping', 'error', 'The frontmatter must be key: value lines (a YAML mapping).', split.startLine));
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
// YAML with errors is not read as data at all: what a parser makes of it is a guess, and no value of it is
|
|
193
|
+
// shown or checked as if it were what the file says.
|
|
194
|
+
if (doc.errors.length) return result;
|
|
195
|
+
let aliases = 0;
|
|
196
|
+
YAML.visit(doc, { Alias() { aliases++; } });
|
|
197
|
+
if (aliases) result.diagnostics.push(diag('yaml-alias', 'warning', `The frontmatter uses ${aliases === 1 ? 'an alias' : `${aliases} aliases`} (*name); the form leaves aliased values alone.`, split.startLine));
|
|
198
|
+
let data;
|
|
199
|
+
try { data = doc.toJS({ maxAliasCount: MAX_ALIAS_COUNT }); } catch (err) {
|
|
200
|
+
result.diagnostics.push(diag('yaml-alias-limit', 'error', String(err?.message || 'Too many aliases.').split('\n')[0], split.startLine));
|
|
201
|
+
return result;
|
|
202
|
+
}
|
|
203
|
+
result.data = plainCopy(data && typeof data === 'object' && !Array.isArray(data) ? data : {});
|
|
204
|
+
let complexKeys = 0;
|
|
205
|
+
for (const pair of root.items) {
|
|
206
|
+
const key = keyName(pair);
|
|
207
|
+
if (key === null) { complexKeys++; continue; }
|
|
208
|
+
const value = pair.value;
|
|
209
|
+
const start = pair.key.range?.[0] ?? 0;
|
|
210
|
+
result.keys.push({
|
|
211
|
+
key,
|
|
212
|
+
line: lineOfIndex(split.yaml, start) + split.startLine - 1,
|
|
213
|
+
kind: valueKind(result.data[key]),
|
|
214
|
+
style: YAML.isScalar(value) ? (value.type || null) : (YAML.isCollection(value) ? (value.flow ? 'flow' : 'block') : (YAML.isAlias(value) ? 'alias' : null)),
|
|
215
|
+
alias: YAML.isAlias(value),
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
if (complexKeys) result.diagnostics.push(diag('yaml-complex-key', 'warning', 'A key of the frontmatter is not plain text; the form leaves the frontmatter alone.', split.startLine));
|
|
219
|
+
result.editable = doc.errors.length === 0 && !root.flow && !complexKeys;
|
|
220
|
+
return result;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ── Patching ──
|
|
224
|
+
|
|
225
|
+
/** How the value a key holds is written, so its new value is written the same way. */
|
|
226
|
+
function styleOf(yamlText, pair) {
|
|
227
|
+
const value = pair.value;
|
|
228
|
+
const style = { type: null, flow: undefined, indentSeq: true, indent: 2 };
|
|
229
|
+
if (YAML.isScalar(value)) style.type = value.type || null;
|
|
230
|
+
if (YAML.isCollection(value)) {
|
|
231
|
+
style.flow = !!value.flow;
|
|
232
|
+
if (!value.flow && value.range) {
|
|
233
|
+
const at = value.range[0];
|
|
234
|
+
const lineStart = yamlText.lastIndexOf('\n', at - 1) + 1;
|
|
235
|
+
const column = at - lineStart;
|
|
236
|
+
if (YAML.isSeq(value)) { style.indentSeq = column > 0; if (column > 0) style.indent = column; }
|
|
237
|
+
else if (column > 0) style.indent = column;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
if (YAML.isScalar(value) && (value.type === 'BLOCK_FOLDED' || value.type === 'BLOCK_LITERAL') && value.range) {
|
|
241
|
+
// The indentation of the block's first line with text.
|
|
242
|
+
const block = yamlText.slice(value.range[0], value.range[1]);
|
|
243
|
+
const m = /\n( +)\S/.exec(block);
|
|
244
|
+
if (m) style.indent = Math.max(1, Math.min(8, m[1].length));
|
|
245
|
+
}
|
|
246
|
+
return style;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** `key: value` as YAML, written in `style` where the value allows it. Ends with "\n". */
|
|
250
|
+
function serializePair(key, value, style = {}) {
|
|
251
|
+
const doc = new YAML.Document();
|
|
252
|
+
doc.contents = doc.createNode({ [key]: value });
|
|
253
|
+
const node = doc.contents.items[0].value;
|
|
254
|
+
if (style.type && YAML.isScalar(node) && typeof value === 'string') node.type = style.type;
|
|
255
|
+
if (style.flow !== undefined && YAML.isCollection(node)) node.flow = style.flow;
|
|
256
|
+
return doc.toString({
|
|
257
|
+
lineWidth: style.type === 'BLOCK_FOLDED' ? 80 : 0,
|
|
258
|
+
minContentWidth: 0,
|
|
259
|
+
indent: style.indent || 2,
|
|
260
|
+
indentSeq: style.indentSeq !== false,
|
|
261
|
+
flowCollectionPadding: false,
|
|
262
|
+
});
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
function findPair(root, key) {
|
|
266
|
+
if (!root || !YAML.isMap(root)) return null;
|
|
267
|
+
return root.items.find((pair) => keyName(pair) === key) || null;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// The text a pair spans: from the start of its key to the end of its value (a value written over several lines
|
|
271
|
+
// takes its last line ending with it). A comment after a one-line value is outside it, and stays.
|
|
272
|
+
function pairSpan(yamlText, pair) {
|
|
273
|
+
const start = pair.key.range[0];
|
|
274
|
+
const lineStart = yamlText.lastIndexOf('\n', start - 1) + 1;
|
|
275
|
+
if (yamlText.slice(lineStart, start).trim() !== '') return null; // the key is not at the start of its line
|
|
276
|
+
let end;
|
|
277
|
+
if (pair.value && pair.value.range) end = pair.value.range[1];
|
|
278
|
+
else {
|
|
279
|
+
const colon = yamlText.indexOf(':', pair.key.range[1]);
|
|
280
|
+
if (colon < 0) return null;
|
|
281
|
+
end = colon + 1;
|
|
282
|
+
}
|
|
283
|
+
return { start, end, lineStart, block: yamlText.charCodeAt(end - 1) === 10 };
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
// The whole lines a pair takes: what removing the key removes. A comment on its last line goes with it.
|
|
287
|
+
function pairLines(yamlText, pair) {
|
|
288
|
+
const span = pairSpan(yamlText, pair);
|
|
289
|
+
if (!span) return null;
|
|
290
|
+
let end = span.block ? span.end : Math.max(span.end, pair.value?.range?.[2] ?? span.end);
|
|
291
|
+
if (yamlText.charCodeAt(end - 1) !== 10) {
|
|
292
|
+
const nl = yamlText.indexOf('\n', end);
|
|
293
|
+
const rest = yamlText.slice(end, nl < 0 ? yamlText.length : nl);
|
|
294
|
+
if (rest.trim() !== '' && !rest.trim().startsWith('#')) return null;
|
|
295
|
+
end = nl < 0 ? yamlText.length : nl + 1;
|
|
296
|
+
}
|
|
297
|
+
return { start: span.lineStart, end };
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function patchOne(text, key, value) {
|
|
301
|
+
const split = splitFrontmatter(text);
|
|
302
|
+
const lead = split.bom ? '' : '';
|
|
303
|
+
if (!split.hasFrontmatter) {
|
|
304
|
+
if (split.unterminated) return { ok: false, reason: 'unterminated' };
|
|
305
|
+
if (value === undefined) return { ok: true, text };
|
|
306
|
+
const pairText = serializePair(key, value);
|
|
307
|
+
return { ok: true, text: `${lead}---\n${pairText}---\n${text.slice(split.bom ? 1 : 0)}` };
|
|
308
|
+
}
|
|
309
|
+
const yamlText = split.yaml;
|
|
310
|
+
let doc;
|
|
311
|
+
try { doc = parseYaml(yamlText); } catch { return { ok: false, reason: 'invalid' }; }
|
|
312
|
+
if (doc.errors.length) return { ok: false, reason: 'invalid' };
|
|
313
|
+
const root = doc.contents;
|
|
314
|
+
if (root !== null && root !== undefined && (!YAML.isMap(root) || root.flow)) return { ok: false, reason: 'unsupported' };
|
|
315
|
+
if (root && root.items.some((pair) => keyName(pair) === null)) return { ok: false, reason: 'unsupported' };
|
|
316
|
+
const pair = findPair(root, key);
|
|
317
|
+
if (pair && YAML.isAlias(pair.value)) return { ok: false, reason: 'alias' };
|
|
318
|
+
let next;
|
|
319
|
+
if (value === undefined) {
|
|
320
|
+
if (!pair) return { ok: true, text };
|
|
321
|
+
const lines = pairLines(yamlText, pair);
|
|
322
|
+
if (!lines) return { ok: false, reason: 'unsupported' };
|
|
323
|
+
next = yamlText.slice(0, lines.start) + yamlText.slice(lines.end);
|
|
324
|
+
} else if (pair) {
|
|
325
|
+
const span = pairSpan(yamlText, pair);
|
|
326
|
+
if (!span) return { ok: false, reason: 'unsupported' };
|
|
327
|
+
let replacement = serializePair(key, value, styleOf(yamlText, pair));
|
|
328
|
+
if (!span.block) replacement = replacement.replace(/\n$/, '');
|
|
329
|
+
next = yamlText.slice(0, span.start) + replacement + yamlText.slice(span.end);
|
|
330
|
+
} else {
|
|
331
|
+
const base = yamlText === '' || yamlText.endsWith('\n') ? yamlText : `${yamlText}\n`;
|
|
332
|
+
next = base + serializePair(key, value);
|
|
333
|
+
}
|
|
334
|
+
return { ok: true, text: text.slice(0, split.yamlStart) + next + text.slice(split.yamlEnd) };
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Change keys of a document's frontmatter, each by its own text, and check the result.
|
|
339
|
+
* @param {string} text the whole document
|
|
340
|
+
* @param {Record<string, any>} changes key → new value; `undefined` removes the key
|
|
341
|
+
* @returns {{ ok: true, text: string, changed: boolean } | { ok: false, text: string, reason: string, key: string }}
|
|
342
|
+
* `reason`: `invalid` (the YAML has errors), `unterminated`, `unsupported` (a layout this does not edit:
|
|
343
|
+
* a flow mapping, a key that is not plain text), `alias`, `mismatch` (the result did not read back as asked)
|
|
344
|
+
*/
|
|
345
|
+
function patch(text, changes) {
|
|
346
|
+
const source = String(text ?? '');
|
|
347
|
+
const before = parse(source);
|
|
348
|
+
if (before.hasFrontmatter && !before.editable) return { ok: false, text: source, reason: before.diagnostics.some((d) => d.severity === 'error') ? 'invalid' : 'unsupported', key: null };
|
|
349
|
+
let current = source;
|
|
350
|
+
const entries = Object.entries(changes || {});
|
|
351
|
+
for (const [key, value] of entries) {
|
|
352
|
+
const step = patchOne(current, key, value);
|
|
353
|
+
if (!step.ok) return { ok: false, text: source, reason: step.reason, key };
|
|
354
|
+
current = step.text;
|
|
355
|
+
}
|
|
356
|
+
if (current === source) return { ok: true, text: source, changed: false };
|
|
357
|
+
// Read back: the keys asked for hold what was asked, every other key what it held, in the same order.
|
|
358
|
+
const after = parse(current);
|
|
359
|
+
if (!after.data || after.diagnostics.some((d) => d.severity === 'error')) return { ok: false, text: source, reason: 'mismatch', key: null };
|
|
360
|
+
const asked = new Map(entries);
|
|
361
|
+
for (const [key, value] of asked) {
|
|
362
|
+
const has = Object.prototype.hasOwnProperty.call(after.data, key);
|
|
363
|
+
if (value === undefined ? has : !(has && deepEqual(after.data[key], plainCopy(value)))) return { ok: false, text: source, reason: 'mismatch', key };
|
|
364
|
+
}
|
|
365
|
+
const kept = (before.keys || []).map((k) => k.key).filter((k) => !asked.has(k));
|
|
366
|
+
const keptAfter = after.keys.map((k) => k.key).filter((k) => !asked.has(k));
|
|
367
|
+
if (!deepEqual(kept, keptAfter)) return { ok: false, text: source, reason: 'mismatch', key: null };
|
|
368
|
+
for (const key of kept) {
|
|
369
|
+
if (!deepEqual(before.data[key], after.data[key])) return { ok: false, text: source, reason: 'mismatch', key };
|
|
370
|
+
}
|
|
371
|
+
if (before.body !== after.body) return { ok: false, text: source, reason: 'mismatch', key: null };
|
|
372
|
+
return { ok: true, text: current, changed: true };
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
return { parse, patch, serializePair, splitFrontmatter, PARSE_OPTIONS };
|
|
376
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Skills Studio: artifact ids in a URL
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The library's ids (`skill:<folder>`, `command:<file.md>`, `agent:<file.md>`) travel in URLs as the base64url of
|
|
6
|
+
// their UTF-8 bytes; the server decodes them the same way (Buffer.from(id, 'base64url').toString('utf-8')).
|
|
7
|
+
// btoa() alone reads a string as Latin-1: a folder named "café" was sent as other bytes than the server reads,
|
|
8
|
+
// and "技能" threw. Every id is encoded from its UTF-8 bytes here. No DOM: the page and the tests load it.
|
|
9
|
+
|
|
10
|
+
/** Bytes as base64, in pieces (one String.fromCharCode call on a large array would overflow). */
|
|
11
|
+
function bytesToBase64(bytes) {
|
|
12
|
+
let bin = '';
|
|
13
|
+
for (let i = 0; i < bytes.length; i += 0x8000) bin += String.fromCharCode.apply(null, bytes.subarray(i, i + 0x8000));
|
|
14
|
+
return btoa(bin);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/** An id as the server reads it: base64url (no padding) of its UTF-8 bytes. */
|
|
18
|
+
export function encodeArtifactId(id) {
|
|
19
|
+
const bytes = new TextEncoder().encode(String(id ?? ''));
|
|
20
|
+
return bytesToBase64(bytes).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** The id an encoded one names ('' when it is not base64url of UTF-8 text). */
|
|
24
|
+
export function decodeArtifactId(encoded) {
|
|
25
|
+
try {
|
|
26
|
+
const text = String(encoded ?? '');
|
|
27
|
+
if (!/^[A-Za-z0-9_-]*$/.test(text)) return '';
|
|
28
|
+
const padded = text.replace(/-/g, '+').replace(/_/g, '/') + '='.repeat((4 - (text.length % 4)) % 4);
|
|
29
|
+
const bin = atob(padded);
|
|
30
|
+
return new TextDecoder('utf-8', { fatal: true }).decode(Uint8Array.from(bin, (c) => c.charCodeAt(0)));
|
|
31
|
+
} catch { return ''; }
|
|
32
|
+
}
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
// ═══════════════════════════════════════════
|
|
2
|
+
// SynaBun Neural Interface — Skills Studio: what is wrong with a skill, a command or an agent
|
|
3
|
+
// ═══════════════════════════════════════════
|
|
4
|
+
//
|
|
5
|
+
// The checks run on what ss-frontmatter.js parse() read. Each problem names its rule, its severity and its
|
|
6
|
+
// line. An error is a file a runtime will misread (YAML that does not parse, a name that is not text, an
|
|
7
|
+
// agent without a description); a warning is one it will read but that is likely wrong (no description, a
|
|
8
|
+
// name that does not match its folder). Nothing here is decided by truthiness: an empty string, a number or
|
|
9
|
+
// a list where text belongs are each said for what they are.
|
|
10
|
+
//
|
|
11
|
+
// Profile: Claude Code (the folders the Studio edits). The limits that the portable Agent Skills format and
|
|
12
|
+
// Codex enforce too (64-character names, 1024-character descriptions) are errors here as well, so a skill that
|
|
13
|
+
// passes can be carried to the other runtimes. No DOM.
|
|
14
|
+
|
|
15
|
+
import { findHiddenChars, inspectName } from './ss-unicode.js';
|
|
16
|
+
|
|
17
|
+
export const LINT_PROFILE = 'claude-code-2026-10';
|
|
18
|
+
export const NAME_MAX = 64;
|
|
19
|
+
export const DESCRIPTION_MAX = 1024;
|
|
20
|
+
const NAME_RE = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
21
|
+
|
|
22
|
+
const BOOLEAN_KEYS = ['user-invocable', 'disable-model-invocation'];
|
|
23
|
+
const STRING_KEYS = ['argument-hint', 'model', 'color', 'license', 'compatibility', 'context', 'agent', 'effort', 'when_to_use', 'shell'];
|
|
24
|
+
const TOOL_KEYS = ['allowed-tools', 'disallowed-tools', 'tools'];
|
|
25
|
+
|
|
26
|
+
const kindOf = (value) => {
|
|
27
|
+
if (value === undefined) return 'absent';
|
|
28
|
+
if (value === null) return 'empty';
|
|
29
|
+
if (Array.isArray(value)) return 'a list';
|
|
30
|
+
if (typeof value === 'object') return 'a mapping';
|
|
31
|
+
return typeof value === 'string' ? 'text' : `a ${typeof value}`;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {object} o
|
|
36
|
+
* @param {'skill' | 'command' | 'agent'} o.type
|
|
37
|
+
* @param {object} o.parsed what ss-frontmatter.js parse() returned
|
|
38
|
+
* @param {string} [o.folderName] a skill's folder, or a command's / agent's file name without `.md`
|
|
39
|
+
* @param {string[]} [o.knownNames] names a skill must not pass for (the bundled ones)
|
|
40
|
+
* @returns {{ profile: string, errors: object[], warnings: object[], infos: object[], all: object[] }}
|
|
41
|
+
* each problem `{ ruleId, severity, message, line }`
|
|
42
|
+
*/
|
|
43
|
+
export function lintArtifact({ type, parsed, folderName = '', knownNames = [] }) {
|
|
44
|
+
const all = [];
|
|
45
|
+
const add = (ruleId, severity, message, line = null) => all.push({ ruleId, severity, message, line });
|
|
46
|
+
const lineOf = (key) => parsed?.keys?.find((k) => k.key === key)?.line ?? (parsed?.split?.startLine || 1);
|
|
47
|
+
|
|
48
|
+
for (const d of parsed?.diagnostics || []) add(d.ruleId, d.severity, d.message, d.line);
|
|
49
|
+
const data = parsed?.data;
|
|
50
|
+
if (!parsed?.hasFrontmatter && !parsed?.split?.unterminated) {
|
|
51
|
+
if (type === 'agent') add('fm-missing', 'error', 'An agent needs frontmatter with a name and a description.', 1);
|
|
52
|
+
else if (type === 'skill') add('fm-missing', 'warning', 'No frontmatter: Claude takes the folder name and has no description to decide when to use the skill.', 1);
|
|
53
|
+
}
|
|
54
|
+
const has = (key) => !!data && Object.prototype.hasOwnProperty.call(data, key);
|
|
55
|
+
// Without frontmatter, "fm-missing" says it all: the fields it would hold are not named one by one.
|
|
56
|
+
const missingSaid = !parsed?.hasFrontmatter;
|
|
57
|
+
|
|
58
|
+
if (data) {
|
|
59
|
+
// name
|
|
60
|
+
if (has('name')) {
|
|
61
|
+
const name = data.name;
|
|
62
|
+
if (typeof name !== 'string') add('name-type', 'error', `"name" must be text; it is ${kindOf(name)}.`, lineOf('name'));
|
|
63
|
+
else if (!name.trim()) add('name-empty', 'error', '"name" is empty.', lineOf('name'));
|
|
64
|
+
else {
|
|
65
|
+
if (name.length > NAME_MAX) add('name-length', 'error', `"name" is ${name.length} characters; the limit is ${NAME_MAX}.`, lineOf('name'));
|
|
66
|
+
const look = inspectName(name, knownNames);
|
|
67
|
+
if (look.hidden.length) add('name-hidden-chars', 'error', `"name" holds characters nobody can see: ${look.hidden.map((h) => h.label).join(', ')}.`, lineOf('name'));
|
|
68
|
+
if (look.confusable) add('name-confusable', 'warning', `"name" mixes in letters of another script that look Latin (reads as "${look.skeleton}").`, lineOf('name'));
|
|
69
|
+
if (look.impersonates) add('name-impersonates', 'warning', `"name" looks like "${look.impersonates}" but is not it.`, lineOf('name'));
|
|
70
|
+
// Claude Code, Codex and the portable format ask for this; a name outside it may not be found everywhere.
|
|
71
|
+
if (type !== 'command' && !look.hidden.length && !NAME_RE.test(name)) add('name-format', 'warning', '"name" should use lowercase letters, digits and single hyphens only (no leading or trailing hyphen).', lineOf('name'));
|
|
72
|
+
if (type === 'skill' && folderName && name !== folderName) add('name-folder', 'warning', `"name" is "${name}" but the folder is "${folderName}": the skill is invoked by its name.`, lineOf('name'));
|
|
73
|
+
}
|
|
74
|
+
} else if (type === 'agent' && !missingSaid) {
|
|
75
|
+
add('name-missing', 'error', 'An agent needs a "name".', lineOf('name'));
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// description
|
|
79
|
+
if (has('description')) {
|
|
80
|
+
const desc = data.description;
|
|
81
|
+
if (typeof desc !== 'string') add('description-type', 'error', `"description" must be text; it is ${kindOf(desc)}.`, lineOf('description'));
|
|
82
|
+
else if (!desc.trim()) add('description-empty', 'error', '"description" is empty.', lineOf('description'));
|
|
83
|
+
else {
|
|
84
|
+
if (desc.length > DESCRIPTION_MAX) add('description-length', 'error', `"description" is ${desc.length} characters; the limit is ${DESCRIPTION_MAX}.`, lineOf('description'));
|
|
85
|
+
const hidden = findHiddenChars(desc);
|
|
86
|
+
if (hidden.length) add('description-hidden-chars', 'error', `"description" holds characters nobody can see: ${hidden.slice(0, 5).map((h) => h.label).join(', ')}${hidden.length > 5 ? '…' : ''}.`, lineOf('description'));
|
|
87
|
+
}
|
|
88
|
+
} else if (missingSaid) {
|
|
89
|
+
// said by fm-missing
|
|
90
|
+
} else if (type === 'agent') {
|
|
91
|
+
add('description-missing', 'error', 'An agent needs a "description": it is how Claude decides to use it.', lineOf('description'));
|
|
92
|
+
} else if (type === 'skill') {
|
|
93
|
+
add('description-missing', 'warning', 'No "description": Claude cannot tell when to use this skill on its own.', lineOf('description'));
|
|
94
|
+
} else {
|
|
95
|
+
add('description-missing', 'info', 'No "description": the command list shows nothing for it.', lineOf('description'));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
for (const key of BOOLEAN_KEYS) {
|
|
99
|
+
if (has(key) && typeof data[key] !== 'boolean') add(`${key}-type`, 'error', `"${key}" must be true or false; it is ${kindOf(data[key])}.`, lineOf(key));
|
|
100
|
+
}
|
|
101
|
+
for (const key of STRING_KEYS) {
|
|
102
|
+
if (has(key) && data[key] !== null && typeof data[key] !== 'string') add(`${key}-type`, 'error', `"${key}" must be text; it is ${kindOf(data[key])}.`, lineOf(key));
|
|
103
|
+
}
|
|
104
|
+
for (const key of TOOL_KEYS) {
|
|
105
|
+
if (!has(key)) continue;
|
|
106
|
+
const value = data[key];
|
|
107
|
+
const ok = typeof value === 'string' || (Array.isArray(value) && value.every((t) => typeof t === 'string' && t.trim()));
|
|
108
|
+
if (!ok) add(`${key}-type`, 'error', `"${key}" must be text or a list of tool names; it is ${kindOf(value)}.`, lineOf(key));
|
|
109
|
+
}
|
|
110
|
+
if (has('maxTurns')) {
|
|
111
|
+
const turns = data.maxTurns;
|
|
112
|
+
if (!Number.isInteger(turns) || turns < 0) add('maxTurns-type', 'error', '"maxTurns" must be a whole number, 0 or more.', lineOf('maxTurns'));
|
|
113
|
+
}
|
|
114
|
+
if (has('hooks') && (data.hooks === null || typeof data.hooks !== 'object' || Array.isArray(data.hooks))) {
|
|
115
|
+
add('hooks-type', 'error', `"hooks" must be a mapping of events; it is ${kindOf(data.hooks)}.`, lineOf('hooks'));
|
|
116
|
+
}
|
|
117
|
+
if (has('metadata') && (data.metadata === null || typeof data.metadata !== 'object' || Array.isArray(data.metadata))) {
|
|
118
|
+
add('metadata-type', 'error', `"metadata" must be a mapping; it is ${kindOf(data.metadata)}.`, lineOf('metadata'));
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const body = String(parsed?.body ?? '');
|
|
123
|
+
if (!body.trim()) add('body-empty', 'warning', 'The body is empty: there are no instructions.', parsed?.bodyLine || 1);
|
|
124
|
+
else {
|
|
125
|
+
const hidden = findHiddenChars(body);
|
|
126
|
+
if (hidden.length) {
|
|
127
|
+
const first = hidden[0];
|
|
128
|
+
add('body-hidden-chars', 'warning', `The instructions hold ${hidden.length === 1 ? 'a character' : `${hidden.length} characters`} nobody can see (${first.label}, line ${first.line + (parsed?.bodyLine || 1) - 1}).`, first.line + (parsed?.bodyLine || 1) - 1);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const rank = { error: 0, warning: 1, info: 2 };
|
|
133
|
+
all.sort((a, b) => (rank[a.severity] - rank[b.severity]) || ((a.line || 0) - (b.line || 0)));
|
|
134
|
+
return {
|
|
135
|
+
profile: LINT_PROFILE,
|
|
136
|
+
errors: all.filter((d) => d.severity === 'error'),
|
|
137
|
+
warnings: all.filter((d) => d.severity === 'warning'),
|
|
138
|
+
infos: all.filter((d) => d.severity === 'info'),
|
|
139
|
+
all,
|
|
140
|
+
};
|
|
141
|
+
}
|