diff --git a/.github/workflows/sdk-publish.yml b/.github/workflows/sdk-publish.yml index de0014c4..9e6d2b31 100644 --- a/.github/workflows/sdk-publish.yml +++ b/.github/workflows/sdk-publish.yml @@ -59,7 +59,7 @@ jobs: - name: Verify tag matches pyproject.toml version run: | - PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('pyproject.toml','rb'))['project']['version'])") + PYPROJECT_VERSION=$(python -c "import tomllib; print(tomllib.load(open('Gradata/pyproject.toml','rb'))['project']['version'])") TAG_VERSION="${{ steps.meta.outputs.version }}" if [ "${PYPROJECT_VERSION}" != "${TAG_VERSION}" ]; then echo "::error::Tag version (${TAG_VERSION}) does not match pyproject.toml version (${PYPROJECT_VERSION})." @@ -95,6 +95,7 @@ jobs: - name: Build wheel and sdist if: steps.check.outputs.already_published != 'true' + working-directory: Gradata run: | rm -rf dist/ uv build @@ -105,7 +106,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: dist - path: dist/ + path: Gradata/dist/ if-no-files-found: error retention-days: 7 diff --git a/.gitignore b/.gitignore index ecdda69e..9502d6ca 100644 --- a/.gitignore +++ b/.gitignore @@ -218,3 +218,13 @@ docs/RELEASE-*-DRAFT.md # Sales exports — should live in brain/leads, never in the SDK repo. apollo-leads-*.csv + +# Phase B reorg — private workspace dirs never ship in public repo +Sprites/ +Hausgem/ +.reorg/ + +# Phase B reorg — paths updated to match new structure +# (Previous entries like `src/...`, `docs/...`, `scripts/*` now live under Gradata/) +Gradata/docs/audit-reports/ +Gradata/docs/gradata-marketing-strategy.md diff --git a/.dockerignore b/Gradata/.dockerignore similarity index 100% rename from .dockerignore rename to Gradata/.dockerignore diff --git a/CHANGELOG.md b/Gradata/CHANGELOG.md similarity index 100% rename from CHANGELOG.md rename to Gradata/CHANGELOG.md diff --git a/CODE_OF_CONDUCT.md b/Gradata/CODE_OF_CONDUCT.md similarity index 100% rename from CODE_OF_CONDUCT.md rename to Gradata/CODE_OF_CONDUCT.md diff --git a/CONTRIBUTING.md b/Gradata/CONTRIBUTING.md similarity index 100% rename from CONTRIBUTING.md rename to Gradata/CONTRIBUTING.md diff --git a/CREDITS.md b/Gradata/CREDITS.md similarity index 100% rename from CREDITS.md rename to Gradata/CREDITS.md diff --git a/Dockerfile b/Gradata/Dockerfile similarity index 100% rename from Dockerfile rename to Gradata/Dockerfile diff --git a/LICENSE b/Gradata/LICENSE similarity index 100% rename from LICENSE rename to Gradata/LICENSE diff --git a/README.md b/Gradata/README.md similarity index 100% rename from README.md rename to Gradata/README.md diff --git a/SECURITY.md b/Gradata/SECURITY.md similarity index 100% rename from SECURITY.md rename to Gradata/SECURITY.md diff --git a/docker-compose.yml b/Gradata/docker-compose.yml similarity index 100% rename from docker-compose.yml rename to Gradata/docker-compose.yml diff --git a/docs/LICENSING.md b/Gradata/docs/LICENSING.md similarity index 100% rename from docs/LICENSING.md rename to Gradata/docs/LICENSING.md diff --git a/docs/META_RULE_EXAMPLES.md b/Gradata/docs/META_RULE_EXAMPLES.md similarity index 100% rename from docs/META_RULE_EXAMPLES.md rename to Gradata/docs/META_RULE_EXAMPLES.md diff --git a/docs/RELEASE-v0.5.0-DRAFT.md b/Gradata/docs/RELEASE-v0.5.0-DRAFT.md similarity index 100% rename from docs/RELEASE-v0.5.0-DRAFT.md rename to Gradata/docs/RELEASE-v0.5.0-DRAFT.md diff --git a/docs/api/brain.md b/Gradata/docs/api/brain.md similarity index 100% rename from docs/api/brain.md rename to Gradata/docs/api/brain.md diff --git a/docs/api/cli.md b/Gradata/docs/api/cli.md similarity index 100% rename from docs/api/cli.md rename to Gradata/docs/api/cli.md diff --git a/docs/api/enhancements.md b/Gradata/docs/api/enhancements.md similarity index 100% rename from docs/api/enhancements.md rename to Gradata/docs/api/enhancements.md diff --git a/docs/api/patterns.md b/Gradata/docs/api/patterns.md similarity index 100% rename from docs/api/patterns.md rename to Gradata/docs/api/patterns.md diff --git a/docs/architecture/agent-graduation.md b/Gradata/docs/architecture/agent-graduation.md similarity index 100% rename from docs/architecture/agent-graduation.md rename to Gradata/docs/architecture/agent-graduation.md diff --git a/docs/architecture/brain.md b/Gradata/docs/architecture/brain.md similarity index 100% rename from docs/architecture/brain.md rename to Gradata/docs/architecture/brain.md diff --git a/docs/architecture/cloud-apply-instructions.md b/Gradata/docs/architecture/cloud-apply-instructions.md similarity index 100% rename from docs/architecture/cloud-apply-instructions.md rename to Gradata/docs/architecture/cloud-apply-instructions.md diff --git a/docs/architecture/cloud-monolith-v2.md b/Gradata/docs/architecture/cloud-monolith-v2.md similarity index 100% rename from docs/architecture/cloud-monolith-v2.md rename to Gradata/docs/architecture/cloud-monolith-v2.md diff --git a/docs/architecture/enhancements.md b/Gradata/docs/architecture/enhancements.md similarity index 100% rename from docs/architecture/enhancements.md rename to Gradata/docs/architecture/enhancements.md diff --git a/docs/architecture/learning-proxy.md b/Gradata/docs/architecture/learning-proxy.md similarity index 100% rename from docs/architecture/learning-proxy.md rename to Gradata/docs/architecture/learning-proxy.md diff --git a/docs/architecture/multi-tenant-future-proofing.md b/Gradata/docs/architecture/multi-tenant-future-proofing.md similarity index 100% rename from docs/architecture/multi-tenant-future-proofing.md rename to Gradata/docs/architecture/multi-tenant-future-proofing.md diff --git a/docs/architecture/overview.md b/Gradata/docs/architecture/overview.md similarity index 100% rename from docs/architecture/overview.md rename to Gradata/docs/architecture/overview.md diff --git a/docs/architecture/patterns.md b/Gradata/docs/architecture/patterns.md similarity index 100% rename from docs/architecture/patterns.md rename to Gradata/docs/architecture/patterns.md diff --git a/docs/assets/favicon.svg b/Gradata/docs/assets/favicon.svg similarity index 100% rename from docs/assets/favicon.svg rename to Gradata/docs/assets/favicon.svg diff --git a/docs/assets/logo.svg b/Gradata/docs/assets/logo.svg similarity index 100% rename from docs/assets/logo.svg rename to Gradata/docs/assets/logo.svg diff --git a/docs/assets/theme.css b/Gradata/docs/assets/theme.css similarity index 100% rename from docs/assets/theme.css rename to Gradata/docs/assets/theme.css diff --git a/docs/changelog.md b/Gradata/docs/changelog.md similarity index 100% rename from docs/changelog.md rename to Gradata/docs/changelog.md diff --git a/docs/cli.md b/Gradata/docs/cli.md similarity index 100% rename from docs/cli.md rename to Gradata/docs/cli.md diff --git a/docs/cloud/api.md b/Gradata/docs/cloud/api.md similarity index 100% rename from docs/cloud/api.md rename to Gradata/docs/cloud/api.md diff --git a/docs/cloud/dashboard.md b/Gradata/docs/cloud/dashboard.md similarity index 100% rename from docs/cloud/dashboard.md rename to Gradata/docs/cloud/dashboard.md diff --git a/docs/cloud/overview.md b/Gradata/docs/cloud/overview.md similarity index 100% rename from docs/cloud/overview.md rename to Gradata/docs/cloud/overview.md diff --git a/docs/concepts/corrections.md b/Gradata/docs/concepts/corrections.md similarity index 100% rename from docs/concepts/corrections.md rename to Gradata/docs/concepts/corrections.md diff --git a/docs/concepts/graduation.md b/Gradata/docs/concepts/graduation.md similarity index 100% rename from docs/concepts/graduation.md rename to Gradata/docs/concepts/graduation.md diff --git a/docs/concepts/meta-rules.md b/Gradata/docs/concepts/meta-rules.md similarity index 100% rename from docs/concepts/meta-rules.md rename to Gradata/docs/concepts/meta-rules.md diff --git a/docs/contributing.md b/Gradata/docs/contributing.md similarity index 100% rename from docs/contributing.md rename to Gradata/docs/contributing.md diff --git a/docs/contributing/autoresearch-loop-protocol.md b/Gradata/docs/contributing/autoresearch-loop-protocol.md similarity index 100% rename from docs/contributing/autoresearch-loop-protocol.md rename to Gradata/docs/contributing/autoresearch-loop-protocol.md diff --git a/docs/contributing/pypi-setup.md b/Gradata/docs/contributing/pypi-setup.md similarity index 100% rename from docs/contributing/pypi-setup.md rename to Gradata/docs/contributing/pypi-setup.md diff --git a/docs/faq.md b/Gradata/docs/faq.md similarity index 100% rename from docs/faq.md rename to Gradata/docs/faq.md diff --git a/docs/getting-started/claude-code-setup.md b/Gradata/docs/getting-started/claude-code-setup.md similarity index 100% rename from docs/getting-started/claude-code-setup.md rename to Gradata/docs/getting-started/claude-code-setup.md diff --git a/docs/getting-started/claude-code.md b/Gradata/docs/getting-started/claude-code.md similarity index 100% rename from docs/getting-started/claude-code.md rename to Gradata/docs/getting-started/claude-code.md diff --git a/docs/getting-started/concepts.md b/Gradata/docs/getting-started/concepts.md similarity index 100% rename from docs/getting-started/concepts.md rename to Gradata/docs/getting-started/concepts.md diff --git a/docs/getting-started/first-brain.md b/Gradata/docs/getting-started/first-brain.md similarity index 100% rename from docs/getting-started/first-brain.md rename to Gradata/docs/getting-started/first-brain.md diff --git a/docs/getting-started/install.md b/Gradata/docs/getting-started/install.md similarity index 100% rename from docs/getting-started/install.md rename to Gradata/docs/getting-started/install.md diff --git a/docs/getting-started/quickstart.md b/Gradata/docs/getting-started/quickstart.md similarity index 100% rename from docs/getting-started/quickstart.md rename to Gradata/docs/getting-started/quickstart.md diff --git a/docs/guides/agent-graduation.md b/Gradata/docs/guides/agent-graduation.md similarity index 100% rename from docs/guides/agent-graduation.md rename to Gradata/docs/guides/agent-graduation.md diff --git a/docs/guides/custom-domains.md b/Gradata/docs/guides/custom-domains.md similarity index 100% rename from docs/guides/custom-domains.md rename to Gradata/docs/guides/custom-domains.md diff --git a/docs/guides/mcp.md b/Gradata/docs/guides/mcp.md similarity index 100% rename from docs/guides/mcp.md rename to Gradata/docs/guides/mcp.md diff --git a/docs/guides/training.md b/Gradata/docs/guides/training.md similarity index 100% rename from docs/guides/training.md rename to Gradata/docs/guides/training.md diff --git a/docs/index.md b/Gradata/docs/index.md similarity index 100% rename from docs/index.md rename to Gradata/docs/index.md diff --git a/docs/middleware.md b/Gradata/docs/middleware.md similarity index 100% rename from docs/middleware.md rename to Gradata/docs/middleware.md diff --git a/docs/requirements.txt b/Gradata/docs/requirements.txt similarity index 100% rename from docs/requirements.txt rename to Gradata/docs/requirements.txt diff --git a/docs/research/s103-validation.md b/Gradata/docs/research/s103-validation.md similarity index 100% rename from docs/research/s103-validation.md rename to Gradata/docs/research/s103-validation.md diff --git a/docs/sdk/brain.md b/Gradata/docs/sdk/brain.md similarity index 100% rename from docs/sdk/brain.md rename to Gradata/docs/sdk/brain.md diff --git a/docs/sdk/middleware.md b/Gradata/docs/sdk/middleware.md similarity index 100% rename from docs/sdk/middleware.md rename to Gradata/docs/sdk/middleware.md diff --git a/docs/sdk/rule-to-hook.md b/Gradata/docs/sdk/rule-to-hook.md similarity index 100% rename from docs/sdk/rule-to-hook.md rename to Gradata/docs/sdk/rule-to-hook.md diff --git a/docs/superpowers/plans/2026-04-10-s101-master-plan.md b/Gradata/docs/superpowers/plans/2026-04-10-s101-master-plan.md similarity index 100% rename from docs/superpowers/plans/2026-04-10-s101-master-plan.md rename to Gradata/docs/superpowers/plans/2026-04-10-s101-master-plan.md diff --git a/docs/superpowers/plans/2026-04-11-hierarchical-rule-tree.md b/Gradata/docs/superpowers/plans/2026-04-11-hierarchical-rule-tree.md similarity index 100% rename from docs/superpowers/plans/2026-04-11-hierarchical-rule-tree.md rename to Gradata/docs/superpowers/plans/2026-04-11-hierarchical-rule-tree.md diff --git a/docs/superpowers/specs/2026-04-10-s101-session-plan.md b/Gradata/docs/superpowers/specs/2026-04-10-s101-session-plan.md similarity index 100% rename from docs/superpowers/specs/2026-04-10-s101-session-plan.md rename to Gradata/docs/superpowers/specs/2026-04-10-s101-session-plan.md diff --git a/docs/superpowers/specs/2026-04-11-cross-validation-gaps-design.md b/Gradata/docs/superpowers/specs/2026-04-11-cross-validation-gaps-design.md similarity index 100% rename from docs/superpowers/specs/2026-04-11-cross-validation-gaps-design.md rename to Gradata/docs/superpowers/specs/2026-04-11-cross-validation-gaps-design.md diff --git a/docs/superpowers/specs/2026-04-11-hierarchical-rule-tree-design.md b/Gradata/docs/superpowers/specs/2026-04-11-hierarchical-rule-tree-design.md similarity index 100% rename from docs/superpowers/specs/2026-04-11-hierarchical-rule-tree-design.md rename to Gradata/docs/superpowers/specs/2026-04-11-hierarchical-rule-tree-design.md diff --git a/docs/system-architecture.md b/Gradata/docs/system-architecture.md similarity index 100% rename from docs/system-architecture.md rename to Gradata/docs/system-architecture.md diff --git a/examples/README.md b/Gradata/examples/README.md similarity index 100% rename from examples/README.md rename to Gradata/examples/README.md diff --git a/examples/basic_usage.py b/Gradata/examples/basic_usage.py similarity index 100% rename from examples/basic_usage.py rename to Gradata/examples/basic_usage.py diff --git a/examples/domain-profiles/call_profile.py b/Gradata/examples/domain-profiles/call_profile.py similarity index 100% rename from examples/domain-profiles/call_profile.py rename to Gradata/examples/domain-profiles/call_profile.py diff --git a/examples/domain-profiles/sales_profile.py b/Gradata/examples/domain-profiles/sales_profile.py similarity index 100% rename from examples/domain-profiles/sales_profile.py rename to Gradata/examples/domain-profiles/sales_profile.py diff --git a/examples/with_claude_code.py b/Gradata/examples/with_claude_code.py similarity index 100% rename from examples/with_claude_code.py rename to Gradata/examples/with_claude_code.py diff --git a/examples/with_openai.py b/Gradata/examples/with_openai.py similarity index 100% rename from examples/with_openai.py rename to Gradata/examples/with_openai.py diff --git a/gradata-install/README.md b/Gradata/gradata-install/README.md similarity index 100% rename from gradata-install/README.md rename to Gradata/gradata-install/README.md diff --git a/gradata-install/bin/gradata-install.js b/Gradata/gradata-install/bin/gradata-install.js similarity index 100% rename from gradata-install/bin/gradata-install.js rename to Gradata/gradata-install/bin/gradata-install.js diff --git a/gradata-install/package.json b/Gradata/gradata-install/package.json similarity index 100% rename from gradata-install/package.json rename to Gradata/gradata-install/package.json diff --git a/hooks/hooks.json b/Gradata/hooks/hooks.json similarity index 100% rename from hooks/hooks.json rename to Gradata/hooks/hooks.json diff --git a/mkdocs.yml b/Gradata/mkdocs.yml similarity index 100% rename from mkdocs.yml rename to Gradata/mkdocs.yml diff --git a/packages/npm/README.md b/Gradata/packages/npm/README.md similarity index 100% rename from packages/npm/README.md rename to Gradata/packages/npm/README.md diff --git a/packages/npm/bin/gradata.mjs b/Gradata/packages/npm/bin/gradata.mjs similarity index 100% rename from packages/npm/bin/gradata.mjs rename to Gradata/packages/npm/bin/gradata.mjs diff --git a/packages/npm/package.json b/Gradata/packages/npm/package.json similarity index 100% rename from packages/npm/package.json rename to Gradata/packages/npm/package.json diff --git a/packages/npm/src/client.test.ts b/Gradata/packages/npm/src/client.test.ts similarity index 100% rename from packages/npm/src/client.test.ts rename to Gradata/packages/npm/src/client.test.ts diff --git a/packages/npm/src/client.ts b/Gradata/packages/npm/src/client.ts similarity index 100% rename from packages/npm/src/client.ts rename to Gradata/packages/npm/src/client.ts diff --git a/packages/npm/tsconfig.json b/Gradata/packages/npm/tsconfig.json similarity index 100% rename from packages/npm/tsconfig.json rename to Gradata/packages/npm/tsconfig.json diff --git a/pyproject.toml b/Gradata/pyproject.toml similarity index 100% rename from pyproject.toml rename to Gradata/pyproject.toml diff --git a/scripts/migrate_legacy_scopes.py b/Gradata/scripts/migrate_legacy_scopes.py similarity index 100% rename from scripts/migrate_legacy_scopes.py rename to Gradata/scripts/migrate_legacy_scopes.py diff --git a/scripts/publish-npm.sh b/Gradata/scripts/publish-npm.sh similarity index 100% rename from scripts/publish-npm.sh rename to Gradata/scripts/publish-npm.sh diff --git a/src/gradata/__init__.py b/Gradata/src/gradata/__init__.py similarity index 100% rename from src/gradata/__init__.py rename to Gradata/src/gradata/__init__.py diff --git a/src/gradata/_brain_manifest.py b/Gradata/src/gradata/_brain_manifest.py similarity index 100% rename from src/gradata/_brain_manifest.py rename to Gradata/src/gradata/_brain_manifest.py diff --git a/src/gradata/_cloud_sync.py b/Gradata/src/gradata/_cloud_sync.py similarity index 100% rename from src/gradata/_cloud_sync.py rename to Gradata/src/gradata/_cloud_sync.py diff --git a/src/gradata/_config.py b/Gradata/src/gradata/_config.py similarity index 100% rename from src/gradata/_config.py rename to Gradata/src/gradata/_config.py diff --git a/src/gradata/_config_paths.py b/Gradata/src/gradata/_config_paths.py similarity index 100% rename from src/gradata/_config_paths.py rename to Gradata/src/gradata/_config_paths.py diff --git a/src/gradata/_context_compile.py b/Gradata/src/gradata/_context_compile.py similarity index 100% rename from src/gradata/_context_compile.py rename to Gradata/src/gradata/_context_compile.py diff --git a/src/gradata/_context_packet.py b/Gradata/src/gradata/_context_packet.py similarity index 100% rename from src/gradata/_context_packet.py rename to Gradata/src/gradata/_context_packet.py diff --git a/src/gradata/_core.py b/Gradata/src/gradata/_core.py similarity index 100% rename from src/gradata/_core.py rename to Gradata/src/gradata/_core.py diff --git a/src/gradata/_data_flow_audit.py b/Gradata/src/gradata/_data_flow_audit.py similarity index 100% rename from src/gradata/_data_flow_audit.py rename to Gradata/src/gradata/_data_flow_audit.py diff --git a/src/gradata/_db.py b/Gradata/src/gradata/_db.py similarity index 100% rename from src/gradata/_db.py rename to Gradata/src/gradata/_db.py diff --git a/src/gradata/_doctor.py b/Gradata/src/gradata/_doctor.py similarity index 100% rename from src/gradata/_doctor.py rename to Gradata/src/gradata/_doctor.py diff --git a/src/gradata/_embed.py b/Gradata/src/gradata/_embed.py similarity index 100% rename from src/gradata/_embed.py rename to Gradata/src/gradata/_embed.py diff --git a/src/gradata/_encryption.py b/Gradata/src/gradata/_encryption.py similarity index 100% rename from src/gradata/_encryption.py rename to Gradata/src/gradata/_encryption.py diff --git a/src/gradata/_env.py b/Gradata/src/gradata/_env.py similarity index 100% rename from src/gradata/_env.py rename to Gradata/src/gradata/_env.py diff --git a/src/gradata/_events.py b/Gradata/src/gradata/_events.py similarity index 100% rename from src/gradata/_events.py rename to Gradata/src/gradata/_events.py diff --git a/src/gradata/_export_brain.py b/Gradata/src/gradata/_export_brain.py similarity index 100% rename from src/gradata/_export_brain.py rename to Gradata/src/gradata/_export_brain.py diff --git a/src/gradata/_fact_extractor.py b/Gradata/src/gradata/_fact_extractor.py similarity index 100% rename from src/gradata/_fact_extractor.py rename to Gradata/src/gradata/_fact_extractor.py diff --git a/src/gradata/_file_lock.py b/Gradata/src/gradata/_file_lock.py similarity index 100% rename from src/gradata/_file_lock.py rename to Gradata/src/gradata/_file_lock.py diff --git a/src/gradata/_http.py b/Gradata/src/gradata/_http.py similarity index 100% rename from src/gradata/_http.py rename to Gradata/src/gradata/_http.py diff --git a/src/gradata/_installer.py b/Gradata/src/gradata/_installer.py similarity index 100% rename from src/gradata/_installer.py rename to Gradata/src/gradata/_installer.py diff --git a/src/gradata/_manifest_helpers.py b/Gradata/src/gradata/_manifest_helpers.py similarity index 100% rename from src/gradata/_manifest_helpers.py rename to Gradata/src/gradata/_manifest_helpers.py diff --git a/src/gradata/_manifest_metrics.py b/Gradata/src/gradata/_manifest_metrics.py similarity index 100% rename from src/gradata/_manifest_metrics.py rename to Gradata/src/gradata/_manifest_metrics.py diff --git a/src/gradata/_manifest_quality.py b/Gradata/src/gradata/_manifest_quality.py similarity index 100% rename from src/gradata/_manifest_quality.py rename to Gradata/src/gradata/_manifest_quality.py diff --git a/src/gradata/_math.py b/Gradata/src/gradata/_math.py similarity index 100% rename from src/gradata/_math.py rename to Gradata/src/gradata/_math.py diff --git a/src/gradata/_migrations/001_add_tenant_id.py b/Gradata/src/gradata/_migrations/001_add_tenant_id.py similarity index 100% rename from src/gradata/_migrations/001_add_tenant_id.py rename to Gradata/src/gradata/_migrations/001_add_tenant_id.py diff --git a/src/gradata/_migrations/__init__.py b/Gradata/src/gradata/_migrations/__init__.py similarity index 100% rename from src/gradata/_migrations/__init__.py rename to Gradata/src/gradata/_migrations/__init__.py diff --git a/src/gradata/_migrations/_runner.py b/Gradata/src/gradata/_migrations/_runner.py similarity index 100% rename from src/gradata/_migrations/_runner.py rename to Gradata/src/gradata/_migrations/_runner.py diff --git a/src/gradata/_migrations/fill_null_tenant.py b/Gradata/src/gradata/_migrations/fill_null_tenant.py similarity index 100% rename from src/gradata/_migrations/fill_null_tenant.py rename to Gradata/src/gradata/_migrations/fill_null_tenant.py diff --git a/src/gradata/_migrations/tenant_uuid.py b/Gradata/src/gradata/_migrations/tenant_uuid.py similarity index 100% rename from src/gradata/_migrations/tenant_uuid.py rename to Gradata/src/gradata/_migrations/tenant_uuid.py diff --git a/src/gradata/_mine_transcripts.py b/Gradata/src/gradata/_mine_transcripts.py similarity index 100% rename from src/gradata/_mine_transcripts.py rename to Gradata/src/gradata/_mine_transcripts.py diff --git a/src/gradata/_paths.py b/Gradata/src/gradata/_paths.py similarity index 100% rename from src/gradata/_paths.py rename to Gradata/src/gradata/_paths.py diff --git a/src/gradata/_platform.py b/Gradata/src/gradata/_platform.py similarity index 100% rename from src/gradata/_platform.py rename to Gradata/src/gradata/_platform.py diff --git a/src/gradata/_query.py b/Gradata/src/gradata/_query.py similarity index 100% rename from src/gradata/_query.py rename to Gradata/src/gradata/_query.py diff --git a/src/gradata/_scope.py b/Gradata/src/gradata/_scope.py similarity index 100% rename from src/gradata/_scope.py rename to Gradata/src/gradata/_scope.py diff --git a/src/gradata/_scoped_brain.py b/Gradata/src/gradata/_scoped_brain.py similarity index 100% rename from src/gradata/_scoped_brain.py rename to Gradata/src/gradata/_scoped_brain.py diff --git a/src/gradata/_stats.py b/Gradata/src/gradata/_stats.py similarity index 100% rename from src/gradata/_stats.py rename to Gradata/src/gradata/_stats.py diff --git a/src/gradata/_tag_taxonomy.py b/Gradata/src/gradata/_tag_taxonomy.py similarity index 100% rename from src/gradata/_tag_taxonomy.py rename to Gradata/src/gradata/_tag_taxonomy.py diff --git a/src/gradata/_telemetry.py b/Gradata/src/gradata/_telemetry.py similarity index 100% rename from src/gradata/_telemetry.py rename to Gradata/src/gradata/_telemetry.py diff --git a/src/gradata/_tenant.py b/Gradata/src/gradata/_tenant.py similarity index 100% rename from src/gradata/_tenant.py rename to Gradata/src/gradata/_tenant.py diff --git a/src/gradata/_text_utils.py b/Gradata/src/gradata/_text_utils.py similarity index 100% rename from src/gradata/_text_utils.py rename to Gradata/src/gradata/_text_utils.py diff --git a/src/gradata/_types.py b/Gradata/src/gradata/_types.py similarity index 100% rename from src/gradata/_types.py rename to Gradata/src/gradata/_types.py diff --git a/src/gradata/_validator.py b/Gradata/src/gradata/_validator.py similarity index 100% rename from src/gradata/_validator.py rename to Gradata/src/gradata/_validator.py diff --git a/src/gradata/_workers.py b/Gradata/src/gradata/_workers.py similarity index 100% rename from src/gradata/_workers.py rename to Gradata/src/gradata/_workers.py diff --git a/src/gradata/adapters/__init__.py b/Gradata/src/gradata/adapters/__init__.py similarity index 100% rename from src/gradata/adapters/__init__.py rename to Gradata/src/gradata/adapters/__init__.py diff --git a/src/gradata/adapters/base.py b/Gradata/src/gradata/adapters/base.py similarity index 100% rename from src/gradata/adapters/base.py rename to Gradata/src/gradata/adapters/base.py diff --git a/src/gradata/adapters/mem0.py b/Gradata/src/gradata/adapters/mem0.py similarity index 100% rename from src/gradata/adapters/mem0.py rename to Gradata/src/gradata/adapters/mem0.py diff --git a/src/gradata/audit.py b/Gradata/src/gradata/audit.py similarity index 100% rename from src/gradata/audit.py rename to Gradata/src/gradata/audit.py diff --git a/src/gradata/benchmarks/__init__.py b/Gradata/src/gradata/benchmarks/__init__.py similarity index 100% rename from src/gradata/benchmarks/__init__.py rename to Gradata/src/gradata/benchmarks/__init__.py diff --git a/src/gradata/brain.py b/Gradata/src/gradata/brain.py similarity index 100% rename from src/gradata/brain.py rename to Gradata/src/gradata/brain.py diff --git a/src/gradata/brain_inspection.py b/Gradata/src/gradata/brain_inspection.py similarity index 100% rename from src/gradata/brain_inspection.py rename to Gradata/src/gradata/brain_inspection.py diff --git a/src/gradata/cli.py b/Gradata/src/gradata/cli.py similarity index 100% rename from src/gradata/cli.py rename to Gradata/src/gradata/cli.py diff --git a/src/gradata/cloud/__init__.py b/Gradata/src/gradata/cloud/__init__.py similarity index 100% rename from src/gradata/cloud/__init__.py rename to Gradata/src/gradata/cloud/__init__.py diff --git a/src/gradata/cloud/client.py b/Gradata/src/gradata/cloud/client.py similarity index 100% rename from src/gradata/cloud/client.py rename to Gradata/src/gradata/cloud/client.py diff --git a/src/gradata/cloud/sync.py b/Gradata/src/gradata/cloud/sync.py similarity index 100% rename from src/gradata/cloud/sync.py rename to Gradata/src/gradata/cloud/sync.py diff --git a/src/gradata/context_wrapper.py b/Gradata/src/gradata/context_wrapper.py similarity index 100% rename from src/gradata/context_wrapper.py rename to Gradata/src/gradata/context_wrapper.py diff --git a/src/gradata/contrib/__init__.py b/Gradata/src/gradata/contrib/__init__.py similarity index 100% rename from src/gradata/contrib/__init__.py rename to Gradata/src/gradata/contrib/__init__.py diff --git a/src/gradata/contrib/enhancements/__init__.py b/Gradata/src/gradata/contrib/enhancements/__init__.py similarity index 100% rename from src/gradata/contrib/enhancements/__init__.py rename to Gradata/src/gradata/contrib/enhancements/__init__.py diff --git a/src/gradata/contrib/enhancements/eval_benchmark.py b/Gradata/src/gradata/contrib/enhancements/eval_benchmark.py similarity index 100% rename from src/gradata/contrib/enhancements/eval_benchmark.py rename to Gradata/src/gradata/contrib/enhancements/eval_benchmark.py diff --git a/src/gradata/contrib/enhancements/install_manifest.py b/Gradata/src/gradata/contrib/enhancements/install_manifest.py similarity index 100% rename from src/gradata/contrib/enhancements/install_manifest.py rename to Gradata/src/gradata/contrib/enhancements/install_manifest.py diff --git a/src/gradata/contrib/enhancements/quality_gates.py b/Gradata/src/gradata/contrib/enhancements/quality_gates.py similarity index 100% rename from src/gradata/contrib/enhancements/quality_gates.py rename to Gradata/src/gradata/contrib/enhancements/quality_gates.py diff --git a/src/gradata/contrib/enhancements/truth_protocol.py b/Gradata/src/gradata/contrib/enhancements/truth_protocol.py similarity index 100% rename from src/gradata/contrib/enhancements/truth_protocol.py rename to Gradata/src/gradata/contrib/enhancements/truth_protocol.py diff --git a/src/gradata/contrib/patterns/__init__.py b/Gradata/src/gradata/contrib/patterns/__init__.py similarity index 100% rename from src/gradata/contrib/patterns/__init__.py rename to Gradata/src/gradata/contrib/patterns/__init__.py diff --git a/src/gradata/contrib/patterns/agent_modes.py b/Gradata/src/gradata/contrib/patterns/agent_modes.py similarity index 100% rename from src/gradata/contrib/patterns/agent_modes.py rename to Gradata/src/gradata/contrib/patterns/agent_modes.py diff --git a/src/gradata/contrib/patterns/context_brackets.py b/Gradata/src/gradata/contrib/patterns/context_brackets.py similarity index 100% rename from src/gradata/contrib/patterns/context_brackets.py rename to Gradata/src/gradata/contrib/patterns/context_brackets.py diff --git a/src/gradata/contrib/patterns/evaluator.py b/Gradata/src/gradata/contrib/patterns/evaluator.py similarity index 100% rename from src/gradata/contrib/patterns/evaluator.py rename to Gradata/src/gradata/contrib/patterns/evaluator.py diff --git a/src/gradata/contrib/patterns/execute_qualify.py b/Gradata/src/gradata/contrib/patterns/execute_qualify.py similarity index 100% rename from src/gradata/contrib/patterns/execute_qualify.py rename to Gradata/src/gradata/contrib/patterns/execute_qualify.py diff --git a/src/gradata/contrib/patterns/guardrails.py b/Gradata/src/gradata/contrib/patterns/guardrails.py similarity index 100% rename from src/gradata/contrib/patterns/guardrails.py rename to Gradata/src/gradata/contrib/patterns/guardrails.py diff --git a/src/gradata/contrib/patterns/human_loop.py b/Gradata/src/gradata/contrib/patterns/human_loop.py similarity index 100% rename from src/gradata/contrib/patterns/human_loop.py rename to Gradata/src/gradata/contrib/patterns/human_loop.py diff --git a/src/gradata/contrib/patterns/loop_detection.py b/Gradata/src/gradata/contrib/patterns/loop_detection.py similarity index 100% rename from src/gradata/contrib/patterns/loop_detection.py rename to Gradata/src/gradata/contrib/patterns/loop_detection.py diff --git a/src/gradata/contrib/patterns/mcp.py b/Gradata/src/gradata/contrib/patterns/mcp.py similarity index 100% rename from src/gradata/contrib/patterns/mcp.py rename to Gradata/src/gradata/contrib/patterns/mcp.py diff --git a/src/gradata/contrib/patterns/memory.py b/Gradata/src/gradata/contrib/patterns/memory.py similarity index 100% rename from src/gradata/contrib/patterns/memory.py rename to Gradata/src/gradata/contrib/patterns/memory.py diff --git a/src/gradata/contrib/patterns/middleware.py b/Gradata/src/gradata/contrib/patterns/middleware.py similarity index 100% rename from src/gradata/contrib/patterns/middleware.py rename to Gradata/src/gradata/contrib/patterns/middleware.py diff --git a/src/gradata/contrib/patterns/orchestrator.py b/Gradata/src/gradata/contrib/patterns/orchestrator.py similarity index 100% rename from src/gradata/contrib/patterns/orchestrator.py rename to Gradata/src/gradata/contrib/patterns/orchestrator.py diff --git a/src/gradata/contrib/patterns/parallel.py b/Gradata/src/gradata/contrib/patterns/parallel.py similarity index 100% rename from src/gradata/contrib/patterns/parallel.py rename to Gradata/src/gradata/contrib/patterns/parallel.py diff --git a/src/gradata/contrib/patterns/pipeline.py b/Gradata/src/gradata/contrib/patterns/pipeline.py similarity index 100% rename from src/gradata/contrib/patterns/pipeline.py rename to Gradata/src/gradata/contrib/patterns/pipeline.py diff --git a/src/gradata/contrib/patterns/q_learning_router.py b/Gradata/src/gradata/contrib/patterns/q_learning_router.py similarity index 100% rename from src/gradata/contrib/patterns/q_learning_router.py rename to Gradata/src/gradata/contrib/patterns/q_learning_router.py diff --git a/src/gradata/contrib/patterns/rag.py b/Gradata/src/gradata/contrib/patterns/rag.py similarity index 100% rename from src/gradata/contrib/patterns/rag.py rename to Gradata/src/gradata/contrib/patterns/rag.py diff --git a/src/gradata/contrib/patterns/reconciliation.py b/Gradata/src/gradata/contrib/patterns/reconciliation.py similarity index 100% rename from src/gradata/contrib/patterns/reconciliation.py rename to Gradata/src/gradata/contrib/patterns/reconciliation.py diff --git a/src/gradata/contrib/patterns/reflection.py b/Gradata/src/gradata/contrib/patterns/reflection.py similarity index 100% rename from src/gradata/contrib/patterns/reflection.py rename to Gradata/src/gradata/contrib/patterns/reflection.py diff --git a/src/gradata/contrib/patterns/sub_agents.py b/Gradata/src/gradata/contrib/patterns/sub_agents.py similarity index 100% rename from src/gradata/contrib/patterns/sub_agents.py rename to Gradata/src/gradata/contrib/patterns/sub_agents.py diff --git a/src/gradata/contrib/patterns/task_escalation.py b/Gradata/src/gradata/contrib/patterns/task_escalation.py similarity index 100% rename from src/gradata/contrib/patterns/task_escalation.py rename to Gradata/src/gradata/contrib/patterns/task_escalation.py diff --git a/src/gradata/contrib/patterns/tools.py b/Gradata/src/gradata/contrib/patterns/tools.py similarity index 100% rename from src/gradata/contrib/patterns/tools.py rename to Gradata/src/gradata/contrib/patterns/tools.py diff --git a/src/gradata/contrib/patterns/tree_of_thoughts.py b/Gradata/src/gradata/contrib/patterns/tree_of_thoughts.py similarity index 100% rename from src/gradata/contrib/patterns/tree_of_thoughts.py rename to Gradata/src/gradata/contrib/patterns/tree_of_thoughts.py diff --git a/src/gradata/correction_detector.py b/Gradata/src/gradata/correction_detector.py similarity index 100% rename from src/gradata/correction_detector.py rename to Gradata/src/gradata/correction_detector.py diff --git a/src/gradata/daemon.py b/Gradata/src/gradata/daemon.py similarity index 100% rename from src/gradata/daemon.py rename to Gradata/src/gradata/daemon.py diff --git a/src/gradata/demo/__init__.py b/Gradata/src/gradata/demo/__init__.py similarity index 100% rename from src/gradata/demo/__init__.py rename to Gradata/src/gradata/demo/__init__.py diff --git a/src/gradata/demo/brain/brain.manifest.json b/Gradata/src/gradata/demo/brain/brain.manifest.json similarity index 100% rename from src/gradata/demo/brain/brain.manifest.json rename to Gradata/src/gradata/demo/brain/brain.manifest.json diff --git a/src/gradata/demo/brain/lessons.md b/Gradata/src/gradata/demo/brain/lessons.md similarity index 100% rename from src/gradata/demo/brain/lessons.md rename to Gradata/src/gradata/demo/brain/lessons.md diff --git a/src/gradata/detection/__init__.py b/Gradata/src/gradata/detection/__init__.py similarity index 100% rename from src/gradata/detection/__init__.py rename to Gradata/src/gradata/detection/__init__.py diff --git a/src/gradata/detection/addition_pattern.py b/Gradata/src/gradata/detection/addition_pattern.py similarity index 100% rename from src/gradata/detection/addition_pattern.py rename to Gradata/src/gradata/detection/addition_pattern.py diff --git a/src/gradata/detection/correction_conflict.py b/Gradata/src/gradata/detection/correction_conflict.py similarity index 100% rename from src/gradata/detection/correction_conflict.py rename to Gradata/src/gradata/detection/correction_conflict.py diff --git a/src/gradata/detection/intent_classifier.py b/Gradata/src/gradata/detection/intent_classifier.py similarity index 100% rename from src/gradata/detection/intent_classifier.py rename to Gradata/src/gradata/detection/intent_classifier.py diff --git a/src/gradata/detection/mode_classifier.py b/Gradata/src/gradata/detection/mode_classifier.py similarity index 100% rename from src/gradata/detection/mode_classifier.py rename to Gradata/src/gradata/detection/mode_classifier.py diff --git a/src/gradata/enhancements/__init__.py b/Gradata/src/gradata/enhancements/__init__.py similarity index 100% rename from src/gradata/enhancements/__init__.py rename to Gradata/src/gradata/enhancements/__init__.py diff --git a/src/gradata/enhancements/_sanitize.py b/Gradata/src/gradata/enhancements/_sanitize.py similarity index 100% rename from src/gradata/enhancements/_sanitize.py rename to Gradata/src/gradata/enhancements/_sanitize.py diff --git a/src/gradata/enhancements/ast_severity.py b/Gradata/src/gradata/enhancements/ast_severity.py similarity index 100% rename from src/gradata/enhancements/ast_severity.py rename to Gradata/src/gradata/enhancements/ast_severity.py diff --git a/src/gradata/enhancements/bandits/__init__.py b/Gradata/src/gradata/enhancements/bandits/__init__.py similarity index 100% rename from src/gradata/enhancements/bandits/__init__.py rename to Gradata/src/gradata/enhancements/bandits/__init__.py diff --git a/src/gradata/enhancements/bandits/collaborative_filter.py b/Gradata/src/gradata/enhancements/bandits/collaborative_filter.py similarity index 100% rename from src/gradata/enhancements/bandits/collaborative_filter.py rename to Gradata/src/gradata/enhancements/bandits/collaborative_filter.py diff --git a/src/gradata/enhancements/bandits/contextual_bandit.py b/Gradata/src/gradata/enhancements/bandits/contextual_bandit.py similarity index 100% rename from src/gradata/enhancements/bandits/contextual_bandit.py rename to Gradata/src/gradata/enhancements/bandits/contextual_bandit.py diff --git a/src/gradata/enhancements/behavioral_engine.py b/Gradata/src/gradata/enhancements/behavioral_engine.py similarity index 100% rename from src/gradata/enhancements/behavioral_engine.py rename to Gradata/src/gradata/enhancements/behavioral_engine.py diff --git a/src/gradata/enhancements/behavioral_extractor.py b/Gradata/src/gradata/enhancements/behavioral_extractor.py similarity index 100% rename from src/gradata/enhancements/behavioral_extractor.py rename to Gradata/src/gradata/enhancements/behavioral_extractor.py diff --git a/src/gradata/enhancements/carl.py b/Gradata/src/gradata/enhancements/carl.py similarity index 100% rename from src/gradata/enhancements/carl.py rename to Gradata/src/gradata/enhancements/carl.py diff --git a/src/gradata/enhancements/causal_chains.py b/Gradata/src/gradata/enhancements/causal_chains.py similarity index 100% rename from src/gradata/enhancements/causal_chains.py rename to Gradata/src/gradata/enhancements/causal_chains.py diff --git a/src/gradata/enhancements/cluster_manager.py b/Gradata/src/gradata/enhancements/cluster_manager.py similarity index 100% rename from src/gradata/enhancements/cluster_manager.py rename to Gradata/src/gradata/enhancements/cluster_manager.py diff --git a/src/gradata/enhancements/clustering.py b/Gradata/src/gradata/enhancements/clustering.py similarity index 100% rename from src/gradata/enhancements/clustering.py rename to Gradata/src/gradata/enhancements/clustering.py diff --git a/src/gradata/enhancements/contradiction_detector.py b/Gradata/src/gradata/enhancements/contradiction_detector.py similarity index 100% rename from src/gradata/enhancements/contradiction_detector.py rename to Gradata/src/gradata/enhancements/contradiction_detector.py diff --git a/src/gradata/enhancements/dedup.py b/Gradata/src/gradata/enhancements/dedup.py similarity index 100% rename from src/gradata/enhancements/dedup.py rename to Gradata/src/gradata/enhancements/dedup.py diff --git a/src/gradata/enhancements/diff_engine.py b/Gradata/src/gradata/enhancements/diff_engine.py similarity index 100% rename from src/gradata/enhancements/diff_engine.py rename to Gradata/src/gradata/enhancements/diff_engine.py diff --git a/src/gradata/enhancements/edit_classifier.py b/Gradata/src/gradata/enhancements/edit_classifier.py similarity index 100% rename from src/gradata/enhancements/edit_classifier.py rename to Gradata/src/gradata/enhancements/edit_classifier.py diff --git a/src/gradata/enhancements/freshness.py b/Gradata/src/gradata/enhancements/freshness.py similarity index 100% rename from src/gradata/enhancements/freshness.py rename to Gradata/src/gradata/enhancements/freshness.py diff --git a/src/gradata/enhancements/git_backfill.py b/Gradata/src/gradata/enhancements/git_backfill.py similarity index 100% rename from src/gradata/enhancements/git_backfill.py rename to Gradata/src/gradata/enhancements/git_backfill.py diff --git a/src/gradata/enhancements/graduation/__init__.py b/Gradata/src/gradata/enhancements/graduation/__init__.py similarity index 100% rename from src/gradata/enhancements/graduation/__init__.py rename to Gradata/src/gradata/enhancements/graduation/__init__.py diff --git a/src/gradata/enhancements/graduation/agent_graduation.py b/Gradata/src/gradata/enhancements/graduation/agent_graduation.py similarity index 100% rename from src/gradata/enhancements/graduation/agent_graduation.py rename to Gradata/src/gradata/enhancements/graduation/agent_graduation.py diff --git a/src/gradata/enhancements/graduation/judgment_decay.py b/Gradata/src/gradata/enhancements/graduation/judgment_decay.py similarity index 100% rename from src/gradata/enhancements/graduation/judgment_decay.py rename to Gradata/src/gradata/enhancements/graduation/judgment_decay.py diff --git a/src/gradata/enhancements/graduation/rules_distillation.py b/Gradata/src/gradata/enhancements/graduation/rules_distillation.py similarity index 100% rename from src/gradata/enhancements/graduation/rules_distillation.py rename to Gradata/src/gradata/enhancements/graduation/rules_distillation.py diff --git a/src/gradata/enhancements/graduation/scoring.py b/Gradata/src/gradata/enhancements/graduation/scoring.py similarity index 100% rename from src/gradata/enhancements/graduation/scoring.py rename to Gradata/src/gradata/enhancements/graduation/scoring.py diff --git a/src/gradata/enhancements/instruction_cache.py b/Gradata/src/gradata/enhancements/instruction_cache.py similarity index 100% rename from src/gradata/enhancements/instruction_cache.py rename to Gradata/src/gradata/enhancements/instruction_cache.py diff --git a/src/gradata/enhancements/learning_pipeline.py b/Gradata/src/gradata/enhancements/learning_pipeline.py similarity index 100% rename from src/gradata/enhancements/learning_pipeline.py rename to Gradata/src/gradata/enhancements/learning_pipeline.py diff --git a/src/gradata/enhancements/lesson_discriminator.py b/Gradata/src/gradata/enhancements/lesson_discriminator.py similarity index 100% rename from src/gradata/enhancements/lesson_discriminator.py rename to Gradata/src/gradata/enhancements/lesson_discriminator.py diff --git a/src/gradata/enhancements/llm_provider.py b/Gradata/src/gradata/enhancements/llm_provider.py similarity index 100% rename from src/gradata/enhancements/llm_provider.py rename to Gradata/src/gradata/enhancements/llm_provider.py diff --git a/src/gradata/enhancements/llm_synthesizer.py b/Gradata/src/gradata/enhancements/llm_synthesizer.py similarity index 100% rename from src/gradata/enhancements/llm_synthesizer.py rename to Gradata/src/gradata/enhancements/llm_synthesizer.py diff --git a/src/gradata/enhancements/memory_taxonomy.py b/Gradata/src/gradata/enhancements/memory_taxonomy.py similarity index 100% rename from src/gradata/enhancements/memory_taxonomy.py rename to Gradata/src/gradata/enhancements/memory_taxonomy.py diff --git a/src/gradata/enhancements/meta_rules.py b/Gradata/src/gradata/enhancements/meta_rules.py similarity index 100% rename from src/gradata/enhancements/meta_rules.py rename to Gradata/src/gradata/enhancements/meta_rules.py diff --git a/src/gradata/enhancements/meta_rules_storage.py b/Gradata/src/gradata/enhancements/meta_rules_storage.py similarity index 100% rename from src/gradata/enhancements/meta_rules_storage.py rename to Gradata/src/gradata/enhancements/meta_rules_storage.py diff --git a/src/gradata/enhancements/metrics.py b/Gradata/src/gradata/enhancements/metrics.py similarity index 100% rename from src/gradata/enhancements/metrics.py rename to Gradata/src/gradata/enhancements/metrics.py diff --git a/src/gradata/enhancements/observation_hooks.py b/Gradata/src/gradata/enhancements/observation_hooks.py similarity index 100% rename from src/gradata/enhancements/observation_hooks.py rename to Gradata/src/gradata/enhancements/observation_hooks.py diff --git a/src/gradata/enhancements/pattern_extractor.py b/Gradata/src/gradata/enhancements/pattern_extractor.py similarity index 100% rename from src/gradata/enhancements/pattern_extractor.py rename to Gradata/src/gradata/enhancements/pattern_extractor.py diff --git a/src/gradata/enhancements/pattern_integration.py b/Gradata/src/gradata/enhancements/pattern_integration.py similarity index 100% rename from src/gradata/enhancements/pattern_integration.py rename to Gradata/src/gradata/enhancements/pattern_integration.py diff --git a/src/gradata/enhancements/pipeline_rewriter.py b/Gradata/src/gradata/enhancements/pipeline_rewriter.py similarity index 100% rename from src/gradata/enhancements/pipeline_rewriter.py rename to Gradata/src/gradata/enhancements/pipeline_rewriter.py diff --git a/src/gradata/enhancements/profiling/__init__.py b/Gradata/src/gradata/enhancements/profiling/__init__.py similarity index 100% rename from src/gradata/enhancements/profiling/__init__.py rename to Gradata/src/gradata/enhancements/profiling/__init__.py diff --git a/src/gradata/enhancements/profiling/tone_profile.py b/Gradata/src/gradata/enhancements/profiling/tone_profile.py similarity index 100% rename from src/gradata/enhancements/profiling/tone_profile.py rename to Gradata/src/gradata/enhancements/profiling/tone_profile.py diff --git a/src/gradata/enhancements/prompt_synthesizer.py b/Gradata/src/gradata/enhancements/prompt_synthesizer.py similarity index 100% rename from src/gradata/enhancements/prompt_synthesizer.py rename to Gradata/src/gradata/enhancements/prompt_synthesizer.py diff --git a/src/gradata/enhancements/quality_monitoring.py b/Gradata/src/gradata/enhancements/quality_monitoring.py similarity index 100% rename from src/gradata/enhancements/quality_monitoring.py rename to Gradata/src/gradata/enhancements/quality_monitoring.py diff --git a/src/gradata/enhancements/reporting.py b/Gradata/src/gradata/enhancements/reporting.py similarity index 100% rename from src/gradata/enhancements/reporting.py rename to Gradata/src/gradata/enhancements/reporting.py diff --git a/src/gradata/enhancements/retrieval_fusion.py b/Gradata/src/gradata/enhancements/retrieval_fusion.py similarity index 100% rename from src/gradata/enhancements/retrieval_fusion.py rename to Gradata/src/gradata/enhancements/retrieval_fusion.py diff --git a/src/gradata/enhancements/router_warmstart.py b/Gradata/src/gradata/enhancements/router_warmstart.py similarity index 100% rename from src/gradata/enhancements/router_warmstart.py rename to Gradata/src/gradata/enhancements/router_warmstart.py diff --git a/src/gradata/enhancements/rule_canary.py b/Gradata/src/gradata/enhancements/rule_canary.py similarity index 100% rename from src/gradata/enhancements/rule_canary.py rename to Gradata/src/gradata/enhancements/rule_canary.py diff --git a/src/gradata/enhancements/rule_context_bridge.py b/Gradata/src/gradata/enhancements/rule_context_bridge.py similarity index 100% rename from src/gradata/enhancements/rule_context_bridge.py rename to Gradata/src/gradata/enhancements/rule_context_bridge.py diff --git a/src/gradata/enhancements/rule_export.py b/Gradata/src/gradata/enhancements/rule_export.py similarity index 100% rename from src/gradata/enhancements/rule_export.py rename to Gradata/src/gradata/enhancements/rule_export.py diff --git a/src/gradata/enhancements/rule_integrity.py b/Gradata/src/gradata/enhancements/rule_integrity.py similarity index 100% rename from src/gradata/enhancements/rule_integrity.py rename to Gradata/src/gradata/enhancements/rule_integrity.py diff --git a/src/gradata/enhancements/rule_pipeline.py b/Gradata/src/gradata/enhancements/rule_pipeline.py similarity index 100% rename from src/gradata/enhancements/rule_pipeline.py rename to Gradata/src/gradata/enhancements/rule_pipeline.py diff --git a/src/gradata/enhancements/rule_to_hook.py b/Gradata/src/gradata/enhancements/rule_to_hook.py similarity index 100% rename from src/gradata/enhancements/rule_to_hook.py rename to Gradata/src/gradata/enhancements/rule_to_hook.py diff --git a/src/gradata/enhancements/rule_verifier.py b/Gradata/src/gradata/enhancements/rule_verifier.py similarity index 100% rename from src/gradata/enhancements/rule_verifier.py rename to Gradata/src/gradata/enhancements/rule_verifier.py diff --git a/src/gradata/enhancements/scoring/__init__.py b/Gradata/src/gradata/enhancements/scoring/__init__.py similarity index 100% rename from src/gradata/enhancements/scoring/__init__.py rename to Gradata/src/gradata/enhancements/scoring/__init__.py diff --git a/src/gradata/enhancements/scoring/brain_scores.py b/Gradata/src/gradata/enhancements/scoring/brain_scores.py similarity index 100% rename from src/gradata/enhancements/scoring/brain_scores.py rename to Gradata/src/gradata/enhancements/scoring/brain_scores.py diff --git a/src/gradata/enhancements/scoring/calibration.py b/Gradata/src/gradata/enhancements/scoring/calibration.py similarity index 100% rename from src/gradata/enhancements/scoring/calibration.py rename to Gradata/src/gradata/enhancements/scoring/calibration.py diff --git a/src/gradata/enhancements/scoring/correction_tracking.py b/Gradata/src/gradata/enhancements/scoring/correction_tracking.py similarity index 100% rename from src/gradata/enhancements/scoring/correction_tracking.py rename to Gradata/src/gradata/enhancements/scoring/correction_tracking.py diff --git a/src/gradata/enhancements/scoring/failure_detectors.py b/Gradata/src/gradata/enhancements/scoring/failure_detectors.py similarity index 100% rename from src/gradata/enhancements/scoring/failure_detectors.py rename to Gradata/src/gradata/enhancements/scoring/failure_detectors.py diff --git a/src/gradata/enhancements/scoring/gate_calibration.py b/Gradata/src/gradata/enhancements/scoring/gate_calibration.py similarity index 100% rename from src/gradata/enhancements/scoring/gate_calibration.py rename to Gradata/src/gradata/enhancements/scoring/gate_calibration.py diff --git a/src/gradata/enhancements/scoring/loop_intelligence.py b/Gradata/src/gradata/enhancements/scoring/loop_intelligence.py similarity index 100% rename from src/gradata/enhancements/scoring/loop_intelligence.py rename to Gradata/src/gradata/enhancements/scoring/loop_intelligence.py diff --git a/src/gradata/enhancements/scoring/memory_extraction.py b/Gradata/src/gradata/enhancements/scoring/memory_extraction.py similarity index 100% rename from src/gradata/enhancements/scoring/memory_extraction.py rename to Gradata/src/gradata/enhancements/scoring/memory_extraction.py diff --git a/src/gradata/enhancements/scoring/reports.py b/Gradata/src/gradata/enhancements/scoring/reports.py similarity index 100% rename from src/gradata/enhancements/scoring/reports.py rename to Gradata/src/gradata/enhancements/scoring/reports.py diff --git a/src/gradata/enhancements/scoring/success_conditions.py b/Gradata/src/gradata/enhancements/scoring/success_conditions.py similarity index 100% rename from src/gradata/enhancements/scoring/success_conditions.py rename to Gradata/src/gradata/enhancements/scoring/success_conditions.py diff --git a/src/gradata/enhancements/self_healing.py b/Gradata/src/gradata/enhancements/self_healing.py similarity index 100% rename from src/gradata/enhancements/self_healing.py rename to Gradata/src/gradata/enhancements/self_healing.py diff --git a/src/gradata/enhancements/self_improvement/__init__.py b/Gradata/src/gradata/enhancements/self_improvement/__init__.py similarity index 100% rename from src/gradata/enhancements/self_improvement/__init__.py rename to Gradata/src/gradata/enhancements/self_improvement/__init__.py diff --git a/src/gradata/enhancements/self_improvement/_confidence.py b/Gradata/src/gradata/enhancements/self_improvement/_confidence.py similarity index 100% rename from src/gradata/enhancements/self_improvement/_confidence.py rename to Gradata/src/gradata/enhancements/self_improvement/_confidence.py diff --git a/src/gradata/enhancements/self_improvement/_graduation.py b/Gradata/src/gradata/enhancements/self_improvement/_graduation.py similarity index 100% rename from src/gradata/enhancements/self_improvement/_graduation.py rename to Gradata/src/gradata/enhancements/self_improvement/_graduation.py diff --git a/src/gradata/enhancements/similarity.py b/Gradata/src/gradata/enhancements/similarity.py similarity index 100% rename from src/gradata/enhancements/similarity.py rename to Gradata/src/gradata/enhancements/similarity.py diff --git a/src/gradata/events_bus.py b/Gradata/src/gradata/events_bus.py similarity index 100% rename from src/gradata/events_bus.py rename to Gradata/src/gradata/events_bus.py diff --git a/src/gradata/exceptions.py b/Gradata/src/gradata/exceptions.py similarity index 100% rename from src/gradata/exceptions.py rename to Gradata/src/gradata/exceptions.py diff --git a/src/gradata/graph.py b/Gradata/src/gradata/graph.py similarity index 100% rename from src/gradata/graph.py rename to Gradata/src/gradata/graph.py diff --git a/src/gradata/hooks/__init__.py b/Gradata/src/gradata/hooks/__init__.py similarity index 100% rename from src/gradata/hooks/__init__.py rename to Gradata/src/gradata/hooks/__init__.py diff --git a/src/gradata/hooks/_base.py b/Gradata/src/gradata/hooks/_base.py similarity index 100% rename from src/gradata/hooks/_base.py rename to Gradata/src/gradata/hooks/_base.py diff --git a/src/gradata/hooks/_generated_runner_core.py b/Gradata/src/gradata/hooks/_generated_runner_core.py similarity index 100% rename from src/gradata/hooks/_generated_runner_core.py rename to Gradata/src/gradata/hooks/_generated_runner_core.py diff --git a/src/gradata/hooks/_installer.py b/Gradata/src/gradata/hooks/_installer.py similarity index 100% rename from src/gradata/hooks/_installer.py rename to Gradata/src/gradata/hooks/_installer.py diff --git a/src/gradata/hooks/_profiles.py b/Gradata/src/gradata/hooks/_profiles.py similarity index 100% rename from src/gradata/hooks/_profiles.py rename to Gradata/src/gradata/hooks/_profiles.py diff --git a/src/gradata/hooks/agent_graduation.py b/Gradata/src/gradata/hooks/agent_graduation.py similarity index 100% rename from src/gradata/hooks/agent_graduation.py rename to Gradata/src/gradata/hooks/agent_graduation.py diff --git a/src/gradata/hooks/agent_precontext.py b/Gradata/src/gradata/hooks/agent_precontext.py similarity index 100% rename from src/gradata/hooks/agent_precontext.py rename to Gradata/src/gradata/hooks/agent_precontext.py diff --git a/src/gradata/hooks/auto_correct.py b/Gradata/src/gradata/hooks/auto_correct.py similarity index 100% rename from src/gradata/hooks/auto_correct.py rename to Gradata/src/gradata/hooks/auto_correct.py diff --git a/src/gradata/hooks/brain_maintain.py b/Gradata/src/gradata/hooks/brain_maintain.py similarity index 100% rename from src/gradata/hooks/brain_maintain.py rename to Gradata/src/gradata/hooks/brain_maintain.py diff --git a/src/gradata/hooks/claude_code.py b/Gradata/src/gradata/hooks/claude_code.py similarity index 100% rename from src/gradata/hooks/claude_code.py rename to Gradata/src/gradata/hooks/claude_code.py diff --git a/src/gradata/hooks/client.py b/Gradata/src/gradata/hooks/client.py similarity index 100% rename from src/gradata/hooks/client.py rename to Gradata/src/gradata/hooks/client.py diff --git a/src/gradata/hooks/config_protection.py b/Gradata/src/gradata/hooks/config_protection.py similarity index 100% rename from src/gradata/hooks/config_protection.py rename to Gradata/src/gradata/hooks/config_protection.py diff --git a/src/gradata/hooks/config_validate.py b/Gradata/src/gradata/hooks/config_validate.py similarity index 100% rename from src/gradata/hooks/config_validate.py rename to Gradata/src/gradata/hooks/config_validate.py diff --git a/src/gradata/hooks/context_inject.py b/Gradata/src/gradata/hooks/context_inject.py similarity index 100% rename from src/gradata/hooks/context_inject.py rename to Gradata/src/gradata/hooks/context_inject.py diff --git a/src/gradata/hooks/daemon.py b/Gradata/src/gradata/hooks/daemon.py similarity index 100% rename from src/gradata/hooks/daemon.py rename to Gradata/src/gradata/hooks/daemon.py diff --git a/src/gradata/hooks/dispatch_post.py b/Gradata/src/gradata/hooks/dispatch_post.py similarity index 100% rename from src/gradata/hooks/dispatch_post.py rename to Gradata/src/gradata/hooks/dispatch_post.py diff --git a/src/gradata/hooks/duplicate_guard.py b/Gradata/src/gradata/hooks/duplicate_guard.py similarity index 100% rename from src/gradata/hooks/duplicate_guard.py rename to Gradata/src/gradata/hooks/duplicate_guard.py diff --git a/src/gradata/hooks/generated_runner.py b/Gradata/src/gradata/hooks/generated_runner.py similarity index 100% rename from src/gradata/hooks/generated_runner.py rename to Gradata/src/gradata/hooks/generated_runner.py diff --git a/src/gradata/hooks/generated_runner_post.py b/Gradata/src/gradata/hooks/generated_runner_post.py similarity index 100% rename from src/gradata/hooks/generated_runner_post.py rename to Gradata/src/gradata/hooks/generated_runner_post.py diff --git a/src/gradata/hooks/implicit_feedback.py b/Gradata/src/gradata/hooks/implicit_feedback.py similarity index 100% rename from src/gradata/hooks/implicit_feedback.py rename to Gradata/src/gradata/hooks/implicit_feedback.py diff --git a/src/gradata/hooks/inject_brain_rules.py b/Gradata/src/gradata/hooks/inject_brain_rules.py similarity index 100% rename from src/gradata/hooks/inject_brain_rules.py rename to Gradata/src/gradata/hooks/inject_brain_rules.py diff --git a/src/gradata/hooks/jit_inject.py b/Gradata/src/gradata/hooks/jit_inject.py similarity index 100% rename from src/gradata/hooks/jit_inject.py rename to Gradata/src/gradata/hooks/jit_inject.py diff --git a/src/gradata/hooks/pre_compact.py b/Gradata/src/gradata/hooks/pre_compact.py similarity index 100% rename from src/gradata/hooks/pre_compact.py rename to Gradata/src/gradata/hooks/pre_compact.py diff --git a/src/gradata/hooks/rule_enforcement.py b/Gradata/src/gradata/hooks/rule_enforcement.py similarity index 100% rename from src/gradata/hooks/rule_enforcement.py rename to Gradata/src/gradata/hooks/rule_enforcement.py diff --git a/src/gradata/hooks/secret_scan.py b/Gradata/src/gradata/hooks/secret_scan.py similarity index 100% rename from src/gradata/hooks/secret_scan.py rename to Gradata/src/gradata/hooks/secret_scan.py diff --git a/src/gradata/hooks/self_review.py b/Gradata/src/gradata/hooks/self_review.py similarity index 100% rename from src/gradata/hooks/self_review.py rename to Gradata/src/gradata/hooks/self_review.py diff --git a/src/gradata/hooks/session_boot.py b/Gradata/src/gradata/hooks/session_boot.py similarity index 100% rename from src/gradata/hooks/session_boot.py rename to Gradata/src/gradata/hooks/session_boot.py diff --git a/src/gradata/hooks/session_close.py b/Gradata/src/gradata/hooks/session_close.py similarity index 100% rename from src/gradata/hooks/session_close.py rename to Gradata/src/gradata/hooks/session_close.py diff --git a/src/gradata/hooks/session_persist.py b/Gradata/src/gradata/hooks/session_persist.py similarity index 100% rename from src/gradata/hooks/session_persist.py rename to Gradata/src/gradata/hooks/session_persist.py diff --git a/src/gradata/hooks/stale_hook_check.py b/Gradata/src/gradata/hooks/stale_hook_check.py similarity index 100% rename from src/gradata/hooks/stale_hook_check.py rename to Gradata/src/gradata/hooks/stale_hook_check.py diff --git a/src/gradata/hooks/status_line.py b/Gradata/src/gradata/hooks/status_line.py similarity index 100% rename from src/gradata/hooks/status_line.py rename to Gradata/src/gradata/hooks/status_line.py diff --git a/src/gradata/hooks/telemetry_summary.py b/Gradata/src/gradata/hooks/telemetry_summary.py similarity index 100% rename from src/gradata/hooks/telemetry_summary.py rename to Gradata/src/gradata/hooks/telemetry_summary.py diff --git a/src/gradata/hooks/templates/__init__.py b/Gradata/src/gradata/hooks/templates/__init__.py similarity index 100% rename from src/gradata/hooks/templates/__init__.py rename to Gradata/src/gradata/hooks/templates/__init__.py diff --git a/src/gradata/hooks/templates/auto_test.js.tmpl b/Gradata/src/gradata/hooks/templates/auto_test.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/auto_test.js.tmpl rename to Gradata/src/gradata/hooks/templates/auto_test.js.tmpl diff --git a/src/gradata/hooks/templates/destructive_block.js.tmpl b/Gradata/src/gradata/hooks/templates/destructive_block.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/destructive_block.js.tmpl rename to Gradata/src/gradata/hooks/templates/destructive_block.js.tmpl diff --git a/src/gradata/hooks/templates/file_size_check.js.tmpl b/Gradata/src/gradata/hooks/templates/file_size_check.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/file_size_check.js.tmpl rename to Gradata/src/gradata/hooks/templates/file_size_check.js.tmpl diff --git a/src/gradata/hooks/templates/fstring_block.js.tmpl b/Gradata/src/gradata/hooks/templates/fstring_block.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/fstring_block.js.tmpl rename to Gradata/src/gradata/hooks/templates/fstring_block.js.tmpl diff --git a/src/gradata/hooks/templates/regex_replace.js.tmpl b/Gradata/src/gradata/hooks/templates/regex_replace.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/regex_replace.js.tmpl rename to Gradata/src/gradata/hooks/templates/regex_replace.js.tmpl diff --git a/src/gradata/hooks/templates/root_file_save.js.tmpl b/Gradata/src/gradata/hooks/templates/root_file_save.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/root_file_save.js.tmpl rename to Gradata/src/gradata/hooks/templates/root_file_save.js.tmpl diff --git a/src/gradata/hooks/templates/secret_scan.js.tmpl b/Gradata/src/gradata/hooks/templates/secret_scan.js.tmpl similarity index 100% rename from src/gradata/hooks/templates/secret_scan.js.tmpl rename to Gradata/src/gradata/hooks/templates/secret_scan.js.tmpl diff --git a/src/gradata/hooks/tool_failure_emit.py b/Gradata/src/gradata/hooks/tool_failure_emit.py similarity index 100% rename from src/gradata/hooks/tool_failure_emit.py rename to Gradata/src/gradata/hooks/tool_failure_emit.py diff --git a/src/gradata/hooks/tool_finding_capture.py b/Gradata/src/gradata/hooks/tool_finding_capture.py similarity index 100% rename from src/gradata/hooks/tool_finding_capture.py rename to Gradata/src/gradata/hooks/tool_finding_capture.py diff --git a/src/gradata/inspection.py b/Gradata/src/gradata/inspection.py similarity index 100% rename from src/gradata/inspection.py rename to Gradata/src/gradata/inspection.py diff --git a/src/gradata/integrations/__init__.py b/Gradata/src/gradata/integrations/__init__.py similarity index 100% rename from src/gradata/integrations/__init__.py rename to Gradata/src/gradata/integrations/__init__.py diff --git a/src/gradata/integrations/anthropic_adapter.py b/Gradata/src/gradata/integrations/anthropic_adapter.py similarity index 100% rename from src/gradata/integrations/anthropic_adapter.py rename to Gradata/src/gradata/integrations/anthropic_adapter.py diff --git a/src/gradata/integrations/crewai_adapter.py b/Gradata/src/gradata/integrations/crewai_adapter.py similarity index 100% rename from src/gradata/integrations/crewai_adapter.py rename to Gradata/src/gradata/integrations/crewai_adapter.py diff --git a/src/gradata/integrations/embeddings.py b/Gradata/src/gradata/integrations/embeddings.py similarity index 100% rename from src/gradata/integrations/embeddings.py rename to Gradata/src/gradata/integrations/embeddings.py diff --git a/src/gradata/integrations/langchain_adapter.py b/Gradata/src/gradata/integrations/langchain_adapter.py similarity index 100% rename from src/gradata/integrations/langchain_adapter.py rename to Gradata/src/gradata/integrations/langchain_adapter.py diff --git a/src/gradata/integrations/openai_adapter.py b/Gradata/src/gradata/integrations/openai_adapter.py similarity index 100% rename from src/gradata/integrations/openai_adapter.py rename to Gradata/src/gradata/integrations/openai_adapter.py diff --git a/src/gradata/integrations/session_history.py b/Gradata/src/gradata/integrations/session_history.py similarity index 100% rename from src/gradata/integrations/session_history.py rename to Gradata/src/gradata/integrations/session_history.py diff --git a/src/gradata/mcp_server.py b/Gradata/src/gradata/mcp_server.py similarity index 100% rename from src/gradata/mcp_server.py rename to Gradata/src/gradata/mcp_server.py diff --git a/src/gradata/mcp_tools.py b/Gradata/src/gradata/mcp_tools.py similarity index 100% rename from src/gradata/mcp_tools.py rename to Gradata/src/gradata/mcp_tools.py diff --git a/src/gradata/middleware/__init__.py b/Gradata/src/gradata/middleware/__init__.py similarity index 100% rename from src/gradata/middleware/__init__.py rename to Gradata/src/gradata/middleware/__init__.py diff --git a/src/gradata/middleware/_core.py b/Gradata/src/gradata/middleware/_core.py similarity index 100% rename from src/gradata/middleware/_core.py rename to Gradata/src/gradata/middleware/_core.py diff --git a/src/gradata/middleware/anthropic_adapter.py b/Gradata/src/gradata/middleware/anthropic_adapter.py similarity index 100% rename from src/gradata/middleware/anthropic_adapter.py rename to Gradata/src/gradata/middleware/anthropic_adapter.py diff --git a/src/gradata/middleware/crewai_adapter.py b/Gradata/src/gradata/middleware/crewai_adapter.py similarity index 100% rename from src/gradata/middleware/crewai_adapter.py rename to Gradata/src/gradata/middleware/crewai_adapter.py diff --git a/src/gradata/middleware/langchain_adapter.py b/Gradata/src/gradata/middleware/langchain_adapter.py similarity index 100% rename from src/gradata/middleware/langchain_adapter.py rename to Gradata/src/gradata/middleware/langchain_adapter.py diff --git a/src/gradata/middleware/openai_adapter.py b/Gradata/src/gradata/middleware/openai_adapter.py similarity index 100% rename from src/gradata/middleware/openai_adapter.py rename to Gradata/src/gradata/middleware/openai_adapter.py diff --git a/src/gradata/notifications.py b/Gradata/src/gradata/notifications.py similarity index 100% rename from src/gradata/notifications.py rename to Gradata/src/gradata/notifications.py diff --git a/src/gradata/onboard.py b/Gradata/src/gradata/onboard.py similarity index 100% rename from src/gradata/onboard.py rename to Gradata/src/gradata/onboard.py diff --git a/src/gradata/patterns/__init__.py b/Gradata/src/gradata/patterns/__init__.py similarity index 100% rename from src/gradata/patterns/__init__.py rename to Gradata/src/gradata/patterns/__init__.py diff --git a/src/gradata/py.typed b/Gradata/src/gradata/py.typed similarity index 100% rename from src/gradata/py.typed rename to Gradata/src/gradata/py.typed diff --git a/src/gradata/rules/__init__.py b/Gradata/src/gradata/rules/__init__.py similarity index 100% rename from src/gradata/rules/__init__.py rename to Gradata/src/gradata/rules/__init__.py diff --git a/src/gradata/rules/cache.py b/Gradata/src/gradata/rules/cache.py similarity index 100% rename from src/gradata/rules/cache.py rename to Gradata/src/gradata/rules/cache.py diff --git a/src/gradata/rules/rule_context.py b/Gradata/src/gradata/rules/rule_context.py similarity index 100% rename from src/gradata/rules/rule_context.py rename to Gradata/src/gradata/rules/rule_context.py diff --git a/src/gradata/rules/rule_engine/__init__.py b/Gradata/src/gradata/rules/rule_engine/__init__.py similarity index 100% rename from src/gradata/rules/rule_engine/__init__.py rename to Gradata/src/gradata/rules/rule_engine/__init__.py diff --git a/src/gradata/rules/rule_engine/_engine.py b/Gradata/src/gradata/rules/rule_engine/_engine.py similarity index 100% rename from src/gradata/rules/rule_engine/_engine.py rename to Gradata/src/gradata/rules/rule_engine/_engine.py diff --git a/src/gradata/rules/rule_engine/_formatting.py b/Gradata/src/gradata/rules/rule_engine/_formatting.py similarity index 100% rename from src/gradata/rules/rule_engine/_formatting.py rename to Gradata/src/gradata/rules/rule_engine/_formatting.py diff --git a/src/gradata/rules/rule_engine/_models.py b/Gradata/src/gradata/rules/rule_engine/_models.py similarity index 100% rename from src/gradata/rules/rule_engine/_models.py rename to Gradata/src/gradata/rules/rule_engine/_models.py diff --git a/src/gradata/rules/rule_engine/_scoring.py b/Gradata/src/gradata/rules/rule_engine/_scoring.py similarity index 100% rename from src/gradata/rules/rule_engine/_scoring.py rename to Gradata/src/gradata/rules/rule_engine/_scoring.py diff --git a/src/gradata/rules/rule_graph.py b/Gradata/src/gradata/rules/rule_graph.py similarity index 100% rename from src/gradata/rules/rule_graph.py rename to Gradata/src/gradata/rules/rule_graph.py diff --git a/src/gradata/rules/rule_ranker.py b/Gradata/src/gradata/rules/rule_ranker.py similarity index 100% rename from src/gradata/rules/rule_ranker.py rename to Gradata/src/gradata/rules/rule_ranker.py diff --git a/src/gradata/rules/rule_tracker.py b/Gradata/src/gradata/rules/rule_tracker.py similarity index 100% rename from src/gradata/rules/rule_tracker.py rename to Gradata/src/gradata/rules/rule_tracker.py diff --git a/src/gradata/rules/rule_tree.py b/Gradata/src/gradata/rules/rule_tree.py similarity index 100% rename from src/gradata/rules/rule_tree.py rename to Gradata/src/gradata/rules/rule_tree.py diff --git a/src/gradata/rules/scope.py b/Gradata/src/gradata/rules/scope.py similarity index 100% rename from src/gradata/rules/scope.py rename to Gradata/src/gradata/rules/scope.py diff --git a/src/gradata/safety.py b/Gradata/src/gradata/safety.py similarity index 100% rename from src/gradata/safety.py rename to Gradata/src/gradata/safety.py diff --git a/src/gradata/security/THREAT_MODEL.md b/Gradata/src/gradata/security/THREAT_MODEL.md similarity index 100% rename from src/gradata/security/THREAT_MODEL.md rename to Gradata/src/gradata/security/THREAT_MODEL.md diff --git a/src/gradata/security/__init__.py b/Gradata/src/gradata/security/__init__.py similarity index 100% rename from src/gradata/security/__init__.py rename to Gradata/src/gradata/security/__init__.py diff --git a/src/gradata/security/adversarial_blocklist.py b/Gradata/src/gradata/security/adversarial_blocklist.py similarity index 100% rename from src/gradata/security/adversarial_blocklist.py rename to Gradata/src/gradata/security/adversarial_blocklist.py diff --git a/src/gradata/security/brain_salt.py b/Gradata/src/gradata/security/brain_salt.py similarity index 100% rename from src/gradata/security/brain_salt.py rename to Gradata/src/gradata/security/brain_salt.py diff --git a/src/gradata/security/correction_hash.py b/Gradata/src/gradata/security/correction_hash.py similarity index 100% rename from src/gradata/security/correction_hash.py rename to Gradata/src/gradata/security/correction_hash.py diff --git a/src/gradata/security/correction_provenance.py b/Gradata/src/gradata/security/correction_provenance.py similarity index 100% rename from src/gradata/security/correction_provenance.py rename to Gradata/src/gradata/security/correction_provenance.py diff --git a/src/gradata/security/manifest_signing.py b/Gradata/src/gradata/security/manifest_signing.py similarity index 100% rename from src/gradata/security/manifest_signing.py rename to Gradata/src/gradata/security/manifest_signing.py diff --git a/src/gradata/security/query_budget.py b/Gradata/src/gradata/security/query_budget.py similarity index 100% rename from src/gradata/security/query_budget.py rename to Gradata/src/gradata/security/query_budget.py diff --git a/src/gradata/security/score_obfuscation.py b/Gradata/src/gradata/security/score_obfuscation.py similarity index 100% rename from src/gradata/security/score_obfuscation.py rename to Gradata/src/gradata/security/score_obfuscation.py diff --git a/src/gradata/sidecar/__init__.py b/Gradata/src/gradata/sidecar/__init__.py similarity index 100% rename from src/gradata/sidecar/__init__.py rename to Gradata/src/gradata/sidecar/__init__.py diff --git a/src/gradata/sidecar/watcher.py b/Gradata/src/gradata/sidecar/watcher.py similarity index 100% rename from src/gradata/sidecar/watcher.py rename to Gradata/src/gradata/sidecar/watcher.py diff --git a/tests/__init__.py b/Gradata/tests/__init__.py similarity index 100% rename from tests/__init__.py rename to Gradata/tests/__init__.py diff --git a/tests/conftest.py b/Gradata/tests/conftest.py similarity index 100% rename from tests/conftest.py rename to Gradata/tests/conftest.py diff --git a/tests/security/__init__.py b/Gradata/tests/security/__init__.py similarity index 100% rename from tests/security/__init__.py rename to Gradata/tests/security/__init__.py diff --git a/tests/security/test_agent_precontext_scope.py b/Gradata/tests/security/test_agent_precontext_scope.py similarity index 100% rename from tests/security/test_agent_precontext_scope.py rename to Gradata/tests/security/test_agent_precontext_scope.py diff --git a/tests/security/test_device_auth_url.py b/Gradata/tests/security/test_device_auth_url.py similarity index 100% rename from tests/security/test_device_auth_url.py rename to Gradata/tests/security/test_device_auth_url.py diff --git a/tests/security/test_https_enforcement.py b/Gradata/tests/security/test_https_enforcement.py similarity index 100% rename from tests/security/test_https_enforcement.py rename to Gradata/tests/security/test_https_enforcement.py diff --git a/tests/security/test_lesson_sanitization.py b/Gradata/tests/security/test_lesson_sanitization.py similarity index 100% rename from tests/security/test_lesson_sanitization.py rename to Gradata/tests/security/test_lesson_sanitization.py diff --git a/tests/test_ablation.py b/Gradata/tests/test_ablation.py similarity index 100% rename from tests/test_ablation.py rename to Gradata/tests/test_ablation.py diff --git a/tests/test_ablation_beta_lb_gate.py b/Gradata/tests/test_ablation_beta_lb_gate.py similarity index 100% rename from tests/test_ablation_beta_lb_gate.py rename to Gradata/tests/test_ablation_beta_lb_gate.py diff --git a/tests/test_adaptations.py b/Gradata/tests/test_adaptations.py similarity index 100% rename from tests/test_adaptations.py rename to Gradata/tests/test_adaptations.py diff --git a/tests/test_adversarial_blocklist.py b/Gradata/tests/test_adversarial_blocklist.py similarity index 100% rename from tests/test_adversarial_blocklist.py rename to Gradata/tests/test_adversarial_blocklist.py diff --git a/tests/test_agent_graduation.py b/Gradata/tests/test_agent_graduation.py similarity index 100% rename from tests/test_agent_graduation.py rename to Gradata/tests/test_agent_graduation.py diff --git a/tests/test_agentic_synthesis.py b/Gradata/tests/test_agentic_synthesis.py similarity index 100% rename from tests/test_agentic_synthesis.py rename to Gradata/tests/test_agentic_synthesis.py diff --git a/tests/test_approval_signals.py b/Gradata/tests/test_approval_signals.py similarity index 100% rename from tests/test_approval_signals.py rename to Gradata/tests/test_approval_signals.py diff --git a/tests/test_ast_severity.py b/Gradata/tests/test_ast_severity.py similarity index 100% rename from tests/test_ast_severity.py rename to Gradata/tests/test_ast_severity.py diff --git a/tests/test_atomic_writes.py b/Gradata/tests/test_atomic_writes.py similarity index 100% rename from tests/test_atomic_writes.py rename to Gradata/tests/test_atomic_writes.py diff --git a/tests/test_audit_provenance.py b/Gradata/tests/test_audit_provenance.py similarity index 100% rename from tests/test_audit_provenance.py rename to Gradata/tests/test_audit_provenance.py diff --git a/tests/test_batch_approval.py b/Gradata/tests/test_batch_approval.py similarity index 100% rename from tests/test_batch_approval.py rename to Gradata/tests/test_batch_approval.py diff --git a/tests/test_bayesian_confidence.py b/Gradata/tests/test_bayesian_confidence.py similarity index 100% rename from tests/test_bayesian_confidence.py rename to Gradata/tests/test_bayesian_confidence.py diff --git a/tests/test_behavioral_extraction.py b/Gradata/tests/test_behavioral_extraction.py similarity index 100% rename from tests/test_behavioral_extraction.py rename to Gradata/tests/test_behavioral_extraction.py diff --git a/tests/test_behavioral_extractor.py b/Gradata/tests/test_behavioral_extractor.py similarity index 100% rename from tests/test_behavioral_extractor.py rename to Gradata/tests/test_behavioral_extractor.py diff --git a/tests/test_beta_scoring.py b/Gradata/tests/test_beta_scoring.py similarity index 100% rename from tests/test_beta_scoring.py rename to Gradata/tests/test_beta_scoring.py diff --git a/tests/test_brain.py b/Gradata/tests/test_brain.py similarity index 100% rename from tests/test_brain.py rename to Gradata/tests/test_brain.py diff --git a/tests/test_brain_add_rule.py b/Gradata/tests/test_brain_add_rule.py similarity index 100% rename from tests/test_brain_add_rule.py rename to Gradata/tests/test_brain_add_rule.py diff --git a/tests/test_brain_benchmark.py b/Gradata/tests/test_brain_benchmark.py similarity index 100% rename from tests/test_brain_benchmark.py rename to Gradata/tests/test_brain_benchmark.py diff --git a/tests/test_brain_events.py b/Gradata/tests/test_brain_events.py similarity index 100% rename from tests/test_brain_events.py rename to Gradata/tests/test_brain_events.py diff --git a/tests/test_brain_learning.py b/Gradata/tests/test_brain_learning.py similarity index 100% rename from tests/test_brain_learning.py rename to Gradata/tests/test_brain_learning.py diff --git a/tests/test_brain_pipeline.py b/Gradata/tests/test_brain_pipeline.py similarity index 100% rename from tests/test_brain_pipeline.py rename to Gradata/tests/test_brain_pipeline.py diff --git a/tests/test_brain_salt.py b/Gradata/tests/test_brain_salt.py similarity index 100% rename from tests/test_brain_salt.py rename to Gradata/tests/test_brain_salt.py diff --git a/tests/test_brain_scores.py b/Gradata/tests/test_brain_scores.py similarity index 100% rename from tests/test_brain_scores.py rename to Gradata/tests/test_brain_scores.py diff --git a/tests/test_bug_fixes.py b/Gradata/tests/test_bug_fixes.py similarity index 100% rename from tests/test_bug_fixes.py rename to Gradata/tests/test_bug_fixes.py diff --git a/tests/test_calibration_coverage.py b/Gradata/tests/test_calibration_coverage.py similarity index 100% rename from tests/test_calibration_coverage.py rename to Gradata/tests/test_calibration_coverage.py diff --git a/tests/test_capture_rule_failure.py b/Gradata/tests/test_capture_rule_failure.py similarity index 100% rename from tests/test_capture_rule_failure.py rename to Gradata/tests/test_capture_rule_failure.py diff --git a/tests/test_cascading_corrections.py b/Gradata/tests/test_cascading_corrections.py similarity index 100% rename from tests/test_cascading_corrections.py rename to Gradata/tests/test_cascading_corrections.py diff --git a/tests/test_causal_chains.py b/Gradata/tests/test_causal_chains.py similarity index 100% rename from tests/test_causal_chains.py rename to Gradata/tests/test_causal_chains.py diff --git a/tests/test_clb.py b/Gradata/tests/test_clb.py similarity index 100% rename from tests/test_clb.py rename to Gradata/tests/test_clb.py diff --git a/tests/test_cli.py b/Gradata/tests/test_cli.py similarity index 100% rename from tests/test_cli.py rename to Gradata/tests/test_cli.py diff --git a/tests/test_cli_seed.py b/Gradata/tests/test_cli_seed.py similarity index 100% rename from tests/test_cli_seed.py rename to Gradata/tests/test_cli_seed.py diff --git a/tests/test_cloud_row_push.py b/Gradata/tests/test_cloud_row_push.py similarity index 100% rename from tests/test_cloud_row_push.py rename to Gradata/tests/test_cloud_row_push.py diff --git a/tests/test_cloud_sync.py b/Gradata/tests/test_cloud_sync.py similarity index 100% rename from tests/test_cloud_sync.py rename to Gradata/tests/test_cloud_sync.py diff --git a/tests/test_cluster_injection.py b/Gradata/tests/test_cluster_injection.py similarity index 100% rename from tests/test_cluster_injection.py rename to Gradata/tests/test_cluster_injection.py diff --git a/tests/test_clustering.py b/Gradata/tests/test_clustering.py similarity index 100% rename from tests/test_clustering.py rename to Gradata/tests/test_clustering.py diff --git a/tests/test_collaborative_filter_coverage.py b/Gradata/tests/test_collaborative_filter_coverage.py similarity index 100% rename from tests/test_collaborative_filter_coverage.py rename to Gradata/tests/test_collaborative_filter_coverage.py diff --git a/tests/test_constitutional_format.py b/Gradata/tests/test_constitutional_format.py similarity index 100% rename from tests/test_constitutional_format.py rename to Gradata/tests/test_constitutional_format.py diff --git a/tests/test_contextual_bandit.py b/Gradata/tests/test_contextual_bandit.py similarity index 100% rename from tests/test_contextual_bandit.py rename to Gradata/tests/test_contextual_bandit.py diff --git a/tests/test_contrastive_embeddings.py b/Gradata/tests/test_contrastive_embeddings.py similarity index 100% rename from tests/test_contrastive_embeddings.py rename to Gradata/tests/test_contrastive_embeddings.py diff --git a/tests/test_convergence.py b/Gradata/tests/test_convergence.py similarity index 100% rename from tests/test_convergence.py rename to Gradata/tests/test_convergence.py diff --git a/tests/test_convergence_gate.py b/Gradata/tests/test_convergence_gate.py similarity index 100% rename from tests/test_convergence_gate.py rename to Gradata/tests/test_convergence_gate.py diff --git a/tests/test_core_behavioral.py b/Gradata/tests/test_core_behavioral.py similarity index 100% rename from tests/test_core_behavioral.py rename to Gradata/tests/test_core_behavioral.py diff --git a/tests/test_correction_hash.py b/Gradata/tests/test_correction_hash.py similarity index 100% rename from tests/test_correction_hash.py rename to Gradata/tests/test_correction_hash.py diff --git a/tests/test_correction_provenance.py b/Gradata/tests/test_correction_provenance.py similarity index 100% rename from tests/test_correction_provenance.py rename to Gradata/tests/test_correction_provenance.py diff --git a/tests/test_correction_tracking.py b/Gradata/tests/test_correction_tracking.py similarity index 100% rename from tests/test_correction_tracking.py rename to Gradata/tests/test_correction_tracking.py diff --git a/tests/test_coverage_gaps.py b/Gradata/tests/test_coverage_gaps.py similarity index 100% rename from tests/test_coverage_gaps.py rename to Gradata/tests/test_coverage_gaps.py diff --git a/tests/test_cusum.py b/Gradata/tests/test_cusum.py similarity index 100% rename from tests/test_cusum.py rename to Gradata/tests/test_cusum.py diff --git a/tests/test_daemon.py b/Gradata/tests/test_daemon.py similarity index 100% rename from tests/test_daemon.py rename to Gradata/tests/test_daemon.py diff --git a/tests/test_daemon_extended.py b/Gradata/tests/test_daemon_extended.py similarity index 100% rename from tests/test_daemon_extended.py rename to Gradata/tests/test_daemon_extended.py diff --git a/tests/test_dedup.py b/Gradata/tests/test_dedup.py similarity index 100% rename from tests/test_dedup.py rename to Gradata/tests/test_dedup.py diff --git a/tests/test_detection_addition.py b/Gradata/tests/test_detection_addition.py similarity index 100% rename from tests/test_detection_addition.py rename to Gradata/tests/test_detection_addition.py diff --git a/tests/test_detection_conflict.py b/Gradata/tests/test_detection_conflict.py similarity index 100% rename from tests/test_detection_conflict.py rename to Gradata/tests/test_detection_conflict.py diff --git a/tests/test_detection_mode.py b/Gradata/tests/test_detection_mode.py similarity index 100% rename from tests/test_detection_mode.py rename to Gradata/tests/test_detection_mode.py diff --git a/tests/test_diff_engine.py b/Gradata/tests/test_diff_engine.py similarity index 100% rename from tests/test_diff_engine.py rename to Gradata/tests/test_diff_engine.py diff --git a/tests/test_dispatch_post.py b/Gradata/tests/test_dispatch_post.py similarity index 100% rename from tests/test_dispatch_post.py rename to Gradata/tests/test_dispatch_post.py diff --git a/tests/test_disposition.py b/Gradata/tests/test_disposition.py similarity index 100% rename from tests/test_disposition.py rename to Gradata/tests/test_disposition.py diff --git a/tests/test_dynamic_similarity.py b/Gradata/tests/test_dynamic_similarity.py similarity index 100% rename from tests/test_dynamic_similarity.py rename to Gradata/tests/test_dynamic_similarity.py diff --git a/tests/test_edit_distance_convergence.py b/Gradata/tests/test_edit_distance_convergence.py similarity index 100% rename from tests/test_edit_distance_convergence.py rename to Gradata/tests/test_edit_distance_convergence.py diff --git a/tests/test_efficiency.py b/Gradata/tests/test_efficiency.py similarity index 100% rename from tests/test_efficiency.py rename to Gradata/tests/test_efficiency.py diff --git a/tests/test_embeddings.py b/Gradata/tests/test_embeddings.py similarity index 100% rename from tests/test_embeddings.py rename to Gradata/tests/test_embeddings.py diff --git a/tests/test_embeddings_integration.py b/Gradata/tests/test_embeddings_integration.py similarity index 100% rename from tests/test_embeddings_integration.py rename to Gradata/tests/test_embeddings_integration.py diff --git a/tests/test_enhancements.py b/Gradata/tests/test_enhancements.py similarity index 100% rename from tests/test_enhancements.py rename to Gradata/tests/test_enhancements.py diff --git a/tests/test_events_bus.py b/Gradata/tests/test_events_bus.py similarity index 100% rename from tests/test_events_bus.py rename to Gradata/tests/test_events_bus.py diff --git a/tests/test_failure_detectors_coverage.py b/Gradata/tests/test_failure_detectors_coverage.py similarity index 100% rename from tests/test_failure_detectors_coverage.py rename to Gradata/tests/test_failure_detectors_coverage.py diff --git a/tests/test_failure_modes.py b/Gradata/tests/test_failure_modes.py similarity index 100% rename from tests/test_failure_modes.py rename to Gradata/tests/test_failure_modes.py diff --git a/tests/test_file_lock.py b/Gradata/tests/test_file_lock.py similarity index 100% rename from tests/test_file_lock.py rename to Gradata/tests/test_file_lock.py diff --git a/tests/test_freshness.py b/Gradata/tests/test_freshness.py similarity index 100% rename from tests/test_freshness.py rename to Gradata/tests/test_freshness.py diff --git a/tests/test_gate_calibration_coverage.py b/Gradata/tests/test_gate_calibration_coverage.py similarity index 100% rename from tests/test_gate_calibration_coverage.py rename to Gradata/tests/test_gate_calibration_coverage.py diff --git a/tests/test_graduation_notification.py b/Gradata/tests/test_graduation_notification.py similarity index 100% rename from tests/test_graduation_notification.py rename to Gradata/tests/test_graduation_notification.py diff --git a/tests/test_graduation_scoring.py b/Gradata/tests/test_graduation_scoring.py similarity index 100% rename from tests/test_graduation_scoring.py rename to Gradata/tests/test_graduation_scoring.py diff --git a/tests/test_hook_profile.py b/Gradata/tests/test_hook_profile.py similarity index 100% rename from tests/test_hook_profile.py rename to Gradata/tests/test_hook_profile.py diff --git a/tests/test_hooks_base.py b/Gradata/tests/test_hooks_base.py similarity index 100% rename from tests/test_hooks_base.py rename to Gradata/tests/test_hooks_base.py diff --git a/tests/test_hooks_intelligence.py b/Gradata/tests/test_hooks_intelligence.py similarity index 100% rename from tests/test_hooks_intelligence.py rename to Gradata/tests/test_hooks_intelligence.py diff --git a/tests/test_hooks_learning.py b/Gradata/tests/test_hooks_learning.py similarity index 100% rename from tests/test_hooks_learning.py rename to Gradata/tests/test_hooks_learning.py diff --git a/tests/test_hooks_safety.py b/Gradata/tests/test_hooks_safety.py similarity index 100% rename from tests/test_hooks_safety.py rename to Gradata/tests/test_hooks_safety.py diff --git a/tests/test_injection_order.py b/Gradata/tests/test_injection_order.py similarity index 100% rename from tests/test_injection_order.py rename to Gradata/tests/test_injection_order.py diff --git a/tests/test_inspection.py b/Gradata/tests/test_inspection.py similarity index 100% rename from tests/test_inspection.py rename to Gradata/tests/test_inspection.py diff --git a/tests/test_instruction_cache.py b/Gradata/tests/test_instruction_cache.py similarity index 100% rename from tests/test_instruction_cache.py rename to Gradata/tests/test_instruction_cache.py diff --git a/tests/test_integration_workflow.py b/Gradata/tests/test_integration_workflow.py similarity index 100% rename from tests/test_integration_workflow.py rename to Gradata/tests/test_integration_workflow.py diff --git a/tests/test_integrations.py b/Gradata/tests/test_integrations.py similarity index 100% rename from tests/test_integrations.py rename to Gradata/tests/test_integrations.py diff --git a/tests/test_intent_classifier.py b/Gradata/tests/test_intent_classifier.py similarity index 100% rename from tests/test_intent_classifier.py rename to Gradata/tests/test_intent_classifier.py diff --git a/tests/test_jit_inject.py b/Gradata/tests/test_jit_inject.py similarity index 100% rename from tests/test_jit_inject.py rename to Gradata/tests/test_jit_inject.py diff --git a/tests/test_judgment_decay.py b/Gradata/tests/test_judgment_decay.py similarity index 100% rename from tests/test_judgment_decay.py rename to Gradata/tests/test_judgment_decay.py diff --git a/tests/test_kill_thresholds.py b/Gradata/tests/test_kill_thresholds.py similarity index 100% rename from tests/test_kill_thresholds.py rename to Gradata/tests/test_kill_thresholds.py diff --git a/tests/test_llm_synthesis.py b/Gradata/tests/test_llm_synthesis.py similarity index 100% rename from tests/test_llm_synthesis.py rename to Gradata/tests/test_llm_synthesis.py diff --git a/tests/test_llm_synthesizer.py b/Gradata/tests/test_llm_synthesizer.py similarity index 100% rename from tests/test_llm_synthesizer.py rename to Gradata/tests/test_llm_synthesizer.py diff --git a/tests/test_loop_and_security.py b/Gradata/tests/test_loop_and_security.py similarity index 100% rename from tests/test_loop_and_security.py rename to Gradata/tests/test_loop_and_security.py diff --git a/tests/test_loop_intelligence.py b/Gradata/tests/test_loop_intelligence.py similarity index 100% rename from tests/test_loop_intelligence.py rename to Gradata/tests/test_loop_intelligence.py diff --git a/tests/test_machine_mode.py b/Gradata/tests/test_machine_mode.py similarity index 100% rename from tests/test_machine_mode.py rename to Gradata/tests/test_machine_mode.py diff --git a/tests/test_manifest_prove.py b/Gradata/tests/test_manifest_prove.py similarity index 100% rename from tests/test_manifest_prove.py rename to Gradata/tests/test_manifest_prove.py diff --git a/tests/test_manifest_signing.py b/Gradata/tests/test_manifest_signing.py similarity index 100% rename from tests/test_manifest_signing.py rename to Gradata/tests/test_manifest_signing.py diff --git a/tests/test_mcp_server.py b/Gradata/tests/test_mcp_server.py similarity index 100% rename from tests/test_mcp_server.py rename to Gradata/tests/test_mcp_server.py diff --git a/tests/test_mem0_adapter.py b/Gradata/tests/test_mem0_adapter.py similarity index 100% rename from tests/test_mem0_adapter.py rename to Gradata/tests/test_mem0_adapter.py diff --git a/tests/test_memory_extraction_coverage.py b/Gradata/tests/test_memory_extraction_coverage.py similarity index 100% rename from tests/test_memory_extraction_coverage.py rename to Gradata/tests/test_memory_extraction_coverage.py diff --git a/tests/test_meta_rule_generalization.py b/Gradata/tests/test_meta_rule_generalization.py similarity index 100% rename from tests/test_meta_rule_generalization.py rename to Gradata/tests/test_meta_rule_generalization.py diff --git a/tests/test_meta_rules.py b/Gradata/tests/test_meta_rules.py similarity index 100% rename from tests/test_meta_rules.py rename to Gradata/tests/test_meta_rules.py diff --git a/tests/test_middleware_anthropic.py b/Gradata/tests/test_middleware_anthropic.py similarity index 100% rename from tests/test_middleware_anthropic.py rename to Gradata/tests/test_middleware_anthropic.py diff --git a/tests/test_middleware_core.py b/Gradata/tests/test_middleware_core.py similarity index 100% rename from tests/test_middleware_core.py rename to Gradata/tests/test_middleware_core.py diff --git a/tests/test_middleware_crewai.py b/Gradata/tests/test_middleware_crewai.py similarity index 100% rename from tests/test_middleware_crewai.py rename to Gradata/tests/test_middleware_crewai.py diff --git a/tests/test_middleware_langchain.py b/Gradata/tests/test_middleware_langchain.py similarity index 100% rename from tests/test_middleware_langchain.py rename to Gradata/tests/test_middleware_langchain.py diff --git a/tests/test_middleware_openai.py b/Gradata/tests/test_middleware_openai.py similarity index 100% rename from tests/test_middleware_openai.py rename to Gradata/tests/test_middleware_openai.py diff --git a/tests/test_migrate_legacy_scopes.py b/Gradata/tests/test_migrate_legacy_scopes.py similarity index 100% rename from tests/test_migrate_legacy_scopes.py rename to Gradata/tests/test_migrate_legacy_scopes.py diff --git a/tests/test_mine_transcripts.py b/Gradata/tests/test_mine_transcripts.py similarity index 100% rename from tests/test_mine_transcripts.py rename to Gradata/tests/test_mine_transcripts.py diff --git a/tests/test_mirofish_sim.py b/Gradata/tests/test_mirofish_sim.py similarity index 100% rename from tests/test_mirofish_sim.py rename to Gradata/tests/test_mirofish_sim.py diff --git a/tests/test_multi_brain_simulation.py b/Gradata/tests/test_multi_brain_simulation.py similarity index 100% rename from tests/test_multi_brain_simulation.py rename to Gradata/tests/test_multi_brain_simulation.py diff --git a/tests/test_nervous_system_integration.py b/Gradata/tests/test_nervous_system_integration.py similarity index 100% rename from tests/test_nervous_system_integration.py rename to Gradata/tests/test_nervous_system_integration.py diff --git a/tests/test_notifications.py b/Gradata/tests/test_notifications.py similarity index 100% rename from tests/test_notifications.py rename to Gradata/tests/test_notifications.py diff --git a/tests/test_pattern_extractor.py b/Gradata/tests/test_pattern_extractor.py similarity index 100% rename from tests/test_pattern_extractor.py rename to Gradata/tests/test_pattern_extractor.py diff --git a/tests/test_pattern_graduation_integration.py b/Gradata/tests/test_pattern_graduation_integration.py similarity index 100% rename from tests/test_pattern_graduation_integration.py rename to Gradata/tests/test_pattern_graduation_integration.py diff --git a/tests/test_patterns.py b/Gradata/tests/test_patterns.py similarity index 100% rename from tests/test_patterns.py rename to Gradata/tests/test_patterns.py diff --git a/tests/test_pii_redaction.py b/Gradata/tests/test_pii_redaction.py similarity index 100% rename from tests/test_pii_redaction.py rename to Gradata/tests/test_pii_redaction.py diff --git a/tests/test_pipeline_e2e.py b/Gradata/tests/test_pipeline_e2e.py similarity index 100% rename from tests/test_pipeline_e2e.py rename to Gradata/tests/test_pipeline_e2e.py diff --git a/tests/test_pipeline_rewriter.py b/Gradata/tests/test_pipeline_rewriter.py similarity index 100% rename from tests/test_pipeline_rewriter.py rename to Gradata/tests/test_pipeline_rewriter.py diff --git a/tests/test_platform_source.py b/Gradata/tests/test_platform_source.py similarity index 100% rename from tests/test_platform_source.py rename to Gradata/tests/test_platform_source.py diff --git a/tests/test_plugin_integration.py b/Gradata/tests/test_plugin_integration.py similarity index 100% rename from tests/test_plugin_integration.py rename to Gradata/tests/test_plugin_integration.py diff --git a/tests/test_prompt_synthesizer.py b/Gradata/tests/test_prompt_synthesizer.py similarity index 100% rename from tests/test_prompt_synthesizer.py rename to Gradata/tests/test_prompt_synthesizer.py diff --git a/tests/test_prove.py b/Gradata/tests/test_prove.py similarity index 100% rename from tests/test_prove.py rename to Gradata/tests/test_prove.py diff --git a/tests/test_prove_stat_validation.py b/Gradata/tests/test_prove_stat_validation.py similarity index 100% rename from tests/test_prove_stat_validation.py rename to Gradata/tests/test_prove_stat_validation.py diff --git a/tests/test_query_budget.py b/Gradata/tests/test_query_budget.py similarity index 100% rename from tests/test_query_budget.py rename to Gradata/tests/test_query_budget.py diff --git a/tests/test_ranking_v2.py b/Gradata/tests/test_ranking_v2.py similarity index 100% rename from tests/test_ranking_v2.py rename to Gradata/tests/test_ranking_v2.py diff --git a/tests/test_reports_coverage.py b/Gradata/tests/test_reports_coverage.py similarity index 100% rename from tests/test_reports_coverage.py rename to Gradata/tests/test_reports_coverage.py diff --git a/tests/test_retain_orchestrator.py b/Gradata/tests/test_retain_orchestrator.py similarity index 100% rename from tests/test_retain_orchestrator.py rename to Gradata/tests/test_retain_orchestrator.py diff --git a/tests/test_retrieval_fusion.py b/Gradata/tests/test_retrieval_fusion.py similarity index 100% rename from tests/test_retrieval_fusion.py rename to Gradata/tests/test_retrieval_fusion.py diff --git a/tests/test_rule_cache.py b/Gradata/tests/test_rule_cache.py similarity index 100% rename from tests/test_rule_cache.py rename to Gradata/tests/test_rule_cache.py diff --git a/tests/test_rule_enforcement_scope.py b/Gradata/tests/test_rule_enforcement_scope.py similarity index 100% rename from tests/test_rule_enforcement_scope.py rename to Gradata/tests/test_rule_enforcement_scope.py diff --git a/tests/test_rule_engine_v2.py b/Gradata/tests/test_rule_engine_v2.py similarity index 100% rename from tests/test_rule_engine_v2.py rename to Gradata/tests/test_rule_engine_v2.py diff --git a/tests/test_rule_export.py b/Gradata/tests/test_rule_export.py similarity index 100% rename from tests/test_rule_export.py rename to Gradata/tests/test_rule_export.py diff --git a/tests/test_rule_graph.py b/Gradata/tests/test_rule_graph.py similarity index 100% rename from tests/test_rule_graph.py rename to Gradata/tests/test_rule_graph.py diff --git a/tests/test_rule_graph_integration.py b/Gradata/tests/test_rule_graph_integration.py similarity index 100% rename from tests/test_rule_graph_integration.py rename to Gradata/tests/test_rule_graph_integration.py diff --git a/tests/test_rule_graph_relationships.py b/Gradata/tests/test_rule_graph_relationships.py similarity index 100% rename from tests/test_rule_graph_relationships.py rename to Gradata/tests/test_rule_graph_relationships.py diff --git a/tests/test_rule_integrity.py b/Gradata/tests/test_rule_integrity.py similarity index 100% rename from tests/test_rule_integrity.py rename to Gradata/tests/test_rule_integrity.py diff --git a/tests/test_rule_metadata.py b/Gradata/tests/test_rule_metadata.py similarity index 100% rename from tests/test_rule_metadata.py rename to Gradata/tests/test_rule_metadata.py diff --git a/tests/test_rule_pipeline.py b/Gradata/tests/test_rule_pipeline.py similarity index 100% rename from tests/test_rule_pipeline.py rename to Gradata/tests/test_rule_pipeline.py diff --git a/tests/test_rule_ranker.py b/Gradata/tests/test_rule_ranker.py similarity index 100% rename from tests/test_rule_ranker.py rename to Gradata/tests/test_rule_ranker.py diff --git a/tests/test_rule_scoping.py b/Gradata/tests/test_rule_scoping.py similarity index 100% rename from tests/test_rule_scoping.py rename to Gradata/tests/test_rule_scoping.py diff --git a/tests/test_rule_to_hook.py b/Gradata/tests/test_rule_to_hook.py similarity index 100% rename from tests/test_rule_to_hook.py rename to Gradata/tests/test_rule_to_hook.py diff --git a/tests/test_rule_to_hook_promotion.py b/Gradata/tests/test_rule_to_hook_promotion.py similarity index 100% rename from tests/test_rule_to_hook_promotion.py rename to Gradata/tests/test_rule_to_hook_promotion.py diff --git a/tests/test_rule_tracker_suppression.py b/Gradata/tests/test_rule_tracker_suppression.py similarity index 100% rename from tests/test_rule_tracker_suppression.py rename to Gradata/tests/test_rule_tracker_suppression.py diff --git a/tests/test_rule_tree.py b/Gradata/tests/test_rule_tree.py similarity index 100% rename from tests/test_rule_tree.py rename to Gradata/tests/test_rule_tree.py diff --git a/tests/test_rule_verifier.py b/Gradata/tests/test_rule_verifier.py similarity index 100% rename from tests/test_rule_verifier.py rename to Gradata/tests/test_rule_verifier.py diff --git a/tests/test_rule_verifier_integration.py b/Gradata/tests/test_rule_verifier_integration.py similarity index 100% rename from tests/test_rule_verifier_integration.py rename to Gradata/tests/test_rule_verifier_integration.py diff --git a/tests/test_rules_distillation.py b/Gradata/tests/test_rules_distillation.py similarity index 100% rename from tests/test_rules_distillation.py rename to Gradata/tests/test_rules_distillation.py diff --git a/tests/test_safety_assertion.py b/Gradata/tests/test_safety_assertion.py similarity index 100% rename from tests/test_safety_assertion.py rename to Gradata/tests/test_safety_assertion.py diff --git a/tests/test_sanitize_lesson_content.py b/Gradata/tests/test_sanitize_lesson_content.py similarity index 100% rename from tests/test_sanitize_lesson_content.py rename to Gradata/tests/test_sanitize_lesson_content.py diff --git a/tests/test_scope.py b/Gradata/tests/test_scope.py similarity index 100% rename from tests/test_scope.py rename to Gradata/tests/test_scope.py diff --git a/tests/test_scope_tagging.py b/Gradata/tests/test_scope_tagging.py similarity index 100% rename from tests/test_scope_tagging.py rename to Gradata/tests/test_scope_tagging.py diff --git a/tests/test_scoped_brain.py b/Gradata/tests/test_scoped_brain.py similarity index 100% rename from tests/test_scoped_brain.py rename to Gradata/tests/test_scoped_brain.py diff --git a/tests/test_scoped_brains.py b/Gradata/tests/test_scoped_brains.py similarity index 100% rename from tests/test_scoped_brains.py rename to Gradata/tests/test_scoped_brains.py diff --git a/tests/test_score_obfuscation.py b/Gradata/tests/test_score_obfuscation.py similarity index 100% rename from tests/test_score_obfuscation.py rename to Gradata/tests/test_score_obfuscation.py diff --git a/tests/test_security_regressions.py b/Gradata/tests/test_security_regressions.py similarity index 100% rename from tests/test_security_regressions.py rename to Gradata/tests/test_security_regressions.py diff --git a/tests/test_self_healing.py b/Gradata/tests/test_self_healing.py similarity index 100% rename from tests/test_self_healing.py rename to Gradata/tests/test_self_healing.py diff --git a/tests/test_self_healing_fix.py b/Gradata/tests/test_self_healing_fix.py similarity index 100% rename from tests/test_self_healing_fix.py rename to Gradata/tests/test_self_healing_fix.py diff --git a/tests/test_self_review.py b/Gradata/tests/test_self_review.py similarity index 100% rename from tests/test_self_review.py rename to Gradata/tests/test_self_review.py diff --git a/tests/test_session_history.py b/Gradata/tests/test_session_history.py similarity index 100% rename from tests/test_session_history.py rename to Gradata/tests/test_session_history.py diff --git a/tests/test_severity_weighting.py b/Gradata/tests/test_severity_weighting.py similarity index 100% rename from tests/test_severity_weighting.py rename to Gradata/tests/test_severity_weighting.py diff --git a/tests/test_sharing.py b/Gradata/tests/test_sharing.py similarity index 100% rename from tests/test_sharing.py rename to Gradata/tests/test_sharing.py diff --git a/tests/test_skill_generator.py b/Gradata/tests/test_skill_generator.py similarity index 100% rename from tests/test_skill_generator.py rename to Gradata/tests/test_skill_generator.py diff --git a/tests/test_spawn_extraction.py b/Gradata/tests/test_spawn_extraction.py similarity index 100% rename from tests/test_spawn_extraction.py rename to Gradata/tests/test_spawn_extraction.py diff --git a/tests/test_spec_compliance.py b/Gradata/tests/test_spec_compliance.py similarity index 100% rename from tests/test_spec_compliance.py rename to Gradata/tests/test_spec_compliance.py diff --git a/tests/test_steals.py b/Gradata/tests/test_steals.py similarity index 100% rename from tests/test_steals.py rename to Gradata/tests/test_steals.py diff --git a/tests/test_structured_correction.py b/Gradata/tests/test_structured_correction.py similarity index 100% rename from tests/test_structured_correction.py rename to Gradata/tests/test_structured_correction.py diff --git a/tests/test_success_conditions_coverage.py b/Gradata/tests/test_success_conditions_coverage.py similarity index 100% rename from tests/test_success_conditions_coverage.py rename to Gradata/tests/test_success_conditions_coverage.py diff --git a/tests/test_tag_taxonomy.py b/Gradata/tests/test_tag_taxonomy.py similarity index 100% rename from tests/test_tag_taxonomy.py rename to Gradata/tests/test_tag_taxonomy.py diff --git a/tests/test_telemetry.py b/Gradata/tests/test_telemetry.py similarity index 100% rename from tests/test_telemetry.py rename to Gradata/tests/test_telemetry.py diff --git a/tests/test_telemetry_summary.py b/Gradata/tests/test_telemetry_summary.py similarity index 100% rename from tests/test_telemetry_summary.py rename to Gradata/tests/test_telemetry_summary.py diff --git a/tests/test_tenant_id_inserts.py b/Gradata/tests/test_tenant_id_inserts.py similarity index 100% rename from tests/test_tenant_id_inserts.py rename to Gradata/tests/test_tenant_id_inserts.py diff --git a/tests/test_types.py b/Gradata/tests/test_types.py similarity index 100% rename from tests/test_types.py rename to Gradata/tests/test_types.py diff --git a/tests/test_wiring_compound.py b/Gradata/tests/test_wiring_compound.py similarity index 100% rename from tests/test_wiring_compound.py rename to Gradata/tests/test_wiring_compound.py diff --git a/tests/test_workers.py b/Gradata/tests/test_workers.py similarity index 100% rename from tests/test_workers.py rename to Gradata/tests/test_workers.py diff --git a/uv.lock b/Gradata/uv.lock similarity index 100% rename from uv.lock rename to Gradata/uv.lock diff --git a/brain/scripts/README-ablation-beta-lb.md b/Sprites/brain/scripts/README-ablation-beta-lb.md similarity index 100% rename from brain/scripts/README-ablation-beta-lb.md rename to Sprites/brain/scripts/README-ablation-beta-lb.md diff --git a/brain/scripts/_common.py b/Sprites/brain/scripts/_common.py similarity index 100% rename from brain/scripts/_common.py rename to Sprites/brain/scripts/_common.py diff --git a/brain/scripts/ab_test_constitutional.py b/Sprites/brain/scripts/ab_test_constitutional.py similarity index 100% rename from brain/scripts/ab_test_constitutional.py rename to Sprites/brain/scripts/ab_test_constitutional.py diff --git a/brain/scripts/ablation_beta_lb_gate.py b/Sprites/brain/scripts/ablation_beta_lb_gate.py similarity index 100% rename from brain/scripts/ablation_beta_lb_gate.py rename to Sprites/brain/scripts/ablation_beta_lb_gate.py diff --git a/Sprites/brain/scripts/audit_duplicates.py b/Sprites/brain/scripts/audit_duplicates.py new file mode 100644 index 00000000..e5acfc58 --- /dev/null +++ b/Sprites/brain/scripts/audit_duplicates.py @@ -0,0 +1,282 @@ +#!/usr/bin/env python3 +""" +audit_duplicates.py — Find duplicate/similar files in the codebase. + +Scans source directories for files with similar names and/or content, +groups them into clusters, and suggests merge actions. + +Usage: + python brain/scripts/audit_duplicates.py [--fix] + +Without --fix: report only (safe). +With --fix: interactive mode — prompts before each merge. +""" + +import os +import sys +import hashlib +from pathlib import Path +from difflib import SequenceMatcher +from collections import defaultdict + +PROJECT_ROOT = Path(__file__).resolve().parent.parent.parent + +# Directories to scan +SCAN_DIRS = [ + "src/gradata", + "sdk/src", + "sdk", + ".claude/hooks", + "brain/scripts", + "tests", +] + +# Skip these +IGNORE_DIRS = { + "node_modules", "__pycache__", ".git", ".tmp", "dist", "build", + ".egg-info", "venv", ".venv", "site-packages", +} + +IGNORE_EXTENSIONS = {".pyc", ".pyo", ".so", ".dll", ".exe", ".bin"} + + +def normalize_name(filename: str) -> str: + """Normalize filename for comparison.""" + name = Path(filename).stem + # Strip " (1)" copy suffixes + import re + name = re.sub(r'\s*\(\d+\)\s*', '', name) + # Normalize separators + name = name.replace('-', '_').replace(' ', '_').lower() + # Strip trailing numbers (version suffixes) + name = re.sub(r'_*\d+$', '', name) + return name + + +def name_similarity(a: str, b: str) -> float: + """Similarity between two filenames (0.0 to 1.0).""" + na, nb = normalize_name(a), normalize_name(b) + if na == nb: + return 1.0 + return SequenceMatcher(None, na, nb).ratio() + + +def content_similarity(path_a: Path, path_b: Path) -> float: + """Similarity between file contents (0.0 to 1.0).""" + try: + a = path_a.read_text(encoding="utf-8", errors="replace") + b = path_b.read_text(encoding="utf-8", errors="replace") + except Exception: + return 0.0 + # Fast check: identical + if a == b: + return 1.0 + # For large files, compare first 5K chars + if len(a) > 5000 or len(b) > 5000: + a, b = a[:5000], b[:5000] + return SequenceMatcher(None, a, b).ratio() + + +def file_hash(path: Path) -> str: + """SHA256 of file content.""" + try: + return hashlib.sha256(path.read_bytes()).hexdigest()[:12] + except Exception: + return "error" + + +def scan_files() -> list[Path]: + """Collect all source files.""" + files = [] + for scan_dir in SCAN_DIRS: + root = PROJECT_ROOT / scan_dir + if not root.exists(): + continue + for dirpath, dirnames, filenames in os.walk(root): + # Filter ignored dirs in-place + dirnames[:] = [d for d in dirnames if d not in IGNORE_DIRS] + for fname in filenames: + fpath = Path(dirpath) / fname + if fpath.suffix in IGNORE_EXTENSIONS: + continue + files.append(fpath) + return files + + +def find_duplicate_clusters(files: list[Path], name_threshold: float = 0.7) -> list[dict]: + """Find clusters of similar files.""" + clusters = [] + used = set() + + for i, fa in enumerate(files): + if i in used: + continue + group = [fa] + for j, fb in enumerate(files): + if j <= i or j in used: + continue + nsim = name_similarity(fa.name, fb.name) + if nsim >= name_threshold: + group.append(fb) + used.add(j) + + if len(group) > 1: + used.add(i) + # Compute content similarity for the group + pairs = [] + for a_idx in range(len(group)): + for b_idx in range(a_idx + 1, len(group)): + csim = content_similarity(group[a_idx], group[b_idx]) + nsim = name_similarity(group[a_idx].name, group[b_idx].name) + pairs.append({ + "a": group[a_idx], + "b": group[b_idx], + "name_sim": nsim, + "content_sim": csim, + }) + clusters.append({ + "files": group, + "pairs": pairs, + "max_content_sim": max(p["content_sim"] for p in pairs), + }) + + # Also find exact content duplicates (different names) + hash_map = defaultdict(list) + for f in files: + h = file_hash(f) + if h != "error": + hash_map[h].append(f) + + for h, dupes in hash_map.items(): + if len(dupes) > 1: + # Check if already captured by name similarity + dupe_set = set(str(d) for d in dupes) + already = False + for c in clusters: + c_set = set(str(f) for f in c["files"]) + if dupe_set & c_set: + # Merge into existing cluster + for d in dupes: + if str(d) not in c_set: + c["files"].append(d) + already = True + break + if not already: + clusters.append({ + "files": dupes, + "pairs": [{"a": dupes[0], "b": dupes[1], "name_sim": 0, "content_sim": 1.0}], + "max_content_sim": 1.0, + }) + + return sorted(clusters, key=lambda c: c["max_content_sim"], reverse=True) + + +def rel(p: Path) -> str: + """Relative path from project root.""" + try: + return str(p.relative_to(PROJECT_ROOT)).replace("\\", "/") + except ValueError: + return str(p) + + +def print_report(clusters: list[dict]): + """Print duplicate report.""" + if not clusters: + print("\n No duplicates found.") + return + + total_files = sum(len(c["files"]) for c in clusters) + print(f"\n Found {len(clusters)} duplicate clusters ({total_files} files total)\n") + + for i, cluster in enumerate(clusters, 1): + max_sim = cluster["max_content_sim"] + severity = "EXACT COPY" if max_sim > 0.95 else "HIGH" if max_sim > 0.7 else "MEDIUM" if max_sim > 0.5 else "LOW" + + print(f" {'='*60}") + print(f" Cluster {i}: {severity} ({max_sim:.0%} content similarity)") + print(f" {'='*60}") + + for f in cluster["files"]: + size = f.stat().st_size if f.exists() else 0 + h = file_hash(f) + print(f" {rel(f)}") + print(f" {size:,} bytes | hash: {h}") + + for pair in cluster["pairs"]: + print(f" ---") + print(f" {rel(pair['a'])}") + print(f" vs {rel(pair['b'])}") + print(f" Name: {pair['name_sim']:.0%} | Content: {pair['content_sim']:.0%}") + + # Suggest action + if max_sim > 0.95: + keeper = max(cluster["files"], key=lambda f: f.stat().st_mtime if f.exists() else 0) + others = [f for f in cluster["files"] if f != keeper] + print(f"\n ACTION: Keep {rel(keeper)}, delete {', '.join(rel(o) for o in others)}") + elif max_sim > 0.5: + print(f"\n ACTION: Review and merge manually. Diff the files to see what's different.") + print() + + +def interactive_fix(clusters: list[dict]): + """Interactive merge mode.""" + for i, cluster in enumerate(clusters, 1): + max_sim = cluster["max_content_sim"] + if max_sim < 0.5: + continue + + print(f"\n Cluster {i}: {len(cluster['files'])} files, {max_sim:.0%} similar") + for j, f in enumerate(cluster["files"]): + mtime = f.stat().st_mtime if f.exists() else 0 + from datetime import datetime + ts = datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M") + print(f" [{j}] {rel(f)} (modified: {ts})") + + if max_sim > 0.95: + print("\n These are near-identical. Which one to KEEP? (number, or 's' to skip)") + choice = input(" > ").strip() + if choice == 's': + continue + try: + keep_idx = int(choice) + keeper = cluster["files"][keep_idx] + for j, f in enumerate(cluster["files"]): + if j != keep_idx and f.exists(): + print(f" Deleting {rel(f)}") + f.unlink() + print(f" Kept: {rel(keeper)}") + except (ValueError, IndexError): + print(" Skipped.") + else: + print("\n These overlap but differ. Run `diff` to compare? (y/n/s)") + choice = input(" > ").strip() + if choice == 'y': + a, b = cluster["files"][0], cluster["files"][1] + os.system(f'diff --color "{a}" "{b}" | head -60') + + +def main(): + fix_mode = "--fix" in sys.argv + + print("\n Duplicate File Audit") + print(f" Project: {PROJECT_ROOT}") + print(f" Scanning: {', '.join(SCAN_DIRS)}") + + files = scan_files() + print(f" Files found: {len(files)}") + + clusters = find_duplicate_clusters(files) + + if fix_mode: + print_report(clusters) + if clusters: + print("\n Entering interactive fix mode...\n") + interactive_fix(clusters) + else: + print_report(clusters) + if clusters: + print(" Run with --fix for interactive merge mode.\n") + + +if __name__ == "__main__": + main() diff --git a/Sprites/brain/scripts/autoresearch_build_scope.py b/Sprites/brain/scripts/autoresearch_build_scope.py new file mode 100644 index 00000000..918ac471 --- /dev/null +++ b/Sprites/brain/scripts/autoresearch_build_scope.py @@ -0,0 +1,101 @@ +"""One-time setup for autoresearch consolidation loop. + +Runs pytest under coverage, then writes a whitelist of files with >= THRESHOLD +line coverage. The autoresearch agent is only permitted to modify files in the +whitelist — low-coverage files stay read-only to prevent silent regressions. + +Output: + .tmp/autoresearch/consolidation/scope_whitelist.txt (one path per line) + .tmp/autoresearch/consolidation/scope_report.json (full report) + +Usage: + python brain/scripts/autoresearch_build_scope.py [--threshold 80] +""" +from __future__ import annotations + +import argparse +import json +import os +import subprocess +import sys +from pathlib import Path + +os.environ["PYTHONUTF8"] = "1" + +OUT_DIR = Path(".tmp/autoresearch/consolidation") +WHITELIST = OUT_DIR / "scope_whitelist.txt" +REPORT = OUT_DIR / "scope_report.json" + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--threshold", type=float, default=80.0, + help="Min line coverage %% to include in whitelist (default 80)") + ap.add_argument("--source", default="src/gradata", + help="Source directory to measure (default src/gradata)") + args = ap.parse_args() + + OUT_DIR.mkdir(parents=True, exist_ok=True) + + print(f"[scope] running coverage over {args.source}...") + env = {**os.environ, "PYTHONUTF8": "1"} + + r1 = subprocess.run( + ["coverage", "run", f"--source={args.source}", "-m", "pytest", "tests/", + "--ignore=tests/test_rule_to_hook.py", "-q", "--tb=no", "--no-header"], + capture_output=True, text=True, env=env, + encoding="utf-8", errors="replace", + ) + r2 = subprocess.run( + ["coverage", "run", f"--source={args.source}", "-a", + "-m", "pytest", "tests/test_rule_to_hook.py", "-q", "--tb=no", "--no-header"], + capture_output=True, text=True, env=env, + encoding="utf-8", errors="replace", + ) + for i, r in enumerate((r1, r2), 1): + if r.returncode not in (0, 1, 5): # 0=ok, 1=tests_failed (we accept), 5=no_tests + print(f"[scope] pytest leg {i} errored (rc={r.returncode}).", file=sys.stderr) + print(r.stdout[-1500:], file=sys.stderr) + return 1 + + j = subprocess.run( + ["coverage", "json", "-o", "-", "--quiet"], + capture_output=True, text=True, + ) + if j.returncode != 0 or not j.stdout.strip(): + print("[scope] coverage json failed.", file=sys.stderr) + return 2 + + data = json.loads(j.stdout) + files = data.get("files", {}) + whitelist: list[str] = [] + rows = [] + for path, info in files.items(): + pct = info.get("summary", {}).get("percent_covered", 0.0) + rows.append((path, round(pct, 1))) + if pct >= args.threshold: + whitelist.append(path) + + rows.sort(key=lambda r: r[1]) + + WHITELIST.write_text("\n".join(sorted(whitelist)) + "\n", encoding="utf-8") + REPORT.write_text(json.dumps({ + "threshold": args.threshold, + "total_files": len(files), + "whitelisted": len(whitelist), + "excluded": len(files) - len(whitelist), + "rows": rows, + }, indent=2), encoding="utf-8") + + print(f"[scope] threshold={args.threshold}% " + f"whitelist={len(whitelist)}/{len(files)} files " + f"wrote {WHITELIST}") + print(f"[scope] lowest-coverage excluded (first 10):") + for p, pct in rows[:10]: + if pct < args.threshold: + print(f" {pct:>5.1f}% {p}") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/Sprites/brain/scripts/autoresearch_consolidation_verify.sh b/Sprites/brain/scripts/autoresearch_consolidation_verify.sh new file mode 100644 index 00000000..7ea896f5 --- /dev/null +++ b/Sprites/brain/scripts/autoresearch_consolidation_verify.sh @@ -0,0 +1,154 @@ +#!/usr/bin/env bash +# Autoresearch verify — composite metric + strengthened cheat-proof gate. +# +# Emits 4 components on stdout (all lower-is-better): +# DURATION - total pytest wall-clock (s) +# LOC - total source LoC (lower = less duplication) +# IMPORTS - intra-package import edges in src/gradata/ (lower = less coupling) +# FILES - source file count +# +# Gate (commit is KEEP-eligible only if ALL pass): +# 1. pytest: pass/skip/fail counts match baseline +# 2. ruff error count does not EXCEED baseline (override with BASELINE_RUFF) +# 3. smoke test exits 0 +# +# Windows quirks handled: +# - PYTHONUTF8=1 forces UTF-8 decoding for subprocess readers +# - test_rule_to_hook.py run in isolation (order-pollution workaround) + +set -u +export PYTHONUTF8=1 + +EXPECTED_PASSED="${EXPECTED_PASSED:-3672}" +EXPECTED_SKIPPED="${EXPECTED_SKIPPED:-21}" +EXPECTED_FAILED="${EXPECTED_FAILED:-0}" +BASELINE_RUFF="${BASELINE_RUFF:-64}" + +parse_count() { + echo "$1" | grep -oE "[0-9]+ $2" | tail -1 | grep -oE '[0-9]+' || echo 0 +} + +# ---- Gate 1: pytest (split for order-pollution workaround) ---- +START=$(python -c "import time; print(time.time())") +MAIN_OUT=$(pytest tests/ -q --no-header --tb=no \ + --ignore=tests/test_rule_to_hook.py 2>&1) || true +ISOL_OUT=$(pytest tests/test_rule_to_hook.py -q --no-header --tb=no 2>&1) || true +END=$(python -c "import time; print(time.time())") +DUR=$(python -c "print(f'{$END - $START:.3f}')") + +PASSED=$(( $(parse_count "$MAIN_OUT" passed) + $(parse_count "$ISOL_OUT" passed) )) +SKIPPED=$(( $(parse_count "$MAIN_OUT" skipped) + $(parse_count "$ISOL_OUT" skipped) )) +FAILED=$(( $(parse_count "$MAIN_OUT" failed) + $(parse_count "$ISOL_OUT" failed) )) +ERRORS=$(( $(parse_count "$MAIN_OUT" error) + $(parse_count "$ISOL_OUT" error) )) + +# ---- Gate 2: ruff (count must not exceed baseline) ---- +RUFF_OUT=$(ruff check src/gradata/ --output-format=concise --exit-zero 2>&1) +RUFF_COUNT=$(echo "$RUFF_OUT" | grep -cE '^[^[:space:]].*:[0-9]+:[0-9]+:' || echo 0) + +# ---- Gate 3: smoke test ---- +SMOKE_OUT=$(python brain/scripts/autoresearch_smoke_test.py 2>&1) || SMOKE_RC=$? +SMOKE_RC="${SMOKE_RC:-0}" + +# ---- Metric: LoC ---- +LOC=$(python - <<'PY' +import os, pathlib +total = 0 +exts = {'.py', '.ts', '.tsx', '.js', '.jsx'} +for root, _, files in os.walk('src'): + if any(s in root for s in ('__pycache__', 'node_modules', '.venv', 'dist', 'build')): + continue + for f in files: + if pathlib.Path(f).suffix in exts: + try: + total += sum(1 for _ in open(os.path.join(root, f), encoding='utf-8', errors='ignore')) + except OSError: + pass +print(total) +PY +) + +# ---- Metric: intra-package import edges (live) ---- +IMPORTS=$(python - <<'PY' +import ast, os, pathlib +pkg_root = pathlib.Path('src/gradata') +count = 0 +for p in pkg_root.rglob('*.py'): + if '__pycache__' in p.parts: + continue + try: + tree = ast.parse(p.read_text(encoding='utf-8', errors='ignore')) + except SyntaxError: + continue + for node in ast.walk(tree): + if isinstance(node, ast.ImportFrom): + if node.module and node.module.startswith('gradata'): + count += len(node.names) or 1 + elif isinstance(node, ast.Import): + for alias in node.names: + if alias.name.startswith('gradata'): + count += 1 +print(count) +PY +) + +# ---- Metric: source file count ---- +FILES=$(python - <<'PY' +import os, pathlib +count = 0 +exts = {'.py', '.ts', '.tsx', '.js', '.jsx'} +for root, _, files in os.walk('src'): + if any(s in root for s in ('__pycache__', 'node_modules', '.venv', 'dist', 'build')): + continue + for f in files: + if pathlib.Path(f).suffix in exts: + count += 1 +print(count) +PY +) + +echo "DURATION=$DUR" +echo "LOC=$LOC" +echo "IMPORTS=$IMPORTS" +echo "FILES=$FILES" +echo "PASSED=$PASSED" +echo "SKIPPED=$SKIPPED" +echo "FAILED=$FAILED" +echo "ERRORS=$ERRORS" +echo "RUFF_COUNT=$RUFF_COUNT" +echo "RUFF_BASELINE=$BASELINE_RUFF" +echo "SMOKE_RC=$SMOKE_RC" + +# ---- Gate ---- +GATE_FAIL=0 +FAIL_REASONS="" + +if [ "$PASSED" != "$EXPECTED_PASSED" ] \ + || [ "$SKIPPED" != "$EXPECTED_SKIPPED" ] \ + || [ "$FAILED" != "$EXPECTED_FAILED" ] \ + || [ "$ERRORS" != "0" ]; then + GATE_FAIL=1 + FAIL_REASONS="$FAIL_REASONS pytest(pass=$PASSED/skip=$SKIPPED/fail=$FAILED/err=$ERRORS)" +fi + +if [ "$RUFF_COUNT" -gt "$BASELINE_RUFF" ]; then + GATE_FAIL=1 + FAIL_REASONS="$FAIL_REASONS ruff($RUFF_COUNT>$BASELINE_RUFF)" +fi + +if [ "$SMOKE_RC" != "0" ]; then + GATE_FAIL=1 + FAIL_REASONS="$FAIL_REASONS smoke(rc=$SMOKE_RC)" +fi + +if [ "$GATE_FAIL" = "1" ]; then + echo "STATUS=crash" + echo "FAIL_REASONS=$FAIL_REASONS" + echo "--- pytest main tail ---"; echo "$MAIN_OUT" | tail -20 + echo "--- pytest isolated tail ---"; echo "$ISOL_OUT" | tail -10 + echo "--- ruff tail ---"; echo "$RUFF_OUT" | tail -10 + echo "--- smoke tail ---"; echo "$SMOKE_OUT" | tail -10 + exit 1 +fi + +echo "STATUS=ok" +exit 0 diff --git a/Sprites/brain/scripts/autoresearch_smoke_test.py b/Sprites/brain/scripts/autoresearch_smoke_test.py new file mode 100644 index 00000000..5d3d068d --- /dev/null +++ b/Sprites/brain/scripts/autoresearch_smoke_test.py @@ -0,0 +1,81 @@ +"""Smoke test for autoresearch consolidation loop. + +Exercises the SDK's critical public flows end-to-end in a throwaway tmpdir. +Runs in ~5-30 seconds. Catches regressions tests miss (broken imports, +bad module-level side effects, public API breakage). + +Exit 0 = all flows green. Non-zero = gate trip, commit discarded. +""" +from __future__ import annotations + +import sys +import tempfile +import traceback +from pathlib import Path + + +def main() -> int: + try: + from gradata import Brain # noqa: F401 — import is itself a gate + except Exception: + print("SMOKE_FAIL: import gradata.Brain", file=sys.stderr) + traceback.print_exc() + return 10 + + with tempfile.TemporaryDirectory() as td: + brain_path = Path(td) / "brain" + try: + brain = Brain.init(str(brain_path)) + except Exception: + print("SMOKE_FAIL: Brain.init", file=sys.stderr) + traceback.print_exc() + return 11 + + try: + brain.log_output( + "draft email about Q2 pricing", + output_type="email", + self_score=7, + ) + except Exception: + print("SMOKE_FAIL: log_output", file=sys.stderr) + traceback.print_exc() + return 12 + + try: + brain.correct( + draft="Hi team, the price is $100.", + final="Hi team — the investment is $100.", + ) + except Exception: + print("SMOKE_FAIL: correct", file=sys.stderr) + traceback.print_exc() + return 13 + + try: + brain.apply_brain_rules("draft message to stakeholder about pricing") + except Exception: + print("SMOKE_FAIL: apply_brain_rules", file=sys.stderr) + traceback.print_exc() + return 14 + + try: + brain.search("pricing objections") + except Exception: + print("SMOKE_FAIL: search", file=sys.stderr) + traceback.print_exc() + return 15 + + try: + brain.manifest() + except Exception: + print("SMOKE_FAIL: manifest", file=sys.stderr) + traceback.print_exc() + return 16 + + print("SMOKE_OK") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/brain/scripts/brain_benchmark.py b/Sprites/brain/scripts/brain_benchmark.py similarity index 100% rename from brain/scripts/brain_benchmark.py rename to Sprites/brain/scripts/brain_benchmark.py diff --git a/Sprites/brain/scripts/cloud_rls_test.sql b/Sprites/brain/scripts/cloud_rls_test.sql new file mode 100644 index 00000000..89e77816 --- /dev/null +++ b/Sprites/brain/scripts/cloud_rls_test.sql @@ -0,0 +1,39 @@ +-- ============================================================================= +-- RLS smoke test — run in Supabase SQL editor with service_role DISABLED +-- (i.e. as an authenticated user via the API, not the SQL console). +-- ============================================================================= +-- The console runs as postgres (bypass RLS), so it will NOT reveal RLS bugs. +-- Use the Supabase CLI `supabase test` or the REST API with two different +-- JWT tokens (userA, userB) to exercise these queries. +-- +-- Expected behavior: +-- userA inserts a private rule -> userA sees it, userB does NOT +-- userA inserts a 'shared' rule -> userB sees it +-- userB cannot UPDATE userA's rows +-- userB cannot DELETE userA's rows +-- Neither user can INSERT a visibility='global' row +-- ============================================================================= + +-- As userA: +-- INSERT INTO meta_rules (id, tenant_id, visibility, principle) +-- VALUES ('test.userA.private.1', auth.uid(), 'private', 'only I see this'); + +-- As userA: +-- INSERT INTO meta_rules (id, tenant_id, visibility, principle) +-- VALUES ('test.userA.shared.1', auth.uid(), 'shared', 'both users see this'); + +-- As userB — must return ZERO rows for userA.private.1: +-- SELECT id, visibility FROM meta_rules WHERE id = 'test.userA.private.1'; + +-- As userB — must return ONE row for userA.shared.1: +-- SELECT id, visibility FROM meta_rules WHERE id = 'test.userA.shared.1'; + +-- As userB — must FAIL (RLS violation) on userA's row: +-- UPDATE meta_rules SET confidence = 0.1 WHERE id = 'test.userA.private.1'; + +-- Neither user — must FAIL (CHECK + policy both block): +-- INSERT INTO meta_rules (id, tenant_id, visibility, principle) +-- VALUES ('test.cant.do.global', NULL, 'global', 'globals require service_role'); + +-- Clean up (as each user for their own rows): +-- DELETE FROM meta_rules WHERE id LIKE 'test.%'; diff --git a/Sprites/brain/scripts/cloud_schema.sql b/Sprites/brain/scripts/cloud_schema.sql new file mode 100644 index 00000000..0682fe9f --- /dev/null +++ b/Sprites/brain/scripts/cloud_schema.sql @@ -0,0 +1,420 @@ +-- ============================================================================= +-- Gradata Cloud Schema v1 (Supabase / Postgres) +-- ============================================================================= +-- Mirrors the critical subset of the local brain schema for multi-tenant sync. +-- Local SQLite stays the source of truth; cloud is for: +-- 1. Backup / cross-device sync per tenant +-- 2. Cross-tenant meta-rule distribution (visibility='global' / 'shared') +-- 3. Proprietary scoring & graduation as a service +-- +-- Identity model: +-- - tenant_id is a UUID matching auth.uid() from Supabase Auth. +-- - Each human/account has exactly one tenant_id. +-- - For Oliver's existing brain (tenant 402bc79c-...), his Supabase auth user +-- MUST be created with that same UUID via the admin API. See bottom of file. +-- - tenant_id IS NULL + visibility='global' => Gradata-curated shared rules. +-- +-- Local tables NOT mirrored in cloud v1 (kept local-only): +-- deals, signals, facts, pipeline_snapshots, daily_metrics (Pipedrive is source) +-- brain_embeddings, brain_fts_* (derived, regenerate) +-- tasks, agent_jobs, enrichment_queue (local execution) +-- activity_log, prep_outcomes, demo_recordings (add when needed) +-- correction_patterns, output_classifications, entities (v2) +-- +-- Apply order: +-- 1. Extensions +-- 2. Tables +-- 3. Indexes +-- 4. RLS enable + policies +-- 5. Seed Oliver (service_role) +-- ============================================================================= + + +-- ----------------------------------------------------------------------------- +-- 1. Extensions +-- ----------------------------------------------------------------------------- +CREATE EXTENSION IF NOT EXISTS "uuid-ossp"; +CREATE EXTENSION IF NOT EXISTS "pgcrypto"; +CREATE EXTENSION IF NOT EXISTS vector; -- reserved for v2 embeddings + + +-- ----------------------------------------------------------------------------- +-- 2. Tables +-- ----------------------------------------------------------------------------- + +-- Schema versioning (applied migrations within the cloud DB itself) +CREATE TABLE IF NOT EXISTS cloud_migrations ( + name TEXT PRIMARY KEY, + applied_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + notes TEXT DEFAULT '' +); + +-- Every tenant in the cloud. auth.uid() MUST equal tenant_id. +CREATE TABLE IF NOT EXISTS tenant_registry ( + tenant_id UUID PRIMARY KEY, -- same as auth.uid() + display_name TEXT, + email TEXT UNIQUE, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + is_primary BOOLEAN DEFAULT FALSE, -- first tenant (Oliver) + tier TEXT DEFAULT 'free', -- billing stub + notes TEXT DEFAULT '' +); + +-- Canonical event log. JSONB data blob preserves local schema flexibility. +CREATE TABLE IF NOT EXISTS events ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + local_id BIGINT, -- id from local SQLite (dedup key) + ts TIMESTAMPTZ NOT NULL, + session INT, + type TEXT NOT NULL, + source TEXT, + data JSONB NOT NULL DEFAULT '{}'::jsonb, + tags JSONB NOT NULL DEFAULT '[]'::jsonb, + scope TEXT DEFAULT 'local', + schema_version INT NOT NULL DEFAULT 1, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (tenant_id, local_id) -- idempotent upsert key +); + +-- Rules with global/shared/private visibility. tenant_id NULL => global. +CREATE TABLE IF NOT EXISTS meta_rules ( + id TEXT PRIMARY KEY, + tenant_id UUID, -- NULL = global + visibility TEXT NOT NULL DEFAULT 'private' + CHECK (visibility IN ('private','shared','global')), + principle TEXT NOT NULL, + source_categories TEXT, + source_lesson_ids TEXT, + confidence REAL, + created_session INT, + last_validated_session INT, + scope TEXT, + examples TEXT, + context_weights TEXT, + applies_when TEXT, + never_when TEXT, + evidence_for TEXT, + evidence_against TEXT, + ablation_status TEXT, + transfer_scope TEXT, + decay_rate TEXT, + activation_count TEXT, + last_activated TEXT, + source TEXT DEFAULT 'deterministic', + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + CHECK ( + (visibility = 'global' AND tenant_id IS NULL) OR + (visibility IN ('private','shared') AND tenant_id IS NOT NULL) + ) +); + +CREATE TABLE IF NOT EXISTS frameworks ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID, + visibility TEXT NOT NULL DEFAULT 'private' + CHECK (visibility IN ('private','shared','global')), + name TEXT NOT NULL, + times_used INT DEFAULT 0, + conversion_rate REAL, + best_persona TEXT, + worst_persona TEXT, + default_for TEXT, + confidence TEXT DEFAULT '[INSUFFICIENT]', + updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + UNIQUE (tenant_id, name) +); + +CREATE TABLE IF NOT EXISTS rule_relationships ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID, + visibility TEXT NOT NULL DEFAULT 'private' + CHECK (visibility IN ('private','shared','global')), + rule_a_id TEXT NOT NULL, + rule_b_id TEXT NOT NULL, + relationship TEXT NOT NULL, + confidence REAL DEFAULT 0.5, + detected_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS rule_provenance ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + rule_id TEXT NOT NULL, + correction_event_id TEXT, + session INT, + ts TIMESTAMPTZ NOT NULL DEFAULT NOW(), + user_context TEXT +); + +CREATE TABLE IF NOT EXISTS correction_severity ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + session INT, + event_index INT, + ts TIMESTAMPTZ, + category TEXT, + levenshtein_ratio REAL, + word_level_ratio REAL, + lines_added INT, + lines_removed INT, + severity_score REAL, + severity_label TEXT, + draft_length INT, + final_length INT, + method TEXT DEFAULT 'text_diff', + confidence REAL DEFAULT 1.0, + detail TEXT +); + +CREATE TABLE IF NOT EXISTS session_metrics ( + tenant_id UUID NOT NULL, + session INT NOT NULL, + date TEXT NOT NULL, + session_type TEXT NOT NULL DEFAULT 'full', + outputs_produced INT DEFAULT 0, + outputs_unedited INT DEFAULT 0, + corrections INT DEFAULT 0, + first_draft_acceptance REAL, + correction_density REAL, + source_coverage REAL, + confidence_calibration REAL, + gate_pass_count INT, + gate_total_count INT, + gate_pass_rate REAL, + gate_result TEXT CHECK (gate_result IN ('PASS','FAIL')), + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + growth_reply_rate REAL, + growth_deal_velocity REAL, + growth_pipeline_trend REAL, + growth_win_rate REAL, + PRIMARY KEY (tenant_id, session) +); + +CREATE TABLE IF NOT EXISTS session_gates ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + session INT NOT NULL, + check_name TEXT NOT NULL, + passed BOOLEAN NOT NULL, + detail TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS audit_scores ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + session INT NOT NULL, + date TEXT NOT NULL, + research INT, + quality INT, + process INT, + learning INT, + outcomes INT, + auditor_avg REAL, + loop_avg REAL, + combined_avg REAL, + lowest_dim TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW() +); + +CREATE TABLE IF NOT EXISTS lesson_transitions ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + lesson_desc TEXT NOT NULL, + category TEXT NOT NULL, + old_state TEXT NOT NULL, + new_state TEXT NOT NULL, + confidence REAL, + fire_count INT DEFAULT 0, + session INT, + transitioned_at TIMESTAMPTZ NOT NULL, + path TEXT DEFAULT '' +); + +CREATE TABLE IF NOT EXISTS ablation_log ( + id BIGSERIAL PRIMARY KEY, + tenant_id UUID NOT NULL, + session INT NOT NULL, + rule_category TEXT NOT NULL, + rule_description TEXT, + rule_confidence REAL, + ablated BOOLEAN DEFAULT TRUE, + error_recurred BOOLEAN, + correction_count INT DEFAULT 0, + notes TEXT, + created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), + window_sessions INT DEFAULT 1, + window_end_session INT +); + +-- Sync bookkeeping: one row per (tenant, source) tracking last push/pull. +CREATE TABLE IF NOT EXISTS sync_state ( + tenant_id UUID NOT NULL, + source TEXT NOT NULL, + last_push_ts TIMESTAMPTZ, + last_pull_ts TIMESTAMPTZ, + last_push_session INT, + items_pushed BIGINT DEFAULT 0, + items_pulled BIGINT DEFAULT 0, + PRIMARY KEY (tenant_id, source) +); + + +-- ----------------------------------------------------------------------------- +-- 3. Indexes +-- ----------------------------------------------------------------------------- +CREATE INDEX IF NOT EXISTS idx_events_tenant_ts ON events (tenant_id, ts DESC); +CREATE INDEX IF NOT EXISTS idx_events_tenant_type ON events (tenant_id, type); +CREATE INDEX IF NOT EXISTS idx_events_session ON events (tenant_id, session); +CREATE INDEX IF NOT EXISTS idx_events_data ON events USING GIN (data); +CREATE INDEX IF NOT EXISTS idx_events_tags ON events USING GIN (tags); + +CREATE INDEX IF NOT EXISTS idx_meta_rules_tenant ON meta_rules (tenant_id); +CREATE INDEX IF NOT EXISTS idx_meta_rules_vis ON meta_rules (visibility); +CREATE INDEX IF NOT EXISTS idx_meta_rules_scope ON meta_rules (scope); + +CREATE INDEX IF NOT EXISTS idx_frameworks_tenant ON frameworks (tenant_id); +CREATE INDEX IF NOT EXISTS idx_provenance_tenant ON rule_provenance (tenant_id, rule_id); +CREATE INDEX IF NOT EXISTS idx_corr_tenant ON correction_severity (tenant_id, session); +CREATE INDEX IF NOT EXISTS idx_sess_metrics_date ON session_metrics (tenant_id, date); + + +-- ----------------------------------------------------------------------------- +-- 4. Row-Level Security +-- ----------------------------------------------------------------------------- + +ALTER TABLE tenant_registry ENABLE ROW LEVEL SECURITY; +ALTER TABLE events ENABLE ROW LEVEL SECURITY; +ALTER TABLE meta_rules ENABLE ROW LEVEL SECURITY; +ALTER TABLE frameworks ENABLE ROW LEVEL SECURITY; +ALTER TABLE rule_relationships ENABLE ROW LEVEL SECURITY; +ALTER TABLE rule_provenance ENABLE ROW LEVEL SECURITY; +ALTER TABLE correction_severity ENABLE ROW LEVEL SECURITY; +ALTER TABLE session_metrics ENABLE ROW LEVEL SECURITY; +ALTER TABLE session_gates ENABLE ROW LEVEL SECURITY; +ALTER TABLE audit_scores ENABLE ROW LEVEL SECURITY; +ALTER TABLE lesson_transitions ENABLE ROW LEVEL SECURITY; +ALTER TABLE ablation_log ENABLE ROW LEVEL SECURITY; +ALTER TABLE sync_state ENABLE ROW LEVEL SECURITY; + +-- tenant_registry: user can see + update their own row. +DROP POLICY IF EXISTS tr_self_read ON tenant_registry; +DROP POLICY IF EXISTS tr_self_write ON tenant_registry; +CREATE POLICY tr_self_read ON tenant_registry FOR SELECT USING (tenant_id = auth.uid()); +CREATE POLICY tr_self_write ON tenant_registry FOR UPDATE + USING (tenant_id = auth.uid()) WITH CHECK (tenant_id = auth.uid()); + +-- Generic per-tenant policy template (applied per table below). +-- A user sees only rows with their tenant_id. No global/shared for these. +DO $$ +DECLARE + t TEXT; + tables TEXT[] := ARRAY[ + 'events', + 'rule_provenance', + 'correction_severity', + 'session_metrics', + 'session_gates', + 'audit_scores', + 'lesson_transitions', + 'ablation_log', + 'sync_state' + ]; +BEGIN + FOREACH t IN ARRAY tables LOOP + EXECUTE format('DROP POLICY IF EXISTS %I_select ON %I', t, t); + EXECUTE format('DROP POLICY IF EXISTS %I_insert ON %I', t, t); + EXECUTE format('DROP POLICY IF EXISTS %I_update ON %I', t, t); + EXECUTE format('DROP POLICY IF EXISTS %I_delete ON %I', t, t); + + EXECUTE format( + 'CREATE POLICY %I_select ON %I FOR SELECT USING (tenant_id = auth.uid())', + t, t); + EXECUTE format( + 'CREATE POLICY %I_insert ON %I FOR INSERT WITH CHECK (tenant_id = auth.uid())', + t, t); + EXECUTE format( + 'CREATE POLICY %I_update ON %I FOR UPDATE USING (tenant_id = auth.uid()) WITH CHECK (tenant_id = auth.uid())', + t, t); + EXECUTE format( + 'CREATE POLICY %I_delete ON %I FOR DELETE USING (tenant_id = auth.uid())', + t, t); + END LOOP; +END $$; + +-- Visibility-aware policies for meta_rules / frameworks / rule_relationships. +-- SELECT: your own rows OR visibility='global'/'shared'. +-- INSERT/UPDATE/DELETE: your own rows only; cannot create globals as a normal user. +DO $$ +DECLARE + t TEXT; + vtables TEXT[] := ARRAY['meta_rules','frameworks','rule_relationships']; +BEGIN + FOREACH t IN ARRAY vtables LOOP + EXECUTE format('DROP POLICY IF EXISTS %I_select ON %I', t, t); + EXECUTE format('DROP POLICY IF EXISTS %I_insert ON %I', t, t); + EXECUTE format('DROP POLICY IF EXISTS %I_update ON %I', t, t); + EXECUTE format('DROP POLICY IF EXISTS %I_delete ON %I', t, t); + + EXECUTE format($f$ + CREATE POLICY %I_select ON %I FOR SELECT USING ( + tenant_id = auth.uid() + OR visibility IN ('global','shared') + )$f$, t, t); + EXECUTE format($f$ + CREATE POLICY %I_insert ON %I FOR INSERT WITH CHECK ( + tenant_id = auth.uid() + AND visibility IN ('private','shared') + )$f$, t, t); + EXECUTE format($f$ + CREATE POLICY %I_update ON %I FOR UPDATE + USING (tenant_id = auth.uid()) + WITH CHECK (tenant_id = auth.uid() AND visibility IN ('private','shared')) + $f$, t, t); + EXECUTE format( + 'CREATE POLICY %I_delete ON %I FOR DELETE USING (tenant_id = auth.uid())', + t, t); + END LOOP; +END $$; + + +-- ----------------------------------------------------------------------------- +-- 5. Record this migration +-- ----------------------------------------------------------------------------- +INSERT INTO cloud_migrations (name, notes) +VALUES ('001_cloud_schema_v1', 'initial cloud schema + RLS') +ON CONFLICT (name) DO NOTHING; + + +-- ============================================================================= +-- SEED: Oliver's primary tenant (RUN WITH service_role ONLY) +-- ============================================================================= +-- After creating the Supabase auth user with id = 402bc79c-1ef3-42e4-a410-52b909babfc6 +-- (via admin API), run: +-- +-- INSERT INTO tenant_registry (tenant_id, display_name, email, is_primary, tier) +-- VALUES ( +-- '402bc79c-1ef3-42e4-a410-52b909babfc6', +-- 'Oliver Le', +-- 'oliver@hausgem.com', +-- TRUE, +-- 'founder' +-- ) +-- ON CONFLICT (tenant_id) DO NOTHING; +-- +-- Seed a global meta-rule example (service_role only): +-- +-- INSERT INTO meta_rules (id, tenant_id, visibility, principle, scope, confidence, source) +-- VALUES ( +-- 'gradata.truth_protocol.v1', +-- NULL, +-- 'global', +-- 'Never report unverified numbers. Cite source or mark [INSUFFICIENT].', +-- 'general', +-- 0.99, +-- 'curated' +-- ) +-- ON CONFLICT (id) DO NOTHING; +-- ============================================================================= diff --git a/brain/scripts/cross_validate.py b/Sprites/brain/scripts/cross_validate.py similarity index 100% rename from brain/scripts/cross_validate.py rename to Sprites/brain/scripts/cross_validate.py diff --git a/Sprites/brain/scripts/fill_null_tenant.py b/Sprites/brain/scripts/fill_null_tenant.py new file mode 100644 index 00000000..4565b35e --- /dev/null +++ b/Sprites/brain/scripts/fill_null_tenant.py @@ -0,0 +1,138 @@ +"""Safety-net backfill for rows inserted with NULL tenant_id. + +Migration 001 backfilled existing rows. Only ``_events.py`` currently +passes tenant_id on new inserts; other call sites (rule_provenance, +meta_rules, brain_fts_content, correction_patterns, activity_log, +prep_outcomes, lesson_transitions, pending_approvals, rule_relationships, +etc.) still write NULL until they're wired in a follow-up pass. + +This script fills those NULLs with the primary tenant UUID. Safe to run +as a post-session hook (wrap-up) or on a schedule. Idempotent. + +Usage: + python brain/scripts/fill_null_tenant.py --brain C:/.../brain + python brain/scripts/fill_null_tenant.py --brain C:/.../brain --dry-run +""" +from __future__ import annotations + +import argparse +import sqlite3 +import sys +from pathlib import Path + +sys.path.insert(0, str(Path(__file__).resolve().parent / "migrations")) +from _runner import column_exists, resolve_brain_db, table_exists # type: ignore[import-not-found] # noqa: E402 +from tenant_uuid import read_tenant_id # type: ignore[import-not-found] # noqa: E402 + + +# Tables with tenant_id that are NOT fully wired in the SDK yet. +# Safe to backfill because the local SDK is single-tenant per brain. +CANDIDATE_TABLES: list[str] = [ + "deals", + "signals", + "audit_scores", + "gate_triggers", + "lesson_applications", + "entities", + "relationships", + "decisions", + "pipeline_snapshots", + "daily_metrics", + "activity_log", + "prep_outcomes", + "events", + "facts", + "session_metrics", + "session_gates", + "output_classifications", + "correction_severity", + "ablation_log", + "rule_provenance", + "correction_patterns", + "tasks", + "agent_jobs", + "demo_recordings", + "pending_approvals", + "brain_embeddings", + "brain_fts_content", + "enrichment_queue", + "enrichment_processed_files", + "rule_canary", + "lesson_transitions", + "sync_state", + "meta_rules", + "frameworks", + "rule_relationships", +] + + +def main() -> int: + ap = argparse.ArgumentParser() + ap.add_argument("--brain", help="Brain directory or system.db path") + ap.add_argument("--dry-run", action="store_true") + args = ap.parse_args() + + db_path = resolve_brain_db(args.brain) + if not db_path.exists(): + print(f"ERROR: brain DB not found at {db_path}", file=sys.stderr) + return 2 + + brain_dir = db_path.parent + tid = read_tenant_id(brain_dir) + if not tid: + print( + f"ERROR: no tenant UUID in {brain_dir}/.tenant_id - run migration 001 first", + file=sys.stderr, + ) + return 2 + + print(f"Brain : {db_path}") + print(f"Tenant : {tid}") + print(f"Mode : {'dry-run' if args.dry_run else 'apply'}") + print() + + conn = sqlite3.connect(str(db_path)) + conn.execute("PRAGMA busy_timeout=5000") + + total = 0 + touched: list[tuple[str, int]] = [] + try: + for t in CANDIDATE_TABLES: + if not table_exists(conn, t): + continue + if not column_exists(conn, t, "tenant_id"): + continue + cnt = conn.execute( + f"SELECT COUNT(*) FROM {t} WHERE tenant_id IS NULL" + ).fetchone()[0] + if not cnt: + continue + if args.dry_run: + touched.append((t, cnt)) + total += cnt + continue + cur = conn.execute( + f"UPDATE {t} SET tenant_id = ? WHERE tenant_id IS NULL", + (tid,), + ) + if cur.rowcount: + touched.append((t, cur.rowcount)) + total += cur.rowcount + if not args.dry_run: + conn.commit() + finally: + conn.close() + + if not touched: + print("no NULL tenant_id rows found") + return 0 + + for t, n in touched: + print(f" {t:32s} {n:>8} rows") + verb = "would backfill" if args.dry_run else "backfilled" + print(f"\n{verb} {total} rows across {len(touched)} tables") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/Sprites/brain/scripts/half_life_fit.py b/Sprites/brain/scripts/half_life_fit.py new file mode 100644 index 00000000..06ec272d --- /dev/null +++ b/Sprites/brain/scripts/half_life_fit.py @@ -0,0 +1,523 @@ +""" +Empirical Settles-Meeder half-life fit for Gradata lesson confidence decay. + +Research question: Are Gradata's 0.40/0.60/0.90 promotion thresholds +(INSTINCT / PATTERN / RULE) empirically defensible, or are they magic +numbers that should be replaced with an empirically fitted decay curve? + +Method: +- Settles & Meeder (2016) half-life regression. p = 2^(-delta/h) + log(p) / log(2) = -delta/h => linear regression on 1/h. + Paper: "A Trainable Spaced Repetition Model for Language Learning" + ACL 2016. https://doi.org/10.18653/v1/P16-1174 +- Observed trajectory per lesson = ordered (confidence, timestamp) pairs + from lesson_transitions table + CORRECTION events (reinforcements). +- We treat a "reinforcement" as a transition where fire_count increases + or a CORRECTION event whose lesson_desc matches. Between reinforcements, + confidence should decay toward 0 per the Settles-Meeder kernel. +- Fit h per category (CONTENT / CODE / PROCESS / TONE / ACCURACY / ...) + because per-lesson data is too sparse. + +Output: .tmp/half-life-fit/{plots, fit_results.json, decay_data.json} + +Run: python brain/scripts/half_life_fit.py +""" +from __future__ import annotations + +import json +import math +import sqlite3 +import sys +from collections import defaultdict +from datetime import datetime, timezone +from pathlib import Path + +import numpy as np + +# Optional scientific stack +try: + from scipy import optimize, stats + HAVE_SCIPY = True +except ImportError: + HAVE_SCIPY = False + +try: + import matplotlib + matplotlib.use("Agg") + import matplotlib.pyplot as plt + HAVE_MPL = True +except ImportError: + HAVE_MPL = False + +# --------------------------------------------------------------------------- +# Config +# --------------------------------------------------------------------------- +BRAIN = Path("C:/Users/olive/SpritesWork/brain") +EVENTS_PATH = BRAIN / "events.jsonl" +DB_PATH = BRAIN / "system.db" +OUT_DIR = Path("C:/Users/olive/OneDrive/Desktop/Sprites Work/.tmp/half-life-fit") +OUT_DIR.mkdir(parents=True, exist_ok=True) + +# Current Gradata promotion thresholds +THRESH_INSTINCT = 0.40 +THRESH_PATTERN = 0.60 +THRESH_RULE = 0.90 + +# Minimum trajectory length (distinct obs) to attempt a fit +MIN_POINTS_PER_LESSON = 3 +MIN_POINTS_PER_CATEGORY = 10 + + +# --------------------------------------------------------------------------- +# Load data +# --------------------------------------------------------------------------- +def parse_ts(ts: str) -> float: + """Return POSIX timestamp in days.""" + try: + dt = datetime.fromisoformat(ts.replace("Z", "+00:00")) + if dt.tzinfo is None: + dt = dt.replace(tzinfo=timezone.utc) + return dt.timestamp() / 86400.0 + except Exception: + return float("nan") + + +def load_lesson_transitions(db: Path) -> list[dict]: + conn = sqlite3.connect(str(db)) + conn.row_factory = sqlite3.Row + rows = conn.execute( + """SELECT lesson_desc, category, old_state, new_state, + confidence, fire_count, transitioned_at + FROM lesson_transitions + ORDER BY lesson_desc, transitioned_at""" + ).fetchall() + conn.close() + out = [] + for r in rows: + t = parse_ts(r["transitioned_at"]) + if math.isnan(t): + continue + out.append( + { + "lesson": r["lesson_desc"], + "category": (r["category"] or "UNKNOWN").upper(), + "old_state": r["old_state"], + "new_state": r["new_state"], + "confidence": float(r["confidence"] or 0.0), + "fire_count": int(r["fire_count"] or 0), + "t_days": t, + } + ) + return out + + +def load_corrections(path: Path) -> list[dict]: + corrections = [] + with path.open("r", encoding="utf-8") as f: + for line in f: + try: + e = json.loads(line) + except Exception: + continue + if e.get("type") != "CORRECTION": + continue + d = e.get("data", {}) or {} + corrections.append( + { + "t_days": parse_ts(e.get("ts", "")), + "category": (d.get("category") or "UNKNOWN").upper(), + "severity": d.get("severity", "?"), + } + ) + return corrections + + +# --------------------------------------------------------------------------- +# Build per-lesson trajectories +# --------------------------------------------------------------------------- +def build_trajectories(transitions: list[dict]) -> dict[str, list[dict]]: + """Group transitions by lesson_desc. Each trajectory is a list of obs + ordered by time, each carrying confidence at t_days plus flag for + reinforcement (new state above old_state in ordering, OR fire_count bump). + """ + by_lesson: dict[str, list[dict]] = defaultdict(list) + for tr in transitions: + by_lesson[tr["lesson"]].append(tr) + + order_rank = {"KILLED": -1, "UNTESTABLE": 0, "INSTINCT": 1, "PATTERN": 2, "RULE": 3} + + trajectories = {} + for lesson, obs in by_lesson.items(): + obs.sort(key=lambda r: r["t_days"]) + out = [] + prev_fc = -1 + for r in obs: + promoted = order_rank.get(r["new_state"], 0) > order_rank.get( + r["old_state"], 0 + ) + reinforced = promoted or (r["fire_count"] > max(prev_fc, 0)) + out.append( + { + "t_days": r["t_days"], + "confidence": r["confidence"], + "new_state": r["new_state"], + "category": r["category"], + "reinforced": reinforced, + } + ) + prev_fc = r["fire_count"] + trajectories[lesson] = out + return trajectories + + +def extract_decay_pairs( + trajectories: dict[str, list[dict]], +) -> dict[str, list[tuple[float, float]]]: + """For each lesson, emit (delta_days_since_last_reinforcement, p) pairs + where p = observed confidence / confidence_at_last_reinforcement. + + p in (0, 1], delta >= 0. These are the regression inputs. + Returns: dict category -> list of (delta, p). + """ + by_cat: dict[str, list[tuple[float, float]]] = defaultdict(list) + for lesson, obs in trajectories.items(): + anchor_t = None + anchor_c = None + category = obs[0]["category"] if obs else "UNKNOWN" + for o in obs: + if o["reinforced"] or anchor_t is None: + if o["confidence"] > 1e-6: + anchor_t = o["t_days"] + anchor_c = o["confidence"] + continue + if anchor_t is None or anchor_c is None: + continue + delta = o["t_days"] - anchor_t + if delta <= 0: + continue + # Skip KILLED (hard terminal, not decay) + if o["new_state"] == "KILLED": + continue + # Normalise confidence + if o["confidence"] <= 0: + # Treat as near-0 recall to fit decay + p = 1e-3 + else: + p = min(1.0, o["confidence"] / max(anchor_c, 1e-6)) + if p <= 0 or p > 1.001: + continue + by_cat[category].append((delta, p)) + return by_cat + + +# --------------------------------------------------------------------------- +# Settles-Meeder fit +# --------------------------------------------------------------------------- +def fit_half_life(deltas: np.ndarray, ps: np.ndarray) -> dict: + """Fit p = 2^(-delta/h). Linearise: y = log2(p) = -delta / h. + So slope m = -1/h; h = -1/m. Use least squares through origin. + + Also fit via nonlinear curve_fit (if scipy) for a better estimate. + """ + if len(deltas) < 3: + return {"n": len(deltas), "h_days": None, "method": "insufficient"} + + # Clip to avoid log(0) + ps_c = np.clip(ps, 1e-4, 1.0) + y = np.log2(ps_c) + + # Linear fit through origin: minimise sum (y + delta/h)^2 -> h = -sum(d^2) / sum(d*y) + num = float(np.sum(deltas * deltas)) + den = float(np.sum(deltas * y)) + if den >= 0 or num <= 0: + h_lin = None + else: + h_lin = -num / den + + # Nonlinear fit: p = 2^(-delta/h) + h_nl = None + ci = None + if HAVE_SCIPY and h_lin is not None and h_lin > 0: + try: + def model(d, h): + h = max(h, 1e-3) + return np.power(2.0, -d / h) + + popt, pcov = optimize.curve_fit( + model, deltas, ps_c, p0=[h_lin], bounds=(1e-3, 1e4), maxfev=5000 + ) + h_nl = float(popt[0]) + if pcov is not None and np.all(np.isfinite(pcov)): + se = float(np.sqrt(pcov[0, 0])) + ci = (max(0.0, h_nl - 1.96 * se), h_nl + 1.96 * se) + except Exception: + h_nl = None + + h = h_nl if h_nl is not None else h_lin + return { + "n": int(len(deltas)), + "h_days": h, + "h_linear": h_lin, + "h_nonlinear": h_nl, + "ci95": ci, + "method": "nonlinear" if h_nl is not None else ("linear" if h_lin else "failed"), + } + + +def bootstrap_h(deltas: np.ndarray, ps: np.ndarray, n_boot: int = 500) -> dict: + """Bootstrap over observations. Return median + 95% CI of h.""" + if len(deltas) < MIN_POINTS_PER_CATEGORY: + return {"median": None, "ci95": None, "n_boot": 0} + rng = np.random.default_rng(42) + hs = [] + n = len(deltas) + for _ in range(n_boot): + idx = rng.integers(0, n, size=n) + f = fit_half_life(deltas[idx], ps[idx]) + if f["h_days"] and f["h_days"] > 0 and f["h_days"] < 1e4: + hs.append(f["h_days"]) + if len(hs) < 20: + return {"median": None, "ci95": None, "n_boot": len(hs)} + hs = np.array(hs) + return { + "median": float(np.median(hs)), + "ci95": (float(np.percentile(hs, 2.5)), float(np.percentile(hs, 97.5))), + "n_boot": int(len(hs)), + } + + +# --------------------------------------------------------------------------- +# Threshold analysis +# --------------------------------------------------------------------------- +def empirical_thresholds( + fits: dict[str, dict], + trajectories: dict[str, list[dict]], + reinforcement_interval_days: float, +) -> dict: + """Given fitted h per category, the steady-state confidence that a + lesson will retain at its typical reinforcement interval (tau) is: + + p_steady = 2^(-tau / h) + + A promotion threshold T is defensible if, at the observed tau, a + lesson reaching T has p_steady > some retention target (say 0.5). + + We compute p_steady at tau = reinforcement_interval_days for each + category. If the observed promotion confidence (0.60 for PATTERN, + 0.90 for RULE) lands above p_steady(tau) the threshold is too + strict for that category; below it, too lax. + """ + out = {} + for cat, f in fits.items(): + h = f.get("h_days") + if not h or h <= 0: + continue + tau = reinforcement_interval_days + p_steady = 2.0 ** (-tau / h) + # Suggested empirical PATTERN threshold = p_steady at 2*tau + # (lesson survives two reinforcement cycles => stable pattern) + p_pattern_suggested = 2.0 ** (-(2 * tau) / h) + # Suggested RULE = p_steady at 4*tau (robust across 4 cycles) + p_rule_suggested = 2.0 ** (-(4 * tau) / h) + out[cat] = { + "h_days": h, + "tau_days": tau, + "p_steady_at_tau": p_steady, + "p_pattern_suggested": p_pattern_suggested, + "p_rule_suggested": p_rule_suggested, + } + return out + + +def estimate_reinforcement_interval(trajectories: dict[str, list[dict]]) -> float: + """Median days between reinforcements across all lessons.""" + gaps = [] + for lesson, obs in trajectories.items(): + last_t = None + for o in obs: + if o["reinforced"]: + if last_t is not None: + gaps.append(o["t_days"] - last_t) + last_t = o["t_days"] + if not gaps: + return 1.0 + gaps = [g for g in gaps if g > 0] + return float(np.median(gaps)) if gaps else 1.0 + + +# --------------------------------------------------------------------------- +# Plotting +# --------------------------------------------------------------------------- +def make_plot( + by_cat_data: dict[str, list[tuple[float, float]]], + fits: dict[str, dict], + out_path: Path, + thresholds: tuple[float, float, float] = (0.40, 0.60, 0.90), +): + if not HAVE_MPL: + return False + categories = [c for c in by_cat_data if fits.get(c, {}).get("h_days")] + categories = sorted(categories, key=lambda c: -len(by_cat_data[c]))[:6] + if not categories: + return False + + fig, ax = plt.subplots(figsize=(9, 6)) + colors = plt.cm.tab10(np.linspace(0, 1, len(categories))) + + # Max delta for x axis + max_delta = 0 + for c in categories: + if by_cat_data[c]: + max_delta = max(max_delta, max(d for d, _ in by_cat_data[c])) + max_delta = min(max_delta, 20.0) if max_delta > 0 else 10.0 + ds = np.linspace(0.01, max_delta, 200) + + for i, cat in enumerate(categories): + pts = by_cat_data[cat] + h = fits[cat]["h_days"] + n = fits[cat]["n"] + xs = np.array([p[0] for p in pts]) + ys = np.array([p[1] for p in pts]) + ax.scatter(xs, ys, s=14, alpha=0.35, color=colors[i]) + curve = np.power(2.0, -ds / h) + ax.plot(ds, curve, color=colors[i], lw=2, label=f"{cat} (h={h:.2f}d, n={n})") + + # Promotion thresholds + for t, name, style in [ + (thresholds[0], "INSTINCT (0.40)", "--"), + (thresholds[1], "PATTERN (0.60)", "--"), + (thresholds[2], "RULE (0.90)", "--"), + ]: + ax.axhline(y=t, color="black", linestyle=style, alpha=0.4, lw=1) + ax.text(max_delta * 0.98, t + 0.01, name, fontsize=8, ha="right", alpha=0.6) + + ax.set_xlabel("Days since last reinforcement") + ax.set_ylabel("Relative recall p = confidence / confidence_at_reinforcement") + ax.set_title( + "Gradata lesson confidence decay (Settles-Meeder fit)\n" + "p = 2^(-delta/h) per category" + ) + ax.set_ylim(-0.02, 1.05) + ax.set_xlim(0, max_delta) + ax.grid(alpha=0.2) + ax.legend(loc="upper right", fontsize=8) + + fig.tight_layout() + fig.savefig(out_path, dpi=150) + plt.close(fig) + return True + + +# --------------------------------------------------------------------------- +# Main +# --------------------------------------------------------------------------- +def main(): + print(f"[load] transitions from {DB_PATH}") + transitions = load_lesson_transitions(DB_PATH) + print(f" -> {len(transitions)} transitions across " + f"{len({t['lesson'] for t in transitions})} lessons") + + print(f"[load] corrections from {EVENTS_PATH}") + corrections = load_corrections(EVENTS_PATH) + print(f" -> {len(corrections)} CORRECTION events") + + trajectories = build_trajectories(transitions) + print(f"[build] {len(trajectories)} lesson trajectories") + + long_enough = {k: v for k, v in trajectories.items() if len(v) >= MIN_POINTS_PER_LESSON} + print(f" -> {len(long_enough)} trajectories with >= {MIN_POINTS_PER_LESSON} points") + + tau = estimate_reinforcement_interval(trajectories) + print(f"[tau] median reinforcement interval: {tau:.3f} days") + + by_cat = extract_decay_pairs(trajectories) + print(f"[decay] category pair counts:") + for c, pairs in sorted(by_cat.items(), key=lambda x: -len(x[1])): + print(f" {c:20s} n={len(pairs)}") + + fits = {} + boots = {} + for cat, pairs in by_cat.items(): + if len(pairs) < MIN_POINTS_PER_CATEGORY: + continue + deltas = np.array([p[0] for p in pairs], dtype=float) + ps = np.array([p[1] for p in pairs], dtype=float) + f = fit_half_life(deltas, ps) + fits[cat] = f + boots[cat] = bootstrap_h(deltas, ps) + + print(f"\n[fits] {len(fits)} categories fit:") + for cat, f in sorted(fits.items(), key=lambda x: -x[1]["n"]): + h = f["h_days"] + ci = f.get("ci95") + b = boots.get(cat, {}) + print( + f" {cat:20s} n={f['n']:3d} h={h:7.3f}d " + f"{('ci=[%.3f,%.3f]' % ci) if ci else ''} " + f"boot_median={b.get('median')}" + ) + + emp = empirical_thresholds(fits, trajectories, tau) + + # Aggregate across categories (weighted by n) + total_n = sum(f["n"] for f in fits.values()) + if total_n: + weighted_h = sum(f["h_days"] * f["n"] for f in fits.values() if f["h_days"]) / total_n + else: + weighted_h = None + + global_p_pattern = ( + 2.0 ** (-(2 * tau) / weighted_h) if weighted_h else None + ) + global_p_rule = ( + 2.0 ** (-(4 * tau) / weighted_h) if weighted_h else None + ) + + # Save + out = { + "dataset": { + "n_transitions": len(transitions), + "n_unique_lessons": len({t["lesson"] for t in transitions}), + "n_trajectories_usable": len(long_enough), + "n_corrections": len(corrections), + "tau_days_median": tau, + }, + "fits_per_category": fits, + "bootstraps_per_category": boots, + "empirical_thresholds_per_category": emp, + "weighted_global": { + "h_days": weighted_h, + "p_pattern_suggested_2tau": global_p_pattern, + "p_rule_suggested_4tau": global_p_rule, + }, + "current_thresholds": { + "INSTINCT": THRESH_INSTINCT, + "PATTERN": THRESH_PATTERN, + "RULE": THRESH_RULE, + }, + "scipy_available": HAVE_SCIPY, + "matplotlib_available": HAVE_MPL, + } + fit_json = OUT_DIR / "fit_results.json" + fit_json.write_text(json.dumps(out, indent=2, default=str), encoding="utf-8") + print(f"\n[save] {fit_json}") + + decay_json = OUT_DIR / "decay_data.json" + decay_json.write_text( + json.dumps({k: v for k, v in by_cat.items()}, indent=2), encoding="utf-8" + ) + print(f"[save] {decay_json}") + + if HAVE_MPL: + plot_path = OUT_DIR / "decay_curves.png" + ok = make_plot(by_cat, fits, plot_path) + if ok: + print(f"[plot] {plot_path}") + else: + print("[plot] matplotlib unavailable; skipping figure.") + + return out + + +if __name__ == "__main__": + main() diff --git a/brain/scripts/migrate_tree_paths.py b/Sprites/brain/scripts/migrate_tree_paths.py similarity index 100% rename from brain/scripts/migrate_tree_paths.py rename to Sprites/brain/scripts/migrate_tree_paths.py diff --git a/Sprites/brain/scripts/migrations/001_add_tenant_id.py b/Sprites/brain/scripts/migrations/001_add_tenant_id.py new file mode 100644 index 00000000..57bf5250 --- /dev/null +++ b/Sprites/brain/scripts/migrations/001_add_tenant_id.py @@ -0,0 +1,316 @@ +"""Migration 001: add tenant_id, visibility, schema_version. + +Makes the brain multi-tenant aware WITHOUT changing how it behaves today. + +What this does: +1. Create ``migrations`` + ``tenant_registry`` tables. +2. Ensure ``/.tenant_id`` exists; read it as "Oliver's tenant UUID". +3. Add ``tenant_id TEXT`` (nullable) to every per-tenant table and backfill + all existing rows to that UUID. +4. Add ``visibility TEXT DEFAULT 'private'`` to meta_rules / frameworks / + rule_relationships and backfill NULLs. +5. Add ``schema_version INTEGER DEFAULT 1`` to events. +6. Create ``idx__tenant`` on each per-tenant table for query perf. +7. Record migration in ``migrations`` table. + +Idempotent: re-running is a no-op once applied. Safe. + +Dry run: + python brain/scripts/migrations/001_add_tenant_id.py --brain --dry-run + +Apply: + python brain/scripts/migrations/001_add_tenant_id.py --brain +""" +from __future__ import annotations + +import argparse +import sqlite3 +import sys +from datetime import datetime, timezone +from pathlib import Path + +# The file name starts with a digit so it's run as a script, not imported +# as a module. Add the parent dir to sys.path for the helper imports. +sys.path.insert(0, str(Path(__file__).resolve().parent)) +from _runner import ( # type: ignore[import-not-found] # noqa: E402 + add_column_if_missing, + column_exists, + create_index_if_missing, + has_applied, + mark_applied, + resolve_brain_db, + table_exists, +) +from tenant_uuid import get_or_create_tenant_id # type: ignore[import-not-found] # noqa: E402 + + +NAME = "001_add_tenant_id" + + +# Tables that belong to ONE tenant (add NOT-NULL-intent tenant_id, backfill). +PER_TENANT_TABLES: list[str] = [ + "deals", + "signals", + "audit_scores", + "gate_triggers", + "lesson_applications", + "entities", + "relationships", + "decisions", + "pipeline_snapshots", + "daily_metrics", + "activity_log", + "prep_outcomes", + "events", + "facts", + "session_metrics", + "session_gates", + "output_classifications", + "correction_severity", + "ablation_log", + "rule_provenance", + "correction_patterns", + "tasks", + "agent_jobs", + "demo_recordings", + "pending_approvals", + "brain_embeddings", + "brain_fts_content", + "enrichment_queue", + "enrichment_processed_files", + "rule_canary", + "lesson_transitions", + "sync_state", +] + +# Tables that are "mixed" — some rows may be shareable across tenants. +# Get tenant_id (nullable; NULL = global) AND a visibility column. +MIXED_VISIBILITY_TABLES: list[str] = [ + "meta_rules", + "frameworks", + "rule_relationships", +] + +# Tables we deliberately DO NOT touch. +# sqlite_sequence — sqlite internals +# sqlite_master — sqlite internals +# brain_fts — FTS5 virtual table (content table handled separately) +# brain_fts_data/idx/docsize/config — FTS5 shadow tables +# migrations — created by the runner +# tenant_registry — created by this migration + + +TENANT_REGISTRY_SQL = """ +CREATE TABLE IF NOT EXISTS tenant_registry ( + tenant_id TEXT PRIMARY KEY, + display_name TEXT, + email TEXT, + created_at TEXT NOT NULL, + is_primary INTEGER DEFAULT 0, + notes TEXT DEFAULT '' +) +""" + + +def plan(conn: sqlite3.Connection) -> dict: + """Return a dict describing what up() would do. Read-only.""" + actions: list[str] = [] + row_backfills: list[tuple[str, int]] = [] + + if not table_exists(conn, "tenant_registry"): + actions.append("CREATE TABLE tenant_registry") + + for t in PER_TENANT_TABLES: + if not table_exists(conn, t): + continue + if not column_exists(conn, t, "tenant_id"): + actions.append(f"ALTER {t} ADD tenant_id TEXT") + # Backfill count: rows where tenant_id is NULL (or column doesn't exist -> all rows) + if column_exists(conn, t, "tenant_id"): + cnt = conn.execute( + f"SELECT COUNT(*) FROM {t} WHERE tenant_id IS NULL" + ).fetchone()[0] + else: + cnt = conn.execute(f"SELECT COUNT(*) FROM {t}").fetchone()[0] + if cnt: + row_backfills.append((t, cnt)) + idx = f"idx_{t}_tenant" + actions.append(f"ensure index {idx}(tenant_id)") + + for t in MIXED_VISIBILITY_TABLES: + if not table_exists(conn, t): + continue + if not column_exists(conn, t, "tenant_id"): + actions.append(f"ALTER {t} ADD tenant_id TEXT") + if not column_exists(conn, t, "visibility"): + actions.append(f"ALTER {t} ADD visibility TEXT DEFAULT 'private'") + + if table_exists(conn, "events") and not column_exists( + conn, "events", "schema_version" + ): + actions.append("ALTER events ADD schema_version INTEGER DEFAULT 1") + + return { + "actions": actions, + "row_backfills": row_backfills, + "total_rows_to_backfill": sum(c for _, c in row_backfills), + } + + +def up(conn: sqlite3.Connection, tenant_id: str) -> dict: + """Apply the migration. Returns summary dict.""" + summary: dict = { + "columns_added": [], + "indexes_created": [], + "rows_backfilled": 0, + "tables_backfilled": {}, + "visibility_backfilled": 0, + } + + # 1. tenant_registry + conn.execute(TENANT_REGISTRY_SQL) + conn.execute( + "INSERT OR IGNORE INTO tenant_registry " + "(tenant_id, display_name, created_at, is_primary, notes) " + "VALUES (?, ?, ?, 1, 'primary tenant — first brain, backfilled')", + (tenant_id, "primary", datetime.now(timezone.utc).isoformat()), + ) + + # 2. Per-tenant tables + for t in PER_TENANT_TABLES: + if not table_exists(conn, t): + continue + if add_column_if_missing(conn, t, "tenant_id", "TEXT"): + summary["columns_added"].append(f"{t}.tenant_id") + # Backfill + cur = conn.execute( + f"UPDATE {t} SET tenant_id = ? WHERE tenant_id IS NULL", + (tenant_id,), + ) + if cur.rowcount: + summary["rows_backfilled"] += cur.rowcount + summary["tables_backfilled"][t] = cur.rowcount + # Index + idx = f"idx_{t}_tenant" + if create_index_if_missing(conn, idx, t, "tenant_id"): + summary["indexes_created"].append(idx) + + # 3. Mixed visibility tables + for t in MIXED_VISIBILITY_TABLES: + if not table_exists(conn, t): + continue + if add_column_if_missing(conn, t, "tenant_id", "TEXT"): + summary["columns_added"].append(f"{t}.tenant_id") + if add_column_if_missing( + conn, t, "visibility", "TEXT DEFAULT 'private'" + ): + summary["columns_added"].append(f"{t}.visibility") + # Backfill visibility for pre-existing NULLs + cur = conn.execute( + f"UPDATE {t} SET visibility = 'private' WHERE visibility IS NULL" + ) + summary["visibility_backfilled"] += cur.rowcount + # Backfill tenant_id: all existing rows belong to primary tenant. + # Future: admin can promote rows to visibility='global' & tenant_id=NULL. + cur = conn.execute( + f"UPDATE {t} SET tenant_id = ? WHERE tenant_id IS NULL", + (tenant_id,), + ) + if cur.rowcount: + summary["rows_backfilled"] += cur.rowcount + summary["tables_backfilled"][t] = ( + summary["tables_backfilled"].get(t, 0) + cur.rowcount + ) + idx = f"idx_{t}_tenant" + if create_index_if_missing(conn, idx, t, "tenant_id"): + summary["indexes_created"].append(idx) + idx_v = f"idx_{t}_visibility" + if create_index_if_missing(conn, idx_v, t, "visibility"): + summary["indexes_created"].append(idx_v) + + # 4. events.schema_version + if table_exists(conn, "events") and add_column_if_missing( + conn, "events", "schema_version", "INTEGER DEFAULT 1" + ): + summary["columns_added"].append("events.schema_version") + conn.execute( + "UPDATE events SET schema_version = 1 WHERE schema_version IS NULL" + ) + + conn.commit() + return summary + + +def _main() -> int: + ap = argparse.ArgumentParser(description=f"Run migration {NAME}") + ap.add_argument( + "--brain", + help="Path to brain directory or system.db file (defaults to $GRADATA_BRAIN_DIR or ./brain)", + ) + ap.add_argument( + "--dry-run", + action="store_true", + help="Print plan without modifying DB", + ) + args = ap.parse_args() + + db_path = resolve_brain_db(args.brain) + if not db_path.exists(): + print(f"ERROR: brain DB not found at {db_path}", file=sys.stderr) + return 2 + + brain_dir = db_path.parent + tid = get_or_create_tenant_id(brain_dir) + print(f"Brain : {db_path}") + print(f"Tenant : {tid}") + + conn = sqlite3.connect(str(db_path)) + conn.execute("PRAGMA journal_mode=WAL") + conn.execute("PRAGMA busy_timeout=5000") + + try: + if has_applied(conn, NAME) and not args.dry_run: + print(f"Already applied: {NAME} (no-op)") + return 0 + + p = plan(conn) + print("\n--- plan ---") + for a in p["actions"]: + print(f" {a}") + print( + f" backfill {p['total_rows_to_backfill']} rows across " + f"{len(p['row_backfills'])} tables" + ) + if p["row_backfills"]: + sample = p["row_backfills"][:10] + for t, c in sample: + print(f" {t:30s} {c:>8} rows") + if len(p["row_backfills"]) > 10: + print(f" ... and {len(p['row_backfills']) - 10} more") + + if args.dry_run: + print("\n(dry-run) no changes made") + return 0 + + print("\n--- applying ---") + summary = up(conn, tenant_id=tid) + mark_applied( + conn, + NAME, + rows_affected=summary["rows_backfilled"], + notes=f"primary tenant {tid}", + ) + print(f"columns_added : {len(summary['columns_added'])}") + for c in summary["columns_added"]: + print(f" + {c}") + print(f"indexes_created : {len(summary['indexes_created'])}") + print(f"rows_backfilled : {summary['rows_backfilled']}") + print(f"visibility_backfilled : {summary['visibility_backfilled']}") + print("\nOK") + return 0 + finally: + conn.close() + + +if __name__ == "__main__": + raise SystemExit(_main()) diff --git a/Sprites/brain/scripts/migrations/__init__.py b/Sprites/brain/scripts/migrations/__init__.py new file mode 100644 index 00000000..d925e351 --- /dev/null +++ b/Sprites/brain/scripts/migrations/__init__.py @@ -0,0 +1,10 @@ +"""Gradata brain migrations. + +Each migration is a numbered module (001_*.py, 002_*.py, ...) exposing: + NAME: str + def up(conn: sqlite3.Connection, tenant_id: str) -> dict + +Applied migrations are recorded in the ``migrations`` table. +Run via ``python -m brain.scripts.migrations.run`` or call a specific +migration module directly with ``--brain ``. +""" diff --git a/Sprites/brain/scripts/migrations/_runner.py b/Sprites/brain/scripts/migrations/_runner.py new file mode 100644 index 00000000..77d99011 --- /dev/null +++ b/Sprites/brain/scripts/migrations/_runner.py @@ -0,0 +1,111 @@ +"""Migration runner helpers. + +Single source of truth for: +- ``migrations`` table (idempotency tracking) +- ``has_applied`` / ``mark_applied`` +- Safe column / index existence checks for SQLite +""" +from __future__ import annotations + +import sqlite3 +from datetime import datetime, timezone +from pathlib import Path + + +MIGRATIONS_TABLE_SQL = """ +CREATE TABLE IF NOT EXISTS migrations ( + name TEXT PRIMARY KEY, + applied_at TEXT NOT NULL, + rows_affected INTEGER DEFAULT 0, + notes TEXT DEFAULT '' +) +""" + + +def ensure_migrations_table(conn: sqlite3.Connection) -> None: + conn.execute(MIGRATIONS_TABLE_SQL) + conn.commit() + + +def has_applied(conn: sqlite3.Connection, name: str) -> bool: + ensure_migrations_table(conn) + row = conn.execute( + "SELECT 1 FROM migrations WHERE name = ?", (name,) + ).fetchone() + return row is not None + + +def mark_applied( + conn: sqlite3.Connection, + name: str, + rows_affected: int = 0, + notes: str = "", +) -> None: + ensure_migrations_table(conn) + conn.execute( + "INSERT OR REPLACE INTO migrations (name, applied_at, rows_affected, notes) " + "VALUES (?, ?, ?, ?)", + (name, datetime.now(timezone.utc).isoformat(), rows_affected, notes), + ) + conn.commit() + + +def column_exists(conn: sqlite3.Connection, table: str, column: str) -> bool: + rows = conn.execute(f"PRAGMA table_info({table})").fetchall() + return any(r[1] == column for r in rows) + + +def table_exists(conn: sqlite3.Connection, table: str) -> bool: + row = conn.execute( + "SELECT 1 FROM sqlite_master WHERE type='table' AND name = ?", (table,) + ).fetchone() + return row is not None + + +def index_exists(conn: sqlite3.Connection, index: str) -> bool: + row = conn.execute( + "SELECT 1 FROM sqlite_master WHERE type='index' AND name = ?", (index,) + ).fetchone() + return row is not None + + +def add_column_if_missing( + conn: sqlite3.Connection, + table: str, + column: str, + decl: str, +) -> bool: + """Returns True if column was added, False if already present.""" + if not table_exists(conn, table): + return False + if column_exists(conn, table, column): + return False + conn.execute(f"ALTER TABLE {table} ADD COLUMN {column} {decl}") + return True + + +def create_index_if_missing( + conn: sqlite3.Connection, + index: str, + table: str, + columns: str, +) -> bool: + if not table_exists(conn, table): + return False + if index_exists(conn, index): + return False + conn.execute(f"CREATE INDEX {index} ON {table} ({columns})") + return True + + +def resolve_brain_db(brain_arg: str | Path | None) -> Path: + """Resolve the brain SQLite path from a CLI arg or env.""" + import os + if brain_arg: + p = Path(brain_arg).expanduser().resolve() + else: + env = os.environ.get("GRADATA_BRAIN_DIR", "").strip() + p = Path(env).expanduser().resolve() if env else Path.cwd() / "brain" + if p.is_file(): + return p + return p / "system.db" diff --git a/Sprites/brain/scripts/migrations/tenant_uuid.py b/Sprites/brain/scripts/migrations/tenant_uuid.py new file mode 100644 index 00000000..bd147cb2 --- /dev/null +++ b/Sprites/brain/scripts/migrations/tenant_uuid.py @@ -0,0 +1,73 @@ +"""Tenant UUID read/create for a given brain directory. + +The tenant UUID is stored at ``/.tenant_id`` as a plain UTF-8 +file. This file is the single local source of truth for which tenant a +brain belongs to. It is intentionally separate from system.db so it +survives DB rebuilds. + +Usage (as module): + from brain.scripts.migrations.tenant_uuid import get_or_create_tenant_id + tid = get_or_create_tenant_id(Path("C:/.../SpritesWork/brain")) + +CLI: + python brain/scripts/migrations/tenant_uuid.py --brain C:/.../brain +""" +from __future__ import annotations + +import argparse +import uuid +from pathlib import Path + + +TENANT_FILE = ".tenant_id" + + +def get_or_create_tenant_id(brain_dir: str | Path) -> str: + brain = Path(brain_dir).expanduser().resolve() + brain.mkdir(parents=True, exist_ok=True) + fpath = brain / TENANT_FILE + if fpath.exists(): + tid = fpath.read_text(encoding="utf-8").strip() + if _is_valid_uuid(tid): + return tid + tid = str(uuid.uuid4()) + fpath.write_text(tid, encoding="utf-8") + return tid + + +def read_tenant_id(brain_dir: str | Path) -> str | None: + fpath = Path(brain_dir).expanduser().resolve() / TENANT_FILE + if not fpath.exists(): + return None + tid = fpath.read_text(encoding="utf-8").strip() + return tid if _is_valid_uuid(tid) else None + + +def _is_valid_uuid(s: str) -> bool: + try: + uuid.UUID(s) + return True + except (ValueError, AttributeError, TypeError): + return False + + +def _main() -> int: + ap = argparse.ArgumentParser(description="Read or create brain tenant UUID") + ap.add_argument("--brain", required=True, help="Path to brain directory") + ap.add_argument("--peek", action="store_true", help="Read only; never create") + args = ap.parse_args() + + if args.peek: + tid = read_tenant_id(args.brain) + if tid is None: + print("(no tenant id)") + return 1 + print(tid) + return 0 + + print(get_or_create_tenant_id(args.brain)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(_main()) diff --git a/Sprites/brain/scripts/mine_cc_transcripts.py b/Sprites/brain/scripts/mine_cc_transcripts.py new file mode 100644 index 00000000..bdf37403 --- /dev/null +++ b/Sprites/brain/scripts/mine_cc_transcripts.py @@ -0,0 +1,245 @@ +"""Mine Claude Code transcript archive for implicit-feedback signals. + +Walks ~/.claude/projects/*/*.jsonl and extracts user messages matching the +pushback/reminder/gap/challenge regexes from .claude/hooks/user-prompt/ +implicit-feedback.js. Emits IMPLICIT_FEEDBACK events in the same format the +live hook produces, so the existing graduation pipeline can consume them. + +Default mode is --dry-run: counts only, writes shadow events.backfill.jsonl. +Pass --commit to append to the live brain's events.jsonl. + +Usage: + python brain/scripts/mine_cc_transcripts.py --dry-run + python brain/scripts/mine_cc_transcripts.py --commit + +Scope: + By default scans every project dir under ~/.claude/projects/. Use + --project NAME to limit to one (e.g. C--Users-olive-OneDrive-Desktop-Sprites-Work). +""" +from __future__ import annotations + +import argparse +import json +import re +import sys +from collections import Counter +from datetime import datetime, timezone +from pathlib import Path + +# ── Paths ── +PROJECTS_ROOT = Path.home() / ".claude" / "projects" +BRAIN_DIR = Path("C:/Users/olive/SpritesWork/brain") +LIVE_EVENTS = BRAIN_DIR / "events.jsonl" +SHADOW_EVENTS = BRAIN_DIR / "events.backfill.jsonl" + +# ── Pushback regexes (ported verbatim from implicit-feedback.js) ── +PUSHBACK = [ + re.compile(r"are you sure", re.I), + re.compile(r"that'?s (?:wrong|not right|incorrect)", re.I), + re.compile(r"no[,.]?\s+(?:not that|don't|stop)", re.I), + re.compile(r"why (?:did|didn't|aren't) you", re.I), + re.compile(r"not accurate", re.I), +] +REMINDER = [ + re.compile(r"make sure", re.I), + re.compile(r"don'?t forget", re.I), + re.compile(r"remember to", re.I), + re.compile(r"i (?:already|just) (?:told|said|asked)", re.I), + re.compile(r"like i said", re.I), +] +GAP = [ + re.compile(r"what about", re.I), + re.compile(r"you (?:forgot|missed|skipped|dropped|ignored)", re.I), + re.compile(r"did you (?:check|verify|test|review)", re.I), +] +CHALLENGE = [ + re.compile(r"are we (?:sure|missing)", re.I), + re.compile(r"won'?t (?:that|people|this)", re.I), + re.compile(r"i feel like", re.I), + re.compile(r"is that (?:right|correct)", re.I), +] +SIGNAL_GROUPS = { + "PUSHBACK": PUSHBACK, + "REMINDER": REMINDER, + "GAP": GAP, + "CHALLENGE": CHALLENGE, +} + + +def detect_signals(text: str) -> list[str]: + """Return list of matched signal names for a given user message.""" + if not text or len(text) < 10: + return [] + hits: list[str] = [] + for name, patterns in SIGNAL_GROUPS.items(): + for pattern in patterns: + if pattern.search(text): + hits.append(name) + break # one hit per group is enough + return hits + + +def extract_user_text(msg: dict) -> str: + """Pull user-prompt text out of a transcript message envelope.""" + if msg.get("type") != "user": + return "" + message = msg.get("message") or {} + if message.get("role") != "user": + return "" + content = message.get("content") + if isinstance(content, str): + return content + if isinstance(content, list): + # Content may be a list of parts (text + tool_result etc.) + parts: list[str] = [] + for part in content: + if isinstance(part, dict) and part.get("type") == "text": + parts.append(part.get("text", "")) + elif isinstance(part, str): + parts.append(part) + return "\n".join(parts) + return "" + + +def iter_jsonl(path: Path): + """Yield parsed JSON objects from a JSONL file, skipping bad lines.""" + try: + with path.open("r", encoding="utf-8", errors="replace") as fh: + for line in fh: + line = line.strip() + if not line: + continue + try: + yield json.loads(line) + except json.JSONDecodeError: + continue + except OSError: + return + + +def mine_session(path: Path) -> list[dict]: + """Return a list of IMPLICIT_FEEDBACK event dicts for one session file.""" + events: list[dict] = [] + for msg in iter_jsonl(path): + text = extract_user_text(msg) + if not text: + continue + # Skip the boilerplate scheduled-task prompts and system-injected payloads + lowered = text[:200].lower() + if " int: + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument( + "--commit", + action="store_true", + help="Append events to live brain events.jsonl (default: shadow file only)", + ) + parser.add_argument( + "--dry-run", + action="store_true", + help="Report counts only; do not write any file (overrides --commit)", + ) + parser.add_argument( + "--project", + default=None, + help="Only scan one project dir (e.g. C--Users-olive-OneDrive-Desktop-Sprites-Work)", + ) + parser.add_argument( + "--projects-root", + default=str(PROJECTS_ROOT), + help=f"Override transcript root (default: {PROJECTS_ROOT})", + ) + args = parser.parse_args() + + root = Path(args.projects_root) + if not root.exists(): + print(f"[err] transcript root not found: {root}", file=sys.stderr) + return 1 + + project_dirs: list[Path] + if args.project: + project_dirs = [root / args.project] + else: + project_dirs = [p for p in root.iterdir() if p.is_dir()] + + total_sessions = 0 + total_events: list[dict] = [] + per_project: Counter = Counter() + per_signal: Counter = Counter() + + for pd in project_dirs: + if not pd.exists(): + print(f"[warn] skip missing {pd}", file=sys.stderr) + continue + jsonl_files = sorted(pd.glob("*.jsonl")) + for jf in jsonl_files: + total_sessions += 1 + evs = mine_session(jf) + for ev in evs: + total_events.append(ev) + per_project[pd.name] += 1 + payload = json.loads(ev["data"]) + for sig in payload["signals"].split(","): + per_signal[sig] += 1 + + # ── Report ── + print(f"\n=== mine_cc_transcripts summary ===") + print(f"Sessions scanned: {total_sessions}") + print(f"Events extracted: {len(total_events)}") + print("\nBy signal:") + for sig, n in per_signal.most_common(): + print(f" {sig:10s} {n}") + print("\nBy project (top 10):") + for proj, n in per_project.most_common(10): + print(f" {n:5d} {proj}") + + if args.dry_run or (not args.commit): + # Default behavior = write shadow file, don't touch live events + if not args.dry_run: + SHADOW_EVENTS.parent.mkdir(parents=True, exist_ok=True) + with SHADOW_EVENTS.open("w", encoding="utf-8") as fh: + for ev in total_events: + fh.write(json.dumps(ev, ensure_ascii=False) + "\n") + print(f"\n[dry] wrote {len(total_events)} events -> {SHADOW_EVENTS}") + print("[dry] review, then re-run with --commit to merge into live brain") + else: + print("\n[dry-run] no file written") + return 0 + + # --commit: append to live events + if not LIVE_EVENTS.exists(): + print(f"[err] live events.jsonl not found: {LIVE_EVENTS}", file=sys.stderr) + return 2 + with LIVE_EVENTS.open("a", encoding="utf-8") as fh: + for ev in total_events: + fh.write(json.dumps(ev, ensure_ascii=False) + "\n") + print(f"\n[commit] appended {len(total_events)} events -> {LIVE_EVENTS}") + print("[commit] run graduation sweep next: python brain/scripts/graduation_sweep.py") + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/brain/scripts/mirofish_config_a.json b/Sprites/brain/scripts/mirofish_config_a.json similarity index 100% rename from brain/scripts/mirofish_config_a.json rename to Sprites/brain/scripts/mirofish_config_a.json diff --git a/brain/scripts/mirofish_config_b.json b/Sprites/brain/scripts/mirofish_config_b.json similarity index 100% rename from brain/scripts/mirofish_config_b.json rename to Sprites/brain/scripts/mirofish_config_b.json diff --git a/brain/scripts/mirofish_sim.py b/Sprites/brain/scripts/mirofish_sim.py similarity index 100% rename from brain/scripts/mirofish_sim.py rename to Sprites/brain/scripts/mirofish_sim.py diff --git a/brain/scripts/optimization_runner.py b/Sprites/brain/scripts/optimization_runner.py similarity index 100% rename from brain/scripts/optimization_runner.py rename to Sprites/brain/scripts/optimization_runner.py