@hcu-lab.me/mcp-device 1.0.9 → 1.0.12
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/PRIVACY.md +21 -0
- package/README.md +58 -3
- package/SECURITY.md +67 -0
- package/config/revoked-approvals.json +1 -0
- package/control-plane/config/mcp-servers.example.toml +52 -0
- package/control-plane/config/mcp-servers.toml +52 -0
- package/control-plane/contracts/device-public-contract-v1.json +733 -0
- package/control-plane/scripts/GATEWAY-LICENSE.txt +7 -0
- package/control-plane/scripts/account-http.mjs +220 -0
- package/control-plane/scripts/account-store.mjs +368 -0
- package/control-plane/scripts/admin.mjs +68 -0
- package/control-plane/scripts/auth-session.mjs +411 -0
- package/control-plane/scripts/authenticated-mcp-wrapper.mjs +1281 -0
- package/control-plane/scripts/benchmark-command.mjs +9 -0
- package/control-plane/scripts/benchmark-mcp-catalog.mjs +174 -0
- package/control-plane/scripts/catalog-budget.mjs +87 -0
- package/control-plane/scripts/check-device-public-contract.mjs +20 -0
- package/control-plane/scripts/custom-tools/index.mjs +221 -0
- package/control-plane/scripts/custom-tools/path-utils.mjs +117 -0
- package/control-plane/scripts/custom-tools/response-utils.mjs +34 -0
- package/control-plane/scripts/dashboard-http.mjs +598 -0
- package/control-plane/scripts/device-access-policy.mjs +205 -0
- package/control-plane/scripts/device-admin.mjs +43 -0
- package/control-plane/scripts/device-audit.mjs +63 -0
- package/control-plane/scripts/device-broker.mjs +1461 -0
- package/control-plane/scripts/device-execution-tool-definitions.mjs +217 -0
- package/control-plane/scripts/device-inventory.mjs +128 -0
- package/control-plane/scripts/device-pairing-http.mjs +109 -0
- package/control-plane/scripts/device-pairing-store.mjs +245 -0
- package/control-plane/scripts/device-public-contract.mjs +165 -0
- package/control-plane/scripts/device-release.mjs +95 -0
- package/control-plane/scripts/device-secure-transport.mjs +128 -0
- package/control-plane/scripts/device-store.mjs +283 -0
- package/control-plane/scripts/device-usage.mjs +657 -0
- package/control-plane/scripts/embedded-device.mjs +149 -0
- package/control-plane/scripts/enduser-ui.mjs +45 -0
- package/control-plane/scripts/external-tool-broker.mjs +179 -0
- package/control-plane/scripts/oauth-authorization-server.mjs +589 -0
- package/control-plane/scripts/operator-dashboard-http.mjs +81 -0
- package/control-plane/scripts/project-inspection.mjs +227 -0
- package/control-plane/scripts/projects/project-id.mjs +69 -0
- package/control-plane/scripts/projects/trusted-roots-projects.mjs +375 -0
- package/control-plane/scripts/prompts/index.mjs +60 -0
- package/control-plane/scripts/remote-process-sessions.mjs +78 -0
- package/control-plane/scripts/remote-tool-result.mjs +9 -0
- package/control-plane/scripts/resources/index.mjs +166 -0
- package/control-plane/scripts/runtime-profile.mjs +54 -0
- package/control-plane/scripts/shell-tool-descriptor.mjs +17 -0
- package/control-plane/scripts/skill-source-safety.mjs +46 -0
- package/control-plane/scripts/skills/README.md +70 -0
- package/control-plane/scripts/skills/_upstream_licenses/anthropic-THIRD_PARTY_NOTICES.md +405 -0
- package/control-plane/scripts/skills/_upstream_licenses/hallmark-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/_upstream_licenses/ponytail-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/_upstream_licenses/stitch-skills-LICENSE.txt +201 -0
- package/control-plane/scripts/skills/_upstream_licenses/superpowers-LICENSE.txt +21 -0
- package/control-plane/scripts/skills/algorithmic-art/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/algorithmic-art/SKILL.md +405 -0
- package/control-plane/scripts/skills/algorithmic-art/templates/generator_template.js +223 -0
- package/control-plane/scripts/skills/algorithmic-art/templates/viewer.html +599 -0
- package/control-plane/scripts/skills/brainstorming/SKILL.md +285 -0
- package/control-plane/scripts/skills/brainstorming/scripts/frame-template.html +213 -0
- package/control-plane/scripts/skills/brainstorming/scripts/helper.js +167 -0
- package/control-plane/scripts/skills/brainstorming/scripts/server.cjs +723 -0
- package/control-plane/scripts/skills/brainstorming/scripts/start-server.sh +209 -0
- package/control-plane/scripts/skills/brainstorming/scripts/stop-server.sh +120 -0
- package/control-plane/scripts/skills/brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/control-plane/scripts/skills/brainstorming/visual-companion.md +299 -0
- package/control-plane/scripts/skills/brand-guidelines/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/brand-guidelines/SKILL.md +73 -0
- package/control-plane/scripts/skills/claude-api/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/claude-api/SKILL.md +570 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/README.md +362 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/batches.md +14 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/files-api.md +25 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/streaming.md +28 -0
- package/control-plane/scripts/skills/claude-api/csharp/claude-api/tool-use.md +164 -0
- package/control-plane/scripts/skills/claude-api/curl/examples.md +256 -0
- package/control-plane/scripts/skills/claude-api/curl/managed-agents.md +371 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/README.md +188 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/files-api.md +23 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/streaming.md +43 -0
- package/control-plane/scripts/skills/claude-api/go/claude-api/tool-use.md +220 -0
- package/control-plane/scripts/skills/claude-api/go/managed-agents/README.md +564 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/README.md +238 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/files-api.md +27 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/streaming.md +24 -0
- package/control-plane/scripts/skills/claude-api/java/claude-api/tool-use.md +228 -0
- package/control-plane/scripts/skills/claude-api/java/managed-agents/README.md +443 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/README.md +174 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/batches.md +15 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/files-api.md +13 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/streaming.md +27 -0
- package/control-plane/scripts/skills/claude-api/php/claude-api/tool-use.md +253 -0
- package/control-plane/scripts/skills/claude-api/php/managed-agents/README.md +443 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/README.md +565 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/batches.md +198 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/files-api.md +170 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/sdk-upgrade.md +286 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/streaming.md +179 -0
- package/control-plane/scripts/skills/claude-api/python/claude-api/tool-use.md +627 -0
- package/control-plane/scripts/skills/claude-api/python/managed-agents/README.md +335 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/README.md +134 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/streaming.md +16 -0
- package/control-plane/scripts/skills/claude-api/ruby/claude-api/tool-use.md +41 -0
- package/control-plane/scripts/skills/claude-api/ruby/managed-agents/README.md +394 -0
- package/control-plane/scripts/skills/claude-api/shared/admin-api.md +179 -0
- package/control-plane/scripts/skills/claude-api/shared/agent-design.md +101 -0
- package/control-plane/scripts/skills/claude-api/shared/anthropic-cli.md +269 -0
- package/control-plane/scripts/skills/claude-api/shared/claude-platform-on-aws.md +59 -0
- package/control-plane/scripts/skills/claude-api/shared/cost-optimization.md +233 -0
- package/control-plane/scripts/skills/claude-api/shared/error-codes.md +262 -0
- package/control-plane/scripts/skills/claude-api/shared/live-sources.md +166 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-api-reference.md +455 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-client-patterns.md +220 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-core.md +359 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-environments.md +226 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-events.md +290 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-memory.md +201 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-multiagent.md +260 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-onboarding.md +82 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-outcomes.md +108 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-overview.md +77 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-scheduled-deployments.md +156 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-self-hosted-sandboxes.md +282 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-tools.md +504 -0
- package/control-plane/scripts/skills/claude-api/shared/managed-agents-webhooks.md +143 -0
- package/control-plane/scripts/skills/claude-api/shared/model-migration.md +1872 -0
- package/control-plane/scripts/skills/claude-api/shared/models.md +142 -0
- package/control-plane/scripts/skills/claude-api/shared/platform-availability.md +52 -0
- package/control-plane/scripts/skills/claude-api/shared/prompt-audit.md +223 -0
- package/control-plane/scripts/skills/claude-api/shared/prompt-caching.md +292 -0
- package/control-plane/scripts/skills/claude-api/shared/token-counting.md +56 -0
- package/control-plane/scripts/skills/claude-api/shared/tool-use-concepts.md +526 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/README.md +405 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/batches.md +106 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/files-api.md +98 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/streaming.md +178 -0
- package/control-plane/scripts/skills/claude-api/typescript/claude-api/tool-use.md +586 -0
- package/control-plane/scripts/skills/claude-api/typescript/managed-agents/README.md +359 -0
- package/control-plane/scripts/skills/dispatching-parallel-agents/SKILL.md +167 -0
- package/control-plane/scripts/skills/enhance-prompt/README.md +34 -0
- package/control-plane/scripts/skills/enhance-prompt/SKILL.md +204 -0
- package/control-plane/scripts/skills/enhance-prompt/references/KEYWORDS.md +114 -0
- package/control-plane/scripts/skills/executing-plans/SKILL.md +371 -0
- package/control-plane/scripts/skills/executing-plans/scripts/task-done +52 -0
- package/control-plane/scripts/skills/executing-plans/scripts/task-start +28 -0
- package/control-plane/scripts/skills/extract-design-md/SKILL.md +358 -0
- package/control-plane/scripts/skills/extract-design-md/examples/DESIGN.md +210 -0
- package/control-plane/scripts/skills/extract-design-md/references/angular.md +122 -0
- package/control-plane/scripts/skills/extract-design-md/references/plain-css.md +161 -0
- package/control-plane/scripts/skills/extract-design-md/references/react-tailwind.md +157 -0
- package/control-plane/scripts/skills/extract-design-md/references/svelte.md +101 -0
- package/control-plane/scripts/skills/extract-design-md/references/vue.md +107 -0
- package/control-plane/scripts/skills/finishing-a-development-branch/SKILL.md +225 -0
- package/control-plane/scripts/skills/frontend-design/LICENSE.txt +177 -0
- package/control-plane/scripts/skills/frontend-design/SKILL.md +71 -0
- package/control-plane/scripts/skills/hallmark/SKILL.md +558 -0
- package/control-plane/scripts/skills/hallmark/references/anti-patterns.md +418 -0
- package/control-plane/scripts/skills/hallmark/references/assets.md +406 -0
- package/control-plane/scripts/skills/hallmark/references/color.md +95 -0
- package/control-plane/scripts/skills/hallmark/references/component-cookbook.md +263 -0
- package/control-plane/scripts/skills/hallmark/references/components/c1-outlined-chip.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/c2-inline-form-as-cta.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/c3-typographic-link.md +8 -0
- package/control-plane/scripts/skills/hallmark/references/components/c4-sticky-bottom-bar.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/f1-bento-grid.md +20 -0
- package/control-plane/scripts/skills/hallmark/references/components/f2-sticky-scroll-stack.md +20 -0
- package/control-plane/scripts/skills/hallmark/references/components/f3-tabular-spec-sheet.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f4-step-sequence.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f5-annotated-screenshot.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/f6-product-card-grid.md +41 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft1-mast-headed.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft2-inline-rule-single-line.md +10 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft3-index-style-category-list.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft4-dense-typographic.md +10 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft5-statement.md +21 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft6-letter-close.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft7-newsletter-first.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/ft8-marquee-scroll.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/components/h1-marquee.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/h2-split-diptych.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/h3-quote-led.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/h4-stat-led.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/h5-letter-hero.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/h6-photographic-fold.md +16 -0
- package/control-plane/scripts/skills/hallmark/references/components/h7-demo-video-clipped-by-viewport-edge.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/h8-mockup-split-browser-framed.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/components/h9-custom-illustration-centerpiece.md +27 -0
- package/control-plane/scripts/skills/hallmark/references/components/n1-wordmark-2-links.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/n10-floating-on-scroll-morph.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/n11-mega-menu.md +40 -0
- package/control-plane/scripts/skills/hallmark/references/components/n12-banner-retract.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/components/n13-inline-cmdk-pill.md +39 -0
- package/control-plane/scripts/skills/hallmark/references/components/n1b-saas-three-section.md +35 -0
- package/control-plane/scripts/skills/hallmark/references/components/n2-floating-chip.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/n3-side-rail.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/components/n4-hidden-behind-k.md +9 -0
- package/control-plane/scripts/skills/hallmark/references/components/n5-floating-pill.md +28 -0
- package/control-plane/scripts/skills/hallmark/references/components/n6-newspaper-masthead.md +24 -0
- package/control-plane/scripts/skills/hallmark/references/components/n7-brutal-slab.md +22 -0
- package/control-plane/scripts/skills/hallmark/references/components/n8-terminal-command.md +21 -0
- package/control-plane/scripts/skills/hallmark/references/components/n9-edge-aligned-minimal.md +17 -0
- package/control-plane/scripts/skills/hallmark/references/components/s1-left-margin-numbered.md +15 -0
- package/control-plane/scripts/skills/hallmark/references/components/s2-hanging.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/s3-sticky-pinned.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/s4-inline-no-break.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/s5-bottom-anchored.md +13 -0
- package/control-plane/scripts/skills/hallmark/references/components/t1-pull-quote-with-marginalia.md +12 -0
- package/control-plane/scripts/skills/hallmark/references/components/t2-logo-wall-hairline.md +19 -0
- package/control-plane/scripts/skills/hallmark/references/components/t3-single-huge-quote.md +11 -0
- package/control-plane/scripts/skills/hallmark/references/components/t4-numbered-stat-strip.md +14 -0
- package/control-plane/scripts/skills/hallmark/references/contract.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/copy.md +182 -0
- package/control-plane/scripts/skills/hallmark/references/custom-craft.md +626 -0
- package/control-plane/scripts/skills/hallmark/references/custom-theme.md +367 -0
- package/control-plane/scripts/skills/hallmark/references/design-md.md +116 -0
- package/control-plane/scripts/skills/hallmark/references/export-formats.md +329 -0
- package/control-plane/scripts/skills/hallmark/references/floating-nav.md +89 -0
- package/control-plane/scripts/skills/hallmark/references/genres/atmospheric.md +69 -0
- package/control-plane/scripts/skills/hallmark/references/genres/editorial.md +72 -0
- package/control-plane/scripts/skills/hallmark/references/genres/modern-minimal.md +71 -0
- package/control-plane/scripts/skills/hallmark/references/genres/playful.md +70 -0
- package/control-plane/scripts/skills/hallmark/references/hero-enrichment.md +475 -0
- package/control-plane/scripts/skills/hallmark/references/imagery-kit.md +170 -0
- package/control-plane/scripts/skills/hallmark/references/interaction-and-states.md +208 -0
- package/control-plane/scripts/skills/hallmark/references/layout-and-space.md +112 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/01-bento-grid.md +35 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/02-long-document.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/03-marquee-hero.md +31 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/04-stat-led.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/05-workbench.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/06-conversational-faq.md +33 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/07-manifesto.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/08-photographic.md +34 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/09-quote-led.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/10-specimen.md +32 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/11-catalogue.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/12-letter.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/13-index-first.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/14-narrative-workflow.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/15-split-studio.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/16-feature-stack.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/17-type-specimen.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/18-portfolio-grid.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/19-map-diagram.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/20-ecosystem-index.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures/21-component-playground.md +23 -0
- package/control-plane/scripts/skills/hallmark/references/macrostructures.md +89 -0
- package/control-plane/scripts/skills/hallmark/references/microinteractions.md +258 -0
- package/control-plane/scripts/skills/hallmark/references/motion.md +109 -0
- package/control-plane/scripts/skills/hallmark/references/preview-examples.md +49 -0
- package/control-plane/scripts/skills/hallmark/references/responsive.md +138 -0
- package/control-plane/scripts/skills/hallmark/references/slop-test.md +192 -0
- package/control-plane/scripts/skills/hallmark/references/structure.md +160 -0
- package/control-plane/scripts/skills/hallmark/references/study.md +509 -0
- package/control-plane/scripts/skills/hallmark/references/themes/carnival.md +301 -0
- package/control-plane/scripts/skills/hallmark/references/themes/cobalt.md +146 -0
- package/control-plane/scripts/skills/hallmark/references/themes/grid.md +161 -0
- package/control-plane/scripts/skills/hallmark/references/themes/hum.md +403 -0
- package/control-plane/scripts/skills/hallmark/references/themes/lumen.md +478 -0
- package/control-plane/scripts/skills/hallmark/references/typography.md +243 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/recipes.md +186 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/study-examples.md +176 -0
- package/control-plane/scripts/skills/hallmark/references/upstream/tokens.css +1221 -0
- package/control-plane/scripts/skills/hallmark/references/verbs/audit.md +25 -0
- package/control-plane/scripts/skills/hallmark/references/verbs/redesign.md +269 -0
- package/control-plane/scripts/skills/human-comms/SKILL.md +29 -0
- package/control-plane/scripts/skills/index.mjs +432 -0
- package/control-plane/scripts/skills/internal-comms/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/internal-comms/SKILL.md +32 -0
- package/control-plane/scripts/skills/internal-comms/examples/3p-updates.md +47 -0
- package/control-plane/scripts/skills/internal-comms/examples/company-newsletter.md +65 -0
- package/control-plane/scripts/skills/internal-comms/examples/faq-answers.md +30 -0
- package/control-plane/scripts/skills/internal-comms/examples/general-comms.md +16 -0
- package/control-plane/scripts/skills/mcp-adapter.mjs +94 -0
- package/control-plane/scripts/skills/mcp-builder/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/mcp-builder/SKILL.md +236 -0
- package/control-plane/scripts/skills/mcp-builder/reference/evaluation.md +602 -0
- package/control-plane/scripts/skills/mcp-builder/reference/mcp_best_practices.md +249 -0
- package/control-plane/scripts/skills/mcp-builder/reference/node_mcp_server.md +970 -0
- package/control-plane/scripts/skills/mcp-builder/reference/python_mcp_server.md +719 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/connections.py +151 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/evaluation.py +373 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/example_evaluation.xml +22 -0
- package/control-plane/scripts/skills/mcp-builder/scripts/requirements.txt +2 -0
- package/control-plane/scripts/skills/ponytail/SKILL.md +120 -0
- package/control-plane/scripts/skills/ponytail-audit/SKILL.md +41 -0
- package/control-plane/scripts/skills/ponytail-debt/SKILL.md +44 -0
- package/control-plane/scripts/skills/ponytail-gain/SKILL.md +50 -0
- package/control-plane/scripts/skills/ponytail-help/SKILL.md +71 -0
- package/control-plane/scripts/skills/ponytail-review/SKILL.md +57 -0
- package/control-plane/scripts/skills/receiving-code-review/SKILL.md +205 -0
- package/control-plane/scripts/skills/requesting-code-review/SKILL.md +95 -0
- package/control-plane/scripts/skills/requesting-code-review/code-reviewer.md +198 -0
- package/control-plane/scripts/skills/skill-creator/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/skill-creator/SKILL.md +485 -0
- package/control-plane/scripts/skills/skill-creator/agents/analyzer.md +274 -0
- package/control-plane/scripts/skills/skill-creator/agents/comparator.md +202 -0
- package/control-plane/scripts/skills/skill-creator/agents/grader.md +223 -0
- package/control-plane/scripts/skills/skill-creator/assets/eval_review.html +146 -0
- package/control-plane/scripts/skills/skill-creator/eval-viewer/generate_review.py +471 -0
- package/control-plane/scripts/skills/skill-creator/eval-viewer/viewer.html +1325 -0
- package/control-plane/scripts/skills/skill-creator/references/schemas.md +430 -0
- package/control-plane/scripts/skills/skill-creator/scripts/__init__.py +0 -0
- package/control-plane/scripts/skills/skill-creator/scripts/aggregate_benchmark.py +401 -0
- package/control-plane/scripts/skills/skill-creator/scripts/generate_report.py +326 -0
- package/control-plane/scripts/skills/skill-creator/scripts/improve_description.py +247 -0
- package/control-plane/scripts/skills/skill-creator/scripts/package_skill.py +136 -0
- package/control-plane/scripts/skills/skill-creator/scripts/quick_validate.py +103 -0
- package/control-plane/scripts/skills/skill-creator/scripts/run_eval.py +310 -0
- package/control-plane/scripts/skills/skill-creator/scripts/run_loop.py +328 -0
- package/control-plane/scripts/skills/skill-creator/scripts/utils.py +47 -0
- package/control-plane/scripts/skills/slack-gif-creator/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/slack-gif-creator/SKILL.md +254 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/easing.py +234 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/frame_composer.py +176 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/gif_builder.py +269 -0
- package/control-plane/scripts/skills/slack-gif-creator/core/validators.py +136 -0
- package/control-plane/scripts/skills/slack-gif-creator/requirements.txt +4 -0
- package/control-plane/scripts/skills/sources.json +401 -0
- package/control-plane/scripts/skills/sources.lock.json +416 -0
- package/control-plane/scripts/skills/subagent-driven-development/SKILL.md +570 -0
- package/control-plane/scripts/skills/subagent-driven-development/implementer-prompt.md +154 -0
- package/control-plane/scripts/skills/subagent-driven-development/re-review-prompt.md +115 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/review-package +53 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/sdd-workspace +82 -0
- package/control-plane/scripts/skills/subagent-driven-development/scripts/task-brief +43 -0
- package/control-plane/scripts/skills/subagent-driven-development/task-reviewer-prompt.md +207 -0
- package/control-plane/scripts/skills/systematic-debugging/CREATION-LOG.md +119 -0
- package/control-plane/scripts/skills/systematic-debugging/SKILL.md +283 -0
- package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting-example.ts +158 -0
- package/control-plane/scripts/skills/systematic-debugging/condition-based-waiting.md +115 -0
- package/control-plane/scripts/skills/systematic-debugging/defense-in-depth.md +122 -0
- package/control-plane/scripts/skills/systematic-debugging/find-polluter.sh +72 -0
- package/control-plane/scripts/skills/systematic-debugging/root-cause-tracing.md +169 -0
- package/control-plane/scripts/skills/systematic-debugging/test-academic.md +14 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-1.md +58 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-2.md +68 -0
- package/control-plane/scripts/skills/systematic-debugging/test-pressure-3.md +69 -0
- package/control-plane/scripts/skills/test-driven-development/SKILL.md +330 -0
- package/control-plane/scripts/skills/test-driven-development/writing-good-tests.md +198 -0
- package/control-plane/scripts/skills/theme-factory/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/theme-factory/SKILL.md +59 -0
- package/control-plane/scripts/skills/theme-factory/theme-showcase.pdf +0 -0
- package/control-plane/scripts/skills/theme-factory/themes/arctic-frost.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/botanical-garden.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/desert-rose.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/forest-canopy.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/golden-hour.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/midnight-galaxy.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/modern-minimalist.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/ocean-depths.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/sunset-boulevard.md +19 -0
- package/control-plane/scripts/skills/theme-factory/themes/tech-innovation.md +19 -0
- package/control-plane/scripts/skills/using-git-worktrees/SKILL.md +167 -0
- package/control-plane/scripts/skills/verification-before-completion/SKILL.md +120 -0
- package/control-plane/scripts/skills/web-artifacts-builder/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/web-artifacts-builder/SKILL.md +74 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/bundle-artifact.sh +54 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/init-artifact.sh +322 -0
- package/control-plane/scripts/skills/web-artifacts-builder/scripts/shadcn-components.tar.gz +0 -0
- package/control-plane/scripts/skills/webapp-testing/LICENSE.txt +202 -0
- package/control-plane/scripts/skills/webapp-testing/SKILL.md +96 -0
- package/control-plane/scripts/skills/webapp-testing/examples/console_logging.py +35 -0
- package/control-plane/scripts/skills/webapp-testing/examples/element_discovery.py +40 -0
- package/control-plane/scripts/skills/webapp-testing/examples/static_html_automation.py +33 -0
- package/control-plane/scripts/skills/webapp-testing/scripts/with_server.py +106 -0
- package/control-plane/scripts/skills/writing-plans/SKILL.md +192 -0
- package/control-plane/scripts/skills/writing-plans/plan-document-reviewer-prompt.md +49 -0
- package/control-plane/scripts/skills/writing-skills/SKILL.md +681 -0
- package/control-plane/scripts/skills/writing-skills/anthropic-best-practices.md +1150 -0
- package/control-plane/scripts/skills/writing-skills/examples/CLAUDE_MD_TESTING.md +189 -0
- package/control-plane/scripts/skills/writing-skills/graphviz-conventions.dot +172 -0
- package/control-plane/scripts/skills/writing-skills/persuasion-principles.md +187 -0
- package/control-plane/scripts/skills/writing-skills/render-graphs.js +169 -0
- package/control-plane/scripts/skills/writing-skills/testing-skills-with-subagents.md +384 -0
- package/control-plane/scripts/smoke-mcp-schemas.mjs +30 -0
- package/control-plane/scripts/surface-config.mjs +45 -0
- package/control-plane/scripts/sync-skills.mjs +370 -0
- package/control-plane/scripts/tool-metrics.mjs +74 -0
- package/control-plane/scripts/tool-risk.mjs +156 -0
- package/control-plane/scripts/tool-surface-stability.mjs +38 -0
- package/control-plane/scripts/upstreams/catalog-cache.mjs +28 -0
- package/control-plane/scripts/upstreams/config.mjs +158 -0
- package/control-plane/scripts/upstreams/diagnostics.mjs +21 -0
- package/control-plane/scripts/upstreams/http-client.mjs +67 -0
- package/control-plane/scripts/upstreams/manager.mjs +535 -0
- package/control-plane/scripts/upstreams/names.mjs +55 -0
- package/control-plane/scripts/upstreams/presets.mjs +60 -0
- package/control-plane/scripts/upstreams/resource-uri.mjs +29 -0
- package/control-plane/scripts/upstreams/stdio-client.mjs +73 -0
- package/control-plane/scripts/workspace-registry.mjs +147 -0
- package/dist/context/authorization.d.ts +5 -0
- package/dist/context/authorization.js +70 -0
- package/dist/context/capture.d.ts +46 -0
- package/dist/context/capture.js +187 -0
- package/dist/context/cli.d.ts +47 -0
- package/dist/context/cli.js +287 -0
- package/dist/context/device-adapter.d.ts +102 -0
- package/dist/context/device-adapter.js +79 -0
- package/dist/context/gateway.d.ts +354 -0
- package/dist/context/gateway.js +50 -0
- package/dist/context/graph.d.ts +63 -0
- package/dist/context/graph.js +214 -0
- package/dist/context/importer.d.ts +8 -0
- package/dist/context/importer.js +69 -0
- package/dist/context/indexer.d.ts +14 -0
- package/dist/context/indexer.js +57 -0
- package/dist/context/migrations/index.d.ts +4 -0
- package/dist/context/migrations/index.js +87 -0
- package/dist/context/redact.d.ts +15 -0
- package/dist/context/redact.js +43 -0
- package/dist/context/repositories.d.ts +9 -0
- package/dist/context/repositories.js +45 -0
- package/dist/context/search.d.ts +63 -0
- package/dist/context/search.js +207 -0
- package/dist/context/service.d.ts +278 -0
- package/dist/context/service.js +160 -0
- package/dist/context/store.d.ts +134 -0
- package/dist/context/store.js +722 -0
- package/dist/context/text.d.ts +4 -0
- package/dist/context/text.js +14 -0
- package/dist/context/tool-contract.d.ts +534 -0
- package/dist/context/tool-contract.js +45 -0
- package/dist/device/core.d.ts +29 -0
- package/dist/device/core.js +66 -0
- package/dist/device/device.d.ts +5 -0
- package/dist/device/device.js +94 -37
- package/dist/device/gateway-channel.d.ts +8 -1
- package/dist/device/gateway-channel.js +51 -3
- package/dist/device/gateway-tool-adapter.d.ts +11 -3
- package/dist/device/gateway-tool-adapter.js +141 -98
- package/dist/device/official-trust.d.ts +12 -0
- package/dist/device/official-trust.js +25 -1
- package/dist/device/runtime-owner.js +9 -1
- package/dist/distribution.d.ts +4 -0
- package/dist/distribution.js +8 -0
- package/dist/handlers/filesystem-handlers.js +53 -37
- package/dist/handlers/search-handlers.d.ts +1 -0
- package/dist/handlers/search-handlers.js +51 -1
- package/dist/log-palace/cli.d.ts +7 -0
- package/dist/log-palace/cli.js +89 -0
- package/dist/log-palace/formats.d.ts +26 -0
- package/dist/log-palace/formats.js +126 -0
- package/dist/log-palace/store.d.ts +129 -0
- package/dist/log-palace/store.js +856 -0
- package/dist/mcp-device.d.ts +1 -1
- package/dist/mcp-device.js +28 -6
- package/dist/npm-scripts/remote.js +1 -0
- package/dist/platform/activity-journal.d.ts +16 -0
- package/dist/platform/activity-journal.js +68 -0
- package/dist/platform/cli.d.ts +1 -0
- package/dist/platform/cli.js +300 -0
- package/dist/platform/coordination-lock.d.ts +45 -0
- package/dist/platform/coordination-lock.js +233 -0
- package/dist/platform/device-runtime.d.ts +16 -0
- package/dist/platform/device-runtime.js +83 -0
- package/dist/platform/knowledge-contract.d.ts +46 -0
- package/dist/platform/knowledge-contract.js +1 -0
- package/dist/platform/knowledge-mcp.d.ts +29 -0
- package/dist/platform/knowledge-mcp.js +95 -0
- package/dist/platform/knowledge-router.d.ts +64 -0
- package/dist/platform/knowledge-router.js +320 -0
- package/dist/platform/plugin-child.d.ts +1 -0
- package/dist/platform/plugin-child.js +115 -0
- package/dist/platform/plugin-contract.d.ts +76 -0
- package/dist/platform/plugin-contract.js +53 -0
- package/dist/platform/plugin-driver.d.ts +8 -0
- package/dist/platform/plugin-driver.js +443 -0
- package/dist/platform/plugin-host.d.ts +88 -0
- package/dist/platform/plugin-host.js +817 -0
- package/dist/platform/plugin-policy.d.ts +3 -0
- package/dist/platform/plugin-policy.js +29 -0
- package/dist/platform/plugin-services.d.ts +14 -0
- package/dist/platform/plugin-services.js +32 -0
- package/dist/platform/process-identity.d.ts +9 -0
- package/dist/platform/process-identity.js +68 -0
- package/dist/platform/runtime-lease.d.ts +4 -0
- package/dist/platform/runtime-lease.js +58 -0
- package/dist/platform/runtime-root.d.ts +9 -0
- package/dist/platform/runtime-root.js +61 -0
- package/dist/platform/state-backup.d.ts +92 -0
- package/dist/platform/state-backup.js +399 -0
- package/dist/plugins/reference/plugin.d.ts +3 -0
- package/dist/plugins/reference/plugin.js +13 -0
- package/dist/search-manager.d.ts +6 -0
- package/dist/search-manager.js +123 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.js +7 -3
- package/dist/terminal-manager.d.ts +16 -0
- package/dist/terminal-manager.js +137 -42
- package/dist/tool-dispatcher.d.ts +11 -0
- package/dist/tool-dispatcher.js +228 -0
- package/dist/tools/edit.js +72 -1
- package/dist/tools/filesystem.d.ts +1 -1
- package/dist/tools/filesystem.js +2 -2
- package/dist/tools/improved-process-tools.js +91 -108
- package/dist/tools/schemas.d.ts +94 -2
- package/dist/tools/schemas.js +20 -6
- package/dist/types.d.ts +8 -0
- package/dist/utils/output-budget.d.ts +6 -0
- package/dist/utils/output-budget.js +15 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/package.json +29 -6
- package/skills/mcp-device-context/SKILL.md +40 -0
|
@@ -0,0 +1,856 @@
|
|
|
1
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
2
|
+
import { createHash } from 'node:crypto';
|
|
3
|
+
import { closeSync, existsSync, fstatSync, mkdirSync, openSync, readSync, readdirSync, realpathSync, statSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { TextDecoder } from 'node:util';
|
|
6
|
+
import { basename, extname, join, resolve, win32, posix } from 'node:path';
|
|
7
|
+
import { CONFIG_DIR } from '../config.js';
|
|
8
|
+
import { detect, parse } from './formats.js';
|
|
9
|
+
const digest = (value) => createHash('sha256').update(value).digest('hex');
|
|
10
|
+
const value = (v) => typeof v === 'string' ? v : '';
|
|
11
|
+
const safeInt = (n, min, max) => {
|
|
12
|
+
if (!Number.isSafeInteger(n) || n < min || n > max)
|
|
13
|
+
throw new Error('BUDGET_EXCEEDED');
|
|
14
|
+
return n;
|
|
15
|
+
};
|
|
16
|
+
const extensionAllowed = (p) => ['.jsonl', '.log', '.txt', '.ndjson'].includes(extname(p).toLowerCase());
|
|
17
|
+
const LIMIT_BYTES = 64 * 1024 * 1024;
|
|
18
|
+
const MAX_PER_FILE = 64 * 1024 * 1024;
|
|
19
|
+
const MAX_RECORD_BYTES = 128 * 1024 * 1024;
|
|
20
|
+
const FTS_TOKEN_CHUNK = 7000;
|
|
21
|
+
const safeSliceEnd = (text, start, wanted) => {
|
|
22
|
+
let end = Math.min(text.length, start + wanted);
|
|
23
|
+
if (end < text.length && end > start) {
|
|
24
|
+
const code = text.charCodeAt(end - 1);
|
|
25
|
+
if (code >= 0xD800 && code <= 0xDBFF)
|
|
26
|
+
end--;
|
|
27
|
+
}
|
|
28
|
+
return Math.max(start + 1, end);
|
|
29
|
+
};
|
|
30
|
+
const splitFtsToken = (token) => {
|
|
31
|
+
const parts = [];
|
|
32
|
+
for (let i = 0; i < token.length;) {
|
|
33
|
+
const end = safeSliceEnd(token, i, FTS_TOKEN_CHUNK);
|
|
34
|
+
parts.push(token.slice(i, end));
|
|
35
|
+
i = end;
|
|
36
|
+
}
|
|
37
|
+
return parts;
|
|
38
|
+
};
|
|
39
|
+
const ftsSafe = (input) => input.replace(/[\p{L}\p{N}_]{7001,}/gu, token => splitFtsToken(token).join(' '));
|
|
40
|
+
/** Only proven absolute paths match across sessions; relative paths need the logged cwd. */
|
|
41
|
+
function fileIdentity(raw, cwd) {
|
|
42
|
+
if (!raw)
|
|
43
|
+
return null;
|
|
44
|
+
const isWindowsAbsolute = (p) => /^(?:[a-z]:[\\/]|\\\\[^\\]+\\[^\\]+)/i.test(p);
|
|
45
|
+
if (isWindowsAbsolute(raw))
|
|
46
|
+
return 'win:' + win32.normalize(raw).toLowerCase();
|
|
47
|
+
if (posix.isAbsolute(raw))
|
|
48
|
+
return 'posix:' + posix.normalize(raw);
|
|
49
|
+
if (!cwd)
|
|
50
|
+
return null;
|
|
51
|
+
if (isWindowsAbsolute(cwd))
|
|
52
|
+
return 'win:' + win32.resolve(cwd, raw).toLowerCase();
|
|
53
|
+
if (posix.isAbsolute(cwd))
|
|
54
|
+
return 'posix:' + posix.resolve(cwd, raw);
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
export class LogPalace {
|
|
58
|
+
constructor(stateDir = process.env.MCP_DEVICE_LOG_PALACE_DIR || join(CONFIG_DIR, 'log-palace')) {
|
|
59
|
+
mkdirSync(stateDir, { recursive: true });
|
|
60
|
+
this.db = new DatabaseSync(join(stateDir, 'index.sqlite'));
|
|
61
|
+
this.db.exec("PRAGMA busy_timeout=3000; PRAGMA journal_mode=WAL; PRAGMA synchronous=NORMAL; PRAGMA foreign_keys=ON;");
|
|
62
|
+
this.db.exec("CREATE TABLE IF NOT EXISTS sources (id INTEGER PRIMARY KEY,path TEXT NOT NULL UNIQUE,format TEXT NOT NULL,dir INTEGER NOT NULL);" +
|
|
63
|
+
"CREATE TABLE IF NOT EXISTS source_scan (source_id INTEGER PRIMARY KEY,queue TEXT NOT NULL,complete INTEGER NOT NULL DEFAULT 0,epoch INTEGER NOT NULL DEFAULT 0," +
|
|
64
|
+
"FOREIGN KEY(source_id) REFERENCES sources(id));" +
|
|
65
|
+
"CREATE TABLE IF NOT EXISTS palace_state (key TEXT PRIMARY KEY,value TEXT NOT NULL);" +
|
|
66
|
+
"CREATE TABLE IF NOT EXISTS files (id INTEGER PRIMARY KEY,source_id INTEGER NOT NULL,path TEXT NOT NULL UNIQUE,format TEXT NOT NULL," +
|
|
67
|
+
"position INTEGER NOT NULL DEFAULT 0,session TEXT,turn TEXT,cwd TEXT,dev TEXT,ino TEXT,head_hash TEXT,tail_hash TEXT,missing INTEGER NOT NULL DEFAULT 0," +
|
|
68
|
+
"FOREIGN KEY(source_id) REFERENCES sources(id));" +
|
|
69
|
+
"CREATE TABLE IF NOT EXISTS records (id INTEGER PRIMARY KEY,ref TEXT NOT NULL UNIQUE,file_id INTEGER NOT NULL,position INTEGER NOT NULL," +
|
|
70
|
+
"bytes INTEGER NOT NULL,part INTEGER NOT NULL,hash TEXT NOT NULL,kind TEXT NOT NULL,session TEXT,turn TEXT,call_id TEXT,tool TEXT," +
|
|
71
|
+
"file_path TEXT,file_key TEXT,outcome TEXT,timestamp TEXT,preview TEXT NOT NULL,FOREIGN KEY(file_id) REFERENCES files(id)," +
|
|
72
|
+
"UNIQUE(file_id,position,part));" +
|
|
73
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS records_fts USING fts5(body,content='',contentless_delete=1);" +
|
|
74
|
+
"CREATE TABLE IF NOT EXISTS record_fts_chunk_map (id INTEGER PRIMARY KEY,record_id INTEGER NOT NULL,FOREIGN KEY(record_id) REFERENCES records(id));" +
|
|
75
|
+
"CREATE INDEX IF NOT EXISTS record_fts_chunk_record_idx ON record_fts_chunk_map(record_id);" +
|
|
76
|
+
"CREATE VIRTUAL TABLE IF NOT EXISTS records_fts_chunks USING fts5(body,content='',contentless_delete=1);" +
|
|
77
|
+
"CREATE INDEX IF NOT EXISTS records_file_idx ON records(file_id,position);" +
|
|
78
|
+
"CREATE INDEX IF NOT EXISTS records_call_idx ON records(call_id,session);" +
|
|
79
|
+
"CREATE INDEX IF NOT EXISTS records_session_idx ON records(session);" +
|
|
80
|
+
"CREATE INDEX IF NOT EXISTS records_path_idx ON records(file_key);");
|
|
81
|
+
const fileColumns = this.db.prepare("PRAGMA table_info(files)").all();
|
|
82
|
+
if (!fileColumns.some(column => value(column.name) === 'seen_epoch'))
|
|
83
|
+
this.db.exec('ALTER TABLE files ADD COLUMN seen_epoch INTEGER NOT NULL DEFAULT 0');
|
|
84
|
+
if (!fileColumns.some(column => value(column.name) === 'scan_position'))
|
|
85
|
+
this.db.exec('ALTER TABLE files ADD COLUMN scan_position INTEGER NOT NULL DEFAULT 0');
|
|
86
|
+
if (!fileColumns.some(column => value(column.name) === 'mtime_ms'))
|
|
87
|
+
this.db.exec('ALTER TABLE files ADD COLUMN mtime_ms REAL');
|
|
88
|
+
if (!fileColumns.some(column => value(column.name) === 'ctime_ms'))
|
|
89
|
+
this.db.exec('ALTER TABLE files ADD COLUMN ctime_ms REAL');
|
|
90
|
+
if (!fileColumns.some(column => value(column.name) === 'content_hash'))
|
|
91
|
+
this.db.exec('ALTER TABLE files ADD COLUMN content_hash TEXT');
|
|
92
|
+
}
|
|
93
|
+
close() { this.db.close(); }
|
|
94
|
+
atomic(label, fn) {
|
|
95
|
+
const savepoint = 'lp_' + label.replace(/[^a-z0-9_]/gi, '_');
|
|
96
|
+
if (this.db.isTransaction) {
|
|
97
|
+
this.db.exec('SAVEPOINT ' + savepoint);
|
|
98
|
+
try {
|
|
99
|
+
const result = fn();
|
|
100
|
+
this.db.exec('RELEASE ' + savepoint);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
this.db.exec('ROLLBACK TO ' + savepoint);
|
|
105
|
+
this.db.exec('RELEASE ' + savepoint);
|
|
106
|
+
throw error;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
this.db.exec('BEGIN IMMEDIATE');
|
|
110
|
+
try {
|
|
111
|
+
const result = fn();
|
|
112
|
+
this.db.exec('COMMIT');
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
catch (error) {
|
|
116
|
+
this.db.exec('ROLLBACK');
|
|
117
|
+
throw error;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
add(sourcePath, format = 'auto') {
|
|
121
|
+
if (!['auto', 'pi', 'codex', 'mcp', 'text'].includes(format))
|
|
122
|
+
throw new Error('FORMAT_UNSUPPORTED');
|
|
123
|
+
const file = realpathSync(resolve(sourcePath));
|
|
124
|
+
const dir = statSync(file).isDirectory() ? 1 : 0;
|
|
125
|
+
if (!dir && !statSync(file).isFile())
|
|
126
|
+
throw new Error('SOURCE_UNSUPPORTED');
|
|
127
|
+
this.db.prepare('INSERT INTO sources(path,format,dir) VALUES(?,?,?) ON CONFLICT(path) DO UPDATE SET format=excluded.format')
|
|
128
|
+
.run(file, format, dir);
|
|
129
|
+
return { path: file, format, kind: dir ? 'directory' : 'file' };
|
|
130
|
+
}
|
|
131
|
+
discover() {
|
|
132
|
+
const roots = [
|
|
133
|
+
[join(homedir(), '.pi', 'agent', 'sessions'), 'pi'],
|
|
134
|
+
[join(homedir(), '.codex', 'sessions'), 'codex'],
|
|
135
|
+
[join(CONFIG_DIR, 'logs', 'history.jsonl'), 'mcp'],
|
|
136
|
+
];
|
|
137
|
+
const added = [];
|
|
138
|
+
for (const [p, format] of roots)
|
|
139
|
+
if (existsSync(p))
|
|
140
|
+
added.push(this.add(p, format));
|
|
141
|
+
return { added };
|
|
142
|
+
}
|
|
143
|
+
sources() {
|
|
144
|
+
return { sources: this.db.prepare('SELECT path,format,dir FROM sources ORDER BY path').all(),
|
|
145
|
+
indexed_files: Number(this.db.prepare('SELECT count(*) n FROM files').get().n),
|
|
146
|
+
indexed_records: Number(this.db.prepare('SELECT count(*) n FROM records').get().n) };
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Read-only bounded feed for evidence consumers. It does not index or verify source bytes.
|
|
150
|
+
* Consumers must call read(ref) before deriving knowledge; the stored hash is a locator,
|
|
151
|
+
* not proof of truth. Resume by opaque ref cursor; restart a completed pass to see inserts.
|
|
152
|
+
*/
|
|
153
|
+
observations(options = {}) {
|
|
154
|
+
const limit = safeInt(options.limit ?? 64, 1, 256), maxBytes = safeInt(options.maxBytes ?? 65536, 1024, 1024 * 1024);
|
|
155
|
+
const cursor = options.cursor ?? '';
|
|
156
|
+
if (typeof cursor !== 'string' || cursor.length > 512)
|
|
157
|
+
throw new Error('INVALID_CURSOR');
|
|
158
|
+
const rows = this.db.prepare('SELECT r.ref,r.kind,r.tool,r.session,r.turn,r.call_id,r.outcome,r.timestamp,r.preview,r.hash,' +
|
|
159
|
+
'r.position,r.bytes,r.part,r.file_path,f.path AS source_path,f.format,f.missing ' +
|
|
160
|
+
'FROM records r JOIN files f ON f.id=r.file_id WHERE r.ref>? ORDER BY r.ref LIMIT ?').all(cursor, limit + 1);
|
|
161
|
+
const items = [];
|
|
162
|
+
let next = cursor;
|
|
163
|
+
for (const row of rows.slice(0, limit)) {
|
|
164
|
+
const candidate = { ...row, source_available: !Number(row.missing) };
|
|
165
|
+
const envelope = { items: [...items, candidate], nextCursor: value(row.ref), complete: false };
|
|
166
|
+
if (Buffer.byteLength(JSON.stringify(envelope), 'utf8') > maxBytes) {
|
|
167
|
+
if (!items.length)
|
|
168
|
+
throw new Error('BUDGET_TOO_SMALL');
|
|
169
|
+
return { items, nextCursor: next, complete: false };
|
|
170
|
+
}
|
|
171
|
+
items.push(candidate);
|
|
172
|
+
next = value(row.ref);
|
|
173
|
+
}
|
|
174
|
+
const complete = rows.length <= limit;
|
|
175
|
+
return { items, nextCursor: complete ? null : next, complete };
|
|
176
|
+
}
|
|
177
|
+
scanPage(source, maxEntries, maxFiles) {
|
|
178
|
+
const sourceId = Number(source.id), root = value(source.path);
|
|
179
|
+
if (!Number(source.dir))
|
|
180
|
+
return { paths: [root], complete: true, epoch: 1 };
|
|
181
|
+
const row = this.db.prepare('SELECT queue,complete,epoch FROM source_scan WHERE source_id=?').get(sourceId);
|
|
182
|
+
let state = { head: 0, items: [] };
|
|
183
|
+
let epoch = Number(row?.epoch ?? 0);
|
|
184
|
+
if (row && !Number(row.complete)) {
|
|
185
|
+
try {
|
|
186
|
+
const parsed = JSON.parse(value(row.queue));
|
|
187
|
+
state = Array.isArray(parsed) ? { head: 0, items: parsed } : parsed;
|
|
188
|
+
}
|
|
189
|
+
catch {
|
|
190
|
+
state = { head: 0, items: [] };
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
if (state.head >= state.items.length) {
|
|
194
|
+
state = { head: 0, items: [{ path: root, depth: 0, kind: 'dir' }] };
|
|
195
|
+
epoch++;
|
|
196
|
+
}
|
|
197
|
+
const out = [];
|
|
198
|
+
let remaining = maxEntries;
|
|
199
|
+
while (state.head < state.items.length && remaining > 0 && out.length < maxFiles) {
|
|
200
|
+
const cursor = state.items[state.head++];
|
|
201
|
+
remaining--;
|
|
202
|
+
if (cursor.kind === 'file') {
|
|
203
|
+
out.push(cursor.path);
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
let entries;
|
|
207
|
+
try {
|
|
208
|
+
entries = readdirSync(cursor.path, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name));
|
|
209
|
+
}
|
|
210
|
+
catch {
|
|
211
|
+
continue;
|
|
212
|
+
}
|
|
213
|
+
for (const e of entries) {
|
|
214
|
+
if (e.isSymbolicLink())
|
|
215
|
+
continue;
|
|
216
|
+
const p = join(cursor.path, e.name);
|
|
217
|
+
if (e.isDirectory() && cursor.depth < 10)
|
|
218
|
+
state.items.push({ path: p, depth: cursor.depth + 1, kind: 'dir' });
|
|
219
|
+
else if (e.isFile() && extensionAllowed(p))
|
|
220
|
+
state.items.push({ path: p, depth: cursor.depth, kind: 'file' });
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
const complete = state.head >= state.items.length;
|
|
224
|
+
if (!complete && state.head >= 1024) {
|
|
225
|
+
state.items = state.items.slice(state.head);
|
|
226
|
+
state.head = 0;
|
|
227
|
+
}
|
|
228
|
+
this.db.prepare('INSERT INTO source_scan(source_id,queue,complete,epoch) VALUES(?,?,?,?) ' +
|
|
229
|
+
'ON CONFLICT(source_id) DO UPDATE SET queue=excluded.queue,complete=excluded.complete,epoch=excluded.epoch').run(sourceId, JSON.stringify(state), complete ? 1 : 0, epoch);
|
|
230
|
+
return { paths: out, complete, epoch };
|
|
231
|
+
}
|
|
232
|
+
fingerprint(fd, start, count) {
|
|
233
|
+
const size = Math.min(4096, Math.max(0, count));
|
|
234
|
+
if (!size)
|
|
235
|
+
return digest('');
|
|
236
|
+
const buf = Buffer.alloc(size);
|
|
237
|
+
const read = readSync(fd, buf, 0, size, start);
|
|
238
|
+
return digest(buf.subarray(0, read));
|
|
239
|
+
}
|
|
240
|
+
contentHash(fd, count) {
|
|
241
|
+
const hash = createHash('sha256'), buf = Buffer.alloc(1024 * 1024);
|
|
242
|
+
let at = 0;
|
|
243
|
+
while (at < count) {
|
|
244
|
+
const wanted = Math.min(buf.length, count - at), n = readSync(fd, buf, 0, wanted, at);
|
|
245
|
+
if (n < 1)
|
|
246
|
+
throw new Error('SOURCE_CHANGED');
|
|
247
|
+
hash.update(buf.subarray(0, n));
|
|
248
|
+
at += n;
|
|
249
|
+
}
|
|
250
|
+
return hash.digest('hex');
|
|
251
|
+
}
|
|
252
|
+
indexSearchText(recordId, text) {
|
|
253
|
+
if (Buffer.byteLength(text, 'utf8') <= 1024 * 1024) {
|
|
254
|
+
this.db.prepare('INSERT INTO records_fts(rowid,body) VALUES(?,?)').run(recordId, ftsSafe(text));
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const map = this.db.prepare('INSERT INTO record_fts_chunk_map(record_id) VALUES(?)');
|
|
258
|
+
const fts = this.db.prepare('INSERT INTO records_fts_chunks(rowid,body) VALUES(?,?)');
|
|
259
|
+
const window = 64 * 1024, stride = 56 * 1024; // 8 KiB overlap > maximum normalized FTS token segment.
|
|
260
|
+
let output = '', carry = '';
|
|
261
|
+
const emit = (body) => { const mapped = map.run(recordId), rowId = Number(mapped.lastInsertRowid); fts.run(rowId, body); };
|
|
262
|
+
const append = (piece) => {
|
|
263
|
+
output += piece;
|
|
264
|
+
while (output.length >= window) {
|
|
265
|
+
emit(output.slice(0, window));
|
|
266
|
+
output = output.slice(stride);
|
|
267
|
+
}
|
|
268
|
+
};
|
|
269
|
+
for (let start = 0; start < text.length;) {
|
|
270
|
+
const end = safeSliceEnd(text, start, 64 * 1024), combined = carry + text.slice(start, end);
|
|
271
|
+
const tail = combined.match(/[\p{L}\p{N}_]+$/u)?.[0] ?? '';
|
|
272
|
+
const stable = combined.slice(0, combined.length - tail.length);
|
|
273
|
+
if (stable)
|
|
274
|
+
append(ftsSafe(stable));
|
|
275
|
+
carry = tail;
|
|
276
|
+
while (carry.length > FTS_TOKEN_CHUNK * 2) {
|
|
277
|
+
const cut = safeSliceEnd(carry, 0, FTS_TOKEN_CHUNK);
|
|
278
|
+
append(carry.slice(0, cut) + ' ');
|
|
279
|
+
carry = carry.slice(cut);
|
|
280
|
+
}
|
|
281
|
+
start = end;
|
|
282
|
+
}
|
|
283
|
+
if (carry)
|
|
284
|
+
append(ftsSafe(carry));
|
|
285
|
+
if (output)
|
|
286
|
+
emit(output);
|
|
287
|
+
}
|
|
288
|
+
reset(id) {
|
|
289
|
+
const rows = this.db.prepare('SELECT id FROM records WHERE file_id=?').all(id);
|
|
290
|
+
const remove = this.db.prepare('DELETE FROM records_fts WHERE rowid=?');
|
|
291
|
+
const chunkRows = this.db.prepare('SELECT id FROM record_fts_chunk_map WHERE record_id=?');
|
|
292
|
+
const removeChunk = this.db.prepare('DELETE FROM records_fts_chunks WHERE rowid=?');
|
|
293
|
+
const removeChunkMap = this.db.prepare('DELETE FROM record_fts_chunk_map WHERE record_id=?');
|
|
294
|
+
for (const row of rows) {
|
|
295
|
+
remove.run(row.id);
|
|
296
|
+
for (const chunk of chunkRows.all(row.id))
|
|
297
|
+
removeChunk.run(chunk.id);
|
|
298
|
+
removeChunkMap.run(row.id);
|
|
299
|
+
}
|
|
300
|
+
this.db.prepare('DELETE FROM records WHERE file_id=?').run(id);
|
|
301
|
+
this.db.prepare('UPDATE files SET position=0,scan_position=0,session=NULL,turn=NULL,cwd=NULL,head_hash=NULL,tail_hash=NULL,mtime_ms=NULL,ctime_ms=NULL,content_hash=NULL WHERE id=?').run(id);
|
|
302
|
+
}
|
|
303
|
+
formatFor(p, preferred) {
|
|
304
|
+
if (preferred !== 'auto')
|
|
305
|
+
return preferred;
|
|
306
|
+
const fd = openSync(p, 'r');
|
|
307
|
+
try {
|
|
308
|
+
const b = Buffer.alloc(32768), n = readSync(fd, b, 0, b.length, 0);
|
|
309
|
+
const first = b.subarray(0, n).toString('utf8').split(/\r?\n/).find(v => v.trim()) ?? '';
|
|
310
|
+
return detect(first);
|
|
311
|
+
}
|
|
312
|
+
finally {
|
|
313
|
+
closeSync(fd);
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
indexFile(source, p, allocation, seenEpoch = 0) {
|
|
317
|
+
const preliminary = statSync(p);
|
|
318
|
+
if (!preliminary.isFile())
|
|
319
|
+
return { lines: 0, records: 0, bytes: 0, reset: false, stalled: false, partial: false };
|
|
320
|
+
const sourceId = Number(source.id), existing = this.db.prepare('SELECT * FROM files WHERE path=?').get(p);
|
|
321
|
+
let fmt = existing ? value(existing.format) : this.formatFor(p, value(source.format));
|
|
322
|
+
if (!existing)
|
|
323
|
+
this.db.prepare('INSERT INTO files(source_id,path,format,seen_epoch) VALUES(?,?,?,?)').run(sourceId, p, fmt, seenEpoch);
|
|
324
|
+
else
|
|
325
|
+
this.db.prepare('UPDATE files SET source_id=?,seen_epoch=?,missing=0 WHERE id=?').run(sourceId, seenEpoch, existing.id);
|
|
326
|
+
let file = this.db.prepare('SELECT * FROM files WHERE path=?').get(p);
|
|
327
|
+
const fd = openSync(p, 'r');
|
|
328
|
+
try {
|
|
329
|
+
const stats = fstatSync(fd);
|
|
330
|
+
let offset = Number(file.position), scan = Math.max(Number(file.scan_position ?? 0), offset), reset = false;
|
|
331
|
+
const identity = String(stats.dev) + '|' + String(stats.ino);
|
|
332
|
+
const oldIdentity = value(file.dev) + '|' + value(file.ino);
|
|
333
|
+
const verifiedAt = Math.max(offset, scan);
|
|
334
|
+
const sameSizeMetadataChanged = stats.size === verifiedAt && file.mtime_ms !== null &&
|
|
335
|
+
(Number(file.mtime_ms) !== stats.mtimeMs || Number(file.ctime_ms) !== stats.ctimeMs);
|
|
336
|
+
const sameSizeContentChanged = stats.size === verifiedAt && file.content_hash !== null &&
|
|
337
|
+
value(file.content_hash) !== this.contentHash(fd, verifiedAt);
|
|
338
|
+
if ((file.dev !== null && oldIdentity !== identity) || stats.size < verifiedAt || sameSizeMetadataChanged || sameSizeContentChanged ||
|
|
339
|
+
(verifiedAt > 0 && (file.head_hash !== this.fingerprint(fd, 0, verifiedAt) ||
|
|
340
|
+
file.tail_hash !== this.fingerprint(fd, Math.max(0, verifiedAt - 4096), Math.min(4096, verifiedAt))))) {
|
|
341
|
+
this.atomic('reset_file', () => this.reset(Number(file.id)));
|
|
342
|
+
offset = 0;
|
|
343
|
+
scan = 0;
|
|
344
|
+
reset = true;
|
|
345
|
+
file = this.db.prepare('SELECT * FROM files WHERE id=?').get(file.id);
|
|
346
|
+
if (source.format === 'auto') {
|
|
347
|
+
fmt = this.formatFor(p, 'auto');
|
|
348
|
+
this.db.prepare('UPDATE files SET format=? WHERE id=?').run(fmt, file.id);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
if (stats.size === offset) {
|
|
352
|
+
this.db.prepare('UPDATE files SET scan_position=?,dev=?,ino=?,mtime_ms=?,ctime_ms=?,content_hash=?,missing=0 WHERE id=?')
|
|
353
|
+
.run(offset, String(stats.dev), String(stats.ino), stats.mtimeMs, stats.ctimeMs, this.contentHash(fd, offset), file.id);
|
|
354
|
+
return { lines: 0, records: 0, bytes: 0, reset, stalled: false, partial: false };
|
|
355
|
+
}
|
|
356
|
+
const toRead = Math.min(stats.size - scan, allocation, MAX_PER_FILE);
|
|
357
|
+
if (toRead < 1)
|
|
358
|
+
return { lines: 0, records: 0, bytes: 0, reset, stalled: false, partial: scan > offset };
|
|
359
|
+
const buffer = Buffer.alloc(toRead), n = readSync(fd, buffer, 0, toRead, scan);
|
|
360
|
+
const firstEnd = buffer.indexOf(10, 0);
|
|
361
|
+
if (firstEnd < 0) {
|
|
362
|
+
const at = scan + n;
|
|
363
|
+
if (at - offset > MAX_RECORD_BYTES)
|
|
364
|
+
throw new Error('RECORD_TOO_LARGE');
|
|
365
|
+
this.db.prepare('UPDATE files SET scan_position=?,dev=?,ino=?,head_hash=?,tail_hash=?,mtime_ms=?,ctime_ms=?,missing=0 WHERE id=?')
|
|
366
|
+
.run(at, String(stats.dev), String(stats.ino), this.fingerprint(fd, 0, at), this.fingerprint(fd, Math.max(0, at - 4096), Math.min(4096, at)), stats.mtimeMs, stats.ctimeMs, file.id);
|
|
367
|
+
return { lines: 0, records: 0, bytes: n, reset, stalled: false, partial: true };
|
|
368
|
+
}
|
|
369
|
+
let processed = 0, count = 0, position = 0;
|
|
370
|
+
let context = { session: file.session, turn: file.turn, cwd: file.cwd };
|
|
371
|
+
const insert = this.db.prepare('INSERT INTO records(ref,file_id,position,bytes,part,hash,kind,session,turn,call_id,tool,file_path,file_key,outcome,timestamp,preview) VALUES(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)');
|
|
372
|
+
const addLine = (line, linePosition, lineBytes) => {
|
|
373
|
+
if (line.at(-1) === 13)
|
|
374
|
+
line = line.subarray(0, line.length - 1);
|
|
375
|
+
const lineHash = digest(line), text = line.toString('utf8');
|
|
376
|
+
const outcome = parse(fmt, text, context);
|
|
377
|
+
context = outcome.context;
|
|
378
|
+
for (const item of outcome.items) {
|
|
379
|
+
const ref = digest(p + '\0' + linePosition + '\0' + item.part + '\0' + lineHash).slice(0, 48);
|
|
380
|
+
const result = insert.run(ref, file.id, linePosition, lineBytes, item.part, lineHash, item.kind, item.session, item.turn, item.callId, item.tool, item.filePath, fileIdentity(item.filePath, context.cwd), item.outcome, item.timestamp, item.preview);
|
|
381
|
+
this.indexSearchText(Number(result.lastInsertRowid), item.searchText);
|
|
382
|
+
count++;
|
|
383
|
+
}
|
|
384
|
+
processed++;
|
|
385
|
+
};
|
|
386
|
+
return this.atomic('record_file', () => {
|
|
387
|
+
let at, consumed;
|
|
388
|
+
if (scan > offset) {
|
|
389
|
+
const absoluteEnd = scan + firstEnd, recordBytes = absoluteEnd - offset + 1;
|
|
390
|
+
if (recordBytes > MAX_RECORD_BYTES)
|
|
391
|
+
throw new Error('RECORD_TOO_LARGE');
|
|
392
|
+
const full = Buffer.alloc(recordBytes - 1), read = readSync(fd, full, 0, full.length, offset);
|
|
393
|
+
if (read !== full.length)
|
|
394
|
+
throw new Error('SOURCE_CHANGED');
|
|
395
|
+
addLine(full, offset, recordBytes);
|
|
396
|
+
at = absoluteEnd + 1;
|
|
397
|
+
consumed = firstEnd + 1;
|
|
398
|
+
}
|
|
399
|
+
else {
|
|
400
|
+
const end = buffer.lastIndexOf(10, n - 1);
|
|
401
|
+
for (let i = 0; i <= end; i++)
|
|
402
|
+
if (buffer[i] === 10) {
|
|
403
|
+
addLine(buffer.subarray(position, i), offset + position, i - position + 1);
|
|
404
|
+
position = i + 1;
|
|
405
|
+
}
|
|
406
|
+
at = offset + position;
|
|
407
|
+
consumed = position;
|
|
408
|
+
}
|
|
409
|
+
this.db.prepare('UPDATE files SET position=?,scan_position=?,session=?,turn=?,cwd=?,dev=?,ino=?,head_hash=?,tail_hash=?,mtime_ms=?,ctime_ms=?,content_hash=?,missing=0 WHERE id=?')
|
|
410
|
+
.run(at, at, context.session, context.turn, context.cwd, String(stats.dev), String(stats.ino), this.fingerprint(fd, 0, at), this.fingerprint(fd, Math.max(0, at - 4096), Math.min(4096, at)), stats.mtimeMs, stats.ctimeMs, at === stats.size ? this.contentHash(fd, at) : null, file.id);
|
|
411
|
+
return { lines: processed, records: count, bytes: consumed, reset, stalled: false, partial: false };
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
finally {
|
|
415
|
+
closeSync(fd);
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
index(options = {}) {
|
|
419
|
+
const maxFiles = safeInt(options.maxFiles ?? 200, 1, 1000), maxBytes = safeInt(options.maxBytes ?? 32 * 1024 * 1024, 1024, LIMIT_BYTES);
|
|
420
|
+
const registered = this.db.prepare('SELECT * FROM sources ORDER BY id').all();
|
|
421
|
+
const candidates = [];
|
|
422
|
+
const saved = new Map(this.db.prepare('SELECT path,position,scan_position FROM files').all()
|
|
423
|
+
.map(f => [value(f.path), Math.max(Number(f.position), Number(f.scan_position ?? 0))]));
|
|
424
|
+
let discoveryIncomplete = maxFiles < registered.length;
|
|
425
|
+
const pages = new Map();
|
|
426
|
+
const rrRow = this.db.prepare("SELECT value FROM palace_state WHERE key='source_rr_start'").get();
|
|
427
|
+
const rrStart = registered.length ? Math.max(0, Number(rrRow?.value ?? 0)) % registered.length : 0;
|
|
428
|
+
const ordered = registered.length ? [...registered.slice(rrStart), ...registered.slice(0, rrStart)] : [];
|
|
429
|
+
const base = registered.length ? Math.floor(maxFiles / registered.length) : 0;
|
|
430
|
+
const remainder = registered.length ? maxFiles % registered.length : 0;
|
|
431
|
+
for (let index = 0; index < ordered.length; index++) {
|
|
432
|
+
const source = ordered[index], quota = base + (index < remainder ? 1 : 0);
|
|
433
|
+
if (quota < 1)
|
|
434
|
+
continue;
|
|
435
|
+
const root = value(source.path);
|
|
436
|
+
if (!existsSync(root)) {
|
|
437
|
+
this.db.prepare('UPDATE files SET missing=1 WHERE source_id=?').run(source.id);
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
const page = this.scanPage(source, Math.max(256, quota * 8), quota);
|
|
441
|
+
discoveryIncomplete || (discoveryIncomplete = !page.complete);
|
|
442
|
+
const items = [];
|
|
443
|
+
const markSeen = this.db.prepare('UPDATE files SET seen_epoch=?,missing=0 WHERE path=? AND source_id=?');
|
|
444
|
+
for (const p of page.paths) {
|
|
445
|
+
try {
|
|
446
|
+
const s = statSync(p);
|
|
447
|
+
markSeen.run(page.epoch, p, source.id);
|
|
448
|
+
items.push({ source, path: p, mtime: s.mtimeMs, size: s.size, epoch: page.epoch });
|
|
449
|
+
}
|
|
450
|
+
catch { }
|
|
451
|
+
}
|
|
452
|
+
items.sort((a, b) => {
|
|
453
|
+
const ap = a.size > (saved.get(a.path) ?? -1) ? 0 : 1;
|
|
454
|
+
const bp = b.size > (saved.get(b.path) ?? -1) ? 0 : 1;
|
|
455
|
+
return ap - bp || b.mtime - a.mtime || a.path.localeCompare(b.path);
|
|
456
|
+
});
|
|
457
|
+
candidates.push(...items);
|
|
458
|
+
pages.set(Number(source.id), { source, complete: page.complete, epoch: page.epoch });
|
|
459
|
+
}
|
|
460
|
+
if (registered.length) {
|
|
461
|
+
const advance = Math.min(maxFiles, registered.length);
|
|
462
|
+
this.db.prepare("INSERT INTO palace_state(key,value) VALUES('source_rr_start',?) ON CONFLICT(key) DO UPDATE SET value=excluded.value")
|
|
463
|
+
.run(String((rrStart + advance) % registered.length));
|
|
464
|
+
}
|
|
465
|
+
let indexed = 0, files = 0, bytes = 0, reset = 0, stalled = 0, partial = 0, lines = 0;
|
|
466
|
+
const failures = [];
|
|
467
|
+
this.atomic('index_batch', () => {
|
|
468
|
+
for (const c of candidates.slice(0, maxFiles)) {
|
|
469
|
+
if (bytes >= maxBytes)
|
|
470
|
+
break;
|
|
471
|
+
try {
|
|
472
|
+
const r = this.indexFile(c.source, c.path, maxBytes - bytes, c.epoch);
|
|
473
|
+
files++;
|
|
474
|
+
bytes += r.bytes;
|
|
475
|
+
indexed += r.records;
|
|
476
|
+
lines += r.lines;
|
|
477
|
+
if (r.reset)
|
|
478
|
+
reset++;
|
|
479
|
+
if (r.stalled)
|
|
480
|
+
stalled++;
|
|
481
|
+
if (r.partial)
|
|
482
|
+
partial++;
|
|
483
|
+
}
|
|
484
|
+
catch (err) {
|
|
485
|
+
failures.push(basename(c.path) + ': ' + (err instanceof Error ? err.message : 'ERROR'));
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
});
|
|
489
|
+
// Missing-path invalidation is amortized: only a completed discovery epoch may declare unseen files stale.
|
|
490
|
+
for (const page of pages.values())
|
|
491
|
+
if (page.complete)
|
|
492
|
+
this.db.prepare('UPDATE files SET missing=1 WHERE source_id=? AND seen_epoch<?').run(page.source.id, page.epoch);
|
|
493
|
+
return { files, records: indexed, lines, bytes, reset, stalled, partial, errors: failures,
|
|
494
|
+
more_files: discoveryIncomplete || candidates.length > maxFiles || bytes >= maxBytes || candidates.some(c => {
|
|
495
|
+
const row = this.db.prepare('SELECT position,scan_position FROM files WHERE path=?').get(c.path);
|
|
496
|
+
return c.size > Math.max(Number(row?.position ?? 0), Number(row?.scan_position ?? 0));
|
|
497
|
+
}),
|
|
498
|
+
total_records: Number(this.db.prepare('SELECT count(*) n FROM records').get().n) };
|
|
499
|
+
}
|
|
500
|
+
record(ref) {
|
|
501
|
+
if (!/^[a-f0-9]{48}$/.test(ref))
|
|
502
|
+
throw new Error('INVALID_REF');
|
|
503
|
+
const row = this.db.prepare("SELECT r.*,f.path,f.missing,f.source_id FROM records r JOIN files f ON f.id=r.file_id WHERE r.ref=?").get(ref);
|
|
504
|
+
if (!row)
|
|
505
|
+
throw new Error('REF_NOT_FOUND');
|
|
506
|
+
if (Number(row.missing) || !existsSync(value(row.path)))
|
|
507
|
+
throw new Error('SOURCE_MISSING');
|
|
508
|
+
return row;
|
|
509
|
+
}
|
|
510
|
+
validUtf8(fd, start, total) {
|
|
511
|
+
const decoder = new TextDecoder('utf-8', { fatal: true }), block = Buffer.alloc(65536);
|
|
512
|
+
try {
|
|
513
|
+
for (let at = 0; at < total;) {
|
|
514
|
+
const n = readSync(fd, block, 0, Math.min(block.length, total - at), start + at);
|
|
515
|
+
if (n < 1)
|
|
516
|
+
return false;
|
|
517
|
+
decoder.decode(block.subarray(0, n), { stream: true });
|
|
518
|
+
at += n;
|
|
519
|
+
}
|
|
520
|
+
decoder.decode();
|
|
521
|
+
return true;
|
|
522
|
+
}
|
|
523
|
+
catch {
|
|
524
|
+
return false;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
selectedPartSpan(fd, row, total) {
|
|
528
|
+
const part = Number(row.record_part ?? -1);
|
|
529
|
+
if (part < 0)
|
|
530
|
+
return { start: 0, end: total };
|
|
531
|
+
const body = Buffer.alloc(total);
|
|
532
|
+
if (readSync(fd, body, 0, total, Number(row.record_position)) !== total)
|
|
533
|
+
return null;
|
|
534
|
+
const ws = (i) => { while (i < body.length && [9, 10, 13, 32].includes(body[i]))
|
|
535
|
+
i++; return i; };
|
|
536
|
+
const stringEnd = (start) => {
|
|
537
|
+
if (body[start] !== 34)
|
|
538
|
+
return -1;
|
|
539
|
+
for (let i = start + 1; i < body.length; i++) {
|
|
540
|
+
if (body[i] === 92) {
|
|
541
|
+
i++;
|
|
542
|
+
continue;
|
|
543
|
+
}
|
|
544
|
+
if (body[i] === 34)
|
|
545
|
+
return i + 1;
|
|
546
|
+
}
|
|
547
|
+
return -1;
|
|
548
|
+
};
|
|
549
|
+
const valueEnd = (start) => {
|
|
550
|
+
start = ws(start);
|
|
551
|
+
if (start >= body.length)
|
|
552
|
+
return -1;
|
|
553
|
+
if (body[start] === 34)
|
|
554
|
+
return stringEnd(start);
|
|
555
|
+
if (body[start] === 123 || body[start] === 91) {
|
|
556
|
+
const stack = [body[start] === 123 ? 125 : 93];
|
|
557
|
+
for (let i = start + 1; i < body.length; i++) {
|
|
558
|
+
const byte = body[i];
|
|
559
|
+
if (byte === 34) {
|
|
560
|
+
const end = stringEnd(i);
|
|
561
|
+
if (end < 0)
|
|
562
|
+
return -1;
|
|
563
|
+
i = end - 1;
|
|
564
|
+
continue;
|
|
565
|
+
}
|
|
566
|
+
if (byte === 123)
|
|
567
|
+
stack.push(125);
|
|
568
|
+
else if (byte === 91)
|
|
569
|
+
stack.push(93);
|
|
570
|
+
else if (byte === 125 || byte === 93) {
|
|
571
|
+
if (stack.pop() !== byte)
|
|
572
|
+
return -1;
|
|
573
|
+
if (!stack.length)
|
|
574
|
+
return i + 1;
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
return -1;
|
|
578
|
+
}
|
|
579
|
+
let i = start;
|
|
580
|
+
while (i < body.length && body[i] !== 44 && body[i] !== 93 && body[i] !== 125)
|
|
581
|
+
i++;
|
|
582
|
+
return ws(i) > start ? i : -1;
|
|
583
|
+
};
|
|
584
|
+
const objectField = (objectStart, key) => {
|
|
585
|
+
let i = ws(objectStart);
|
|
586
|
+
if (body[i] !== 123)
|
|
587
|
+
return null;
|
|
588
|
+
i = ws(i + 1);
|
|
589
|
+
let found = null;
|
|
590
|
+
while (i < body.length && body[i] !== 125) {
|
|
591
|
+
const keyEnd = stringEnd(i);
|
|
592
|
+
if (keyEnd < 0)
|
|
593
|
+
return null;
|
|
594
|
+
let parsedKey;
|
|
595
|
+
try {
|
|
596
|
+
parsedKey = JSON.parse(body.subarray(i, keyEnd).toString('utf8'));
|
|
597
|
+
}
|
|
598
|
+
catch {
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
i = ws(keyEnd);
|
|
602
|
+
if (body[i] !== 58)
|
|
603
|
+
return null;
|
|
604
|
+
const start = ws(i + 1), end = valueEnd(start);
|
|
605
|
+
if (end < 0)
|
|
606
|
+
return null;
|
|
607
|
+
if (parsedKey === key) {
|
|
608
|
+
if (found)
|
|
609
|
+
return null;
|
|
610
|
+
found = { start, end };
|
|
611
|
+
}
|
|
612
|
+
i = ws(end);
|
|
613
|
+
if (body[i] === 44)
|
|
614
|
+
i = ws(i + 1);
|
|
615
|
+
else if (body[i] !== 125)
|
|
616
|
+
return null;
|
|
617
|
+
}
|
|
618
|
+
return found;
|
|
619
|
+
};
|
|
620
|
+
const arrayItem = (arrayStart, index) => {
|
|
621
|
+
let i = ws(arrayStart);
|
|
622
|
+
if (body[i] !== 91)
|
|
623
|
+
return null;
|
|
624
|
+
i = ws(i + 1);
|
|
625
|
+
for (let current = 0; i < body.length && body[i] !== 93; current++) {
|
|
626
|
+
const start = i, end = valueEnd(start);
|
|
627
|
+
if (end < 0)
|
|
628
|
+
return null;
|
|
629
|
+
if (current === index)
|
|
630
|
+
return { start, end };
|
|
631
|
+
i = ws(end);
|
|
632
|
+
if (body[i] === 44)
|
|
633
|
+
i = ws(i + 1);
|
|
634
|
+
else if (body[i] !== 93)
|
|
635
|
+
return null;
|
|
636
|
+
}
|
|
637
|
+
return null;
|
|
638
|
+
};
|
|
639
|
+
const message = objectField(0, 'message');
|
|
640
|
+
if (!message)
|
|
641
|
+
return null;
|
|
642
|
+
const content = objectField(message.start, 'content');
|
|
643
|
+
if (!content)
|
|
644
|
+
return null;
|
|
645
|
+
return arrayItem(content.start, part);
|
|
646
|
+
}
|
|
647
|
+
/** Locate FTS terms in original UTF-8 source bytes, never in synthetic search text. */
|
|
648
|
+
sourceHit(row, terms) {
|
|
649
|
+
const usable = terms.filter(term => term.length > 0).map(term => ({
|
|
650
|
+
term, matcher: new RegExp(term.replace(/[|\\{}()[\]^$+*?.]/g, '\\$&'), 'iu'),
|
|
651
|
+
bytes: Buffer.byteLength(term, 'utf8'),
|
|
652
|
+
}));
|
|
653
|
+
if (!usable.length)
|
|
654
|
+
return { hit_offset: null, hit_term: null, anchor_status: 'unmapped' };
|
|
655
|
+
let fd;
|
|
656
|
+
try {
|
|
657
|
+
fd = openSync(value(row.path), 'r');
|
|
658
|
+
}
|
|
659
|
+
catch {
|
|
660
|
+
return { hit_offset: null, hit_term: null, anchor_status: 'source_unavailable' };
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
const total = Math.max(0, Number(row.record_bytes) - 1), recordStart = Number(row.record_position);
|
|
664
|
+
const span = this.selectedPartSpan(fd, row, total);
|
|
665
|
+
if (!span)
|
|
666
|
+
return { hit_offset: null, hit_term: null, anchor_status: 'unmapped' };
|
|
667
|
+
const spanLength = span.end - span.start, start = recordStart + span.start;
|
|
668
|
+
const overlap = Math.min(65540, Math.max(...usable.map(term => Math.min(term.bytes, 65536))) + 4);
|
|
669
|
+
const block = Buffer.alloc(65536);
|
|
670
|
+
let carry = Buffer.alloc(0), at = 0;
|
|
671
|
+
while (at < spanLength) {
|
|
672
|
+
const n = readSync(fd, block, 0, Math.min(block.length, spanLength - at), start + at);
|
|
673
|
+
if (n < 1)
|
|
674
|
+
return { hit_offset: null, hit_term: null, anchor_status: 'source_unavailable' };
|
|
675
|
+
const data = carry.length ? Buffer.concat([carry, block.subarray(0, n)]) : block.subarray(0, n);
|
|
676
|
+
const base = at - carry.length;
|
|
677
|
+
// A scan boundary can bisect UTF-8; align before mapping character indexes to bytes.
|
|
678
|
+
let aligned = 0;
|
|
679
|
+
while (aligned < data.length && (data[aligned] & 0xC0) === 0x80)
|
|
680
|
+
aligned++;
|
|
681
|
+
const text = data.subarray(aligned).toString('utf8');
|
|
682
|
+
let first;
|
|
683
|
+
for (const entry of usable) {
|
|
684
|
+
const match = entry.matcher.exec(text);
|
|
685
|
+
if (!match)
|
|
686
|
+
continue;
|
|
687
|
+
const offset = span.start + base + aligned + Buffer.byteLength(text.slice(0, match.index), 'utf8');
|
|
688
|
+
if (!first || offset < first.offset)
|
|
689
|
+
first = { offset, term: entry.term };
|
|
690
|
+
}
|
|
691
|
+
if (first)
|
|
692
|
+
return { hit_offset: first.offset, hit_term: first.term, anchor_status: 'raw_match' };
|
|
693
|
+
const kept = Math.min(overlap, data.length);
|
|
694
|
+
carry = Buffer.from(data.subarray(data.length - kept));
|
|
695
|
+
at += n;
|
|
696
|
+
}
|
|
697
|
+
return { hit_offset: null, hit_term: null, anchor_status: 'unmapped' };
|
|
698
|
+
}
|
|
699
|
+
catch {
|
|
700
|
+
return { hit_offset: null, hit_term: null, anchor_status: 'source_unavailable' };
|
|
701
|
+
}
|
|
702
|
+
finally {
|
|
703
|
+
closeSync(fd);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
search(query, limit = 5) {
|
|
707
|
+
const tokens = query.normalize('NFKC').match(/[\p{L}\p{N}_]+/gu) ?? [];
|
|
708
|
+
if (!tokens.length)
|
|
709
|
+
throw new Error('QUERY_REQUIRED');
|
|
710
|
+
safeInt(limit, 1, 20);
|
|
711
|
+
const safeTokens = tokens.flatMap(token => token.length > FTS_TOKEN_CHUNK ? splitFtsToken(token) : [token]);
|
|
712
|
+
if (safeTokens.length > 24)
|
|
713
|
+
throw new Error('BUDGET_EXCEEDED');
|
|
714
|
+
const quoted = safeTokens.map(t => '"' + t.replace(/"/g, '""') + '"'), match = quoted.join(' AND ');
|
|
715
|
+
const chunkIntersection = quoted.map(() => "SELECT DISTINCT m.record_id FROM records_fts_chunks JOIN record_fts_chunk_map m ON m.id=records_fts_chunks.rowid " +
|
|
716
|
+
"WHERE records_fts_chunks MATCH ?").join(" INTERSECT ");
|
|
717
|
+
const sql = "WITH chunk_hits(record_id) AS (" + chunkIntersection + "), hits(record_id,rank) AS (" +
|
|
718
|
+
"SELECT rowid,bm25(records_fts) FROM records_fts WHERE records_fts MATCH ? UNION ALL " +
|
|
719
|
+
"SELECT record_id,0.0 FROM chunk_hits) " +
|
|
720
|
+
"SELECT r.ref,r.kind,r.tool,r.session,r.turn,r.file_path,r.timestamp,r.preview,f.path," +
|
|
721
|
+
"r.position AS record_position,r.bytes AS record_bytes,r.part AS record_part,min(h.rank) rank " +
|
|
722
|
+
"FROM hits h JOIN records r ON r.id=h.record_id JOIN files f ON f.id=r.file_id WHERE f.missing=0 " +
|
|
723
|
+
"GROUP BY r.id ORDER BY rank,r.id DESC LIMIT ?";
|
|
724
|
+
const rows = this.db.prepare(sql).all(...quoted, match, limit);
|
|
725
|
+
return { query, items: rows.map(row => {
|
|
726
|
+
const { rank, record_position, record_bytes, record_part, ...item } = row;
|
|
727
|
+
return { ...item, ...this.sourceHit(row, tokens), hit_unit: 'source_record_byte' };
|
|
728
|
+
}), count: rows.length };
|
|
729
|
+
}
|
|
730
|
+
read(ref, maxBytes = 8192, fromByte) {
|
|
731
|
+
safeInt(maxBytes, 128, 65536);
|
|
732
|
+
const row = this.record(ref), fd = openSync(value(row.path), 'r');
|
|
733
|
+
try {
|
|
734
|
+
const len = Number(row.bytes);
|
|
735
|
+
if (len > MAX_RECORD_BYTES)
|
|
736
|
+
throw new Error('LINE_TOO_LARGE');
|
|
737
|
+
const physical = Math.max(0, len - 1), hash = createHash('sha256');
|
|
738
|
+
const block = Buffer.alloc(65536), base = Number(row.position);
|
|
739
|
+
const stats = fstatSync(fd);
|
|
740
|
+
if (base + len > stats.size)
|
|
741
|
+
throw new Error('SOURCE_CHANGED');
|
|
742
|
+
let suffix = Buffer.alloc(0);
|
|
743
|
+
const requested = fromByte ?? 0;
|
|
744
|
+
if (!Number.isSafeInteger(requested) || requested < 0 || requested > physical)
|
|
745
|
+
throw new Error('INVALID_OFFSET');
|
|
746
|
+
const prefix = Buffer.alloc(Math.min(maxBytes + 4, Math.max(0, physical - requested)));
|
|
747
|
+
let copied = 0;
|
|
748
|
+
for (let at = 0; at < physical;) {
|
|
749
|
+
const n = readSync(fd, block, 0, Math.min(block.length, physical - at), base + at);
|
|
750
|
+
if (n < 1)
|
|
751
|
+
throw new Error('SOURCE_CHANGED');
|
|
752
|
+
const part = block.subarray(0, n);
|
|
753
|
+
const begin = Math.max(0, requested - at), end = Math.min(n, requested + prefix.length - at);
|
|
754
|
+
if (begin < end) {
|
|
755
|
+
part.copy(prefix, copied, begin, end);
|
|
756
|
+
copied += end - begin;
|
|
757
|
+
}
|
|
758
|
+
// Hash excludes trailing CR, but always includes all other original bytes.
|
|
759
|
+
const combined = suffix.length ? Buffer.concat([suffix, part]) : part;
|
|
760
|
+
if (combined.length > 1) {
|
|
761
|
+
hash.update(combined.subarray(0, combined.length - 1));
|
|
762
|
+
suffix = Buffer.from(combined.subarray(combined.length - 1));
|
|
763
|
+
}
|
|
764
|
+
else
|
|
765
|
+
suffix = combined;
|
|
766
|
+
at += n;
|
|
767
|
+
}
|
|
768
|
+
if (suffix.length && suffix[0] !== 13)
|
|
769
|
+
hash.update(suffix);
|
|
770
|
+
if (hash.digest('hex') !== row.hash)
|
|
771
|
+
throw new Error('SOURCE_CHANGED');
|
|
772
|
+
if (!this.validUtf8(fd, base, physical))
|
|
773
|
+
throw new Error('SOURCE_ENCODING_UNSUPPORTED');
|
|
774
|
+
const terminal = Buffer.alloc(1);
|
|
775
|
+
if (readSync(fd, terminal, 0, 1, base + physical) !== 1 || terminal[0] !== 10)
|
|
776
|
+
throw new Error('SOURCE_CHANGED');
|
|
777
|
+
const endByte = physical - (suffix.length && suffix[0] === 13 ? 1 : 0);
|
|
778
|
+
if (requested > endByte)
|
|
779
|
+
throw new Error('INVALID_OFFSET');
|
|
780
|
+
const take = Math.min(copied, maxBytes, endByte - requested);
|
|
781
|
+
let first = 0;
|
|
782
|
+
if (take > 0 && (prefix[0] & 0xC0) === 0x80)
|
|
783
|
+
throw new Error('INVALID_OFFSET');
|
|
784
|
+
let end = take;
|
|
785
|
+
while (end > 0 && (prefix[end] & 0xC0) === 0x80)
|
|
786
|
+
end--;
|
|
787
|
+
const raw = prefix.subarray(first, end);
|
|
788
|
+
let text = raw.toString('utf8'), view = fromByte === undefined && Number(row.part) >= 0 ? 'selected_part' : 'raw_source';
|
|
789
|
+
let total = endByte;
|
|
790
|
+
if (view === 'selected_part') {
|
|
791
|
+
// Keep the old selected Pi content-part semantics for unanchored reads.
|
|
792
|
+
const whole = Buffer.alloc(physical);
|
|
793
|
+
if (readSync(fd, whole, 0, physical, base) !== physical)
|
|
794
|
+
throw new Error('SOURCE_CHANGED');
|
|
795
|
+
try {
|
|
796
|
+
const parsed = JSON.parse(whole.toString('utf8'));
|
|
797
|
+
const selected = parsed?.message?.content?.[Number(row.part)];
|
|
798
|
+
if (selected !== undefined) {
|
|
799
|
+
text = JSON.stringify(selected);
|
|
800
|
+
total = Buffer.byteLength(text, 'utf8');
|
|
801
|
+
}
|
|
802
|
+
}
|
|
803
|
+
catch { }
|
|
804
|
+
const encoded = Buffer.from(text);
|
|
805
|
+
let cut = Math.min(maxBytes, encoded.length);
|
|
806
|
+
while (cut > 0 && (encoded[cut] & 0xC0) === 0x80)
|
|
807
|
+
cut--;
|
|
808
|
+
text = encoded.subarray(0, cut).toString('utf8');
|
|
809
|
+
return { ref, source: row.path, kind: row.kind, tool: row.tool, session: row.session, turn: row.turn, call_id: row.call_id,
|
|
810
|
+
offset: row.position, bytes: row.bytes, text, view, range_start: 0, range_end: cut, next_offset: null, resumable: false, truncated: cut < total };
|
|
811
|
+
}
|
|
812
|
+
return { ref, source: row.path, kind: row.kind, tool: row.tool, session: row.session, turn: row.turn, call_id: row.call_id,
|
|
813
|
+
offset: row.position, bytes: row.bytes, text, view, range_start: requested, range_end: requested + end,
|
|
814
|
+
next_offset: requested + end < endByte ? requested + end : null, truncated: requested > 0 || requested + end < endByte };
|
|
815
|
+
}
|
|
816
|
+
finally {
|
|
817
|
+
closeSync(fd);
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
graph(ref, limit = 12) {
|
|
821
|
+
safeInt(limit, 1, 30);
|
|
822
|
+
const focus = this.record(ref), rels = [];
|
|
823
|
+
const push = (relation, basis, query, args) => {
|
|
824
|
+
const rows = this.db.prepare(query).all(...args);
|
|
825
|
+
for (const row of rows)
|
|
826
|
+
if (row.ref !== ref)
|
|
827
|
+
rels.push({ relation, basis, row });
|
|
828
|
+
};
|
|
829
|
+
const base = "SELECT r.ref,r.kind,r.tool,r.preview,r.session,r.turn,r.file_path FROM records r JOIN files f ON f.id=r.file_id " +
|
|
830
|
+
"WHERE f.missing=0 AND r.ref<>?";
|
|
831
|
+
if (focus.call_id) {
|
|
832
|
+
// When session identity exists, never fall back to a file-level join across other sessions.
|
|
833
|
+
const scoped = focus.session !== null;
|
|
834
|
+
push('same_tool_call', 'explicit call_id with session/file scope', base + (scoped ? ' AND r.call_id=? AND r.session=? AND r.file_id=? LIMIT ?' : ' AND r.call_id=? AND r.file_id=? LIMIT ?'), scoped ? [ref, value(focus.call_id), value(focus.session), Number(focus.file_id), limit] : [ref, value(focus.call_id), Number(focus.file_id), limit]);
|
|
835
|
+
}
|
|
836
|
+
if (focus.file_key)
|
|
837
|
+
push('same_file', 'explicit argument path resolved against logged cwd', base + " AND r.file_key=? LIMIT ?", [ref, value(focus.file_key), limit]);
|
|
838
|
+
if (focus.turn && focus.session)
|
|
839
|
+
push('same_turn', 'explicit session and turn within registered source lineage', base + " AND r.session=? AND r.turn=? AND f.source_id=? LIMIT ?", [ref, value(focus.session), value(focus.turn), Number(focus.source_id), limit]);
|
|
840
|
+
if (focus.session)
|
|
841
|
+
push('same_session', 'explicit session identity within registered source lineage', base + " AND r.session=? AND f.source_id=? LIMIT ?", [ref, value(focus.session), Number(focus.source_id), limit]);
|
|
842
|
+
const seen = new Set(), neighbors = [];
|
|
843
|
+
for (const relation of rels) {
|
|
844
|
+
const key = value(relation.row.ref);
|
|
845
|
+
if (seen.has(key))
|
|
846
|
+
continue;
|
|
847
|
+
seen.add(key);
|
|
848
|
+
neighbors.push({ ref: key, relation: relation.relation, basis: relation.basis, kind: relation.row.kind,
|
|
849
|
+
tool: relation.row.tool, preview: relation.row.preview });
|
|
850
|
+
if (neighbors.length >= limit)
|
|
851
|
+
break;
|
|
852
|
+
}
|
|
853
|
+
return { ref, kind: focus.kind, session: focus.session, call_id: focus.call_id, neighbors, count: neighbors.length,
|
|
854
|
+
note: 'Associations are grounded in recorded IDs/paths; no causality is inferred.' };
|
|
855
|
+
}
|
|
856
|
+
}
|