diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100644 index 0000000..3d514e3 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,3 @@ +#!/bin/sh +# One-time setup: git config core.hooksPath .githooks +exec bun scripts/check-private.ts --staged diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..5a4d60a --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,78 @@ +# Push/PR checks, one job per project so a red job names what broke. core, ui and the shell each +# typecheck (and ui builds) on their own; boundaries and private-guard are what make "on their own" +# true rather than assumed (BASED-PKG-BOUNDARIES, BASED-PRIVATE-GUARD). No Rust here - the Tauri +# build is exercised by build-macos.yml and release.yml. +name: ci + +on: + push: + branches: ["main"] + pull_request: + +jobs: + core: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run --cwd core typecheck + + ui: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run --cwd ui typecheck + - run: bun run --cwd ui build + + shell-tauri: + runs-on: ubuntu-latest + timeout-minutes: 10 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run --cwd shell-tauri typecheck + + tests: + # Windows because the suite exercises keyring, platform paths and the native addons on the + # platform we actually ship first; integration tests self-skip without their databases. + runs-on: windows-2025 + timeout-minutes: 20 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun install --frozen-lockfile + - run: bun run test + + boundaries: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun scripts/check-boundaries.ts + + private-guard: + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - uses: actions/checkout@v4 + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + - run: bun scripts/check-private.ts --tree diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3aad80c..a016741 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -234,3 +234,40 @@ jobs: git add Casks/based.rb git commit -m "based $VERSION" git push + + # BASED-CORE-PUBLISH: the same tag publishes @cyronius/based-core at the same version, so based.ai and + # anyone else embedding core pin to a number that matches a desktop release. Gated on the + # Windows job because that is where typecheck and the test suite run. + publish-core: + needs: [windows] + runs-on: ubuntu-latest + timeout-minutes: 10 + permissions: + contents: read + id-token: write + steps: + - uses: actions/checkout@v4 + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - uses: actions/setup-node@v4 + with: + node-version: 22 + registry-url: https://registry.npmjs.org + + - name: Check tag matches core/package.json version + run: | + VERSION=$(jq -r .version core/package.json) + TAG="${GITHUB_REF_NAME#v}" + if [ "$VERSION" != "$TAG" ]; then + echo "Tag v$TAG does not match core/package.json version $VERSION" >&2 + exit 1 + fi + + - name: Publish @cyronius/based-core + working-directory: core + run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.gitignore b/.gitignore index e5b37a5..b24b301 100644 --- a/.gitignore +++ b/.gitignore @@ -26,3 +26,9 @@ shell-tauri/icons/ios/ *.pem *.key *_rsa_key* + +# The paid product (based.ai) lives in a sibling repo, never here. Backstop for scripts/check-private.ts. +based-ai/ +paid/ +private/ +*.private/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 4e6aab4..5ee5a21 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -9,10 +9,13 @@ Start with [docs/development.md](docs/development.md) for setup and dev loops. ```sh bun run typecheck +bun run check bun test ``` -Both must pass. Integration tests self-skip without a SQL Server — that's expected and fine. +All three must pass. Integration tests self-skip without a SQL Server — that's expected and fine. +`check` keeps `core/` and `ui/` from importing each other and keeps private paths out of the repo; +run `git config core.hooksPath .githooks` once so the second half also runs before each commit. Match the surrounding code. This codebase has a consistent house style: comments explain *why*, not *what*, and non-obvious workarounds carry the diagnosis that justifies them. Several files diff --git a/README.md b/README.md index 18f573c..43b667c 100644 --- a/README.md +++ b/README.md @@ -573,9 +573,11 @@ weeks old, not years. Specifically: adding an engine is a descriptor plus an adapter — but these three are what's implemented. - **The installer is unsigned** — see the SmartScreen note above. - **No auto-update.** Watch releases, or check the version in the status bar against the latest. +- **Core is a package.** `core/` publishes to npm as `@cyronius/based-core` at each release, so the engine + can be embedded without the desktop shell. The frontend talks to it over HTTP only. - Expect rough edges, and please [file them](https://github.com/Cyronius/based/issues). -What isn't rough: there are **164 numbered requirements** in +What isn't rough: there are **167 numbered requirements** in [specs/based/spec.md](specs/based/spec.md), each with either an executable test or a written verification procedure. If you want to know exactly what's specified to work, that's the file. diff --git a/bun.lock b/bun.lock index eece9aa..fc5979e 100644 --- a/bun.lock +++ b/bun.lock @@ -6,7 +6,8 @@ "name": "based", }, "core": { - "name": "@based/core", + "name": "@cyronius/based-core", + "version": "0.1.6", "dependencies": { "@ag-ui/core": "^0.0.57", "@ag-ui/encoder": "^0.0.57", @@ -37,7 +38,7 @@ "shell-tauri": { "name": "@based/shell-tauri", "dependencies": { - "@based/core": "workspace:*", + "@cyronius/based-core": "workspace:*", }, "devDependencies": { "@types/bun": "^1.3.14", @@ -47,7 +48,7 @@ "specs": { "name": "@based/specs", "dependencies": { - "@based/core": "workspace:*", + "@cyronius/based-core": "workspace:*", "exceljs": "^4.4.0", }, "devDependencies": { @@ -108,7 +109,7 @@ "@ag-ui/client": ["@ag-ui/client@0.0.47", "", { "dependencies": { "@ag-ui/core": "0.0.47", "@ag-ui/encoder": "0.0.47", "@ag-ui/proto": "0.0.47", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-zciVwYV6gmYzdMyWwSDR/VBcOj5x1lHPG/ZGDKyxDHbCeIdGp7sKoGORKHAiPAsfUhReGLKXsR/4IknqwY2PgQ=="], - "@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + "@ag-ui/core": ["@ag-ui/core@0.0.47", "", { "dependencies": { "rxjs": "7.8.1", "zod": "^3.22.4" } }, "sha512-fHat7ZErAH028R90psYclTWaj6PdcvN2GJxzwWPF/j1c5ceqbF2+6xe+t06Psg+gCzZneI9QE3IkOkdJNplZ5A=="], "@ag-ui/encoder": ["@ag-ui/encoder@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@ag-ui/proto": "0.0.57" } }, "sha512-ifD9NctR4xyPDR58xF9GK1bj/S8oECFkTeDfuYD8tXdbcOstIJ2TOqU2zhiCKnw7Vw+zR9Qv3TbsM9E7Gi9X3Q=="], @@ -120,7 +121,7 @@ "@ag-ui/mcp-middleware": ["@ag-ui/mcp-middleware@0.0.1", "", { "dependencies": { "@ag-ui/client": "0.0.54", "@modelcontextprotocol/sdk": "^1.0.0" }, "peerDependencies": { "rxjs": "7.8.1" } }, "sha512-TayUu7kB+jXUTPRUJesNvJYrP+0weTL9F2VJJ8QQ4sWxY/Ihjo+GgFYgJZYNcLwbo1DKgmVJtdm2XUouPCbxeg=="], - "@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@ag-ui/proto": ["@ag-ui/proto@0.0.47", "", { "dependencies": { "@ag-ui/core": "0.0.47", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-+KCrkeVeR6MulWoYUq9Fwm22gFlI9aKG50eKYRtbTM/Yuei/Che5vjbF297XfSnGUunzScrOmdnTeTCprrtb7A=="], "@ai-sdk/anthropic": ["@ai-sdk/anthropic@4.0.20", "", { "dependencies": { "@ai-sdk/provider": "4.0.3", "@ai-sdk/provider-utils": "5.0.12" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-5QdZt6AkWIKIKuwRSjAQIBhN0JSSvthaIcAJKTHP7HPCvWMcNJFF6rz0uKfUlpI3h7nVbY+1Cy5dNl5zjzJ6qA=="], @@ -290,8 +291,6 @@ "@babel/types": ["@babel/types@7.29.7", "", { "dependencies": { "@babel/helper-string-parser": "^7.29.7", "@babel/helper-validator-identifier": "^7.29.7" } }, "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA=="], - "@based/core": ["@based/core@workspace:core"], - "@based/shell-tauri": ["@based/shell-tauri@workspace:shell-tauri"], "@based/specs": ["@based/specs@workspace:specs"], @@ -322,6 +321,8 @@ "@copilotkit/shared": ["@copilotkit/shared@1.63.1", "", { "dependencies": { "@ag-ui/client": "0.0.57", "@copilotkit/license-verifier": "~0.5.0", "@segment/analytics-node": "^2.1.2", "@standard-schema/spec": "^1.0.0", "chalk": "4.1.2", "graphql": "^16.8.1", "partial-json": "^0.1.7", "uuid": "^11.1.0", "zod": "^3.23.3", "zod-to-json-schema": "^3.23.5" }, "peerDependencies": { "@ag-ui/core": ">=0.0.48" } }, "sha512-jm7eNDS4AcA+s1lkOnd9U1BKelgGrFLXg64e5T4AVN5BUJyi2GRC67unBjT+9cvMgZGXI2Dj4jrsY5rQyiaXAw=="], + "@cyronius/based-core": ["@cyronius/based-core@workspace:core"], + "@dabh/diagnostics": ["@dabh/diagnostics@2.0.8", "", { "dependencies": { "@so-ric/colorspace": "^1.1.6", "enabled": "2.0.x", "kuler": "^2.0.0" } }, "sha512-R4MSXTVnuMzGD7bzHdW2ZhhdPC/igELENcq5IjEverBvq5hn1SXCWcsi6eSsdWP0/Ur+SItRRjAktmdoX/8R/Q=="], "@dagrejs/dagre": ["@dagrejs/dagre@3.0.0", "", { "dependencies": { "@dagrejs/graphlib": "4.0.1" } }, "sha512-ZzhnTy1rfuoew9Ez3EIw4L2znPGnYYhfn8vc9c4oB8iw6QAsszbiU0vRhlxWPFnmmNSFAkrYeF1PhM5m4lAN0Q=="], @@ -2026,7 +2027,7 @@ "rxjs": ["rxjs@7.8.1", "", { "dependencies": { "tslib": "^2.1.0" } }, "sha512-AA3TVj+0A2iuIoQkWEK/tqFjBq2j+6PO6Y0zJcvzLAFhEFIO3HL0vls9hWLncZbAAbK0mar7oZ4V079I/qPMxg=="], - "safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], "safe-stable-stringify": ["safe-stable-stringify@2.5.0", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="], @@ -2042,7 +2043,7 @@ "secure-json-parse": ["secure-json-parse@2.7.0", "", {}, "sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw=="], - "semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "send": ["send@0.19.2", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "~0.5.2", "http-errors": "~2.0.1", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "~2.4.1", "range-parser": "~1.2.1", "statuses": "~2.0.2" } }, "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg=="], @@ -2160,7 +2161,7 @@ "type-is": ["type-is@1.6.18", "", { "dependencies": { "media-typer": "0.3.0", "mime-types": "~2.1.24" } }, "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g=="], - "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], "typical": ["typical@4.0.0", "", {}, "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw=="], @@ -2262,14 +2263,14 @@ "@a2a-js/sdk/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], - "@ag-ui/client/@ag-ui/core": ["@ag-ui/core@0.0.47", "", { "dependencies": { "rxjs": "7.8.1", "zod": "^3.22.4" } }, "sha512-fHat7ZErAH028R90psYclTWaj6PdcvN2GJxzwWPF/j1c5ceqbF2+6xe+t06Psg+gCzZneI9QE3IkOkdJNplZ5A=="], - "@ag-ui/client/@ag-ui/encoder": ["@ag-ui/encoder@0.0.47", "", { "dependencies": { "@ag-ui/core": "0.0.47", "@ag-ui/proto": "0.0.47" } }, "sha512-AgKTM/DEHtaNrcbMa0z1UQ7lKiKtalbFAjBTTP0vCh+lJ6JWIu9LrpCJQ4EjON1IRoAZtJBORlCj1KY+F14HXQ=="], - "@ag-ui/client/@ag-ui/proto": ["@ag-ui/proto@0.0.47", "", { "dependencies": { "@ag-ui/core": "0.0.47", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-+KCrkeVeR6MulWoYUq9Fwm22gFlI9aKG50eKYRtbTM/Yuei/Che5vjbF297XfSnGUunzScrOmdnTeTCprrtb7A=="], - "@ag-ui/client/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "@ag-ui/encoder/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + + "@ag-ui/encoder/@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@ag-ui/mcp-middleware/@ag-ui/client": ["@ag-ui/client@0.0.54", "", { "dependencies": { "@ag-ui/core": "0.0.54", "@ag-ui/encoder": "0.0.54", "@ag-ui/proto": "0.0.54", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-N5UVXEBV5gPHqTuMoR/21brconRn42URf+MB4L8OniCJKqLcl/qUJb5kMamK0nnfBhDfPs/uq7LxDn6bsDJzJg=="], "@ai-sdk/google/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], @@ -2304,26 +2305,24 @@ "@azure/core-xml/fast-xml-parser": ["fast-xml-parser@5.10.1", "", { "dependencies": { "@nodable/entities": "^3.0.0", "fast-xml-builder": "^1.2.0", "is-unsafe": "^2.0.0", "path-expression-matcher": "^1.6.2", "strnum": "^2.4.1", "xml-naming": "^0.3.0" }, "bin": { "fxparser": "src/cli/cli.js" } }, "sha512-IEMIf7298kXuZSRFoGfMYrl7is8LpavODgbNz1cwIudv7KwVFnuU+UsMporfq6PD6aXSlawZlARiA3UywCTfMw=="], - "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@babel/helper-compilation-targets/lru-cache": ["lru-cache@5.1.1", "", { "dependencies": { "yallist": "^3.0.2" } }, "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w=="], - "@babel/helper-compilation-targets/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - - "@based/shell-tauri/typescript": ["typescript@7.0.2", "", { "optionalDependencies": { "@typescript/typescript-aix-ppc64": "7.0.2", "@typescript/typescript-darwin-arm64": "7.0.2", "@typescript/typescript-darwin-x64": "7.0.2", "@typescript/typescript-freebsd-arm64": "7.0.2", "@typescript/typescript-freebsd-x64": "7.0.2", "@typescript/typescript-linux-arm": "7.0.2", "@typescript/typescript-linux-arm64": "7.0.2", "@typescript/typescript-linux-loong64": "7.0.2", "@typescript/typescript-linux-mips64el": "7.0.2", "@typescript/typescript-linux-ppc64": "7.0.2", "@typescript/typescript-linux-riscv64": "7.0.2", "@typescript/typescript-linux-s390x": "7.0.2", "@typescript/typescript-linux-x64": "7.0.2", "@typescript/typescript-netbsd-arm64": "7.0.2", "@typescript/typescript-netbsd-x64": "7.0.2", "@typescript/typescript-openbsd-arm64": "7.0.2", "@typescript/typescript-openbsd-x64": "7.0.2", "@typescript/typescript-sunos-x64": "7.0.2", "@typescript/typescript-win32-arm64": "7.0.2", "@typescript/typescript-win32-x64": "7.0.2" }, "bin": { "tsc": "bin/tsc" } }, "sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA=="], - - "@based/ui/@ag-ui/core": ["@ag-ui/core@0.0.47", "", { "dependencies": { "rxjs": "7.8.1", "zod": "^3.22.4" } }, "sha512-fHat7ZErAH028R90psYclTWaj6PdcvN2GJxzwWPF/j1c5ceqbF2+6xe+t06Psg+gCzZneI9QE3IkOkdJNplZ5A=="], - "@based/ui/@itkennel/lm-ag-ui": ["@itkennel/lm-ag-ui@file:ui/vendor/lm-ag-ui", { "dependencies": { "uuid": "^11.1.0" }, "peerDependencies": { "@ag-ui/client": "^0.0.47", "@ag-ui/core": "^0.0.47", "react": ">=18" } }], + "@based/ui/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@copilotkit/channels-core/@ag-ui/client": ["@ag-ui/client@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@ag-ui/encoder": "0.0.57", "@ag-ui/proto": "0.0.57", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-Xap2alG9Z0/j5kb3x4D7oTpe2sw1dfrC9rgJJr2NZu5vKcm8dzIPNd31mF2B4zS3BKqYIu245yxKPhEtT30MHw=="], + "@copilotkit/channels-core/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + "@copilotkit/channels-intelligence/@ag-ui/client": ["@ag-ui/client@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@ag-ui/encoder": "0.0.57", "@ag-ui/proto": "0.0.57", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-Xap2alG9Z0/j5kb3x4D7oTpe2sw1dfrC9rgJJr2NZu5vKcm8dzIPNd31mF2B4zS3BKqYIu245yxKPhEtT30MHw=="], "@copilotkit/core/@ag-ui/client": ["@ag-ui/client@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@ag-ui/encoder": "0.0.57", "@ag-ui/proto": "0.0.57", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-Xap2alG9Z0/j5kb3x4D7oTpe2sw1dfrC9rgJJr2NZu5vKcm8dzIPNd31mF2B4zS3BKqYIu245yxKPhEtT30MHw=="], "@copilotkit/runtime/@ag-ui/client": ["@ag-ui/client@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@ag-ui/encoder": "0.0.57", "@ag-ui/proto": "0.0.57", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-Xap2alG9Z0/j5kb3x4D7oTpe2sw1dfrC9rgJJr2NZu5vKcm8dzIPNd31mF2B4zS3BKqYIu245yxKPhEtT30MHw=="], + "@copilotkit/runtime/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + "@copilotkit/runtime/@ai-sdk/anthropic": ["@ai-sdk/anthropic@3.0.99", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-/q4MW3nfwpIHDhfcPcaW6PF8twF7WhfMWVhKMgmFTwFo8oCoFUebp/+Rt4YVSu1a/rEzm7Ap4sQXhfmmzF5xuw=="], "@copilotkit/runtime/@ai-sdk/openai": ["@ai-sdk/openai@3.0.86", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@ai-sdk/provider-utils": "4.0.40" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-np3jjvNmWVZyBjI02a1fx4TzWVnOaOp3yJAfOJw9dxEAPmvnOhyx22TkFcJ91LqyFuCbGKZKtSBj6ee7ARsiPw=="], @@ -2334,8 +2333,14 @@ "@copilotkit/shared/@ag-ui/client": ["@ag-ui/client@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@ag-ui/encoder": "0.0.57", "@ag-ui/proto": "0.0.57", "@types/uuid": "^10.0.0", "compare-versions": "^6.1.1", "fast-json-patch": "^3.1.1", "rxjs": "7.8.1", "untruncate-json": "^0.0.1", "uuid": "^11.1.0", "zod": "^3.22.4" } }, "sha512-Xap2alG9Z0/j5kb3x4D7oTpe2sw1dfrC9rgJJr2NZu5vKcm8dzIPNd31mF2B4zS3BKqYIu245yxKPhEtT30MHw=="], + "@copilotkit/shared/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + "@copilotkit/shared/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "@cyronius/based-core/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + + "@cyronius/based-core/typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], + "@fast-csv/format/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], "@fast-csv/parse/@types/node": ["@types/node@14.18.63", "", {}, "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ=="], @@ -2396,6 +2401,8 @@ "command-line-usage/typical": ["typical@7.3.0", "", {}, "sha512-ya4mg/30vm+DOWfBg4YK3j2WD6TWtRkCbasOJr40CseYENzCUby/7rIvXA99JGsQHeNxLbnXdyLLxKSv3tauFw=="], + "content-disposition/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "cytoscape-fcose/cose-base": ["cose-base@2.2.0", "", { "dependencies": { "layout-base": "^2.0.0" } }, "sha512-AzlgcsCbUMymkADOJtQm3wO9S3ltPfYOFD5033keQn9NJzIbtnZj+UdBJe7DYml/8TdbtHJW3j58SOnKhWY/5g=="], "d3-dsv/commander": ["commander@7.2.0", "", {}, "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw=="], @@ -2408,8 +2415,12 @@ "duplexer2/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "ecdsa-sig-formatter/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "express/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], + "express/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "finalhandler/debug": ["debug@2.6.9", "", { "dependencies": { "ms": "2.0.0" } }, "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA=="], "gaxios/node-fetch": ["node-fetch@3.3.2", "", { "dependencies": { "data-uri-to-buffer": "^4.0.0", "fetch-blob": "^3.1.4", "formdata-polyfill": "^4.0.10" } }, "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA=="], @@ -2420,8 +2431,14 @@ "is-inside-container/is-docker": ["is-docker@3.0.0", "", { "bin": { "is-docker": "cli.js" } }, "sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ=="], + "jsonwebtoken/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "jszip/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], + "jwa/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + + "jws/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "katex/commander": ["commander@8.3.0", "", {}, "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww=="], "lazystream/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], @@ -2468,10 +2485,14 @@ "streamdown/marked": ["marked@17.0.6", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA=="], + "string_decoder/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="], + "table-layout/array-back": ["array-back@6.2.3", "", {}, "sha512-SGDvmg6QTYiTxCBkYVmThcoa67uLl35pyzRHdpCGBOcqFy6BtwnphoFPk7LhJshD+Yk1Kt35WGWeZPTgwR4Fhw=="], "tar-stream/bl": ["bl@4.1.0", "", { "dependencies": { "buffer": "^5.5.0", "inherits": "^2.0.4", "readable-stream": "^3.4.0" } }, "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w=="], + "type-graphql/semver": ["semver@7.8.5", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA=="], + "unzipper/readable-stream": ["readable-stream@2.3.8", "", { "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", "isarray": "~1.0.0", "process-nextick-args": "~2.0.0", "safe-buffer": "~5.1.1", "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } }, "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA=="], "winston/is-stream": ["is-stream@2.0.1", "", {}, "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg=="], @@ -2492,12 +2513,24 @@ "@based/ui/@itkennel/lm-ag-ui/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "@copilotkit/channels-core/@ag-ui/client/@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@copilotkit/channels-core/@ag-ui/client/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "@copilotkit/channels-intelligence/@ag-ui/client/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + + "@copilotkit/channels-intelligence/@ag-ui/client/@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@copilotkit/channels-intelligence/@ag-ui/client/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "@copilotkit/core/@ag-ui/client/@ag-ui/core": ["@ag-ui/core@0.0.57", "", { "dependencies": { "zod": "^3.22.4" } }, "sha512-gho1OWjNE6E3Rl7ZEZ1wr2CEpUHjLFU0FqzCZZk439TicLu+BfLCMkMokB07bMGlRmbJ60hM6LW60iOVauCx+Q=="], + + "@copilotkit/core/@ag-ui/client/@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@copilotkit/core/@ag-ui/client/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], + "@copilotkit/runtime/@ag-ui/client/@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@copilotkit/runtime/@ag-ui/client/uuid": ["uuid@11.1.1", "", { "bin": { "uuid": "dist/esm/bin/uuid" } }, "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ=="], "@copilotkit/runtime/@ai-sdk/anthropic/@ai-sdk/provider": ["@ai-sdk/provider@3.0.14", "", { "dependencies": { "json-schema": "^0.4.0" } }, "sha512-5X1k57JBJ4H7H1QjX7CnJYAB1I19r/trVZTMcSms7/kLNZ8RaU4Nt2agcwZzv82Hfx6Q7/TOLU7agAKeFfc8cA=="], @@ -2514,6 +2547,8 @@ "@copilotkit/runtime/ai/@ai-sdk/provider-utils": ["@ai-sdk/provider-utils@4.0.40", "", { "dependencies": { "@ai-sdk/provider": "3.0.14", "@standard-schema/spec": "^1.1.0", "eventsource-parser": "^3.0.8" }, "peerDependencies": { "zod": "^3.25.76 || ^4.1.8" } }, "sha512-OL5IrpUm9Y8Dwy+w/vvFwPotS6m52O9W0op2oXgXdCROMJIBalBI0oro6OIBYkPxvm5Xg02GSkoQN25RlR0bnw=="], + "@copilotkit/shared/@ag-ui/client/@ag-ui/proto": ["@ag-ui/proto@0.0.57", "", { "dependencies": { "@ag-ui/core": "0.0.57", "@bufbuild/protobuf": "^2.2.5", "@protobuf-ts/protoc": "^2.11.1" } }, "sha512-pPENOZt0P6ibH8sCTgq05wLYXi5t3P9B5r/1bWYehXjUxtyOdnukSlWM++SsCIwUXsQdm/b3aBgGjEeTF7RenA=="], + "@glideapps/glide-data-grid/react-dom/scheduler": ["scheduler@0.23.2", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ=="], "@langchain/langgraph-sdk/p-queue/eventemitter3": ["eventemitter3@5.0.4", "", {}, "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw=="], @@ -2546,8 +2581,6 @@ "apache-arrow/@types/node/undici-types": ["undici-types@6.21.0", "", {}, "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ=="], - "archiver-utils/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "archiver-utils/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "axios/https-proxy-agent/agent-base": ["agent-base@6.0.2", "", { "dependencies": { "debug": "4" } }, "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ=="], @@ -2562,8 +2595,6 @@ "d3-sankey/d3-shape/d3-path": ["d3-path@1.0.9", "", {}, "sha512-VLaYcn81dtHVTjEHd8B+pbe9yHWpXKZUC87PzoFmsFrJqgFwDe/qxfp5MlfsfM1V5E/iVt0MmEbWQ7FVIXh/bg=="], - "duplexer2/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "duplexer2/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "express/debug/ms": ["ms@2.0.0", "", {}, "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A=="], @@ -2572,12 +2603,8 @@ "glob/minimatch/brace-expansion": ["brace-expansion@1.1.16", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw=="], - "jszip/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "jszip/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], - "lazystream/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "lazystream/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "mssql/tedious/@js-joda/core": ["@js-joda/core@5.7.0", "", {}, "sha512-WBu4ULVVxySLLzK1Ppq+OdfP+adRS4ntmDQT915rzDJ++i95gc2jZkM5B6LWEAwN3lGXpfie3yPABozdD3K3Vg=="], @@ -2592,8 +2619,6 @@ "tar-stream/bl/buffer": ["buffer@5.7.1", "", { "dependencies": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" } }, "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ=="], - "unzipper/readable-stream/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], - "unzipper/readable-stream/string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="], "@modelcontextprotocol/sdk/express/accepts/negotiator": ["negotiator@1.0.0", "", {}, "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg=="], diff --git a/core/package.json b/core/package.json index dae01a6..cf5e36b 100644 --- a/core/package.json +++ b/core/package.json @@ -1,8 +1,23 @@ { - "name": "@based/core", - "private": true, + "name": "@cyronius/based-core", + "version": "0.1.6", + "description": "The based engine: database adapters, the agent, storage and the HTTP server, as a Bun package.", "license": "MIT", "type": "module", + "repository": { + "type": "git", + "url": "git+https://github.com/Cyronius/based.git", + "directory": "core" + }, + "files": [ + "src" + ], + "publishConfig": { + "access": "public" + }, + "engines": { + "bun": ">=1.2" + }, "exports": { ".": "./src/index.ts", "./mssql": "./src/db/mssqlAdapter.ts", diff --git a/core/src/index.ts b/core/src/index.ts index cb456b4..e126def 100644 --- a/core/src/index.ts +++ b/core/src/index.ts @@ -1,8 +1,8 @@ export { APP_VERSION } from "./version"; export { startServer, type ServerOptions, type RunningServer } from "./server"; // Concrete adapter classes are deliberately NOT re-exported here (BASED-LAZY-ENGINES): importing -// @based/core must not evaluate an engine's native stack. Use @based/core/mssql or -// @based/core/lancedb to reach a concrete class (tests do); runtime code goes through createAdapter. +// @cyronius/based-core must not evaluate an engine's native stack. Use @cyronius/based-core/mssql or +// @cyronius/based-core/lancedb to reach a concrete class (tests do); runtime code goes through createAdapter. export { serializeLanceValue } from "./db/lanceSerialize"; export { encodeVectorSample, decodeVectorSample } from "./db/vectorWire"; export { createAdapter, engineOf, testConnection } from "./db/adapterFactory"; diff --git a/docs/architecture.md b/docs/architecture.md index 4cfde9c..973ddc8 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -44,6 +44,15 @@ same thinness is what makes the macOS port tractable. **Core holds every secret.** The webview never sees a connection password or an API key. It gets a per-launch bearer token in its URL hash and talks to loopback. +## Products + +The same core serves two products. **based desktop** is this repo: core plus ui in the Tauri shell, +free and MIT. **based.ai** is a hosted, paid service in a separate private repo; it runs one core +process per tenant and puts a web frontend in front of it. That is why `ui/` never imports `core/` +(the frontend must be swappable), why `startServer` takes its data dir and token as options (a +control plane must be able to run many), and why `core/` is published to npm at each release +(`@cyronius/based-core`, same version as the app). The boundary is enforced by `scripts/check-boundaries.ts`. + ## The Ledger The layout was chosen from two rounds of concept exploration: an editorial three-pane workbench, @@ -89,7 +98,7 @@ Engine differences are expressed as **capabilities**, not as per-screen conditio `indexIntrospect`) that are wired end to end — the UI, the server routes, and the agent's toolset all light up or gray out from the same source of truth. -Engine dependencies **load on demand**. Importing `@based/core` must not evaluate a native stack, so +Engine dependencies **load on demand**. Importing `@cyronius/based-core` must not evaluate a native stack, so concrete adapter classes are reachable only through `createAdapterFor` or an explicit subpath import. diff --git a/docs/development.md b/docs/development.md index a9d78e1..02c30e2 100644 --- a/docs/development.md +++ b/docs/development.md @@ -1,6 +1,30 @@ # Development -based is a Bun workspace with four packages: +## Three projects + +based is really three projects. Two live in this repo, one does not. + +| Project | Where | Builds from | +|---|---|---| +| **core** (`@cyronius/based-core`) | `core/` | its own deps only; published to npm at each release | +| **frontend** (`@based/ui`) | `ui/` | its own deps only; talks to core over HTTP, never imports it | +| **based.ai** (paid, hosted) | a sibling repo, `../based-ai` | `@cyronius/based-core` from npm, or `bun link` locally | + +The desktop app is core plus ui in the Tauri shell. `bun run check` enforces the two rules that +keep the projects separable: `scripts/check-boundaries.ts` fails if core and ui import each other, +and `scripts/check-private.ts` fails if anything from the paid product lands in this tree. Both run +in CI on every push; the second also runs as a pre-commit hook after a one-time + +```sh +git config core.hooksPath .githooks +``` + +To work on based.ai against an unpublished core: `cd core && bun link`, then in the based-ai +checkout `bun link @cyronius/based-core`. + +## Packages + +This repo is a Bun workspace with four packages: | Package | What it is | |---|---| @@ -51,6 +75,7 @@ bun run dev:ui # Vite alone on 5183 bun run build:ui # -> ui/dist bun run shell # Tauri window + core child over ui/dist bun run typecheck # core, ui, shell-tauri +bun run check # package boundaries + private-path guard bun test # specs ``` diff --git a/package.json b/package.json index 09e456c..54d73b8 100644 --- a/package.json +++ b/package.json @@ -25,7 +25,8 @@ "build:ui": "bun run --cwd ui build", "shell": "bun run --cwd shell-tauri dev", "test": "bun run --cwd specs test", - "typecheck": "bun run --cwd core typecheck && bun run --cwd ui typecheck && bun run --cwd shell-tauri typecheck" + "typecheck": "bun run --cwd core typecheck && bun run --cwd ui typecheck && bun run --cwd shell-tauri typecheck", + "check": "bun scripts/check-boundaries.ts && bun scripts/check-private.ts --tree" }, "overrides": { "zod": "3.25.76" diff --git a/scripts/bump-version.ps1 b/scripts/bump-version.ps1 index 9c50a38..56103d1 100644 --- a/scripts/bump-version.ps1 +++ b/scripts/bump-version.ps1 @@ -8,8 +8,8 @@ # # shell-tauri/tauri.conf.json is the single source of truth: it is what the Tauri build and the # installer read (scripts/package-win.ps1). shell-tauri/Cargo.toml carries the same version (it -# feeds the exe's file-version metadata), so both are rewritten together; core/src/version.ts is -# generated. There is never a second version to forget. +# feeds the exe's file-version metadata) and core/package.json is what npm publishes, so all three +# are rewritten together; core/src/version.ts is generated. There is never a second version to forget. # # NOTE: keep this file ASCII-only. Windows PowerShell 5.1 reads .ps1 as ANSI, so a UTF-8 em-dash # or curly quote in a comment corrupts the parse of the whole script. @@ -32,6 +32,7 @@ $ErrorActionPreference = "Stop" $repo = Split-Path -Parent $PSScriptRoot $confPath = Join-Path $repo "shell-tauri\tauri.conf.json" $cargoPath = Join-Path $repo "shell-tauri\Cargo.toml" +$corePkgPath = Join-Path $repo "core\package.json" $versionTsPath = Join-Path $repo "core\src\version.ts" # The "version" line of tauri.conf.json / the version line of Cargo.toml [package]. Line-targeted @@ -50,6 +51,9 @@ if ($current -notmatch '^\d+\.\d+\.\d+$') { } $cargo = [System.IO.File]::ReadAllText($cargoPath) if ($cargo -notmatch $cargoLine) { throw "Could not find version in $cargoPath" } +# core/package.json is published to npm at the same version (BASED-CORE-PUBLISH). +$corePkg = [System.IO.File]::ReadAllText($corePkgPath) +if ($corePkg -notmatch $confLine) { throw "Could not find version in $corePkgPath" } # --- compute next --- if ($Version) { @@ -69,6 +73,7 @@ Write-Host "$current -> $next" -ForegroundColor Cyan # --- write --- $updatedConf = [regex]::Replace($conf, $confLine, { param($m) "$($m.Groups[1].Value)$next$($m.Groups[3].Value)" }, 1) $updatedCargo = [regex]::Replace($cargo, $cargoLine, { param($m) "$($m.Groups[1].Value)$next$($m.Groups[3].Value)" }, 1) +$updatedCorePkg = [regex]::Replace($corePkg, $confLine, { param($m) "$($m.Groups[1].Value)$next$($m.Groups[3].Value)" }, 1) $versionTs = @" // GENERATED by scripts/bump-version.ps1 -- do not edit. @@ -77,15 +82,17 @@ $versionTs = @" export const APP_VERSION = "$next"; "@ -if ($PSCmdlet.ShouldProcess("$confPath, $cargoPath and $versionTsPath", "write version $next")) { +if ($PSCmdlet.ShouldProcess("$confPath, $cargoPath, $corePkgPath and $versionTsPath", "write version $next")) { # UTF8Encoding($false) = no BOM; a BOM would break the JSON/TOML parsers. Only the version lines # change, so line endings are preserved as found. $utf8NoBom = New-Object System.Text.UTF8Encoding($false) [System.IO.File]::WriteAllText($confPath, $updatedConf, $utf8NoBom) [System.IO.File]::WriteAllText($cargoPath, $updatedCargo, $utf8NoBom) + [System.IO.File]::WriteAllText($corePkgPath, $updatedCorePkg, $utf8NoBom) [System.IO.File]::WriteAllText($versionTsPath, ($versionTs -replace "`r`n", "`n") + "`n", $utf8NoBom) Write-Host " updated shell-tauri\tauri.conf.json" -ForegroundColor DarkGray Write-Host " updated shell-tauri\Cargo.toml" -ForegroundColor DarkGray + Write-Host " updated core\package.json" -ForegroundColor DarkGray Write-Host " wrote core\src\version.ts" -ForegroundColor DarkGray } else { Write-Host " (WhatIf) no files written" -ForegroundColor DarkGray diff --git a/scripts/check-boundaries.ts b/scripts/check-boundaries.ts new file mode 100644 index 0000000..63c8a7a --- /dev/null +++ b/scripts/check-boundaries.ts @@ -0,0 +1,81 @@ +// Keeps core and ui from importing each other. ui talks to core over HTTP only, and core knows +// nothing about the webview - that is what lets each build on its own and lets based.ai run core +// without the desktop frontend. The shell is the one package allowed to import core. +// +// bun scripts/check-boundaries.ts +import { readdirSync, readFileSync, statSync } from "node:fs"; +import { join, posix, relative } from "node:path"; + +const PACKAGES = ["core", "ui", "shell-tauri"] as const; +type Pkg = (typeof PACKAGES)[number]; + +const PACKAGE_NAMES: Record = { + "@cyronius/based-core": "core", + "@based/ui": "ui", + "@based/shell-tauri": "shell-tauri", +}; + +const ALLOWED: Record = { + core: [], + ui: [], + "shell-tauri": ["core"], +}; + +function norm(p: string): string { + return p.replace(/\\/g, "/"); +} + +function packageOf(repoPath: string): Pkg | null { + const top = norm(repoPath).split("/")[0]; + return (PACKAGES as readonly string[]).includes(top) ? (top as Pkg) : null; +} + +/** True when `specifier`, imported from `fromFile` (repo-relative), lands in a sibling package the + * importing package is not allowed to depend on. */ +export function crossesBoundary(fromFile: string, specifier: string): boolean { + const from = packageOf(fromFile); + if (!from) return false; + const spec = norm(specifier); + + let target: Pkg | null = null; + if (spec.startsWith(".")) { + const resolved = posix.normalize(posix.join(posix.dirname(norm(fromFile)), spec)); + target = packageOf(resolved); + } else { + const scoped = spec.split("/").slice(0, 2).join("/"); + target = PACKAGE_NAMES[scoped] ?? null; + } + if (!target || target === from) return false; + return !ALLOWED[from].includes(target); +} + +const IMPORT_RE = /(?:from\s*|import\s*\(?\s*|require\s*\(\s*)["']([^"']+)["']/g; + +function* walk(dir: string): Generator { + for (const name of readdirSync(dir)) { + if (name === "node_modules" || name === "dist" || name === "vendor" || name === "target") continue; + const full = join(dir, name); + if (statSync(full).isDirectory()) yield* walk(full); + else if (/\.(ts|tsx)$/.test(name)) yield full; + } +} + +if (import.meta.main) { + const root = join(import.meta.dir, ".."); + const roots = [join(root, "core", "src"), join(root, "ui", "src"), join(root, "shell-tauri")]; + const violations: string[] = []; + for (const r of roots) { + for (const file of walk(r)) { + const rel = norm(relative(root, file)); + const src = readFileSync(file, "utf8"); + for (const m of src.matchAll(IMPORT_RE)) { + if (crossesBoundary(rel, m[1]!)) violations.push(`${rel}: imports ${m[1]}`); + } + } + } + if (violations.length) { + console.error("package boundary violations:\n " + violations.join("\n ")); + process.exit(1); + } + console.log("boundaries ok"); +} diff --git a/scripts/check-private.ts b/scripts/check-private.ts new file mode 100644 index 0000000..49c18bb --- /dev/null +++ b/scripts/check-private.ts @@ -0,0 +1,45 @@ +// The paid product (based.ai) lives in its own private repo next to this one, never inside it. +// This is the backstop: refuses a commit or a tree that carries paid-product directories, env +// files, or private keys. Runs from .githooks/pre-commit (--staged) and CI (--tree). +// +// bun scripts/check-private.ts --staged +// bun scripts/check-private.ts --tree + +const PRIVATE_DIRS = ["based-ai", "paid", "private"]; +const KEY_FILE = /(\.p8|\.pem|\.key)$|_rsa_key/; + +export function isPrivatePath(path: string): boolean { + const parts = path.replace(/\\/g, "/").split("/").filter(Boolean); + if (parts.length === 0) return false; + const file = parts[parts.length - 1]!; + if (parts.some((seg) => PRIVATE_DIRS.includes(seg) || seg.endsWith(".private"))) return true; + if (file === ".env" || file.startsWith(".env.")) return true; + return KEY_FILE.test(file); +} + +async function gitLines(args: string[]): Promise { + const proc = Bun.spawn(["git", ...args], { stdout: "pipe", stderr: "inherit" }); + const out = await new Response(proc.stdout).text(); + if ((await proc.exited) !== 0) throw new Error(`git ${args.join(" ")} failed`); + return out.split(/\r?\n/).filter(Boolean); +} + +if (import.meta.main) { + const mode = process.argv[2]; + const files = + mode === "--staged" + ? await gitLines(["diff", "--cached", "--name-only", "--diff-filter=ACMR"]) + : mode === "--tree" + ? await gitLines(["ls-files"]) + : null; + if (!files) { + console.error("usage: bun scripts/check-private.ts --staged | --tree"); + process.exit(2); + } + const hits = files.filter(isPrivatePath); + if (hits.length) { + console.error("private paths must not be committed to this repo:\n " + hits.join("\n ")); + process.exit(1); + } + console.log(`private guard ok (${files.length} files)`); +} diff --git a/shell-tauri/core-child.ts b/shell-tauri/core-child.ts index fdd27ba..84e821e 100644 --- a/shell-tauri/core-child.ts +++ b/shell-tauri/core-child.ts @@ -3,7 +3,7 @@ // boundary as single stdout lines the Rust shell parses (see shell-tauri/src/main.rs). import { existsSync } from "node:fs"; import { resolve, join, dirname } from "node:path"; -import { startServer } from "@based/core"; +import { startServer } from "@cyronius/based-core"; /** Packaged layout: this bundle runs as /core/index.js with the UI beside it at * /ui/dist. Dev fallback: walk up from cwd (the Rust shell sets cwd to the repo root). */ diff --git a/shell-tauri/package.json b/shell-tauri/package.json index abf5728..1b4c5e7 100644 --- a/shell-tauri/package.json +++ b/shell-tauri/package.json @@ -10,7 +10,7 @@ "typecheck": "tsc -p tsconfig.json" }, "dependencies": { - "@based/core": "workspace:*" + "@cyronius/based-core": "workspace:*" }, "devDependencies": { "@types/bun": "^1.3.14", diff --git a/specs/based/plans/project-split.md b/specs/based/plans/project-split.md new file mode 100644 index 0000000..2219247 --- /dev/null +++ b/specs/based/plans/project-split.md @@ -0,0 +1,103 @@ +# Project split: core, open-source frontend, based.ai + +Written 2026-09-10. Phase 1 of the based.ai roadmap; later phases are sketched at the end so the split is made with them in view. + +**Status:** Phase 1 implemented 2026-09-10 on branch `project-split`. Private repo `Cyronius/based-ai` created and linked. Package name is `@cyronius/based-core` under the existing npm org. Still open: add the `NPM_TOKEN` repo secret before the next tag. Phases 2 to 7 remain. + +## Spec impact + +New requirements: `BASED-PKG-BOUNDARIES` (unit), `BASED-PRIVATE-GUARD` (unit), `BASED-CORE-PUBLISH` (manual). +Modified: `BASED-RELEASE-CI` gains an npm publish job. +Removed: none. +Everything else in this plan is packaging and repo layout with no behavior change. + +## Shape + +Two git repos, three buildable projects. + +| Project | Where | Visibility | Depends on | +|---|---|---|---| +| core (`@cyronius/based-core`) | `based/core/` (this repo) | public, MIT | nothing in-repo | +| frontend (`@based/ui`) | `based/ui/` (this repo) | public, MIT | nothing in-repo (HTTP only) | +| based.ai | `C:\code\based-ai\` (new repo, sibling directory) | private | `@cyronius/based-core` from npm | + +`shell-tauri/` stays in this repo as the desktop packaging of core plus ui. `specs/` stays as the home of `BASED-*` requirements and tests. + +**Why core stays in this repo for now.** Most desktop work touches core and ui together. Moving core to its own repo turns every such change into a publish-and-bump cycle before ui can see it. Revisit once based.ai's release cadence diverges from the desktop app's; the boundary check below keeps the extraction cheap whenever that happens. + +**Why the paid piece is not a directory in this tree.** A gitignored or nested-repo directory is one `git add --force`, one stray `git add .` gitlink, or one badly written ignore rule away from leaking. A sibling directory cannot be committed to this repo by any command. The guard below is a backstop, not the mechanism. + +**How based.ai consumes core.** From npm at a pinned version in CI and production. Locally via `bun link`: `cd based/core && bun link`, then `cd based-ai && bun link @cyronius/based-core`. Core is shipped as TypeScript source (its `exports` already point at `src/`), which is fine because every consumer runs under Bun and core needs `bun:sqlite` anyway. + +## Current state that makes this cheap + +- The workspace already has `core`, `ui`, `shell-tauri`, and `specs` as separate packages with their own `typecheck` scripts. +- `ui/src` has zero imports from core. The one place that would want them, `ui/src/api/client.ts`, hand-mirrors the types and says so. +- `startServer` in `core/src/server.ts` takes `dbPath`, `agentDbPath`, `token`, and `staticDir`, so a hosted control plane can run one core per tenant without touching core. +- There is no PR or push CI today, only tag-triggered release and a manual macOS build. Independent builds need a CI file to be true in practice, not just locally. + +## Work items + +### 1. Make core publishable + +- `core/package.json`: drop `private`, add `version` (kept in step by the bump script), `files: ["src"]`, `publishConfig.access: "public"`, `engines.bun`. Keep `exports` on `src/`. +- `scripts/bump-version.ps1`: rewrite `core/package.json` version alongside `tauri.conf.json`, `Cargo.toml`, and `version.ts`. Still one source of truth. +- `.github/workflows/release.yml`: add a `publish-core` job after the Windows job's typecheck and tests pass. `npm publish --provenance` with an `NPM_TOKEN` repo secret. Runs on the same `v*` tag; the desktop version and the core package version are the same number. +- `BASED-CORE-PUBLISH` (manual): tagging `vX.Y.Z` publishes `@cyronius/based-core@X.Y.Z`; `bun add @cyronius/based-core@X.Y.Z` in a fresh Bun project resolves and `startServer` is importable. + +Name: `@cyronius/based-core`, under the npm org Josh already owns. `@based/core` was the first choice but the scope was unverifiable without an npm login, and an org we control is the safer home anyway. + +### 2. Prove the builds are independent + +- `scripts/check-boundaries.ts`: exports `crossesBoundary(fromFile, importSpecifier)` and a CLI that walks `core/src`, `ui/src`, and `shell-tauri/*.ts`. Fails if ui imports core or shell, core imports ui or shell, or either imports a workspace sibling through a relative path. `shell-tauri` importing `@cyronius/based-core` is allowed (that is its job). +- `BASED-PKG-BOUNDARIES` (unit): `crossesBoundary("ui/src/x.ts", "../../core/src/y")` is true; `crossesBoundary("ui/src/x.ts", "./y")` is false; `crossesBoundary("shell-tauri/core-child.ts", "@cyronius/based-core")` is false; `crossesBoundary("core/src/x.ts", "@based/ui")` is true. +- New `.github/workflows/ci.yml` on push and pull request, one job per project so a red job names the project: `core` (typecheck), `ui` (typecheck plus `vite build`), `shell-tauri` (typecheck), `tests` (`bun test`), `boundaries`, `private-guard`. Rust is not needed for any of these, so the jobs stay fast. +- Root `package.json`: add `check` script that runs boundaries and the private guard so `bun run check` locally matches CI. + +Tests in `specs/` import both core and ui today (the grid, plan XML, font metrics, and capi tests are ui-side). They stay in one `tests` job. Splitting them per package is optional later and is not part of this plan. + +### 3. Private guard in this repo + +- `.gitignore`: add `based-ai/`, `paid/`, `private/`, `*.private/`. +- `scripts/check-private.ts`: exports `isPrivatePath(path)` covering those prefixes plus `.env*` and the key patterns already in `.gitignore` (`*.p8`, `*.pem`, `*.key`, `*_rsa_key*`). CLI: `--staged` checks `git diff --cached --name-only`; `--tree` checks `git ls-files`. +- `.githooks/pre-commit` (tracked) runs `bun scripts/check-private.ts --staged`. `docs/development.md` and `CONTRIBUTING.md` get the one-time `git config core.hooksPath .githooks` line. No postinstall magic. +- CI `private-guard` job runs `--tree`, so a bypassed hook still fails the build. +- `BASED-PRIVATE-GUARD` (unit): `isPrivatePath("based-ai/src/x.ts")` true; `isPrivatePath("paid/README.md")` true; `isPrivatePath(".env.production")` true; `isPrivatePath("core/src/x.ts")` false; `isPrivatePath("ui/vendor/lm-ag-ui/dist/index.js")` false. + +### 4. Scaffold based-ai + +- `C:\code\based-ai\`: `git init`, `bun init`, `package.json` with `@cyronius/based-core` pinned once published (linked until then), `README.md` stating what the project is and the link workflow, `specs/basedai/spec.md` with the `BASEDAI` prefix and no requirements yet, `.gitignore`. +- Private GitHub repo `Cyronius/based-ai`. This is the one outward-facing step; it is a `gh repo create --private` and I will ask before running it. +- Nothing else. The control plane is Phase 5 below. A `src/index.ts` that imports `startServer` and boots one core on a temp data dir is enough to prove the dependency resolves. + +### 5. Docs + +- `docs/development.md`: add a "Three projects" section above the package table, the hook setup line, and the `bun link` loop. +- `docs/architecture.md`: two paragraphs under a "Products" heading. based desktop is core plus ui in a Tauri shell; based.ai is a private control plane that runs the same core per tenant. +- `README.md`: one line in Status that core is published on npm. + +### Verification + +- `bun run check` passes locally; `ci.yml` is green on the PR. +- Temporarily creating `based-ai/x.ts` in this tree and running `git add -f` then `bun scripts/check-private.ts --staged` fails; removing it passes. +- Fresh clone of `based-ai`, `bun install`, `bun run src/index.ts` starts a core on a loopback port and `/api/health` answers. + +### Order + +Items 2 and 3 first (they are pure additions and can merge on their own). Then 1, which touches release. Then 4, once 1 has published at least one version. Docs land with whichever item they describe. + +## Follow-up phases + +Each gets its own plan. Order was agreed on 2026-09-10. + +**Phase 2: Postgres with pgvector.** One adapter closes the biggest engine gap in the competitor matrix and adds a second vector engine. Design question to settle first: today SQL engines and LanceDB have different agent surfaces, and pgvector is a SQL engine that also does vector search. The registry's capability model needs to express both on one connection so `run_query` and `vector_search` coexist. Ships in the desktop app; hosted use follows for free because Neon, Supabase, RDS, and Azure Database for PostgreSQL are all cloud-reachable. + +**Phase 3: Metering and caps in core.** Per-run step and token ceilings in the agent loop with a visible "run hit its budget" outcome. A usage table next to the audit log keyed by run: model, input and output tokens, tool calls, engine. Model routing resolved from the AI profile so a tenant's "ours" or "my key" choice maps to Cerebras, OpenRouter, or their endpoint. Useful to desktop users on its own, and a hard prerequisite for charging anyone. + +**Phase 4: Headless core.** A `SecretStore` interface with the keyring implementation for desktop and an envelope-encrypted implementation for hosted use. Native-only routes (dialogs, new window, file open) become optional so a headless core answers 404 on them cleanly. Static egress IP documentation for Snowflake network policies and Azure SQL firewalls. + +**Phase 5: based.ai v1.** Control plane in the private repo: sign-up and auth, Stripe billing, one core process per tenant on a sandbox host with static egress, engines limited to Snowflake, LanceDB Cloud, Azure SQL, and cloud Postgres. Hosted model profile seeded server-side, BYO-key tier unmetered. Chat-first web UI reusing grid and agent components from ui, which means ui grows a library entry point alongside the app. SOC 2 readiness starts here. No on-prem connector and no hosting of databases ourselves. + +**Phase 6: More vector engines.** Qdrant and Pinecone by inbound demand. Both have cloud offerings and no SQL, so they land like LanceDB Cloud: search tools only. Milvus, Weaviate, and Chroma wait until someone asks. + +**Phase 7: Saved questions, then dashboards.** An agent answer pinned to a URL with a refresh is a dashboard tile. This is the layer that gives a non-DBA a reason to pay and where based.ai starts overlapping Basedash and Hex deliberately. An MCP server belongs in the same phase, since it is becoming table stakes for "agents talk to your data." diff --git a/specs/based/spec.md b/specs/based/spec.md index 5e68113..7191c9e 100644 --- a/specs/based/spec.md +++ b/specs/based/spec.md @@ -2941,10 +2941,10 @@ Local LanceDB connections get a real SQL query tab — an embedded DuckDB (`@duc **Applies to:** based (core) **Test category:** integration -Importing `@based/core` shall evaluate no engine module: `mssql`/tedious, `@lancedb/lancedb`, `@duckdb/node-api`, and `snowflake-sdk` load only when a connection of that engine is used. `createAdapter` is async and resolves through the registry, whose descriptors hold `loadAdapter`/`loadLsp` **loaders** — importing `core/src/engines/registry` must therefore stay free of native stacks. The barrel re-exports no concrete adapter class (tests import them via the `@based/core/mssql` / `@based/core/lancedb` / `@based/core/lancedb-sql` / `@based/core/snowflake` subpath exports). +Importing `@cyronius/based-core` shall evaluate no engine module: `mssql`/tedious, `@lancedb/lancedb`, `@duckdb/node-api`, and `snowflake-sdk` load only when a connection of that engine is used. `createAdapter` is async and resolves through the registry, whose descriptors hold `loadAdapter`/`loadLsp` **loaders** — importing `core/src/engines/registry` must therefore stay free of native stacks. The barrel re-exports no concrete adapter class (tests import them via the `@cyronius/based-core/mssql` / `@cyronius/based-core/lancedb` / `@cyronius/based-core/lancedb-sql` / `@cyronius/based-core/snowflake` subpath exports). **Acceptance criteria:** -- A fresh process that imports `@based/core` has no mssql/tedious, `@lancedb`, `@duckdb`, or `snowflake-sdk` module in `require.cache` +- A fresh process that imports `@cyronius/based-core` has no mssql/tedious, `@lancedb`, `@duckdb`, or `snowflake-sdk` module in `require.cache` - `createAdapter` resolves the MSSQL class for `engine: "mssql"` and for an engine-less legacy config, the LanceDB class for `engine: "lancedb"`, and the Snowflake class for `engine: "snowflake"` - The full suite stays green @@ -3307,11 +3307,66 @@ check that the tag matches `tauri.conf.json`'s version; a publish job then creat release carrying both artifacts, their SHA-256s, the CHANGELOG section for that version, and the per-platform unsigned-install instructions, then bumps the Homebrew cask (skipping with a warning when the `TAP_PUSH_TOKEN` secret is absent, so a missing tap never blocks the release). +A `publish-core` job, gated on the Windows job, publishes `@cyronius/based-core` at the same version +(BASED-CORE-PUBLISH). **Verification procedure:** cut a release with `scripts/release.ps1`; confirm the workflow goes green, the release page shows both artifacts with install notes, and `Casks/based.rb` in the tap points at the new version with the DMG's real SHA-256. +### BASED-CORE-PUBLISH: core is an npm package at the app's version +**Applies to:** based (core, repo) +**Test category:** manual + +`core/` is publishable as `@cyronius/based-core` (MIT, public, TypeScript source under `src/`, Bun +runtime). Its `package.json` version is rewritten by `scripts/bump-version.ps1` alongside +`tauri.conf.json`, `Cargo.toml`, and `version.ts`, so the package version always equals the +desktop release it shipped with. The `publish-core` job in `release.yml` refuses a tag whose +number differs from `core/package.json` and publishes with npm provenance using the `NPM_TOKEN` +secret. Consumers outside this repo (based.ai) depend on the published package or on a local +`bun link`, never on a path into this tree. + +**Verification procedure:** +1. `scripts/bump-version.ps1 patch -WhatIf` reports `core/package.json` among the files it would write. +2. After a `v*` tag, the `publish-core` job is green and `npm view @cyronius/based-core version` prints the tag's number. +3. In a fresh Bun project, `bun add @cyronius/based-core@` resolves and `import { startServer } from "@cyronius/based-core"` typechecks. + +### BASED-PKG-BOUNDARIES: core and ui build independently +**Applies to:** based (core, ui, shell-tauri) +**Test category:** unit + +`core/src` never imports from `ui` or `shell-tauri`; `ui/src` never imports from `core` or +`shell-tauri`; the shell may import `@cyronius/based-core` (it is the packaging of core plus ui). ui +talks to core over HTTP only. `scripts/check-boundaries.ts` walks all three packages and fails on +a violation, whether by package name or by a relative path that escapes the package; `bun run +check` and the `boundaries` CI job run it. + +**Acceptance criteria:** +- `crossesBoundary("ui/src/api/client.ts", "../../../core/src/db/types")` → `true` +- `crossesBoundary("ui/src/App.tsx", "@cyronius/based-core")` → `true` +- `crossesBoundary("core/src/server.ts", "@based/ui")` → `true` +- `crossesBoundary("shell-tauri/core-child.ts", "@cyronius/based-core")` → `false` +- `crossesBoundary("core/src/server.ts", "../db/types")` → `false` +- Third-party and `bun:` specifiers are never violations; Windows separators are normalized + +### BASED-PRIVATE-GUARD: the paid product never enters this repo +**Applies to:** based (repo) +**Test category:** unit + +based.ai lives in its own private repository beside this one. As a backstop, `.gitignore` lists +`based-ai/`, `paid/`, `private/`, and `*.private/`, and `scripts/check-private.ts` refuses any +staged or tracked path under those directories, any `.env` / `.env.*` file, or any private key +(`*.p8`, `*.pem`, `*.key`, `*_rsa_key*`). It runs as the tracked pre-commit hook in `.githooks/` +(`git config core.hooksPath .githooks`, one-time) against the index, and as the `private-guard` +CI job against `git ls-files`, so a bypassed hook still fails the build. + +**Acceptance criteria:** +- `isPrivatePath("based-ai/src/index.ts")`, `isPrivatePath("paid/README.md")`, `isPrivatePath("x.private/a.ts")` → `true` +- `isPrivatePath(".env.production")`, `isPrivatePath("based_rsa_key.p8")`, `isPrivatePath("certs/server.pem")` → `true` +- `isPrivatePath("core/src/server.ts")`, `isPrivatePath("ui/vendor/lm-ag-ui/dist/index.js")` → `false` +- A path that merely contains a private word (`docs/based-ai-roadmap.md`, `core/src/env.ts`) → `false` +- Windows separators are normalized + ### BASED-SQL-ASSOC-WIN: .sql "Open with" registration **Applies to:** based (installer) **Test category:** manual diff --git a/specs/based/tests/_devDb.ts b/specs/based/tests/_devDb.ts index 69b1f38..571fa5f 100644 --- a/specs/based/tests/_devDb.ts +++ b/specs/based/tests/_devDb.ts @@ -4,8 +4,8 @@ // no fallback: a real hostname must never be baked into the repo, and a silent default would make // a green run ambiguous about which database it actually hit. Unset env → every live suite skips. // See docs/development.md for the setup. -import { testConnection } from "@based/core"; -import type { ConnectionConfig } from "@based/core"; +import { testConnection } from "@cyronius/based-core"; +import type { ConnectionConfig } from "@cyronius/based-core"; const server = process.env.BASED_TEST_SERVER ?? ""; const database = process.env.BASED_TEST_DB ?? ""; diff --git a/specs/based/tests/helpers/lazyEnginesChild.ts b/specs/based/tests/helpers/lazyEnginesChild.ts index 3d16426..6f677b1 100644 --- a/specs/based/tests/helpers/lazyEnginesChild.ts +++ b/specs/based/tests/helpers/lazyEnginesChild.ts @@ -1,6 +1,6 @@ // Traces: BASED-LAZY-ENGINES — child probe run by integration.lazyEngines.test.ts in a fresh bun -// process: import the @based/core barrel, then report which engine modules got evaluated. -await import("@based/core"); +// process: import the @cyronius/based-core barrel, then report which engine modules got evaluated. +await import("@cyronius/based-core"); const keys = Object.keys(require.cache); const hit = (re: RegExp) => keys.filter((k) => re.test(k)); console.log( diff --git a/specs/based/tests/helpers/snowflakeEnvChild.ts b/specs/based/tests/helpers/snowflakeEnvChild.ts index 18a81b2..7ee0023 100644 --- a/specs/based/tests/helpers/snowflakeEnvChild.ts +++ b/specs/based/tests/helpers/snowflakeEnvChild.ts @@ -7,12 +7,12 @@ // have set the variable already. A fresh process is what makes it meaningful — in the shared test // process another suite may already have loaded the driver. import { createRequire } from "node:module"; -import type { ConnectionConfig } from "@based/core"; +import type { ConnectionConfig } from "@cyronius/based-core"; const driverLoaded = (): boolean => Object.keys(require.cache).some((k) => /[\\/]node_modules[\\/].*snowflake-sdk/i.test(k)); -const { SnowflakeAdapter } = await import("@based/core/snowflake"); +const { SnowflakeAdapter } = await import("@cyronius/based-core/snowflake"); // Importing the adapter must not pull the driver in (the value import is lazy); the env var is // therefore still untouched at this point, which is exactly why it can't be a module side effect. const lazyOnImport = !driverLoaded(); @@ -39,7 +39,7 @@ for (let i = 0; i < 150 && !driverLoaded(); i++) await Bun.sleep(100); // snowflake-sdk is a dependency of core, not of specs, so resolve it from the adapter module rather // than from here — no path arithmetic to rot. It is CJS, so this returns the very module instance // the driver itself is holding: the real detection result, not a second run of it. -const pd = createRequire(import.meta.resolve("@based/core/snowflake"))( +const pd = createRequire(import.meta.resolve("@cyronius/based-core/snowflake"))( "snowflake-sdk/dist/lib/telemetry/platform_detection", ) as { getDetectedPlatforms: () => Promise }; diff --git a/specs/based/tests/integration.agent.test.ts b/specs/based/tests/integration.agent.test.ts index 697edf4..6d3ab62 100644 --- a/specs/based/tests/integration.agent.test.ts +++ b/specs/based/tests/integration.agent.test.ts @@ -28,9 +28,9 @@ import { setAiKey, getAiKey, deleteAiKey, -} from "@based/core"; -import { MssqlAdapter } from "@based/core/mssql"; -import type { ConnectionConfig } from "@based/core"; +} from "@cyronius/based-core"; +import { MssqlAdapter } from "@cyronius/based-core/mssql"; +import type { ConnectionConfig } from "@cyronius/based-core"; import { DEV_DB_AVAILABLE, devConnection, warnDevDbSkip } from "./_devDb"; const dir = mkdtempSync(join(tmpdir(), "based-spec-agent-")); @@ -561,7 +561,7 @@ describe("BASED-AGENT-THREADS: thread history GET/DELETE", () => { const resourceId = "conn-1"; test("seeded thread round-trips as AG-UI messages; DELETE empties it; unknown thread → []", async () => { - const { createAgentMemory } = await import("@based/core"); + const { createAgentMemory } = await import("@cyronius/based-core"); const memory = createAgentMemory(join(dir, "agent.db")); const now = new Date(); await memory.saveThread({ thread: { id: threadId, resourceId, title: "t", createdAt: now, updatedAt: now } }); @@ -654,7 +654,7 @@ describe("BASED-CHAT-HISTORY-PICKER: thread list + lifecycle", () => { } async function seededMemory() { - const { createAgentMemory } = await import("@based/core"); + const { createAgentMemory } = await import("@cyronius/based-core"); return createAgentMemory(join(dir, "agent.db")); } @@ -722,7 +722,7 @@ describe("BASED-AGENT-DELEGATE-ISOLATION: a delegated run leaves the parent thre test("thread history is unchanged across a delegated run, and the child's SQL is tagged", async () => { const { createAgentMemory, createSubagentRunner, defaultCapabilitiesFor: caps, AuditStore: Audit, openDb: open } = - await import("@based/core"); + await import("@cyronius/based-core"); const { MockLanguageModelV4 } = await import("ai/test"); const memory = createAgentMemory(join(dir, "agent.db")); diff --git a/specs/based/tests/integration.agentInstructions.test.ts b/specs/based/tests/integration.agentInstructions.test.ts index 752f47a..1834203 100644 --- a/specs/based/tests/integration.agentInstructions.test.ts +++ b/specs/based/tests/integration.agentInstructions.test.ts @@ -3,7 +3,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; -import { startServer, openDb, AgentInstructionsStore, GENERIC_CORE, MSSQL_PERSONA, LANCE_PERSONA, SNOWFLAKE_PERSONA } from "@based/core"; +import { startServer, openDb, AgentInstructionsStore, GENERIC_CORE, MSSQL_PERSONA, LANCE_PERSONA, SNOWFLAKE_PERSONA } from "@cyronius/based-core"; const TOKEN = "spec-instructions-token"; const dbPath = join(mkdtempSync(join(tmpdir(), "based-spec-instructions-")), "app.db"); diff --git a/specs/based/tests/integration.contextRecovery.test.ts b/specs/based/tests/integration.contextRecovery.test.ts index 2f6c26c..05e243a 100644 --- a/specs/based/tests/integration.contextRecovery.test.ts +++ b/specs/based/tests/integration.contextRecovery.test.ts @@ -27,7 +27,7 @@ import { SHED_TOOL_RESULT_NOTE, type DatabaseAdapter, type ToolDeps, -} from "@based/core"; +} from "@cyronius/based-core"; const MSSQL = defaultCapabilitiesFor("mssql"); const usage = { inputTokens: 1, outputTokens: 1, totalTokens: 2 }; diff --git a/specs/based/tests/integration.lanceSql.test.ts b/specs/based/tests/integration.lanceSql.test.ts index d4fbd62..848aaf7 100644 --- a/specs/based/tests/integration.lanceSql.test.ts +++ b/specs/based/tests/integration.lanceSql.test.ts @@ -9,8 +9,8 @@ import { mkdirSync, mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; import * as lancedb from "@lancedb/lancedb"; -import type { ConnectionConfig, QueryChunk, WireValue } from "@based/core"; -import { LanceDbAdapter } from "@based/core/lancedb"; +import type { ConnectionConfig, QueryChunk, WireValue } from "@cyronius/based-core"; +import { LanceDbAdapter } from "@cyronius/based-core/lancedb"; const DIM = 8; const soloDir = mkdtempSync(join(tmpdir(), "based-lancesql-solo-")); @@ -228,7 +228,7 @@ describe("Lance SQL (embedded DuckDB)", () => { }, 120_000); test("BASED-LANCE-SQL: bridge boot failure emits a descriptive error and later runs retry the boot", async () => { - const { LanceSqlBridge, LanceSqlSetupError } = await import("@based/core/lancedb-sql"); + const { LanceSqlBridge, LanceSqlSetupError } = await import("@cyronius/based-core/lancedb-sql"); // A folder name containing a double quote cannot be attached — a deterministic boot failure // (the INSTALL-offline variant carries the same LanceSqlSetupError copy but needs a blocked // network to reproduce; that path is exercised manually). @@ -249,7 +249,7 @@ describe("Lance SQL (embedded DuckDB)", () => { }, 120_000); test("BASED-LANCE-SQL: querying a table in an unreadable attached dir errors gracefully", async () => { - const { LanceSqlBridge } = await import("@based/core/lancedb-sql"); + const { LanceSqlBridge } = await import("@cyronius/based-core/lancedb-sql"); // ATTACH of a nonexistent dir is lazy (no boot error); the failure surfaces at query time. const bridge = new LanceSqlBridge({ dir: join(soloDir, "does-not-exist-at-all"), folders: null }, 100); const chunks: QueryChunk[] = []; diff --git a/specs/based/tests/integration.lancedb.test.ts b/specs/based/tests/integration.lancedb.test.ts index ae71e7a..5307f6e 100644 Binary files a/specs/based/tests/integration.lancedb.test.ts and b/specs/based/tests/integration.lancedb.test.ts differ diff --git a/specs/based/tests/integration.lazyEngines.test.ts b/specs/based/tests/integration.lazyEngines.test.ts index a53757c..dddc1e6 100644 --- a/specs/based/tests/integration.lazyEngines.test.ts +++ b/specs/based/tests/integration.lazyEngines.test.ts @@ -1,11 +1,11 @@ // Traces: BASED-LAZY-ENGINES, BASED-LANCE-ENGINE // Engine adapters (and their native stacks — tedious, @lancedb napi, DuckDB) must not evaluate when -// @based/core is imported; they load on demand inside createAdapter. The import-graph assertions run +// @cyronius/based-core is imported; they load on demand inside createAdapter. The import-graph assertions run // in a child bun process because this test process itself loads adapters via sibling test files. import { join } from "node:path"; import { describe, expect, test } from "bun:test"; -import { createAdapter } from "@based/core"; -import type { ConnectionConfig } from "@based/core"; +import { createAdapter } from "@cyronius/based-core"; +import type { ConnectionConfig } from "@cyronius/based-core"; // A separate script file (not `bun -e`): multiline -e scripts break Windows argv quoting. const CHILD_SCRIPT_PATH = join(import.meta.dir, "helpers", "lazyEnginesChild.ts"); @@ -27,7 +27,7 @@ function cfgFor(engine?: "mssql" | "lancedb" | "snowflake"): ConnectionConfig { } describe("lazy engine loading", () => { - test("BASED-LAZY-ENGINES: importing @based/core evaluates no engine module", async () => { + test("BASED-LAZY-ENGINES: importing @cyronius/based-core evaluates no engine module", async () => { const proc = Bun.spawn(["bun", CHILD_SCRIPT_PATH], { cwd: import.meta.dir, stdout: "pipe", @@ -50,8 +50,8 @@ describe("lazy engine loading", () => { }, 30_000); test("BASED-LAZY-ENGINES: async createAdapter resolves the right class per engine", async () => { - const { MssqlAdapter } = await import("@based/core/mssql"); - const { LanceDbAdapter } = await import("@based/core/lancedb"); + const { MssqlAdapter } = await import("@cyronius/based-core/mssql"); + const { LanceDbAdapter } = await import("@cyronius/based-core/lancedb"); const noSecret = () => null; const mssql = await createAdapter(cfgFor("mssql"), noSecret); @@ -60,7 +60,7 @@ describe("lazy engine loading", () => { const lance = await createAdapter(cfgFor("lancedb"), noSecret); expect(lance).toBeInstanceOf(LanceDbAdapter); - const { SnowflakeAdapter } = await import("@based/core/snowflake"); + const { SnowflakeAdapter } = await import("@cyronius/based-core/snowflake"); const snowflake = await createAdapter(cfgFor("snowflake"), noSecret); expect(snowflake).toBeInstanceOf(SnowflakeAdapter); diff --git a/specs/based/tests/integration.lsp.test.ts b/specs/based/tests/integration.lsp.test.ts index e6c7d9a..1d8ff6b 100644 --- a/specs/based/tests/integration.lsp.test.ts +++ b/specs/based/tests/integration.lsp.test.ts @@ -9,7 +9,7 @@ import { tmpdir } from "node:os"; import { join } from "node:path"; import * as lancedb from "@lancedb/lancedb"; -import { startServer } from "@based/core"; +import { startServer } from "@cyronius/based-core"; const dir = mkdtempSync(join(tmpdir(), "based-lsp-lance-")); const appDir = mkdtempSync(join(tmpdir(), "based-lsp-app-")); @@ -107,9 +107,9 @@ class TestLsp { // Traces: BASED-LSP-MSSQL-NATIVE — the in-house MSSQL server driven as plain JSON-RPC against a // real dev-DB adapter over azure-cli auth: the exact configuration the old sqls bridge could // never serve (or test). Self-skips without the dev DB, like integration.mssql. -import { MssqlAdapter } from "@based/core/mssql"; +import { MssqlAdapter } from "@cyronius/based-core/mssql"; import { MssqlLspServer } from "../../../core/src/lsp/mssqlLsp"; -import type { ConnectionConfig, JsonRpcMessage } from "@based/core"; +import type { ConnectionConfig, JsonRpcMessage } from "@cyronius/based-core"; import { DEV_DB_AVAILABLE, devConnection, warnDevDbSkip } from "./_devDb"; const devCfg: ConnectionConfig = devConnection("spec-lsp-dev"); diff --git a/specs/based/tests/integration.mssql.test.ts b/specs/based/tests/integration.mssql.test.ts index 95f8776..a5efe85 100644 --- a/specs/based/tests/integration.mssql.test.ts +++ b/specs/based/tests/integration.mssql.test.ts @@ -4,9 +4,9 @@ // Runs against the Phase 0 dev DB via AzureCliCredential. Read-only suites need only connect; the // table-edit suite additionally needs CREATE/DROP TABLE and self-skips when that permission is absent. import { describe, expect, test } from "bun:test"; -import { buildEditCommands, testConnection } from "@based/core"; -import { MssqlAdapter } from "@based/core/mssql"; -import type { ConnectionConfig, ExecuteOptions, QueryChunk } from "@based/core"; +import { buildEditCommands, testConnection } from "@cyronius/based-core"; +import { MssqlAdapter } from "@cyronius/based-core/mssql"; +import type { ConnectionConfig, ExecuteOptions, QueryChunk } from "@cyronius/based-core"; import { DEV_DB_AVAILABLE, devConnection, warnDevDbSkip } from "./_devDb"; const cfg: ConnectionConfig = devConnection("spec-dev"); @@ -411,7 +411,7 @@ dw("table browse + transactional edit against a scratch table", () => { expect(plainDet.triggers).toEqual([]); // The scripted CREATE from these details is runnable against the same DB (round-trip) - const { scriptCreateTable } = await import("@based/core"); + const { scriptCreateTable } = await import("@cyronius/based-core"); const ddl = scriptCreateTable({ ...det, name: `${tbl}_rt` }).replaceAll(`[${tbl}]`, `[${tbl}_rt]`).replaceAll(`FK_${tbl}]`, `FK_${tbl}_rt]`).replaceAll(`CK_${tbl}]`, `CK_${tbl}_rt]`).replaceAll(`DF_${tbl}]`, `DF_${tbl}_rt]`).replaceAll(`PK_${tbl}]`, `PK_${tbl}_rt]`).replaceAll(`IX_${tbl}]`, `IX_${tbl}_rt]`); const rt = await collect(adapter, ddl); expect(rt.status).toBe("ok"); diff --git a/specs/based/tests/integration.openFiles.test.ts b/specs/based/tests/integration.openFiles.test.ts index 92bf241..a79c1d8 100644 --- a/specs/based/tests/integration.openFiles.test.ts +++ b/specs/based/tests/integration.openFiles.test.ts @@ -7,7 +7,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; -import { startServer } from "@based/core"; +import { startServer } from "@cyronius/based-core"; const TOKEN = "spec-token"; const server = startServer({ token: TOKEN, dbPath: join(mkdtempSync(join(tmpdir(), "based-spec-open-")), "app.db") }); diff --git a/specs/based/tests/integration.saveFile.test.ts b/specs/based/tests/integration.saveFile.test.ts index 9423114..8d4285f 100644 --- a/specs/based/tests/integration.saveFile.test.ts +++ b/specs/based/tests/integration.saveFile.test.ts @@ -6,8 +6,8 @@ import { describe, expect, test } from "bun:test"; import { existsSync, mkdtempSync, readdirSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { agentSurfaceFor, AuditStore, MAX_SAVE_FILE_BYTES, openDb, type ToolDeps } from "@based/core"; -import type { EngineCapabilities } from "@based/core"; +import { agentSurfaceFor, AuditStore, MAX_SAVE_FILE_BYTES, openDb, type ToolDeps } from "@cyronius/based-core"; +import type { EngineCapabilities } from "@cyronius/based-core"; import type { Message } from "@ag-ui/core"; const MSSQL: EngineCapabilities = { diff --git a/specs/based/tests/integration.secrets.test.ts b/specs/based/tests/integration.secrets.test.ts index b2bbcfa..2769232 100644 --- a/specs/based/tests/integration.secrets.test.ts +++ b/specs/based/tests/integration.secrets.test.ts @@ -1,14 +1,14 @@ // Traces: BASED-SECRET-STORE, BASED-SNOWFLAKE-AUTH import { createRequire } from "node:module"; import { describe, expect, test } from "bun:test"; -import { MAX_SECRET_BYTES, setSecret, getSecret, deleteSecret } from "@based/core"; +import { MAX_SECRET_BYTES, setSecret, getSecret, deleteSecret } from "@cyronius/based-core"; const TEST_ID = `spec-test-${process.pid}`; const SERVICE = process.env.BASED_KEYRING_SERVICE ?? "based-db-client"; // keyring is core's dependency, not specs'. Resolved the way core would, so the legacy-format test // can write a credential the old way — the one thing the public API deliberately cannot do. -const { Entry } = createRequire(import.meta.resolve("@based/core"))("@napi-rs/keyring") as { +const { Entry } = createRequire(import.meta.resolve("@cyronius/based-core"))("@napi-rs/keyring") as { Entry: new (service: string, account: string) => { setPassword(v: string): void }; }; diff --git a/specs/based/tests/integration.server.test.ts b/specs/based/tests/integration.server.test.ts index 0645d5a..2e87cac 100644 --- a/specs/based/tests/integration.server.test.ts +++ b/specs/based/tests/integration.server.test.ts @@ -4,9 +4,9 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; -import { startServer, getSecret } from "@based/core"; -import { MssqlAdapter } from "@based/core/mssql"; -import type { ConnectionInput, ConnectionConfig } from "@based/core"; +import { startServer, getSecret } from "@cyronius/based-core"; +import { MssqlAdapter } from "@cyronius/based-core/mssql"; +import type { ConnectionInput, ConnectionConfig } from "@cyronius/based-core"; import { DEV_DB_AVAILABLE, devConnection, warnDevDbSkip } from "./_devDb"; const TOKEN = "spec-token"; diff --git a/specs/based/tests/integration.settings.test.ts b/specs/based/tests/integration.settings.test.ts index 662e96f..3e63262 100644 --- a/specs/based/tests/integration.settings.test.ts +++ b/specs/based/tests/integration.settings.test.ts @@ -3,7 +3,7 @@ import { afterAll, describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; -import { startServer, SettingsStore, DEFAULT_SETTINGS, openDb } from "@based/core"; +import { startServer, SettingsStore, DEFAULT_SETTINGS, openDb } from "@cyronius/based-core"; const TOKEN = "spec-token"; const dbPath = join(mkdtempSync(join(tmpdir(), "based-spec-settings-")), "app.db"); diff --git a/specs/based/tests/integration.shellDialogs.test.ts b/specs/based/tests/integration.shellDialogs.test.ts index e7ea91b..0d3e6fb 100644 --- a/specs/based/tests/integration.shellDialogs.test.ts +++ b/specs/based/tests/integration.shellDialogs.test.ts @@ -8,7 +8,7 @@ import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { startServer } from "@based/core"; +import { startServer } from "@cyronius/based-core"; const appDir = mkdtempSync(join(tmpdir(), "based-shelldialog-")); const TOKEN = "shell-dialog-spec-token"; diff --git a/specs/based/tests/integration.snowflakeConnect.test.ts b/specs/based/tests/integration.snowflakeConnect.test.ts index 24e4fdf..bad6ed6 100644 --- a/specs/based/tests/integration.snowflakeConnect.test.ts +++ b/specs/based/tests/integration.snowflakeConnect.test.ts @@ -13,7 +13,7 @@ // Neither test needs Snowflake credentials or a reachable account. import { join } from "node:path"; import { describe, expect, test } from "bun:test"; -import type { ConnectionConfig } from "@based/core"; +import type { ConnectionConfig } from "@cyronius/based-core"; // A separate script file (not `bun -e`): multiline -e scripts break Windows argv quoting. const CHILD_SCRIPT_PATH = join(import.meta.dir, "helpers", "snowflakeEnvChild.ts"); @@ -69,7 +69,7 @@ describe("Snowflake connect", () => { }, 60_000); test("BASED-CONN-TEST: probe on an unreachable account fails with an error instead of hanging", async () => { - const { SnowflakeAdapter } = await import("@based/core/snowflake"); + const { SnowflakeAdapter } = await import("@cyronius/based-core/snowflake"); // A syntactically valid but non-existent account: the host does not resolve, so this exercises // the failure path end to end without touching anyone's real Snowflake. const adapter = new SnowflakeAdapter( @@ -89,7 +89,7 @@ describe("Snowflake connect", () => { }, 90_000); test("BASED-SNOWFLAKE-AUTH: a 404 from the login endpoint is reported as a bad account identifier", async () => { - const { errMessage, snowflakeAccountNotFound } = await import("@based/core/snowflake"); + const { errMessage, snowflakeAccountNotFound } = await import("@cyronius/based-core/snowflake"); // Exactly what the driver hands back when the wildcard domain resolves but Snowflake's shared // load balancer hosts no such account — a legacy locator given without its region and cloud. const balancer404 = Object.assign(new Error("Request to Snowflake failed."), { diff --git a/specs/based/tests/integration.storage.test.ts b/specs/based/tests/integration.storage.test.ts index db7fced..cdc7786 100644 --- a/specs/based/tests/integration.storage.test.ts +++ b/specs/based/tests/integration.storage.test.ts @@ -3,8 +3,8 @@ import { describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { mkdtempSync } from "node:fs"; -import { openDb, ConnectionStore, TabStore, WindowStateStore, HistoryStore, settingStr } from "@based/core"; -import type { ConnectionInput } from "@based/core"; +import { openDb, ConnectionStore, TabStore, WindowStateStore, HistoryStore, settingStr } from "@cyronius/based-core"; +import type { ConnectionInput } from "@cyronius/based-core"; function tempDbPath(): string { return join(mkdtempSync(join(tmpdir(), "based-spec-")), "app.db"); diff --git a/specs/based/tests/unit.batch.test.ts b/specs/based/tests/unit.batch.test.ts index bc16c6a..397f870 100644 --- a/specs/based/tests/unit.batch.test.ts +++ b/specs/based/tests/unit.batch.test.ts @@ -1,6 +1,6 @@ // Traces: BASED-BATCH-GO import { describe, expect, test } from "bun:test"; -import { splitBatches } from "@based/core"; +import { splitBatches } from "@cyronius/based-core"; describe("BASED-BATCH-GO: GO batch splitting", () => { test("splits on GO lines", () => { diff --git a/specs/based/tests/unit.classify.test.ts b/specs/based/tests/unit.classify.test.ts index 467f323..c1f289c 100644 --- a/specs/based/tests/unit.classify.test.ts +++ b/specs/based/tests/unit.classify.test.ts @@ -1,6 +1,6 @@ // Traces: BASED-AGENT-RUNQUERY (read-only classifier) import { describe, expect, test } from "bun:test"; -import { isReadOnly, firstKeyword } from "@based/core"; +import { isReadOnly, firstKeyword } from "@cyronius/based-core"; describe("BASED-AGENT-RUNQUERY: isReadOnly classifier", () => { test("plain SELECT and leading CTE are read-only", () => { diff --git a/specs/based/tests/unit.connectionSettings.test.ts b/specs/based/tests/unit.connectionSettings.test.ts index 85852f3..2f1f932 100644 --- a/specs/based/tests/unit.connectionSettings.test.ts +++ b/specs/based/tests/unit.connectionSettings.test.ts @@ -6,8 +6,8 @@ // their next save, so there is never a half-migrated table, and `settingStr`/`settingBool` fall back // to a legacy top-level field so a config that never passed through the store still resolves. import { describe, expect, test } from "bun:test"; -import { migrateConnection, settingBool, settingStr } from "@based/core"; -import type { ConnectionConfig } from "@based/core"; +import { migrateConnection, settingBool, settingStr } from "@cyronius/based-core"; +import type { ConnectionConfig } from "@cyronius/based-core"; /** A row as it was written before the bag existed. */ function legacyRow(): ConnectionConfig { diff --git a/specs/based/tests/unit.csvImport.test.ts b/specs/based/tests/unit.csvImport.test.ts index e87328b..03b12b9 100644 --- a/specs/based/tests/unit.csvImport.test.ts +++ b/specs/based/tests/unit.csvImport.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from "bun:test"; import { CsvParser, parseCsv } from "../../../core/src/import/csvParse"; import { coerceCsv } from "../../../core/src/import/coerce"; import { buildInsertBatches } from "../../../core/src/import/csvImport"; -import type { TableColumn } from "@based/core"; +import type { TableColumn } from "@cyronius/based-core"; function col(partial: Partial & { name: string; type: string }): TableColumn { return { diff --git a/specs/based/tests/unit.delegate.test.ts b/specs/based/tests/unit.delegate.test.ts index 2a7be59..575bdb1 100644 --- a/specs/based/tests/unit.delegate.test.ts +++ b/specs/based/tests/unit.delegate.test.ts @@ -32,7 +32,7 @@ import { type SubagentRunner, type SubagentTask, type ToolDeps, -} from "@based/core"; +} from "@cyronius/based-core"; const MSSQL = defaultCapabilitiesFor("mssql"); const LANCE = defaultCapabilitiesFor("lancedb"); diff --git a/specs/based/tests/unit.describeTable.test.ts b/specs/based/tests/unit.describeTable.test.ts index 3a1503f..9f35806 100644 --- a/specs/based/tests/unit.describeTable.test.ts +++ b/specs/based/tests/unit.describeTable.test.ts @@ -8,8 +8,8 @@ import { describe, expect, test } from "bun:test"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { agentSurfaceFor, AuditStore, openDb } from "@based/core"; -import type { DatabaseAdapter, EngineCapabilities, TableColumn, ToolDeps } from "@based/core"; +import { agentSurfaceFor, AuditStore, openDb } from "@cyronius/based-core"; +import type { DatabaseAdapter, EngineCapabilities, TableColumn, ToolDeps } from "@cyronius/based-core"; const MSSQL: EngineCapabilities = { sql: true, diff --git a/specs/based/tests/unit.dialect.test.ts b/specs/based/tests/unit.dialect.test.ts index fe5887a..0039f7b 100644 --- a/specs/based/tests/unit.dialect.test.ts +++ b/specs/based/tests/unit.dialect.test.ts @@ -16,7 +16,7 @@ import { SNOWFLAKE_DIALECT, TSQL_DIALECT, type SqlDialect, -} from "@based/core"; +} from "@cyronius/based-core"; const ALL: SqlDialect[] = [TSQL_DIALECT, SNOWFLAKE_DIALECT, DUCKDB_DIALECT]; diff --git a/specs/based/tests/unit.export.test.ts b/specs/based/tests/unit.export.test.ts index 6c79815..e55c2e5 100644 --- a/specs/based/tests/unit.export.test.ts +++ b/specs/based/tests/unit.export.test.ts @@ -3,8 +3,8 @@ import { describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import ExcelJS from "exceljs"; -import { toCsv, writeXlsx } from "@based/core"; -import type { ColumnInfo, WireValue } from "@based/core"; +import { toCsv, writeXlsx } from "@cyronius/based-core"; +import type { ColumnInfo, WireValue } from "@cyronius/based-core"; describe("BASED-EXPORT-CSV: CSV export", () => { const cols: ColumnInfo[] = [ diff --git a/specs/based/tests/unit.exportData.test.ts b/specs/based/tests/unit.exportData.test.ts index d946217..4bcbc73 100644 --- a/specs/based/tests/unit.exportData.test.ts +++ b/specs/based/tests/unit.exportData.test.ts @@ -4,8 +4,8 @@ import { describe, expect, test } from "bun:test"; import { mkdtempSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { agentSurfaceFor, AuditStore, exportData, openDb, sanitizeExportFileName, toCsv } from "@based/core"; -import type { DatabaseAdapter, EngineCapabilities, TablePage, ToolDeps } from "@based/core"; +import { agentSurfaceFor, AuditStore, exportData, openDb, sanitizeExportFileName, toCsv } from "@cyronius/based-core"; +import type { DatabaseAdapter, EngineCapabilities, TablePage, ToolDeps } from "@cyronius/based-core"; function fakeTableAdapter(rowCount: number): { adapter: DatabaseAdapter; pageCalls: number[] } { const pageCalls: number[] = []; diff --git a/specs/based/tests/unit.labelClusters.test.ts b/specs/based/tests/unit.labelClusters.test.ts index bfc5d36..9bb43d7 100644 --- a/specs/based/tests/unit.labelClusters.test.ts +++ b/specs/based/tests/unit.labelClusters.test.ts @@ -3,7 +3,7 @@ // guard) and response parsing (models wrap JSON in prose/fences; parsing must survive that and // fall back per-cluster to the TF-IDF hint). import { describe, expect, test } from "bun:test"; -import { buildLabelPrompt, clampClusters, parseLabelResponse } from "@based/core"; +import { buildLabelPrompt, clampClusters, parseLabelResponse } from "@cyronius/based-core"; const clusters = [ { id: 0, hint: "invoice, payment", samples: ["Invoice overdue for order 12", "Payment failed on retry"] }, diff --git a/specs/based/tests/unit.lanceDescribe.test.ts b/specs/based/tests/unit.lanceDescribe.test.ts index a61d2ae..815c41f 100644 --- a/specs/based/tests/unit.lanceDescribe.test.ts +++ b/specs/based/tests/unit.lanceDescribe.test.ts @@ -1,7 +1,7 @@ // Traces: BASED-SCRIPT-OBJECT (LanceDB half) — pure schema description, no DB. import { describe, expect, test } from "bun:test"; -import { describeLanceSchema } from "@based/core"; -import type { TableColumn } from "@based/core"; +import { describeLanceSchema } from "@cyronius/based-core"; +import type { TableColumn } from "@cyronius/based-core"; function col(over: Partial & { name: string; type: string }): TableColumn { return { diff --git a/specs/based/tests/unit.lanceSchema.test.ts b/specs/based/tests/unit.lanceSchema.test.ts index 597efbd..c3def0d 100644 --- a/specs/based/tests/unit.lanceSchema.test.ts +++ b/specs/based/tests/unit.lanceSchema.test.ts @@ -2,7 +2,7 @@ // The output must survive the lancedb SDK's own sanitizer, so that (not our shape) is what the // round-trip assertions run through. import { describe, expect, test } from "bun:test"; -import { buildLanceSchema, type LanceColumnSpec } from "@based/core"; +import { buildLanceSchema, type LanceColumnSpec } from "@cyronius/based-core"; // The SDK's sanitizer is the authority on whether a structural schema is valid. // eslint-disable-next-line import/no-relative-packages import { sanitizeSchema } from "../../../core/node_modules/@lancedb/lancedb/dist/sanitize.js"; diff --git a/specs/based/tests/unit.pkgBoundaries.test.ts b/specs/based/tests/unit.pkgBoundaries.test.ts new file mode 100644 index 0000000..ed70e00 --- /dev/null +++ b/specs/based/tests/unit.pkgBoundaries.test.ts @@ -0,0 +1,43 @@ +// Traces: BASED-PKG-BOUNDARIES +import { describe, expect, test } from "bun:test"; +import { crossesBoundary } from "../../../scripts/check-boundaries"; + +describe("BASED-PKG-BOUNDARIES: core and ui never import each other", () => { + test("ui reaching into core by relative path is a violation", () => { + expect(crossesBoundary("ui/src/api/client.ts", "../../../core/src/db/types")).toBe(true); + }); + + test("ui importing the core package is a violation", () => { + expect(crossesBoundary("ui/src/App.tsx", "@cyronius/based-core")).toBe(true); + }); + + test("core importing ui is a violation", () => { + expect(crossesBoundary("core/src/server.ts", "@based/ui")).toBe(true); + expect(crossesBoundary("core/src/server.ts", "../../ui/src/api/client")).toBe(true); + }); + + test("core or ui importing the shell is a violation", () => { + expect(crossesBoundary("core/src/server.ts", "../../shell-tauri/core-child")).toBe(true); + expect(crossesBoundary("ui/src/App.tsx", "@based/shell-tauri")).toBe(true); + }); + + test("relative imports inside a package are fine", () => { + expect(crossesBoundary("ui/src/App.tsx", "./components/DataGrid")).toBe(false); + expect(crossesBoundary("core/src/server.ts", "../db/types")).toBe(false); + expect(crossesBoundary("core/src/agent/tools/mssql.ts", "../../db/types")).toBe(false); + }); + + test("the shell may import core, that is its job", () => { + expect(crossesBoundary("shell-tauri/core-child.ts", "@cyronius/based-core")).toBe(false); + }); + + test("third-party packages are fine", () => { + expect(crossesBoundary("ui/src/App.tsx", "react")).toBe(false); + expect(crossesBoundary("core/src/server.ts", "@mastra/core")).toBe(false); + expect(crossesBoundary("core/src/storage/db.ts", "bun:sqlite")).toBe(false); + }); + + test("windows separators are normalized", () => { + expect(crossesBoundary("ui\\src\\App.tsx", "..\\..\\core\\src\\index")).toBe(true); + }); +}); diff --git a/specs/based/tests/unit.planWrap.test.ts b/specs/based/tests/unit.planWrap.test.ts index ce4b384..f7f27ca 100644 --- a/specs/based/tests/unit.planWrap.test.ts +++ b/specs/based/tests/unit.planWrap.test.ts @@ -1,6 +1,6 @@ // Traces: BASED-EXEC-PLAN, BASED-CLIENT-STATS import { describe, expect, test } from "bun:test"; -import { skipsWrap, wrapBatch } from "@based/core"; +import { skipsWrap, wrapBatch } from "@cyronius/based-core"; describe("BASED-EXEC-PLAN, BASED-CLIENT-STATS: wrapBatch", () => { test("neither flag set: only the defensive OFF prefix is added", () => { diff --git a/specs/based/tests/unit.platformPaths.test.ts b/specs/based/tests/unit.platformPaths.test.ts index e5f8035..db27e0e 100644 --- a/specs/based/tests/unit.platformPaths.test.ts +++ b/specs/based/tests/unit.platformPaths.test.ts @@ -3,7 +3,7 @@ import { describe, expect, test } from "bun:test"; import { join } from "node:path"; import { tmpdir } from "node:os"; import { mkdtempSync, existsSync } from "node:fs"; -import { appDataRoot, dataDir } from "@based/core"; +import { appDataRoot, dataDir } from "@cyronius/based-core"; /** Compare by path segments so these assertions hold on any build host, not just the one * whose separator matches the platform under test. */ diff --git a/specs/based/tests/unit.privateGuard.test.ts b/specs/based/tests/unit.privateGuard.test.ts new file mode 100644 index 0000000..6ab53ce --- /dev/null +++ b/specs/based/tests/unit.privateGuard.test.ts @@ -0,0 +1,38 @@ +// Traces: BASED-PRIVATE-GUARD +import { describe, expect, test } from "bun:test"; +import { isPrivatePath } from "../../../scripts/check-private"; + +describe("BASED-PRIVATE-GUARD: paid and secret paths never enter this repo", () => { + test("the paid product's directories are private", () => { + expect(isPrivatePath("based-ai/src/index.ts")).toBe(true); + expect(isPrivatePath("paid/README.md")).toBe(true); + expect(isPrivatePath("private/notes.md")).toBe(true); + expect(isPrivatePath("control-plane.private/x.ts")).toBe(true); + }); + + test("env files and private keys are private", () => { + expect(isPrivatePath(".env")).toBe(true); + expect(isPrivatePath(".env.production")).toBe(true); + expect(isPrivatePath("core/.env.local")).toBe(true); + expect(isPrivatePath("based_rsa_key.p8")).toBe(true); + expect(isPrivatePath("certs/server.pem")).toBe(true); + expect(isPrivatePath("certs/server.key")).toBe(true); + }); + + test("ordinary source and vendored files are not", () => { + expect(isPrivatePath("core/src/server.ts")).toBe(false); + expect(isPrivatePath("ui/vendor/lm-ag-ui/dist/index.js")).toBe(false); + expect(isPrivatePath("docs/development.md")).toBe(false); + expect(isPrivatePath("specs/based/tests/unit.privateGuard.test.ts")).toBe(false); + }); + + test("names that merely contain a private word are not", () => { + expect(isPrivatePath("ui/src/components/PrivateBadge.tsx")).toBe(false); + expect(isPrivatePath("docs/based-ai-roadmap.md")).toBe(false); + expect(isPrivatePath("core/src/env.ts")).toBe(false); + }); + + test("windows separators are normalized", () => { + expect(isPrivatePath("based-ai\\src\\index.ts")).toBe(true); + }); +}); diff --git a/specs/based/tests/unit.provider.test.ts b/specs/based/tests/unit.provider.test.ts index e5d407b..7e1f673 100644 --- a/specs/based/tests/unit.provider.test.ts +++ b/specs/based/tests/unit.provider.test.ts @@ -11,7 +11,7 @@ import { sanitizeRequestBody, DEFAULT_AI_TIMEOUT_SECONDS, AI_RUN_TIMEOUT_MULTIPLIER, -} from "@based/core"; +} from "@cyronius/based-core"; const base = { id: "p1", baseUrl: "", model: "m", deployment: undefined as string | undefined }; diff --git a/specs/based/tests/unit.readTable.test.ts b/specs/based/tests/unit.readTable.test.ts index 09c9e20..2be649e 100644 --- a/specs/based/tests/unit.readTable.test.ts +++ b/specs/based/tests/unit.readTable.test.ts @@ -10,8 +10,8 @@ import { describe, expect, test } from "bun:test"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { agentSurfaceFor, AuditStore, openDb } from "@based/core"; -import type { DatabaseAdapter, EngineCapabilities, TablePage, ToolDeps } from "@based/core"; +import { agentSurfaceFor, AuditStore, openDb } from "@cyronius/based-core"; +import type { DatabaseAdapter, EngineCapabilities, TablePage, ToolDeps } from "@cyronius/based-core"; interface Call { schema: string; diff --git a/specs/based/tests/unit.registry.test.ts b/specs/based/tests/unit.registry.test.ts index 85c86ec..3c01ae8 100644 --- a/specs/based/tests/unit.registry.test.ts +++ b/specs/based/tests/unit.registry.test.ts @@ -20,7 +20,7 @@ import { engineProfiles, openDb, type ToolDeps, -} from "@based/core"; +} from "@cyronius/based-core"; function deps(): ToolDeps { return { diff --git a/specs/based/tests/unit.reranker.test.ts b/specs/based/tests/unit.reranker.test.ts index d22e53b..a03621b 100644 --- a/specs/based/tests/unit.reranker.test.ts +++ b/specs/based/tests/unit.reranker.test.ts @@ -4,7 +4,7 @@ // at every layer above: the adapter sorts by it, finds the order already "correct", and returns // confidently-ranked garbage. These assert the parse fails loudly instead. import { describe, expect, test } from "bun:test"; -import { parseCohereRerankResults, scoreFromTopLogprobs } from "@based/core"; +import { parseCohereRerankResults, scoreFromTopLogprobs } from "@cyronius/based-core"; describe("BASED-LANCE-RERANK-PIPELINE: Cohere/TEI response parsing", () => { test("reads relevance_score, and score as the TEI alternative", () => { diff --git a/specs/based/tests/unit.retry.test.ts b/specs/based/tests/unit.retry.test.ts index e42de20..8d83662 100644 --- a/specs/based/tests/unit.retry.test.ts +++ b/specs/based/tests/unit.retry.test.ts @@ -1,6 +1,6 @@ // Traces: BASED-RECONNECT-RETRY import { describe, expect, test } from "bun:test"; -import { isRetryableError, MAX_RECONNECT_ATTEMPTS, withReconnect } from "@based/core"; +import { isRetryableError, MAX_RECONNECT_ATTEMPTS, withReconnect } from "@cyronius/based-core"; // No-op delay so backoff tests run instantly instead of waiting out real timers. const noDelay = async () => {}; diff --git a/specs/based/tests/unit.rowcap.test.ts b/specs/based/tests/unit.rowcap.test.ts index 195d6f8..8fa465d 100644 --- a/specs/based/tests/unit.rowcap.test.ts +++ b/specs/based/tests/unit.rowcap.test.ts @@ -1,7 +1,7 @@ // Traces: BASED-ROWCAP import { describe, expect, test } from "bun:test"; -import { RowCollector } from "@based/core"; -import type { WireValue } from "@based/core"; +import { RowCollector } from "@cyronius/based-core"; +import type { WireValue } from "@cyronius/based-core"; describe("BASED-ROWCAP: display row cap", () => { test("caps forwarded rows, keeps counting, marks truncated", () => { diff --git a/specs/based/tests/unit.saveFile.test.ts b/specs/based/tests/unit.saveFile.test.ts index 4bc3bf5..847282c 100644 --- a/specs/based/tests/unit.saveFile.test.ts +++ b/specs/based/tests/unit.saveFile.test.ts @@ -5,7 +5,7 @@ import { describe, expect, test } from "bun:test"; import { existsSync, mkdtempSync, readFileSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { MAX_SAVE_FILE_BYTES, SAVE_FILE_EXTENSIONS, sanitizeSaveFileName, writeTextFileUnique } from "@based/core"; +import { MAX_SAVE_FILE_BYTES, SAVE_FILE_EXTENSIONS, sanitizeSaveFileName, writeTextFileUnique } from "@cyronius/based-core"; const dir = mkdtempSync(join(tmpdir(), "based-savefile-")); diff --git a/specs/based/tests/unit.scripter.test.ts b/specs/based/tests/unit.scripter.test.ts index b8186ea..13d6c81 100644 --- a/specs/based/tests/unit.scripter.test.ts +++ b/specs/based/tests/unit.scripter.test.ts @@ -13,7 +13,7 @@ import { scriptObject, scriptSelectTemplate, } from "../../../core/src/db/scripter"; -import type { ScriptTableColumn, TableDetails } from "@based/core"; +import type { ScriptTableColumn, TableDetails } from "@cyronius/based-core"; function col(partial: Partial & { name: string; type: string }): ScriptTableColumn { return { diff --git a/specs/based/tests/unit.serialize.test.ts b/specs/based/tests/unit.serialize.test.ts index 78e6c33..475867b 100644 --- a/specs/based/tests/unit.serialize.test.ts +++ b/specs/based/tests/unit.serialize.test.ts @@ -1,6 +1,6 @@ // Traces: BASED-VALUE-SAFETY import { describe, expect, test } from "bun:test"; -import { serializeValue, formatSqlDate } from "@based/core"; +import { serializeValue, formatSqlDate } from "@cyronius/based-core"; describe("BASED-VALUE-SAFETY: safe cell serialization", () => { test("SQL NULL → wire null; string 'null' stays a string", () => { diff --git a/specs/based/tests/unit.skills.test.ts b/specs/based/tests/unit.skills.test.ts index 3846c88..686e33d 100644 --- a/specs/based/tests/unit.skills.test.ts +++ b/specs/based/tests/unit.skills.test.ts @@ -1,6 +1,6 @@ // Traces: BASED-SKILL-REGISTRY (canonical spec: specs/based/spec.md) import { describe, expect, test } from "bun:test"; -import { skills } from "@based/core"; +import { skills } from "@cyronius/based-core"; describe("BASED-SKILL-REGISTRY: registry & prompt catalog", () => { test("catalog() carries every skill's name + description and none of the body text", () => { diff --git a/specs/based/tests/unit.surface.test.ts b/specs/based/tests/unit.surface.test.ts index 8070ae9..e564616 100644 --- a/specs/based/tests/unit.surface.test.ts +++ b/specs/based/tests/unit.surface.test.ts @@ -14,8 +14,8 @@ import { describe, expect, test } from "bun:test"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import { agentInstructions, agentSurfaceFor, AuditStore, GENERIC_CORE, openDb, skills, type ToolDeps } from "@based/core"; -import type { EngineCapabilities } from "@based/core"; +import { agentInstructions, agentSurfaceFor, AuditStore, GENERIC_CORE, openDb, skills, type ToolDeps } from "@cyronius/based-core"; +import type { EngineCapabilities } from "@cyronius/based-core"; function deps(): ToolDeps { return { diff --git a/specs/based/tests/unit.tabContext.test.ts b/specs/based/tests/unit.tabContext.test.ts index df715cb..3999ee1 100644 --- a/specs/based/tests/unit.tabContext.test.ts +++ b/specs/based/tests/unit.tabContext.test.ts @@ -1,11 +1,11 @@ // Traces: BASED-AGENT-TAB-CONTEXT — the server-side renderer for the client's workspace snapshot. // (The UI-side builders are covered in unit.uiTabContext.test.ts.) import { describe, expect, test } from "bun:test"; -import { renderTabContext, buildAgent, GENERIC_CORE, agentInstructions, MSSQL_PERSONA, mssqlBriefing, defaultCapabilitiesFor, createAgentMemory } from "@based/core"; +import { renderTabContext, buildAgent, GENERIC_CORE, agentInstructions, MSSQL_PERSONA, mssqlBriefing, defaultCapabilitiesFor, createAgentMemory } from "@cyronius/based-core"; import { mkdtempSync } from "node:fs"; import { tmpdir } from "node:os"; import { join } from "node:path"; -import type { ToolDeps } from "@based/core"; +import type { ToolDeps } from "@cyronius/based-core"; describe("BASED-AGENT-TAB-CONTEXT: renderTabContext", () => { test("absent or garbage input renders nothing", () => { diff --git a/specs/based/tests/unit.tableEdit.test.ts b/specs/based/tests/unit.tableEdit.test.ts index 9fa6ae4..a74a3ae 100644 --- a/specs/based/tests/unit.tableEdit.test.ts +++ b/specs/based/tests/unit.tableEdit.test.ts @@ -1,7 +1,7 @@ // Traces: BASED-TABLE-DML (canonical spec: specs/based/spec.md) // Pure edit→SQL builder: parameterized, bracket-quoted, identifier-validated, PK-required for update/delete. import { describe, expect, test } from "bun:test"; -import { buildEditCommands, type TableChangeSet } from "@based/core"; +import { buildEditCommands, type TableChangeSet } from "@cyronius/based-core"; const cols = [ { name: "id", isPrimaryKey: true }, diff --git a/specs/based/tests/unit.threadMessages.test.ts b/specs/based/tests/unit.threadMessages.test.ts index cb906ed..32b8428 100644 --- a/specs/based/tests/unit.threadMessages.test.ts +++ b/specs/based/tests/unit.threadMessages.test.ts @@ -1,7 +1,7 @@ // Traces: BASED-AGENT-THREADS — Mastra DB messages → AG-UI messages for thread history restore. import { describe, expect, test } from "bun:test"; -import { mapDbMessagesToAgui } from "@based/core"; -import type { DbMessageLike } from "@based/core"; +import { mapDbMessagesToAgui } from "@cyronius/based-core"; +import type { DbMessageLike } from "@cyronius/based-core"; function textMsg(id: string, role: string, text: string): DbMessageLike { return { id, role, content: { format: 2, parts: [{ type: "text", text }] } }; diff --git a/specs/based/tests/unit.toolPayload.test.ts b/specs/based/tests/unit.toolPayload.test.ts index 3d3b4b4..267d568 100644 --- a/specs/based/tests/unit.toolPayload.test.ts +++ b/specs/based/tests/unit.toolPayload.test.ts @@ -7,8 +7,8 @@ // The other half is honesty. A clipped value that doesn't say it was clipped is worse than no // value — the model quotes it back as the whole thing — so every cut has to show up in the result. import { describe, expect, test } from "bun:test"; -import { boundRows, payloadBudget, TOOL_CELL_CAP, TOOL_PAYLOAD_CAP } from "@based/core"; -import type { WireValue } from "@based/core"; +import { boundRows, payloadBudget, TOOL_CELL_CAP, TOOL_PAYLOAD_CAP } from "@cyronius/based-core"; +import type { WireValue } from "@cyronius/based-core"; /** The real shape of the failure: a conversation-log column holding whole transcripts. */ function wideRows(count: number, chars: number): WireValue[][] { diff --git a/specs/based/tests/unit.transcript.test.ts b/specs/based/tests/unit.transcript.test.ts index cbbd4d8..da13158 100644 --- a/specs/based/tests/unit.transcript.test.ts +++ b/specs/based/tests/unit.transcript.test.ts @@ -2,7 +2,7 @@ // the save_chat_transcript tool (messages from agent memory) and the chat header's download button // (messages straight off the live AgentClient), so it is asserted against the shared AG-UI shape. import { describe, expect, test } from "bun:test"; -import { transcriptMarkdown } from "@based/core"; +import { transcriptMarkdown } from "@cyronius/based-core"; import type { Message } from "@ag-ui/core"; const AT = "2026-07-26T12:00:00.000Z"; diff --git a/specs/based/tests/unit.vectorWire.test.ts b/specs/based/tests/unit.vectorWire.test.ts index cd69fb4..0d0d3af 100644 --- a/specs/based/tests/unit.vectorWire.test.ts +++ b/specs/based/tests/unit.vectorWire.test.ts @@ -3,8 +3,8 @@ // 4-byte alignment][raw float32 block]. The alignment guarantee is what lets the client build a // Float32Array view directly over the response buffer without a copy. import { describe, expect, test } from "bun:test"; -import { encodeVectorSample, decodeVectorSample } from "@based/core"; -import type { VectorSampleResult } from "@based/core"; +import { encodeVectorSample, decodeVectorSample } from "@cyronius/based-core"; +import type { VectorSampleResult } from "@cyronius/based-core"; function sample(overrides: Partial = {}): VectorSampleResult { return { diff --git a/specs/package.json b/specs/package.json index 61b11c3..019d80c 100644 --- a/specs/package.json +++ b/specs/package.json @@ -7,7 +7,7 @@ "test": "bun test ." }, "dependencies": { - "@based/core": "workspace:*", + "@cyronius/based-core": "workspace:*", "exceljs": "^4.4.0" }, "devDependencies": { diff --git a/ui/src/agent/aiTimeouts.ts b/ui/src/agent/aiTimeouts.ts index ac52dec..47d5b0d 100644 --- a/ui/src/agent/aiTimeouts.ts +++ b/ui/src/agent/aiTimeouts.ts @@ -1,5 +1,5 @@ // Traces: BASED-AI-PROFILE-TIMEOUT, BASED-AI-PROFILE-STEPCAP -// Mirror of core/src/agent/provider.ts's timeout resolution. The UI doesn't import @based/core +// Mirror of core/src/agent/provider.ts's timeout resolution. The UI doesn't import @cyronius/based-core // (ui/src/api/types.ts duplicates the core shapes by the same convention), so the constants and the // fallback rule live here too — keep the two in step. import type { AiProfile } from "../api/types";