@jlongo78/agent-spaces 0.7.5 → 0.7.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.next/standalone/.claude/settings.local.json +55 -0
- package/.next/standalone/.claude/spaces-env.json +1 -0
- package/.next/standalone/.next/BUILD_ID +1 -1
- package/.next/standalone/.next/app-path-routes-manifest.json +2 -1
- package/.next/standalone/.next/build-manifest.json +5 -5
- package/.next/standalone/.next/prerender-manifest.json +27 -3
- package/.next/standalone/.next/required-server-files.json +19 -19
- package/.next/standalone/.next/routes-manifest.json +6 -0
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/admin/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/admin/users/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/analytics/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/analytics/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/cortex/page/react-loadable-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/cortex/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/cortex/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/network/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/network/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/sessions/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/settings/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/terminal/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/pane/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/(desktop)/terminal/remote/[nodeId]/[workspaceId]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/(desktop)/workspaces/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_global-error/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_global-error.html +2 -2
- package/.next/standalone/.next/server/app/_global-error.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/_not-found.html +1 -1
- package/.next/standalone/.next/server/app/_not-found.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.html +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.rsc +7 -6
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_full.segment.rsc +7 -6
- package/.next/standalone/.next/server/app/admin/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.html +1 -1
- package/.next/standalone/.next/server/app/admin/users.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin/users.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap/admin.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/admin/users.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/admin/users.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.html +1 -1
- package/.next/standalone/.next/server/app/analytics.rsc +3 -3
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap/analytics.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/analytics.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/analytics.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/analytics.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/api/analytics/overview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/bulk/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/config/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/assess/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/publish/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/refine/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/review/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/curation/seed/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/export/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/pending/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/resolve/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/federation/teach/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/edges/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/entities/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/entities/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/graph/populate/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/import/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/import/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/ingest/bootstrap/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/ingest/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/knowledge/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/knowledge/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/lobes/share/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/marketplace/browse/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/marketplace/preview/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/mcp/call/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/mcp/tools/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/settings/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/status/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/timeline/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/usage/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/cortex/workspace/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/events/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/folders/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/handshake/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/network/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/panes/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/projects/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/search/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/chat/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/sync/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tags/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/tier/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/[key]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/context/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/[msgId]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/messages/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/[id]/sessions/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/api/workspaces/route.js.nft.json +1 -1
- package/.next/standalone/.next/server/app/cortex.html +1 -1
- package/.next/standalone/.next/server/app/cortex.rsc +3 -3
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap/cortex.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/cortex.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/cortex.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/cortex.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/login.html +1 -1
- package/.next/standalone/.next/server/app/login.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/login.segments/login/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/login.segments/login.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/m/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/m/projects/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/projects.html +1 -1
- package/.next/standalone/.next/server/app/m/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/projects.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions/[id]/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/m/sessions/[id]/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/m/sessions/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/sessions.html +1 -1
- package/.next/standalone/.next/server/app/m/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/sessions.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/m/settings/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/settings.html +1 -1
- package/.next/standalone/.next/server/app/m/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/settings.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal/page/build-manifest.json +3 -3
- package/.next/standalone/.next/server/app/m/terminal/page_client-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/app/m/terminal.html +1 -1
- package/.next/standalone/.next/server/app/m/terminal.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m/terminal.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.html +1 -1
- package/.next/standalone/.next/server/app/m.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/m.segments/m/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/m.segments/m.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.html +1 -1
- package/.next/standalone/.next/server/app/network.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap/network.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/network.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/network.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.html +1 -1
- package/.next/standalone/.next/server/app/projects.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap/projects.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/projects.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/projects.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.html +1 -1
- package/.next/standalone/.next/server/app/sessions.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap/sessions.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/sessions.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/sessions.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.html +1 -1
- package/.next/standalone/.next/server/app/settings.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap/settings.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/settings.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/settings.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.html +1 -1
- package/.next/standalone/.next/server/app/terminal.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal/__PAGE__.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap/terminal.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_full.segment.rsc +3 -3
- package/.next/standalone/.next/server/app/terminal.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/terminal.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/terminal.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/vr/page/app-paths-manifest.json +3 -0
- package/.next/standalone/.next/server/app/vr/page/build-manifest.json +18 -0
- package/.next/standalone/.next/server/app/vr/page/next-font-manifest.json +11 -0
- package/.next/standalone/.next/server/app/vr/page/react-loadable-manifest.json +11 -0
- package/.next/standalone/.next/server/app/vr/page/server-reference-manifest.json +4 -0
- package/.next/standalone/.next/server/app/vr/page.js +17 -0
- package/.next/standalone/.next/server/app/vr/page.js.map +5 -0
- package/.next/standalone/.next/server/app/vr/page.js.nft.json +1 -0
- package/.next/standalone/.next/server/app/vr/page_client-reference-manifest.js +2 -0
- package/.next/standalone/.next/server/app/vr.html +1 -0
- package/.next/standalone/.next/server/app/vr.meta +15 -0
- package/.next/standalone/.next/server/app/vr.rsc +21 -0
- package/.next/standalone/.next/server/app/vr.segments/_full.segment.rsc +21 -0
- package/.next/standalone/.next/server/app/vr.segments/_head.segment.rsc +6 -0
- package/.next/standalone/.next/server/app/vr.segments/_index.segment.rsc +6 -0
- package/.next/standalone/.next/server/app/vr.segments/_tree.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/vr.segments/vr/__PAGE__.segment.rsc +9 -0
- package/.next/standalone/.next/server/app/vr.segments/vr.segment.rsc +4 -0
- package/.next/standalone/.next/server/app/workspaces.html +1 -1
- package/.next/standalone/.next/server/app/workspaces.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces/__PAGE__.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap/workspaces.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/!KGRlc2t0b3Ap.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_full.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_head.segment.rsc +1 -1
- package/.next/standalone/.next/server/app/workspaces.segments/_index.segment.rsc +2 -2
- package/.next/standalone/.next/server/app/workspaces.segments/_tree.segment.rsc +2 -2
- package/.next/standalone/.next/server/app-paths-manifest.json +2 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0041efe4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__00bf0ace._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__08a68343._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0add852f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0c113ed0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e1a27e0._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e71d908._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__0e9142f3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__10e47926._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1194f2c1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1665dc78._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__175cbabf._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__19c2d094._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1adae357._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1d359752._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1e8fabeb._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__1f8deca0._.js +8 -8
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__253fdda1._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__28e6434f._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2a386564._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2acbd703._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2acefabb._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__2c20fb38._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__309132cd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__33fec964._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3786d8ae._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3ae92407._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3beda9fe._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__3e3f25a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4619e9bd._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__4a051043._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__50208a5f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__508002e4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5086c373._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5913e097._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5b5f68d2._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5c1f2459._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5ec8c977._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__5f8c694a._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__63cebc6c._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__64d30d4d._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6c54fc2e._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6dc1fb7e._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6e568102._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__6faa04c0._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__727d05f1._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__74a34dc3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__75d12b32._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__7e7250a4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8309e0a4._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__86cc0e2b._.js +6 -6
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8915603e._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__89c2565a._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__8d178ad9._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__93ee06f3._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__9e4c154a._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a1fbc199._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__a9d2e1d3._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ae53d343._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b3a04cef._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b4270b77._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b6b6ce60._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__b9545dd9._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c200e21a._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c3c74ca4._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__c88b63f7._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cba5f007._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cbf4ceb0._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cefdba2f._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__cf9e82bb._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d15515e3._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d2897392._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d3b2d856._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d73273ca._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__d8417eb6._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__db4726bc._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc2a55de._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__dc6e2e5f._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e0d4690b._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e3ecfd17._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e678dd53._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__e9223f55._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ea630076._.js +3 -3
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__eb8acb65._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f26ca49d._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f33e1101._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f3a4c668._.js +1 -1
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__f515f865._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fceb5d60._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__fed41403._.js +2 -2
- package/.next/standalone/.next/server/chunks/[root-of-the-server]__ff2e98c2._.js +2 -2
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_339169c8.js +1 -1
- package/.next/standalone/.next/server/chunks/node_modules_next_dist_esm_build_templates_app-route_97dac613.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__0d8d81ca._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1425c64f._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__1d2ce8f1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__31137509._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3633a587._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__3c79441b._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__4ca0f26b._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__5b90d3ad._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__62a0b363._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__66aca5d4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__68205a46._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__69fd2efa._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__85dcf0f7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__8c53a5da._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__aecb1873._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b02cd143._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__b9bcde11._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__cac90169._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__d25de2f0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__e2f86be8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__ee626b5b._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f39a9e98._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f3c566cd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/[root-of-the-server]__f76aa221._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_149d7fd4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_2e0dd6a7._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_3cd2355c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_3d206597._.js +4 -0
- package/.next/standalone/.next/server/chunks/ssr/_47cc9af0._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_5cf334fd._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_7082788b._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_7154d8ae._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_75bb1b9a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/{_aeeff784._.js → _81abf587._.js} +2 -2
- package/.next/standalone/.next/server/chunks/ssr/_8acf81e2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_8c36feb8._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_91e9bb86._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_ac4c1838._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_ad8515fc._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_b1f49e81._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_c0fe7614._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_d4825f5a._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_da10a9f4._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_db0abd0a._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_dee5d4a1._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_ef482c0c._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_efe43d2f._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_f4a4e116._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_f4d525d2._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/_f4e57187._.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/_next-internal_server_app_vr_page_actions_3fb70d92.js +3 -0
- package/.next/standalone/.next/server/chunks/ssr/node_modules_32f9d62f._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_build_templates_app-page_02f39477.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/node_modules_next_dist_esm_eedfc1fd._.js +1 -1
- package/.next/standalone/.next/server/chunks/ssr/src_40fa36ce._.js +7 -0
- package/.next/standalone/.next/server/chunks/ssr/src_app_(desktop)_cortex_page_tsx_0f33d8b3._.js +3 -0
- package/.next/standalone/.next/server/edge/chunks/[root-of-the-server]__32a0045c._.js +1 -1
- package/.next/standalone/.next/server/edge/chunks/_d73df637._.js +1 -1
- package/.next/standalone/.next/server/middleware-build-manifest.js +3 -3
- package/.next/standalone/.next/server/middleware-manifest.json +5 -5
- package/.next/standalone/.next/server/next-font-manifest.js +1 -1
- package/.next/standalone/.next/server/next-font-manifest.json +4 -0
- package/.next/standalone/.next/server/pages/404.html +1 -1
- package/.next/standalone/.next/server/pages/500.html +2 -2
- package/.next/standalone/.next/server/server-reference-manifest.js +1 -1
- package/.next/standalone/.next/server/server-reference-manifest.json +1 -1
- package/.next/standalone/.next/static/chunks/045c83caa4d15373.js +1 -0
- package/.next/standalone/.next/static/chunks/07ea09e6024a523b.js +1 -0
- package/.next/standalone/.next/static/chunks/232d8aae4fefab70.js +1 -0
- package/.next/standalone/.next/static/chunks/2ad22562bb37ecad.js +1011 -0
- package/.next/standalone/.next/static/chunks/396eac60f496f178.js +1 -0
- package/.next/standalone/.next/static/chunks/{a4e5c700421eaa46.js → 412140a02893327a.js} +1 -1
- package/.next/standalone/.next/static/chunks/481cc11ae80b08b1.js +1 -0
- package/.next/standalone/.next/static/chunks/5325351ef49cb65f.js +1 -0
- package/.next/standalone/.next/static/chunks/559735e598ca3cbb.js +1 -0
- package/.next/standalone/.next/static/chunks/59c63d5af5cf3daf.js +1 -0
- package/.next/standalone/.next/static/chunks/5d5d7b0095dd52ae.js +1 -0
- package/.next/standalone/.next/static/chunks/6ae575967d091df4.js +1 -0
- package/.next/standalone/.next/static/chunks/7a7c0d9d875332a3.js +1 -0
- package/.next/standalone/.next/static/chunks/7f8455bb855a6c84.js +1 -0
- package/.next/standalone/.next/static/chunks/898f380eba90427a.js +1 -0
- package/.next/standalone/.next/static/chunks/95339e55722bb4ca.js +5 -0
- package/.next/standalone/.next/static/chunks/9cd594813c539df9.js +1 -0
- package/.next/standalone/.next/static/chunks/ad1423eed05d129b.js +1 -0
- package/.next/standalone/.next/static/chunks/ae7b146884c67d2a.js +1 -0
- package/.next/standalone/.next/static/chunks/b84072d72aa86417.js +1 -0
- package/.next/standalone/.next/static/chunks/c1a95aebf6725f64.css +3 -0
- package/.next/standalone/.next/static/chunks/c515eb77d9410aa0.js +5 -0
- package/.next/standalone/.next/static/chunks/{9899cf4c2bdbe61d.js → d9ae203a7f123546.js} +2 -2
- package/.next/standalone/.next/static/chunks/e23f20b51a75a5bb.js +757 -0
- package/.next/standalone/.next/static/chunks/fdc09bd135846960.js +1 -0
- package/.next/standalone/.next/static/chunks/ff0196911449e745.js +1 -0
- package/.next/standalone/.next/static/chunks/{turbopack-4c21186b79fb4c10.js → turbopack-e1a0994ed4af988c.js} +1 -1
- package/.next/standalone/.spaces/cortex-context.md +70 -0
- package/.next/standalone/bin/cortex-hook.sh +62 -62
- package/.next/standalone/bin/cortex-mcp.js +60 -60
- package/.next/standalone/docs/superpowers/plans/2026-03-13-cortex-wiring.md +1387 -1387
- package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-entity-graph.md +1923 -1923
- package/.next/standalone/docs/superpowers/plans/2026-03-14-cortex-v2-knowledge-evolution.md +1113 -1113
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-boundary-engine.md +853 -853
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-context-engine.md +1274 -1274
- package/.next/standalone/docs/superpowers/plans/2026-03-15-cortex-v2-signal-ingestion.md +933 -933
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-lobes.md +1080 -1080
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-gravity-system.md +768 -768
- package/.next/standalone/docs/superpowers/plans/2026-03-16-cortex-v2-ui.md +1108 -1108
- package/.next/standalone/docs/superpowers/plans/2026-03-18-cortex-ui-integration.md +1846 -1846
- package/.next/standalone/docs/superpowers/specs/2026-03-13-cortex-wiring-design.md +268 -268
- package/.next/standalone/docs/superpowers/specs/2026-03-14-cortex-v2-design.md +623 -623
- package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-lobes-design.md +263 -263
- package/.next/standalone/docs/superpowers/specs/2026-03-16-cortex-v2-ui-design.md +240 -240
- package/.next/standalone/docs/superpowers/specs/2026-03-18-cortex-ui-integration-design.md +341 -341
- package/.next/standalone/node_modules/@img/sharp-win32-x64/lib/sharp-win32-x64.node +0 -0
- package/.next/standalone/node_modules/@img/{sharp-linux-x64 → sharp-win32-x64}/package.json +39 -46
- package/.next/standalone/package.json +103 -102
- package/.next/standalone/server.js +1 -1
- package/.next/standalone/src/app/(desktop)/cortex/page.tsx +78 -78
- package/.next/standalone/src/app/api/cortex/context/route.ts +78 -78
- package/.next/standalone/src/app/api/cortex/curation/assess/route.ts +27 -27
- package/.next/standalone/src/app/api/cortex/curation/publish/route.ts +23 -23
- package/.next/standalone/src/app/api/cortex/curation/refine/route.ts +23 -23
- package/.next/standalone/src/app/api/cortex/curation/review/route.ts +29 -29
- package/.next/standalone/src/app/api/cortex/curation/seed/route.ts +23 -23
- package/.next/standalone/src/app/api/cortex/export/route.ts +40 -40
- package/.next/standalone/src/app/api/cortex/federation/pending/route.ts +20 -20
- package/.next/standalone/src/app/api/cortex/federation/resolve/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/federation/search/route.ts +35 -35
- package/.next/standalone/src/app/api/cortex/federation/teach/route.ts +76 -76
- package/.next/standalone/src/app/api/cortex/graph/edges/route.ts +112 -112
- package/.next/standalone/src/app/api/cortex/graph/entities/[id]/route.ts +73 -73
- package/.next/standalone/src/app/api/cortex/graph/entities/route.ts +75 -75
- package/.next/standalone/src/app/api/cortex/graph/populate/route.ts +203 -203
- package/.next/standalone/src/app/api/cortex/import/route.ts +75 -75
- package/.next/standalone/src/app/api/cortex/import/status/route.ts +15 -15
- package/.next/standalone/src/app/api/cortex/ingest/bootstrap/route.ts +29 -29
- package/.next/standalone/src/app/api/cortex/ingest/status/route.ts +15 -15
- package/.next/standalone/src/app/api/cortex/knowledge/[id]/route.ts +91 -91
- package/.next/standalone/src/app/api/cortex/knowledge/route.ts +93 -93
- package/.next/standalone/src/app/api/cortex/lobes/[id]/route.ts +67 -67
- package/.next/standalone/src/app/api/cortex/lobes/route.ts +22 -22
- package/.next/standalone/src/app/api/cortex/lobes/share/route.ts +80 -80
- package/.next/standalone/src/app/api/cortex/marketplace/browse/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/marketplace/preview/route.ts +46 -46
- package/.next/standalone/src/app/api/cortex/mcp/call/route.ts +11 -11
- package/.next/standalone/src/app/api/cortex/mcp/tools/route.ts +6 -6
- package/.next/standalone/src/app/api/cortex/search/route.ts +43 -43
- package/.next/standalone/src/app/api/cortex/settings/route.ts +33 -33
- package/.next/standalone/src/app/api/cortex/status/route.ts +169 -169
- package/.next/standalone/src/app/api/cortex/timeline/route.ts +42 -42
- package/.next/standalone/src/app/api/cortex/usage/route.ts +31 -31
- package/.next/standalone/src/app/api/cortex/workspace/[id]/context/route.ts +41 -41
- package/.next/standalone/src/components/cortex/constants.ts +29 -29
- package/.next/standalone/src/components/cortex/cortex-dashboard.tsx +304 -304
- package/.next/standalone/src/components/cortex/cortex-indicator.tsx +44 -44
- package/.next/standalone/src/components/cortex/cortex-panel.tsx +140 -140
- package/.next/standalone/src/components/cortex/cortex-settings.tsx +221 -221
- package/.next/standalone/src/components/cortex/curation-tab.tsx +810 -810
- package/.next/standalone/src/components/cortex/entity-detail.tsx +101 -101
- package/.next/standalone/src/components/cortex/entity-graph.tsx +382 -382
- package/.next/standalone/src/components/cortex/import-dialog.tsx +212 -212
- package/.next/standalone/src/components/cortex/injection-badge.tsx +72 -72
- package/.next/standalone/src/components/cortex/knowledge-card.tsx +109 -109
- package/.next/standalone/src/components/cortex/knowledge-tab.tsx +158 -158
- package/.next/standalone/src/components/cortex/lobe-settings.tsx +215 -215
- package/.next/standalone/src/components/cortex/marketplace-card.tsx +126 -126
- package/.next/standalone/src/components/cortex/marketplace-tab.tsx +113 -113
- package/.next/standalone/src/lib/cortex/config.ts +40 -40
- package/.next/standalone/src/lib/cortex/debug.ts +10 -10
- package/.next/standalone/src/lib/cortex/distillation/usage-store.ts +18 -18
- package/.next/standalone/src/lib/cortex/graph/resolver.ts +10 -10
- package/.next/standalone/src/lib/cortex/graph/types.ts +22 -22
- package/.next/standalone/src/lib/cortex/index.ts +56 -56
- package/.next/standalone/src/lib/cortex/ingestion/bootstrap.ts +14 -14
- package/.next/standalone/src/lib/cortex/knowledge/compat.ts +14 -14
- package/.next/standalone/src/lib/cortex/knowledge/contradiction.ts +10 -10
- package/.next/standalone/src/lib/cortex/knowledge/types.ts +67 -67
- package/.next/standalone/src/lib/cortex/lobes/config.ts +16 -16
- package/.next/standalone/src/lib/cortex/lobes/resolver.ts +8 -8
- package/.next/standalone/src/lib/cortex/lobes/shares.ts +14 -14
- package/.next/standalone/src/lib/cortex/mcp/server.ts +8 -8
- package/.next/standalone/src/lib/cortex/portability/exporter.ts +6 -6
- package/.next/standalone/src/lib/cortex/portability/importer.ts +10 -10
- package/.next/standalone/src/lib/cortex/retrieval/context-engine.ts +10 -10
- package/.next/standalone/src/lib/cortex/types.ts +39 -39
- package/.next/standalone/tsconfig.json +34 -34
- package/LICENSE +661 -661
- package/README.md +131 -131
- package/bin/cortex-hook.sh +62 -62
- package/bin/cortex-mcp.js +60 -60
- package/bin/fix-standalone-externals.js +79 -79
- package/bin/lib/auto-setup.js +110 -110
- package/bin/mdns-service.js +171 -171
- package/bin/postinstall.js +35 -35
- package/bin/setup-admin.js +195 -195
- package/bin/spaces-dev.js +208 -208
- package/bin/spaces-install.js +599 -599
- package/bin/spaces-reset-totp.js +50 -50
- package/bin/spaces-service.js +1020 -1020
- package/bin/spaces-setup.js +253 -253
- package/bin/spaces.js +776 -776
- package/bin/ssh-auth-keys.sh +68 -68
- package/bin/terminal-server.js +1683 -1649
- package/package.json +103 -102
- package/.next/standalone/.next/server/chunks/ssr/_078dd64d._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_701606d5._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_72b1de37._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/_950142a4._.js +0 -3
- package/.next/standalone/.next/server/chunks/ssr/src_components_terminal_terminal-pane_tsx_803c5e2c._.js +0 -7
- package/.next/standalone/.next/static/chunks/18f168665aef1aab.js +0 -1
- package/.next/standalone/.next/static/chunks/25b7a243a404a1a7.js +0 -1
- package/.next/standalone/.next/static/chunks/4a50d2a3e9bc9b41.js +0 -1
- package/.next/standalone/.next/static/chunks/6c78a1dfa7ec2959.css +0 -3
- package/.next/standalone/.next/static/chunks/7e0091ab6c5ee8bd.js +0 -1
- package/.next/standalone/.next/static/chunks/869f562dc32e55f4.js +0 -1
- package/.next/standalone/.next/static/chunks/8b3f4572fec83caa.js +0 -5
- package/.next/standalone/.next/static/chunks/8d5419afc4b9116b.js +0 -1
- package/.next/standalone/.next/static/chunks/9b2c5451f0b67975.js +0 -1
- package/.next/standalone/.next/static/chunks/ac339e970df82fa5.js +0 -5
- package/.next/standalone/.next/static/chunks/e7772d64463868eb.js +0 -1
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/README.md +0 -46
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/glib-2.0/include/glibconfig.h +0 -221
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/index.js +0 -1
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linux-x64/package.json +0 -42
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/README.md +0 -46
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/glib-2.0/include/glibconfig.h +0 -221
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/index.js +0 -1
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/lib/libvips-cpp.so.8.17.3 +0 -0
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/package.json +0 -42
- package/.next/standalone/node_modules/@img/sharp-libvips-linuxmusl-x64/versions.json +0 -30
- package/.next/standalone/node_modules/@img/sharp-linux-x64/lib/sharp-linux-x64.node +0 -0
- package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/lib/sharp-linuxmusl-x64.node +0 -0
- package/.next/standalone/node_modules/@img/sharp-linuxmusl-x64/package.json +0 -46
- /package/.next/standalone/.next/static/{77VYbwIoyxFNr5xevTrCu → ncDe4k4gvD0788HAnq_3G}/_buildManifest.js +0 -0
- /package/.next/standalone/.next/static/{77VYbwIoyxFNr5xevTrCu → ncDe4k4gvD0788HAnq_3G}/_clientMiddlewareManifest.json +0 -0
- /package/.next/standalone/.next/static/{77VYbwIoyxFNr5xevTrCu → ncDe4k4gvD0788HAnq_3G}/_ssgManifest.js +0 -0
- /package/.next/standalone/node_modules/@img/{sharp-libvips-linux-x64 → sharp-win32-x64}/versions.json +0 -0
|
@@ -1,933 +1,933 @@
|
|
|
1
|
-
# Cortex v2 — Pillar 5: Observable Signal Ingestion
|
|
2
|
-
|
|
3
|
-
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
-
|
|
5
|
-
**Goal:** Build the adapter-based signal ingestion framework and implement the 3 core adapters (Conversations, Git History, Documents). The remaining 4 adapters (PR Reviews, Test Signals, Deployment, Behavioral) are deferred — they require external webhooks/APIs and can be added incrementally since the adapter interface is extensible.
|
|
6
|
-
|
|
7
|
-
**Architecture:** A new `src/lib/cortex/signals/` module with a `SignalPipeline` class that consumes `SignalEnvelope` objects from any adapter. Each adapter implements a `SignalAdapter` interface with `extract()` (AsyncIterable) and `healthCheck()`. The existing `IngestionPipeline` is wrapped as the Conversation adapter for backward compat. The Git adapter parses `git log` output. The Document adapter watches `docs/**` for ADRs/READMEs.
|
|
8
|
-
|
|
9
|
-
**Tech Stack:** TypeScript, vitest, child_process (for git), fs (for docs)
|
|
10
|
-
|
|
11
|
-
**Spec:** `docs/superpowers/specs/2026-03-14-cortex-v2-design.md` — Pillar 5
|
|
12
|
-
|
|
13
|
-
**Depends on:** Pillars 1-4 (all completed)
|
|
14
|
-
|
|
15
|
-
**Deferred to future:** PR Review adapter, Test Signal adapter, Deployment adapter, Behavioral Inference adapter
|
|
16
|
-
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
## File Structure
|
|
20
|
-
|
|
21
|
-
```
|
|
22
|
-
New files:
|
|
23
|
-
├── src/lib/cortex/signals/types.ts — SignalEnvelope, SignalAdapter interfaces
|
|
24
|
-
├── src/lib/cortex/signals/pipeline.ts — Unified SignalPipeline
|
|
25
|
-
├── src/lib/cortex/signals/adapters/conversation.ts — Wraps existing IngestionPipeline
|
|
26
|
-
├── src/lib/cortex/signals/adapters/git.ts — Git history adapter
|
|
27
|
-
├── src/lib/cortex/signals/adapters/document.ts — Document/ADR adapter
|
|
28
|
-
├── src/lib/cortex/signals/index.ts — Barrel export
|
|
29
|
-
|
|
30
|
-
Test files:
|
|
31
|
-
├── tests/lib/cortex/signals/pipeline.test.ts
|
|
32
|
-
├── tests/lib/cortex/signals/adapters/git.test.ts
|
|
33
|
-
├── tests/lib/cortex/signals/adapters/document.test.ts
|
|
34
|
-
```
|
|
35
|
-
|
|
36
|
-
---
|
|
37
|
-
|
|
38
|
-
## Chunk 1: Signal Types and Unified Pipeline
|
|
39
|
-
|
|
40
|
-
### Task 1: Signal types
|
|
41
|
-
|
|
42
|
-
**Files:**
|
|
43
|
-
- Create: `src/lib/cortex/signals/types.ts`
|
|
44
|
-
|
|
45
|
-
- [ ] **Step 1: Create types file**
|
|
46
|
-
|
|
47
|
-
```typescript
|
|
48
|
-
// src/lib/cortex/signals/types.ts
|
|
49
|
-
import type { KnowledgeType, SensitivityClass, Origin, EntityLink } from '../knowledge/types';
|
|
50
|
-
|
|
51
|
-
export interface SignalEnvelope {
|
|
52
|
-
text: string;
|
|
53
|
-
origin: Origin;
|
|
54
|
-
entities: EntityLink[];
|
|
55
|
-
suggested_type: KnowledgeType;
|
|
56
|
-
suggested_sensitivity: SensitivityClass;
|
|
57
|
-
raw_metadata: Record<string, unknown>;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
export interface SignalAdapter {
|
|
61
|
-
name: string;
|
|
62
|
-
schedule: 'realtime' | 'polling' | 'webhook' | 'cron';
|
|
63
|
-
extract(): AsyncIterable<SignalEnvelope>;
|
|
64
|
-
healthCheck(): Promise<boolean>;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
export interface IngestResult {
|
|
68
|
-
accepted: number;
|
|
69
|
-
skipped: number; // dedup
|
|
70
|
-
errors: string[];
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/**
|
|
74
|
-
* Graph edge update carried in raw_metadata.
|
|
75
|
-
* Adapters can include these to update the entity graph during ingestion.
|
|
76
|
-
*/
|
|
77
|
-
export interface EdgeUpdate {
|
|
78
|
-
source_id: string;
|
|
79
|
-
target_id: string;
|
|
80
|
-
relation: string;
|
|
81
|
-
weight_delta: number; // increment (not absolute)
|
|
82
|
-
}
|
|
83
|
-
```
|
|
84
|
-
|
|
85
|
-
- [ ] **Step 2: Commit**
|
|
86
|
-
|
|
87
|
-
```bash
|
|
88
|
-
git add src/lib/cortex/signals/types.ts
|
|
89
|
-
git commit -m "feat(cortex): add signal ingestion type definitions"
|
|
90
|
-
```
|
|
91
|
-
|
|
92
|
-
---
|
|
93
|
-
|
|
94
|
-
### Task 2: Unified SignalPipeline
|
|
95
|
-
|
|
96
|
-
**Files:**
|
|
97
|
-
- Create: `src/lib/cortex/signals/pipeline.ts`
|
|
98
|
-
- Create: `tests/lib/cortex/signals/pipeline.test.ts`
|
|
99
|
-
|
|
100
|
-
- [ ] **Step 1: Write failing tests**
|
|
101
|
-
|
|
102
|
-
```typescript
|
|
103
|
-
// tests/lib/cortex/signals/pipeline.test.ts
|
|
104
|
-
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
105
|
-
import { SignalPipeline } from '@/lib/cortex/signals/pipeline';
|
|
106
|
-
import type { SignalEnvelope } from '@/lib/cortex/signals/types';
|
|
107
|
-
|
|
108
|
-
const mockStore = {
|
|
109
|
-
add: vi.fn().mockResolvedValue(undefined),
|
|
110
|
-
search: vi.fn().mockResolvedValue([]),
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
const mockEmbedding = {
|
|
114
|
-
embed: vi.fn().mockResolvedValue([[0.1, 0.2, 0.3]]),
|
|
115
|
-
dimensions: 3,
|
|
116
|
-
name: 'mock',
|
|
117
|
-
init: vi.fn(),
|
|
118
|
-
};
|
|
119
|
-
|
|
120
|
-
const mockGraph = {
|
|
121
|
-
createEdge: vi.fn(),
|
|
122
|
-
incrementEdgeWeight: vi.fn(),
|
|
123
|
-
getEntity: vi.fn().mockReturnValue(null),
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
const mockResolver = {
|
|
127
|
-
extractEntities: vi.fn().mockReturnValue([]),
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
function makeEnvelope(overrides: Partial<SignalEnvelope> = {}): SignalEnvelope {
|
|
131
|
-
return {
|
|
132
|
-
text: 'Fix auth timeout by increasing pool size',
|
|
133
|
-
origin: { source_type: 'git_commit', source_ref: 'abc123', creator_entity_id: 'person-alice' },
|
|
134
|
-
entities: [],
|
|
135
|
-
suggested_type: 'error_fix',
|
|
136
|
-
suggested_sensitivity: 'internal',
|
|
137
|
-
raw_metadata: {},
|
|
138
|
-
...overrides,
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
describe('SignalPipeline', () => {
|
|
143
|
-
let pipeline: SignalPipeline;
|
|
144
|
-
|
|
145
|
-
beforeEach(() => {
|
|
146
|
-
vi.clearAllMocks();
|
|
147
|
-
pipeline = new SignalPipeline({
|
|
148
|
-
store: mockStore as any,
|
|
149
|
-
embedding: mockEmbedding as any,
|
|
150
|
-
graph: mockGraph as any,
|
|
151
|
-
resolver: mockResolver as any,
|
|
152
|
-
});
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('ingests a signal envelope and stores it', async () => {
|
|
156
|
-
const result = await pipeline.ingest(makeEnvelope());
|
|
157
|
-
expect(result.accepted).toBe(1);
|
|
158
|
-
expect(mockEmbedding.embed).toHaveBeenCalledWith(['Fix auth timeout by increasing pool size']);
|
|
159
|
-
expect(mockStore.add).toHaveBeenCalledTimes(1);
|
|
160
|
-
});
|
|
161
|
-
|
|
162
|
-
it('uses suggested_type from envelope', async () => {
|
|
163
|
-
await pipeline.ingest(makeEnvelope({ suggested_type: 'decision' }));
|
|
164
|
-
const addCall = mockStore.add.mock.calls[0];
|
|
165
|
-
expect(addCall[1].type).toBe('decision');
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
it('auto-classifies sensitivity (most restrictive wins)', async () => {
|
|
169
|
-
// Text contains API key → confidential, overrides suggested 'internal'
|
|
170
|
-
await pipeline.ingest(makeEnvelope({
|
|
171
|
-
text: 'Set API_KEY=sk-ant-abc123 in production',
|
|
172
|
-
suggested_sensitivity: 'internal',
|
|
173
|
-
}));
|
|
174
|
-
const addCall = mockStore.add.mock.calls[0];
|
|
175
|
-
expect(addCall[1].sensitivity).toBe('confidential');
|
|
176
|
-
});
|
|
177
|
-
|
|
178
|
-
it('keeps suggested_sensitivity when more restrictive than auto-classification', async () => {
|
|
179
|
-
await pipeline.ingest(makeEnvelope({
|
|
180
|
-
text: 'General technical note', // auto-classifies as internal
|
|
181
|
-
suggested_sensitivity: 'restricted', // more restrictive
|
|
182
|
-
}));
|
|
183
|
-
const addCall = mockStore.add.mock.calls[0];
|
|
184
|
-
expect(addCall[1].sensitivity).toBe('restricted');
|
|
185
|
-
});
|
|
186
|
-
|
|
187
|
-
it('processes edge updates from raw_metadata', async () => {
|
|
188
|
-
await pipeline.ingest(makeEnvelope({
|
|
189
|
-
raw_metadata: {
|
|
190
|
-
edge_updates: [
|
|
191
|
-
{ source_id: 'person-alice', target_id: 'topic-auth', relation: 'expert_in', weight_delta: 0.05 },
|
|
192
|
-
],
|
|
193
|
-
},
|
|
194
|
-
}));
|
|
195
|
-
expect(mockGraph.incrementEdgeWeight).toHaveBeenCalledWith(
|
|
196
|
-
'person-alice', 'topic-auth', 'expert_in', 0.05
|
|
197
|
-
);
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
it('deduplicates by text hash', async () => {
|
|
201
|
-
const envelope = makeEnvelope();
|
|
202
|
-
await pipeline.ingest(envelope);
|
|
203
|
-
const result = await pipeline.ingest(envelope); // same text
|
|
204
|
-
expect(result.skipped).toBe(1);
|
|
205
|
-
expect(result.accepted).toBe(0);
|
|
206
|
-
expect(mockStore.add).toHaveBeenCalledTimes(1); // only first call
|
|
207
|
-
});
|
|
208
|
-
|
|
209
|
-
it('ingests batch of envelopes', async () => {
|
|
210
|
-
const envelopes = [
|
|
211
|
-
makeEnvelope({ text: 'First signal' }),
|
|
212
|
-
makeEnvelope({ text: 'Second signal' }),
|
|
213
|
-
makeEnvelope({ text: 'Third signal' }),
|
|
214
|
-
];
|
|
215
|
-
const result = await pipeline.ingestBatch(envelopes);
|
|
216
|
-
expect(result.accepted).toBe(3);
|
|
217
|
-
expect(mockStore.add).toHaveBeenCalledTimes(3);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
it('handles embedding failures gracefully', async () => {
|
|
221
|
-
mockEmbedding.embed.mockRejectedValueOnce(new Error('embed failed'));
|
|
222
|
-
const result = await pipeline.ingest(makeEnvelope());
|
|
223
|
-
expect(result.errors).toHaveLength(1);
|
|
224
|
-
expect(result.accepted).toBe(0);
|
|
225
|
-
});
|
|
226
|
-
});
|
|
227
|
-
```
|
|
228
|
-
|
|
229
|
-
- [ ] **Step 2: Run tests to verify they fail**
|
|
230
|
-
|
|
231
|
-
Run: `npx vitest run tests/lib/cortex/signals/pipeline.test.ts`
|
|
232
|
-
|
|
233
|
-
- [ ] **Step 3: Implement SignalPipeline**
|
|
234
|
-
|
|
235
|
-
```typescript
|
|
236
|
-
// src/lib/cortex/signals/pipeline.ts
|
|
237
|
-
import { createHash } from 'crypto';
|
|
238
|
-
import type { CortexStore } from '../store';
|
|
239
|
-
import type { EmbeddingProvider } from '../embeddings';
|
|
240
|
-
import type { EntityGraph } from '../graph/entity-graph';
|
|
241
|
-
import type { EntityResolver } from '../graph/resolver';
|
|
242
|
-
import type { KnowledgeUnit } from '../knowledge/types';
|
|
243
|
-
import { classifySensitivity } from '../boundary/classifier';
|
|
244
|
-
import { layerToScope, scopeToLayerKey } from '../knowledge/compat';
|
|
245
|
-
import type { SignalEnvelope, IngestResult, EdgeUpdate } from './types';
|
|
246
|
-
|
|
247
|
-
const SENSITIVITY_PRIORITY: Record<string, number> = {
|
|
248
|
-
public: 0, internal: 1, restricted: 2, confidential: 3,
|
|
249
|
-
};
|
|
250
|
-
|
|
251
|
-
export interface SignalPipelineDeps {
|
|
252
|
-
store: CortexStore;
|
|
253
|
-
embedding: EmbeddingProvider;
|
|
254
|
-
graph: EntityGraph;
|
|
255
|
-
resolver: EntityResolver;
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export class SignalPipeline {
|
|
259
|
-
private hashSet = new Set<string>();
|
|
260
|
-
private deps: SignalPipelineDeps;
|
|
261
|
-
|
|
262
|
-
constructor(deps: SignalPipelineDeps) {
|
|
263
|
-
this.deps = deps;
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
async ingest(envelope: SignalEnvelope): Promise<IngestResult> {
|
|
267
|
-
const result: IngestResult = { accepted: 0, skipped: 0, errors: [] };
|
|
268
|
-
|
|
269
|
-
try {
|
|
270
|
-
// 1. Dedup by text hash
|
|
271
|
-
const hash = createHash('sha256')
|
|
272
|
-
.update(envelope.text.replace(/\s+/g, ' ').trim())
|
|
273
|
-
.digest('hex');
|
|
274
|
-
|
|
275
|
-
if (this.hashSet.has(hash)) {
|
|
276
|
-
result.skipped = 1;
|
|
277
|
-
return result;
|
|
278
|
-
}
|
|
279
|
-
this.hashSet.add(hash);
|
|
280
|
-
|
|
281
|
-
// 2. Sensitivity: most restrictive wins between suggested and auto-classified
|
|
282
|
-
const autoSensitivity = classifySensitivity(envelope.text);
|
|
283
|
-
const suggestedPriority = SENSITIVITY_PRIORITY[envelope.suggested_sensitivity] ?? 1;
|
|
284
|
-
const autoPriority = SENSITIVITY_PRIORITY[autoSensitivity] ?? 1;
|
|
285
|
-
const sensitivity = suggestedPriority >= autoPriority
|
|
286
|
-
? envelope.suggested_sensitivity : autoSensitivity;
|
|
287
|
-
|
|
288
|
-
// 3. Embed
|
|
289
|
-
const [vector] = await this.deps.embedding.embed([envelope.text]);
|
|
290
|
-
|
|
291
|
-
// 4. Build scope from origin
|
|
292
|
-
const scope = layerToScope('personal', null, envelope.origin.creator_entity_id.replace('person-', ''));
|
|
293
|
-
const layerKey = scopeToLayerKey(scope);
|
|
294
|
-
const layer = 'personal' as const; // default; adapters can override via metadata
|
|
295
|
-
|
|
296
|
-
// 5. Build KnowledgeUnit
|
|
297
|
-
const unit: KnowledgeUnit = {
|
|
298
|
-
id: crypto.randomUUID(),
|
|
299
|
-
vector,
|
|
300
|
-
text: envelope.text,
|
|
301
|
-
type: envelope.suggested_type,
|
|
302
|
-
layer,
|
|
303
|
-
workspace_id: (envelope.raw_metadata.workspace_id as number) ?? null,
|
|
304
|
-
session_id: (envelope.raw_metadata.session_id as string) ?? null,
|
|
305
|
-
agent_type: 'claude',
|
|
306
|
-
project_path: (envelope.raw_metadata.project_path as string) ?? null,
|
|
307
|
-
file_refs: (envelope.raw_metadata.file_refs as string[]) ?? [],
|
|
308
|
-
confidence: 0.8,
|
|
309
|
-
created: new Date().toISOString(),
|
|
310
|
-
source_timestamp: new Date().toISOString(),
|
|
311
|
-
stale_score: 0,
|
|
312
|
-
access_count: 0,
|
|
313
|
-
last_accessed: null,
|
|
314
|
-
metadata: { source: envelope.origin.source_type },
|
|
315
|
-
// v2 fields
|
|
316
|
-
scope,
|
|
317
|
-
entity_links: envelope.entities,
|
|
318
|
-
evidence_score: 0.8,
|
|
319
|
-
corroborations: 0,
|
|
320
|
-
contradiction_refs: [],
|
|
321
|
-
sensitivity,
|
|
322
|
-
creator_scope: null,
|
|
323
|
-
origin: envelope.origin,
|
|
324
|
-
propagation_path: [],
|
|
325
|
-
};
|
|
326
|
-
|
|
327
|
-
// 6. Store
|
|
328
|
-
await this.deps.store.add(layerKey, unit);
|
|
329
|
-
result.accepted = 1;
|
|
330
|
-
|
|
331
|
-
// 7. Process edge updates
|
|
332
|
-
const edgeUpdates = (envelope.raw_metadata.edge_updates as EdgeUpdate[]) ?? [];
|
|
333
|
-
for (const update of edgeUpdates) {
|
|
334
|
-
try {
|
|
335
|
-
this.deps.graph.incrementEdgeWeight(
|
|
336
|
-
update.source_id, update.target_id, update.relation as any, update.weight_delta
|
|
337
|
-
);
|
|
338
|
-
} catch {
|
|
339
|
-
// Edge entities may not exist yet, skip
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
} catch (err: any) {
|
|
344
|
-
result.errors.push(err.message);
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
return result;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
async ingestBatch(envelopes: SignalEnvelope[]): Promise<IngestResult> {
|
|
351
|
-
const totals: IngestResult = { accepted: 0, skipped: 0, errors: [] };
|
|
352
|
-
for (const envelope of envelopes) {
|
|
353
|
-
const r = await this.ingest(envelope);
|
|
354
|
-
totals.accepted += r.accepted;
|
|
355
|
-
totals.skipped += r.skipped;
|
|
356
|
-
totals.errors.push(...r.errors);
|
|
357
|
-
}
|
|
358
|
-
return totals;
|
|
359
|
-
}
|
|
360
|
-
}
|
|
361
|
-
```
|
|
362
|
-
|
|
363
|
-
- [ ] **Step 4: Run tests to verify they pass**
|
|
364
|
-
|
|
365
|
-
Run: `npx vitest run tests/lib/cortex/signals/pipeline.test.ts`
|
|
366
|
-
Expected: PASS (8 tests)
|
|
367
|
-
|
|
368
|
-
- [ ] **Step 5: Commit**
|
|
369
|
-
|
|
370
|
-
```bash
|
|
371
|
-
git add src/lib/cortex/signals/pipeline.ts tests/lib/cortex/signals/pipeline.test.ts
|
|
372
|
-
git commit -m "feat(cortex): add unified SignalPipeline for multi-source ingestion"
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
---
|
|
376
|
-
|
|
377
|
-
## Chunk 2: Core Adapters
|
|
378
|
-
|
|
379
|
-
### Task 3: Conversation adapter (wraps existing pipeline)
|
|
380
|
-
|
|
381
|
-
**Files:**
|
|
382
|
-
- Create: `src/lib/cortex/signals/adapters/conversation.ts`
|
|
383
|
-
|
|
384
|
-
- [ ] **Step 1: Implement conversation adapter**
|
|
385
|
-
|
|
386
|
-
This adapter wraps the existing `IngestionPipeline` to produce `SignalEnvelope` objects from Claude Code session transcripts. It does NOT replace the existing pipeline — it wraps it so conversations flow through the unified `SignalPipeline`.
|
|
387
|
-
|
|
388
|
-
```typescript
|
|
389
|
-
// src/lib/cortex/signals/adapters/conversation.ts
|
|
390
|
-
import type { SignalAdapter, SignalEnvelope } from '../types';
|
|
391
|
-
|
|
392
|
-
/**
|
|
393
|
-
* Conversation adapter — wraps the learn hook's output format.
|
|
394
|
-
* This is a "pull" adapter: it doesn't actively extract.
|
|
395
|
-
* Instead, the learn hook POSTs to the knowledge API, and this adapter
|
|
396
|
-
* can be used to convert raw session messages into SignalEnvelopes
|
|
397
|
-
* for batch processing.
|
|
398
|
-
*/
|
|
399
|
-
export class ConversationAdapter implements SignalAdapter {
|
|
400
|
-
name = 'conversation';
|
|
401
|
-
schedule = 'realtime' as const;
|
|
402
|
-
|
|
403
|
-
async *extract(): AsyncIterable<SignalEnvelope> {
|
|
404
|
-
// No-op for the conversation adapter.
|
|
405
|
-
// Conversations are ingested in real-time via the learn hook → knowledge API.
|
|
406
|
-
// This adapter exists to satisfy the interface and for future batch reprocessing.
|
|
407
|
-
}
|
|
408
|
-
|
|
409
|
-
async healthCheck(): Promise<boolean> {
|
|
410
|
-
return true;
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
/**
|
|
414
|
-
* Convert a raw Q&A pair into a SignalEnvelope (used by learn hook).
|
|
415
|
-
*/
|
|
416
|
-
static fromQA(question: string, answer: string, sessionId: string, type: string = 'conversation'): SignalEnvelope {
|
|
417
|
-
return {
|
|
418
|
-
text: `Q: ${question}\nA: ${answer}`,
|
|
419
|
-
origin: {
|
|
420
|
-
source_type: 'conversation',
|
|
421
|
-
source_ref: sessionId,
|
|
422
|
-
creator_entity_id: 'person-default-user',
|
|
423
|
-
},
|
|
424
|
-
entities: [],
|
|
425
|
-
suggested_type: type as any,
|
|
426
|
-
suggested_sensitivity: 'internal',
|
|
427
|
-
raw_metadata: { session_id: sessionId },
|
|
428
|
-
};
|
|
429
|
-
}
|
|
430
|
-
}
|
|
431
|
-
```
|
|
432
|
-
|
|
433
|
-
- [ ] **Step 2: Commit**
|
|
434
|
-
|
|
435
|
-
```bash
|
|
436
|
-
git add src/lib/cortex/signals/adapters/conversation.ts
|
|
437
|
-
git commit -m "feat(cortex): add conversation signal adapter"
|
|
438
|
-
```
|
|
439
|
-
|
|
440
|
-
---
|
|
441
|
-
|
|
442
|
-
### Task 4: Git history adapter
|
|
443
|
-
|
|
444
|
-
**Files:**
|
|
445
|
-
- Create: `src/lib/cortex/signals/adapters/git.ts`
|
|
446
|
-
- Create: `tests/lib/cortex/signals/adapters/git.test.ts`
|
|
447
|
-
|
|
448
|
-
- [ ] **Step 1: Write failing tests**
|
|
449
|
-
|
|
450
|
-
```typescript
|
|
451
|
-
// tests/lib/cortex/signals/adapters/git.test.ts
|
|
452
|
-
import { describe, it, expect, vi } from 'vitest';
|
|
453
|
-
import { GitAdapter, parseGitLog } from '@/lib/cortex/signals/adapters/git';
|
|
454
|
-
import type { SignalEnvelope } from '@/lib/cortex/signals/types';
|
|
455
|
-
|
|
456
|
-
describe('parseGitLog', () => {
|
|
457
|
-
it('parses a commit into a SignalEnvelope', () => {
|
|
458
|
-
const logEntry = {
|
|
459
|
-
sha: 'abc123def',
|
|
460
|
-
author: 'alice@acme.com',
|
|
461
|
-
authorName: 'Alice Smith',
|
|
462
|
-
date: '2026-03-15T10:00:00Z',
|
|
463
|
-
message: 'fix(auth): increase connection pool to handle concurrent load\n\nThe default pool of 10 was exhausted under peak traffic.',
|
|
464
|
-
files: ['src/services/auth/pool.ts', 'config/auth.yaml'],
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
const envelopes = parseGitLog(logEntry);
|
|
468
|
-
expect(envelopes.length).toBeGreaterThanOrEqual(1);
|
|
469
|
-
|
|
470
|
-
const main = envelopes[0];
|
|
471
|
-
expect(main.origin.source_type).toBe('git_commit');
|
|
472
|
-
expect(main.origin.source_ref).toBe('abc123def');
|
|
473
|
-
expect(main.suggested_type).toBe('error_fix'); // "fix" in message
|
|
474
|
-
expect(main.text).toContain('increase connection pool');
|
|
475
|
-
expect(main.raw_metadata.file_refs).toEqual(['src/services/auth/pool.ts', 'config/auth.yaml']);
|
|
476
|
-
});
|
|
477
|
-
|
|
478
|
-
it('classifies refactor commits as decisions', () => {
|
|
479
|
-
const logEntry = {
|
|
480
|
-
sha: 'def456',
|
|
481
|
-
author: 'bob@acme.com',
|
|
482
|
-
authorName: 'Bob',
|
|
483
|
-
date: '2026-03-15T11:00:00Z',
|
|
484
|
-
message: 'refactor: migrate auth from Express to Fastify',
|
|
485
|
-
files: ['src/server.ts'],
|
|
486
|
-
};
|
|
487
|
-
|
|
488
|
-
const envelopes = parseGitLog(logEntry);
|
|
489
|
-
expect(envelopes[0].suggested_type).toBe('decision');
|
|
490
|
-
});
|
|
491
|
-
|
|
492
|
-
it('classifies generic commits as context', () => {
|
|
493
|
-
const logEntry = {
|
|
494
|
-
sha: 'ghi789',
|
|
495
|
-
author: 'charlie@acme.com',
|
|
496
|
-
authorName: 'Charlie',
|
|
497
|
-
date: '2026-03-15T12:00:00Z',
|
|
498
|
-
message: 'update dependencies',
|
|
499
|
-
files: ['package.json'],
|
|
500
|
-
};
|
|
501
|
-
|
|
502
|
-
const envelopes = parseGitLog(logEntry);
|
|
503
|
-
expect(envelopes[0].suggested_type).toBe('context');
|
|
504
|
-
});
|
|
505
|
-
|
|
506
|
-
it('includes edge updates for author TOUCHES files', () => {
|
|
507
|
-
const logEntry = {
|
|
508
|
-
sha: 'jkl012',
|
|
509
|
-
author: 'alice@acme.com',
|
|
510
|
-
authorName: 'Alice Smith',
|
|
511
|
-
date: '2026-03-15T13:00:00Z',
|
|
512
|
-
message: 'feat: add new endpoint',
|
|
513
|
-
files: ['src/api/users.ts'],
|
|
514
|
-
};
|
|
515
|
-
|
|
516
|
-
const envelopes = parseGitLog(logEntry);
|
|
517
|
-
const edgeUpdates = envelopes[0].raw_metadata.edge_updates as any[];
|
|
518
|
-
expect(edgeUpdates).toBeDefined();
|
|
519
|
-
expect(edgeUpdates.length).toBeGreaterThanOrEqual(1);
|
|
520
|
-
expect(edgeUpdates[0].relation).toBe('touches');
|
|
521
|
-
});
|
|
522
|
-
|
|
523
|
-
it('skips merge commits', () => {
|
|
524
|
-
const logEntry = {
|
|
525
|
-
sha: 'mno345',
|
|
526
|
-
author: 'alice@acme.com',
|
|
527
|
-
authorName: 'Alice',
|
|
528
|
-
date: '2026-03-15T14:00:00Z',
|
|
529
|
-
message: 'Merge branch \'feature/foo\' into main',
|
|
530
|
-
files: [],
|
|
531
|
-
};
|
|
532
|
-
|
|
533
|
-
const envelopes = parseGitLog(logEntry);
|
|
534
|
-
expect(envelopes).toHaveLength(0);
|
|
535
|
-
});
|
|
536
|
-
});
|
|
537
|
-
```
|
|
538
|
-
|
|
539
|
-
- [ ] **Step 2: Implement git adapter**
|
|
540
|
-
|
|
541
|
-
```typescript
|
|
542
|
-
// src/lib/cortex/signals/adapters/git.ts
|
|
543
|
-
import type { SignalAdapter, SignalEnvelope, EdgeUpdate } from '../types';
|
|
544
|
-
import { slugify } from '../../graph/types';
|
|
545
|
-
|
|
546
|
-
export interface GitLogEntry {
|
|
547
|
-
sha: string;
|
|
548
|
-
author: string; // email
|
|
549
|
-
authorName: string;
|
|
550
|
-
date: string; // ISO timestamp
|
|
551
|
-
message: string;
|
|
552
|
-
files: string[];
|
|
553
|
-
}
|
|
554
|
-
|
|
555
|
-
const FIX_PATTERNS = [/^fix[:(]/, /\bfix\b/i, /\bbug\b/i, /\bhotfix\b/i];
|
|
556
|
-
const DECISION_PATTERNS = [/^refactor[:(]/, /\bmigrat/i, /\bswitch\s+to\b/i, /\breplace\b.*\bwith\b/i, /^feat[:(]/];
|
|
557
|
-
const MERGE_PATTERN = /^Merge\s+(branch|pull\s+request|remote)/i;
|
|
558
|
-
|
|
559
|
-
/**
|
|
560
|
-
* Parse a git log entry into SignalEnvelopes.
|
|
561
|
-
*/
|
|
562
|
-
export function parseGitLog(entry: GitLogEntry): SignalEnvelope[] {
|
|
563
|
-
// Skip merge commits
|
|
564
|
-
if (MERGE_PATTERN.test(entry.message)) return [];
|
|
565
|
-
|
|
566
|
-
// Skip very short messages
|
|
567
|
-
const body = entry.message.trim();
|
|
568
|
-
if (body.length < 10) return [];
|
|
569
|
-
|
|
570
|
-
// Classify commit type
|
|
571
|
-
let suggestedType: string = 'context';
|
|
572
|
-
if (FIX_PATTERNS.some(p => p.test(body))) suggestedType = 'error_fix';
|
|
573
|
-
else if (DECISION_PATTERNS.some(p => p.test(body))) suggestedType = 'decision';
|
|
574
|
-
|
|
575
|
-
const authorSlug = slugify(entry.authorName);
|
|
576
|
-
const creatorEntityId = `person-${authorSlug}`;
|
|
577
|
-
|
|
578
|
-
// Build edge updates: author TOUCHES each file
|
|
579
|
-
const edgeUpdates: EdgeUpdate[] = entry.files.map(file => ({
|
|
580
|
-
source_id: creatorEntityId,
|
|
581
|
-
target_id: `module-${slugify(file)}`,
|
|
582
|
-
relation: 'touches',
|
|
583
|
-
weight_delta: 0.05,
|
|
584
|
-
}));
|
|
585
|
-
|
|
586
|
-
const envelope: SignalEnvelope = {
|
|
587
|
-
text: body,
|
|
588
|
-
origin: {
|
|
589
|
-
source_type: 'git_commit',
|
|
590
|
-
source_ref: entry.sha,
|
|
591
|
-
creator_entity_id: creatorEntityId,
|
|
592
|
-
},
|
|
593
|
-
entities: [],
|
|
594
|
-
suggested_type: suggestedType as any,
|
|
595
|
-
suggested_sensitivity: 'internal',
|
|
596
|
-
raw_metadata: {
|
|
597
|
-
file_refs: entry.files,
|
|
598
|
-
edge_updates: edgeUpdates,
|
|
599
|
-
author_email: entry.author,
|
|
600
|
-
commit_date: entry.date,
|
|
601
|
-
},
|
|
602
|
-
};
|
|
603
|
-
|
|
604
|
-
return [envelope];
|
|
605
|
-
}
|
|
606
|
-
|
|
607
|
-
/**
|
|
608
|
-
* Git adapter — extracts knowledge from git history.
|
|
609
|
-
* Uses `git log` to scan recent commits.
|
|
610
|
-
*/
|
|
611
|
-
export class GitAdapter implements SignalAdapter {
|
|
612
|
-
name = 'git';
|
|
613
|
-
schedule = 'polling' as const;
|
|
614
|
-
|
|
615
|
-
constructor(private repoPath: string, private sinceDate?: string) {}
|
|
616
|
-
|
|
617
|
-
async *extract(): AsyncIterable<SignalEnvelope> {
|
|
618
|
-
const { execSync } = await import('child_process');
|
|
619
|
-
const since = this.sinceDate ?? new Date(Date.now() - 86400000).toISOString(); // default: last 24h
|
|
620
|
-
|
|
621
|
-
try {
|
|
622
|
-
const log = execSync(
|
|
623
|
-
`git log --since="${since}" --format="%H|%ae|%an|%aI|%s" --name-only`,
|
|
624
|
-
{ cwd: this.repoPath, encoding: 'utf-8', timeout: 10000 }
|
|
625
|
-
);
|
|
626
|
-
|
|
627
|
-
const entries = this.parseLogOutput(log);
|
|
628
|
-
for (const entry of entries) {
|
|
629
|
-
for (const envelope of parseGitLog(entry)) {
|
|
630
|
-
yield envelope;
|
|
631
|
-
}
|
|
632
|
-
}
|
|
633
|
-
} catch {
|
|
634
|
-
// Git not available or not a repo, yield nothing
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
async healthCheck(): Promise<boolean> {
|
|
639
|
-
try {
|
|
640
|
-
const { execSync } = await import('child_process');
|
|
641
|
-
execSync('git rev-parse HEAD', { cwd: this.repoPath, encoding: 'utf-8', timeout: 5000 });
|
|
642
|
-
return true;
|
|
643
|
-
} catch {
|
|
644
|
-
return false;
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
private parseLogOutput(log: string): GitLogEntry[] {
|
|
649
|
-
const entries: GitLogEntry[] = [];
|
|
650
|
-
const lines = log.split('\n');
|
|
651
|
-
let current: GitLogEntry | null = null;
|
|
652
|
-
|
|
653
|
-
for (const line of lines) {
|
|
654
|
-
if (line.includes('|') && line.split('|').length >= 5) {
|
|
655
|
-
if (current) entries.push(current);
|
|
656
|
-
const [sha, author, authorName, date, ...messageParts] = line.split('|');
|
|
657
|
-
current = {
|
|
658
|
-
sha, author, authorName, date,
|
|
659
|
-
message: messageParts.join('|'),
|
|
660
|
-
files: [],
|
|
661
|
-
};
|
|
662
|
-
} else if (line.trim() && current) {
|
|
663
|
-
current.files.push(line.trim());
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
if (current) entries.push(current);
|
|
668
|
-
return entries;
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
```
|
|
672
|
-
|
|
673
|
-
- [ ] **Step 3: Run tests, commit**
|
|
674
|
-
|
|
675
|
-
Run: `npx vitest run tests/lib/cortex/signals/adapters/git.test.ts`
|
|
676
|
-
|
|
677
|
-
```bash
|
|
678
|
-
git commit -m "feat(cortex): add git history signal adapter"
|
|
679
|
-
```
|
|
680
|
-
|
|
681
|
-
---
|
|
682
|
-
|
|
683
|
-
### Task 5: Document adapter
|
|
684
|
-
|
|
685
|
-
**Files:**
|
|
686
|
-
- Create: `src/lib/cortex/signals/adapters/document.ts`
|
|
687
|
-
- Create: `tests/lib/cortex/signals/adapters/document.test.ts`
|
|
688
|
-
|
|
689
|
-
- [ ] **Step 1: Write failing tests**
|
|
690
|
-
|
|
691
|
-
```typescript
|
|
692
|
-
// tests/lib/cortex/signals/adapters/document.test.ts
|
|
693
|
-
import { describe, it, expect } from 'vitest';
|
|
694
|
-
import { parseDocument, classifyDocument } from '@/lib/cortex/signals/adapters/document';
|
|
695
|
-
|
|
696
|
-
describe('classifyDocument', () => {
|
|
697
|
-
it('classifies ADR files as decisions', () => {
|
|
698
|
-
expect(classifyDocument('docs/adr/001-use-postgres.md')).toBe('decision');
|
|
699
|
-
expect(classifyDocument('docs/ADR-002.md')).toBe('decision');
|
|
700
|
-
});
|
|
701
|
-
|
|
702
|
-
it('classifies runbook files as pattern', () => {
|
|
703
|
-
expect(classifyDocument('docs/runbooks/deploy-production.md')).toBe('pattern');
|
|
704
|
-
});
|
|
705
|
-
|
|
706
|
-
it('classifies README as context', () => {
|
|
707
|
-
expect(classifyDocument('README.md')).toBe('context');
|
|
708
|
-
expect(classifyDocument('docs/getting-started.md')).toBe('context');
|
|
709
|
-
});
|
|
710
|
-
});
|
|
711
|
-
|
|
712
|
-
describe('parseDocument', () => {
|
|
713
|
-
it('creates envelope from document content', () => {
|
|
714
|
-
const envelope = parseDocument({
|
|
715
|
-
path: 'docs/adr/001-use-postgres.md',
|
|
716
|
-
content: '# ADR-001: Use PostgreSQL\n\nWe decided to use PostgreSQL for all new services due to its reliability and JSON support.',
|
|
717
|
-
});
|
|
718
|
-
|
|
719
|
-
expect(envelope.origin.source_type).toBe('document');
|
|
720
|
-
expect(envelope.origin.source_ref).toBe('docs/adr/001-use-postgres.md');
|
|
721
|
-
expect(envelope.suggested_type).toBe('decision');
|
|
722
|
-
expect(envelope.text).toContain('PostgreSQL');
|
|
723
|
-
expect(envelope.suggested_sensitivity).toBe('internal');
|
|
724
|
-
});
|
|
725
|
-
|
|
726
|
-
it('truncates very long documents', () => {
|
|
727
|
-
const longContent = 'x'.repeat(10000);
|
|
728
|
-
const envelope = parseDocument({
|
|
729
|
-
path: 'docs/guide.md',
|
|
730
|
-
content: longContent,
|
|
731
|
-
});
|
|
732
|
-
expect(envelope.text.length).toBeLessThanOrEqual(4000);
|
|
733
|
-
});
|
|
734
|
-
|
|
735
|
-
it('sets higher authority via raw_metadata', () => {
|
|
736
|
-
const envelope = parseDocument({
|
|
737
|
-
path: 'docs/adr/001.md',
|
|
738
|
-
content: 'ADR content',
|
|
739
|
-
});
|
|
740
|
-
expect(envelope.raw_metadata.authority_boost).toBe(true);
|
|
741
|
-
});
|
|
742
|
-
});
|
|
743
|
-
```
|
|
744
|
-
|
|
745
|
-
- [ ] **Step 2: Implement document adapter**
|
|
746
|
-
|
|
747
|
-
```typescript
|
|
748
|
-
// src/lib/cortex/signals/adapters/document.ts
|
|
749
|
-
import type { SignalAdapter, SignalEnvelope } from '../types';
|
|
750
|
-
import type { KnowledgeType } from '../../knowledge/types';
|
|
751
|
-
|
|
752
|
-
const MAX_DOC_LENGTH = 4000;
|
|
753
|
-
|
|
754
|
-
const DOC_TYPE_PATTERNS: [RegExp, KnowledgeType][] = [
|
|
755
|
-
[/\badr[s]?\b/i, 'decision'],
|
|
756
|
-
[/\bADR[-_]/i, 'decision'],
|
|
757
|
-
[/\brunbook/i, 'pattern'],
|
|
758
|
-
[/\bplaybook/i, 'pattern'],
|
|
759
|
-
[/\bREADME/i, 'context'],
|
|
760
|
-
[/\bguide/i, 'context'],
|
|
761
|
-
[/\bchangelog/i, 'summary'],
|
|
762
|
-
];
|
|
763
|
-
|
|
764
|
-
export function classifyDocument(filepath: string): KnowledgeType {
|
|
765
|
-
for (const [pattern, type] of DOC_TYPE_PATTERNS) {
|
|
766
|
-
if (pattern.test(filepath)) return type;
|
|
767
|
-
}
|
|
768
|
-
return 'context';
|
|
769
|
-
}
|
|
770
|
-
|
|
771
|
-
export interface DocumentInput {
|
|
772
|
-
path: string;
|
|
773
|
-
content: string;
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
export function parseDocument(input: DocumentInput): SignalEnvelope {
|
|
777
|
-
const type = classifyDocument(input.path);
|
|
778
|
-
const text = input.content.length > MAX_DOC_LENGTH
|
|
779
|
-
? input.content.slice(0, MAX_DOC_LENGTH)
|
|
780
|
-
: input.content;
|
|
781
|
-
|
|
782
|
-
return {
|
|
783
|
-
text,
|
|
784
|
-
origin: {
|
|
785
|
-
source_type: 'document',
|
|
786
|
-
source_ref: input.path,
|
|
787
|
-
creator_entity_id: 'person-default-user',
|
|
788
|
-
},
|
|
789
|
-
entities: [],
|
|
790
|
-
suggested_type: type,
|
|
791
|
-
suggested_sensitivity: 'internal',
|
|
792
|
-
raw_metadata: {
|
|
793
|
-
file_path: input.path,
|
|
794
|
-
authority_boost: true, // documents have higher authority
|
|
795
|
-
},
|
|
796
|
-
};
|
|
797
|
-
}
|
|
798
|
-
|
|
799
|
-
/**
|
|
800
|
-
* Document adapter — scans docs directories for markdown files.
|
|
801
|
-
*/
|
|
802
|
-
export class DocumentAdapter implements SignalAdapter {
|
|
803
|
-
name = 'document';
|
|
804
|
-
schedule = 'polling' as const;
|
|
805
|
-
|
|
806
|
-
constructor(private docPaths: string[]) {}
|
|
807
|
-
|
|
808
|
-
async *extract(): AsyncIterable<SignalEnvelope> {
|
|
809
|
-
const fs = await import('fs');
|
|
810
|
-
const path = await import('path');
|
|
811
|
-
|
|
812
|
-
for (const docDir of this.docPaths) {
|
|
813
|
-
try {
|
|
814
|
-
const files = this.walkDir(docDir, fs, path);
|
|
815
|
-
for (const file of files) {
|
|
816
|
-
if (!file.endsWith('.md')) continue;
|
|
817
|
-
try {
|
|
818
|
-
const content = fs.readFileSync(file, 'utf-8');
|
|
819
|
-
yield parseDocument({ path: file, content });
|
|
820
|
-
} catch {
|
|
821
|
-
// File not readable, skip
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
} catch {
|
|
825
|
-
// Directory not accessible, skip
|
|
826
|
-
}
|
|
827
|
-
}
|
|
828
|
-
}
|
|
829
|
-
|
|
830
|
-
async healthCheck(): Promise<boolean> {
|
|
831
|
-
const fs = await import('fs');
|
|
832
|
-
return this.docPaths.some(p => {
|
|
833
|
-
try { return fs.statSync(p).isDirectory(); } catch { return false; }
|
|
834
|
-
});
|
|
835
|
-
}
|
|
836
|
-
|
|
837
|
-
private walkDir(dir: string, fs: any, path: any): string[] {
|
|
838
|
-
const results: string[] = [];
|
|
839
|
-
try {
|
|
840
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
841
|
-
for (const entry of entries) {
|
|
842
|
-
const full = path.join(dir, entry.name);
|
|
843
|
-
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
844
|
-
results.push(...this.walkDir(full, fs, path));
|
|
845
|
-
} else if (entry.isFile()) {
|
|
846
|
-
results.push(full);
|
|
847
|
-
}
|
|
848
|
-
}
|
|
849
|
-
} catch { /* not accessible */ }
|
|
850
|
-
return results;
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
```
|
|
854
|
-
|
|
855
|
-
- [ ] **Step 3: Run tests, commit**
|
|
856
|
-
|
|
857
|
-
Run: `npx vitest run tests/lib/cortex/signals/adapters/document.test.ts`
|
|
858
|
-
|
|
859
|
-
```bash
|
|
860
|
-
git commit -m "feat(cortex): add document signal adapter"
|
|
861
|
-
```
|
|
862
|
-
|
|
863
|
-
---
|
|
864
|
-
|
|
865
|
-
## Chunk 3: Integration and Barrel Export
|
|
866
|
-
|
|
867
|
-
### Task 6: Barrel export and CortexInstance integration
|
|
868
|
-
|
|
869
|
-
**Files:**
|
|
870
|
-
- Create: `src/lib/cortex/signals/index.ts`
|
|
871
|
-
- Modify: `src/lib/cortex/index.ts`
|
|
872
|
-
|
|
873
|
-
- [ ] **Step 1: Create barrel export**
|
|
874
|
-
|
|
875
|
-
```typescript
|
|
876
|
-
// src/lib/cortex/signals/index.ts
|
|
877
|
-
export { SignalPipeline } from './pipeline';
|
|
878
|
-
export type { SignalPipelineDeps } from './pipeline';
|
|
879
|
-
export { ConversationAdapter } from './adapters/conversation';
|
|
880
|
-
export { GitAdapter, parseGitLog } from './adapters/git';
|
|
881
|
-
export type { GitLogEntry } from './adapters/git';
|
|
882
|
-
export { DocumentAdapter, parseDocument, classifyDocument } from './adapters/document';
|
|
883
|
-
export type { DocumentInput } from './adapters/document';
|
|
884
|
-
export type { SignalEnvelope, SignalAdapter, IngestResult, EdgeUpdate } from './types';
|
|
885
|
-
```
|
|
886
|
-
|
|
887
|
-
- [ ] **Step 2: Add SignalPipeline to CortexInstance**
|
|
888
|
-
|
|
889
|
-
Read `src/lib/cortex/index.ts`. Add:
|
|
890
|
-
|
|
891
|
-
1. Import: `import { SignalPipeline } from './signals/pipeline';`
|
|
892
|
-
2. Add `signalPipeline?: SignalPipeline` to CortexInstance interface
|
|
893
|
-
3. In getCortex(), after graph/resolver initialization:
|
|
894
|
-
|
|
895
|
-
```typescript
|
|
896
|
-
const signalPipeline = new SignalPipeline({ store, embedding, graph, resolver });
|
|
897
|
-
```
|
|
898
|
-
|
|
899
|
-
4. Include in instance object.
|
|
900
|
-
|
|
901
|
-
- [ ] **Step 3: Run full test suite**
|
|
902
|
-
|
|
903
|
-
Run: `npx vitest run tests/lib/cortex/`
|
|
904
|
-
|
|
905
|
-
- [ ] **Step 4: Commit**
|
|
906
|
-
|
|
907
|
-
```bash
|
|
908
|
-
git add src/lib/cortex/signals/index.ts src/lib/cortex/index.ts
|
|
909
|
-
git commit -m "feat(cortex): add signal module barrel export and CortexInstance integration"
|
|
910
|
-
```
|
|
911
|
-
|
|
912
|
-
---
|
|
913
|
-
|
|
914
|
-
## Summary
|
|
915
|
-
|
|
916
|
-
| Task | Component | Tests | Status |
|
|
917
|
-
|------|-----------|-------|--------|
|
|
918
|
-
| 1 | Signal types | — | |
|
|
919
|
-
| 2 | Unified SignalPipeline | 8 | |
|
|
920
|
-
| 3 | Conversation adapter | — | |
|
|
921
|
-
| 4 | Git history adapter | 5 | |
|
|
922
|
-
| 5 | Document adapter | 6 | |
|
|
923
|
-
| 6 | Barrel export + integration | regression | |
|
|
924
|
-
|
|
925
|
-
**Total: 6 tasks, ~19 new tests, 3 chunks**
|
|
926
|
-
|
|
927
|
-
**Deferred adapters** (require external APIs/webhooks — implement when infrastructure is ready):
|
|
928
|
-
- PR Review adapter (GitHub API)
|
|
929
|
-
- Test Signal adapter (CI pipeline webhook)
|
|
930
|
-
- Deployment adapter (deploy system webhook)
|
|
931
|
-
- Behavioral Inference adapter (daily cron analyzing accumulated signals)
|
|
932
|
-
|
|
933
|
-
Each deferred adapter = implement `SignalAdapter` interface + test, zero changes to SignalPipeline.
|
|
1
|
+
# Cortex v2 — Pillar 5: Observable Signal Ingestion
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED: Use superpowers:subagent-driven-development (if subagents available) or superpowers:executing-plans to implement this plan. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Build the adapter-based signal ingestion framework and implement the 3 core adapters (Conversations, Git History, Documents). The remaining 4 adapters (PR Reviews, Test Signals, Deployment, Behavioral) are deferred — they require external webhooks/APIs and can be added incrementally since the adapter interface is extensible.
|
|
6
|
+
|
|
7
|
+
**Architecture:** A new `src/lib/cortex/signals/` module with a `SignalPipeline` class that consumes `SignalEnvelope` objects from any adapter. Each adapter implements a `SignalAdapter` interface with `extract()` (AsyncIterable) and `healthCheck()`. The existing `IngestionPipeline` is wrapped as the Conversation adapter for backward compat. The Git adapter parses `git log` output. The Document adapter watches `docs/**` for ADRs/READMEs.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** TypeScript, vitest, child_process (for git), fs (for docs)
|
|
10
|
+
|
|
11
|
+
**Spec:** `docs/superpowers/specs/2026-03-14-cortex-v2-design.md` — Pillar 5
|
|
12
|
+
|
|
13
|
+
**Depends on:** Pillars 1-4 (all completed)
|
|
14
|
+
|
|
15
|
+
**Deferred to future:** PR Review adapter, Test Signal adapter, Deployment adapter, Behavioral Inference adapter
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## File Structure
|
|
20
|
+
|
|
21
|
+
```
|
|
22
|
+
New files:
|
|
23
|
+
├── src/lib/cortex/signals/types.ts — SignalEnvelope, SignalAdapter interfaces
|
|
24
|
+
├── src/lib/cortex/signals/pipeline.ts — Unified SignalPipeline
|
|
25
|
+
├── src/lib/cortex/signals/adapters/conversation.ts — Wraps existing IngestionPipeline
|
|
26
|
+
├── src/lib/cortex/signals/adapters/git.ts — Git history adapter
|
|
27
|
+
├── src/lib/cortex/signals/adapters/document.ts — Document/ADR adapter
|
|
28
|
+
├── src/lib/cortex/signals/index.ts — Barrel export
|
|
29
|
+
|
|
30
|
+
Test files:
|
|
31
|
+
├── tests/lib/cortex/signals/pipeline.test.ts
|
|
32
|
+
├── tests/lib/cortex/signals/adapters/git.test.ts
|
|
33
|
+
├── tests/lib/cortex/signals/adapters/document.test.ts
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Chunk 1: Signal Types and Unified Pipeline
|
|
39
|
+
|
|
40
|
+
### Task 1: Signal types
|
|
41
|
+
|
|
42
|
+
**Files:**
|
|
43
|
+
- Create: `src/lib/cortex/signals/types.ts`
|
|
44
|
+
|
|
45
|
+
- [ ] **Step 1: Create types file**
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// src/lib/cortex/signals/types.ts
|
|
49
|
+
import type { KnowledgeType, SensitivityClass, Origin, EntityLink } from '../knowledge/types';
|
|
50
|
+
|
|
51
|
+
export interface SignalEnvelope {
|
|
52
|
+
text: string;
|
|
53
|
+
origin: Origin;
|
|
54
|
+
entities: EntityLink[];
|
|
55
|
+
suggested_type: KnowledgeType;
|
|
56
|
+
suggested_sensitivity: SensitivityClass;
|
|
57
|
+
raw_metadata: Record<string, unknown>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export interface SignalAdapter {
|
|
61
|
+
name: string;
|
|
62
|
+
schedule: 'realtime' | 'polling' | 'webhook' | 'cron';
|
|
63
|
+
extract(): AsyncIterable<SignalEnvelope>;
|
|
64
|
+
healthCheck(): Promise<boolean>;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export interface IngestResult {
|
|
68
|
+
accepted: number;
|
|
69
|
+
skipped: number; // dedup
|
|
70
|
+
errors: string[];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Graph edge update carried in raw_metadata.
|
|
75
|
+
* Adapters can include these to update the entity graph during ingestion.
|
|
76
|
+
*/
|
|
77
|
+
export interface EdgeUpdate {
|
|
78
|
+
source_id: string;
|
|
79
|
+
target_id: string;
|
|
80
|
+
relation: string;
|
|
81
|
+
weight_delta: number; // increment (not absolute)
|
|
82
|
+
}
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- [ ] **Step 2: Commit**
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
git add src/lib/cortex/signals/types.ts
|
|
89
|
+
git commit -m "feat(cortex): add signal ingestion type definitions"
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Task 2: Unified SignalPipeline
|
|
95
|
+
|
|
96
|
+
**Files:**
|
|
97
|
+
- Create: `src/lib/cortex/signals/pipeline.ts`
|
|
98
|
+
- Create: `tests/lib/cortex/signals/pipeline.test.ts`
|
|
99
|
+
|
|
100
|
+
- [ ] **Step 1: Write failing tests**
|
|
101
|
+
|
|
102
|
+
```typescript
|
|
103
|
+
// tests/lib/cortex/signals/pipeline.test.ts
|
|
104
|
+
import { describe, it, expect, vi, beforeEach } from 'vitest';
|
|
105
|
+
import { SignalPipeline } from '@/lib/cortex/signals/pipeline';
|
|
106
|
+
import type { SignalEnvelope } from '@/lib/cortex/signals/types';
|
|
107
|
+
|
|
108
|
+
const mockStore = {
|
|
109
|
+
add: vi.fn().mockResolvedValue(undefined),
|
|
110
|
+
search: vi.fn().mockResolvedValue([]),
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
const mockEmbedding = {
|
|
114
|
+
embed: vi.fn().mockResolvedValue([[0.1, 0.2, 0.3]]),
|
|
115
|
+
dimensions: 3,
|
|
116
|
+
name: 'mock',
|
|
117
|
+
init: vi.fn(),
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const mockGraph = {
|
|
121
|
+
createEdge: vi.fn(),
|
|
122
|
+
incrementEdgeWeight: vi.fn(),
|
|
123
|
+
getEntity: vi.fn().mockReturnValue(null),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const mockResolver = {
|
|
127
|
+
extractEntities: vi.fn().mockReturnValue([]),
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
function makeEnvelope(overrides: Partial<SignalEnvelope> = {}): SignalEnvelope {
|
|
131
|
+
return {
|
|
132
|
+
text: 'Fix auth timeout by increasing pool size',
|
|
133
|
+
origin: { source_type: 'git_commit', source_ref: 'abc123', creator_entity_id: 'person-alice' },
|
|
134
|
+
entities: [],
|
|
135
|
+
suggested_type: 'error_fix',
|
|
136
|
+
suggested_sensitivity: 'internal',
|
|
137
|
+
raw_metadata: {},
|
|
138
|
+
...overrides,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
describe('SignalPipeline', () => {
|
|
143
|
+
let pipeline: SignalPipeline;
|
|
144
|
+
|
|
145
|
+
beforeEach(() => {
|
|
146
|
+
vi.clearAllMocks();
|
|
147
|
+
pipeline = new SignalPipeline({
|
|
148
|
+
store: mockStore as any,
|
|
149
|
+
embedding: mockEmbedding as any,
|
|
150
|
+
graph: mockGraph as any,
|
|
151
|
+
resolver: mockResolver as any,
|
|
152
|
+
});
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('ingests a signal envelope and stores it', async () => {
|
|
156
|
+
const result = await pipeline.ingest(makeEnvelope());
|
|
157
|
+
expect(result.accepted).toBe(1);
|
|
158
|
+
expect(mockEmbedding.embed).toHaveBeenCalledWith(['Fix auth timeout by increasing pool size']);
|
|
159
|
+
expect(mockStore.add).toHaveBeenCalledTimes(1);
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it('uses suggested_type from envelope', async () => {
|
|
163
|
+
await pipeline.ingest(makeEnvelope({ suggested_type: 'decision' }));
|
|
164
|
+
const addCall = mockStore.add.mock.calls[0];
|
|
165
|
+
expect(addCall[1].type).toBe('decision');
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('auto-classifies sensitivity (most restrictive wins)', async () => {
|
|
169
|
+
// Text contains API key → confidential, overrides suggested 'internal'
|
|
170
|
+
await pipeline.ingest(makeEnvelope({
|
|
171
|
+
text: 'Set API_KEY=sk-ant-abc123 in production',
|
|
172
|
+
suggested_sensitivity: 'internal',
|
|
173
|
+
}));
|
|
174
|
+
const addCall = mockStore.add.mock.calls[0];
|
|
175
|
+
expect(addCall[1].sensitivity).toBe('confidential');
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('keeps suggested_sensitivity when more restrictive than auto-classification', async () => {
|
|
179
|
+
await pipeline.ingest(makeEnvelope({
|
|
180
|
+
text: 'General technical note', // auto-classifies as internal
|
|
181
|
+
suggested_sensitivity: 'restricted', // more restrictive
|
|
182
|
+
}));
|
|
183
|
+
const addCall = mockStore.add.mock.calls[0];
|
|
184
|
+
expect(addCall[1].sensitivity).toBe('restricted');
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it('processes edge updates from raw_metadata', async () => {
|
|
188
|
+
await pipeline.ingest(makeEnvelope({
|
|
189
|
+
raw_metadata: {
|
|
190
|
+
edge_updates: [
|
|
191
|
+
{ source_id: 'person-alice', target_id: 'topic-auth', relation: 'expert_in', weight_delta: 0.05 },
|
|
192
|
+
],
|
|
193
|
+
},
|
|
194
|
+
}));
|
|
195
|
+
expect(mockGraph.incrementEdgeWeight).toHaveBeenCalledWith(
|
|
196
|
+
'person-alice', 'topic-auth', 'expert_in', 0.05
|
|
197
|
+
);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it('deduplicates by text hash', async () => {
|
|
201
|
+
const envelope = makeEnvelope();
|
|
202
|
+
await pipeline.ingest(envelope);
|
|
203
|
+
const result = await pipeline.ingest(envelope); // same text
|
|
204
|
+
expect(result.skipped).toBe(1);
|
|
205
|
+
expect(result.accepted).toBe(0);
|
|
206
|
+
expect(mockStore.add).toHaveBeenCalledTimes(1); // only first call
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
it('ingests batch of envelopes', async () => {
|
|
210
|
+
const envelopes = [
|
|
211
|
+
makeEnvelope({ text: 'First signal' }),
|
|
212
|
+
makeEnvelope({ text: 'Second signal' }),
|
|
213
|
+
makeEnvelope({ text: 'Third signal' }),
|
|
214
|
+
];
|
|
215
|
+
const result = await pipeline.ingestBatch(envelopes);
|
|
216
|
+
expect(result.accepted).toBe(3);
|
|
217
|
+
expect(mockStore.add).toHaveBeenCalledTimes(3);
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('handles embedding failures gracefully', async () => {
|
|
221
|
+
mockEmbedding.embed.mockRejectedValueOnce(new Error('embed failed'));
|
|
222
|
+
const result = await pipeline.ingest(makeEnvelope());
|
|
223
|
+
expect(result.errors).toHaveLength(1);
|
|
224
|
+
expect(result.accepted).toBe(0);
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- [ ] **Step 2: Run tests to verify they fail**
|
|
230
|
+
|
|
231
|
+
Run: `npx vitest run tests/lib/cortex/signals/pipeline.test.ts`
|
|
232
|
+
|
|
233
|
+
- [ ] **Step 3: Implement SignalPipeline**
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
// src/lib/cortex/signals/pipeline.ts
|
|
237
|
+
import { createHash } from 'crypto';
|
|
238
|
+
import type { CortexStore } from '../store';
|
|
239
|
+
import type { EmbeddingProvider } from '../embeddings';
|
|
240
|
+
import type { EntityGraph } from '../graph/entity-graph';
|
|
241
|
+
import type { EntityResolver } from '../graph/resolver';
|
|
242
|
+
import type { KnowledgeUnit } from '../knowledge/types';
|
|
243
|
+
import { classifySensitivity } from '../boundary/classifier';
|
|
244
|
+
import { layerToScope, scopeToLayerKey } from '../knowledge/compat';
|
|
245
|
+
import type { SignalEnvelope, IngestResult, EdgeUpdate } from './types';
|
|
246
|
+
|
|
247
|
+
const SENSITIVITY_PRIORITY: Record<string, number> = {
|
|
248
|
+
public: 0, internal: 1, restricted: 2, confidential: 3,
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
export interface SignalPipelineDeps {
|
|
252
|
+
store: CortexStore;
|
|
253
|
+
embedding: EmbeddingProvider;
|
|
254
|
+
graph: EntityGraph;
|
|
255
|
+
resolver: EntityResolver;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
export class SignalPipeline {
|
|
259
|
+
private hashSet = new Set<string>();
|
|
260
|
+
private deps: SignalPipelineDeps;
|
|
261
|
+
|
|
262
|
+
constructor(deps: SignalPipelineDeps) {
|
|
263
|
+
this.deps = deps;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async ingest(envelope: SignalEnvelope): Promise<IngestResult> {
|
|
267
|
+
const result: IngestResult = { accepted: 0, skipped: 0, errors: [] };
|
|
268
|
+
|
|
269
|
+
try {
|
|
270
|
+
// 1. Dedup by text hash
|
|
271
|
+
const hash = createHash('sha256')
|
|
272
|
+
.update(envelope.text.replace(/\s+/g, ' ').trim())
|
|
273
|
+
.digest('hex');
|
|
274
|
+
|
|
275
|
+
if (this.hashSet.has(hash)) {
|
|
276
|
+
result.skipped = 1;
|
|
277
|
+
return result;
|
|
278
|
+
}
|
|
279
|
+
this.hashSet.add(hash);
|
|
280
|
+
|
|
281
|
+
// 2. Sensitivity: most restrictive wins between suggested and auto-classified
|
|
282
|
+
const autoSensitivity = classifySensitivity(envelope.text);
|
|
283
|
+
const suggestedPriority = SENSITIVITY_PRIORITY[envelope.suggested_sensitivity] ?? 1;
|
|
284
|
+
const autoPriority = SENSITIVITY_PRIORITY[autoSensitivity] ?? 1;
|
|
285
|
+
const sensitivity = suggestedPriority >= autoPriority
|
|
286
|
+
? envelope.suggested_sensitivity : autoSensitivity;
|
|
287
|
+
|
|
288
|
+
// 3. Embed
|
|
289
|
+
const [vector] = await this.deps.embedding.embed([envelope.text]);
|
|
290
|
+
|
|
291
|
+
// 4. Build scope from origin
|
|
292
|
+
const scope = layerToScope('personal', null, envelope.origin.creator_entity_id.replace('person-', ''));
|
|
293
|
+
const layerKey = scopeToLayerKey(scope);
|
|
294
|
+
const layer = 'personal' as const; // default; adapters can override via metadata
|
|
295
|
+
|
|
296
|
+
// 5. Build KnowledgeUnit
|
|
297
|
+
const unit: KnowledgeUnit = {
|
|
298
|
+
id: crypto.randomUUID(),
|
|
299
|
+
vector,
|
|
300
|
+
text: envelope.text,
|
|
301
|
+
type: envelope.suggested_type,
|
|
302
|
+
layer,
|
|
303
|
+
workspace_id: (envelope.raw_metadata.workspace_id as number) ?? null,
|
|
304
|
+
session_id: (envelope.raw_metadata.session_id as string) ?? null,
|
|
305
|
+
agent_type: 'claude',
|
|
306
|
+
project_path: (envelope.raw_metadata.project_path as string) ?? null,
|
|
307
|
+
file_refs: (envelope.raw_metadata.file_refs as string[]) ?? [],
|
|
308
|
+
confidence: 0.8,
|
|
309
|
+
created: new Date().toISOString(),
|
|
310
|
+
source_timestamp: new Date().toISOString(),
|
|
311
|
+
stale_score: 0,
|
|
312
|
+
access_count: 0,
|
|
313
|
+
last_accessed: null,
|
|
314
|
+
metadata: { source: envelope.origin.source_type },
|
|
315
|
+
// v2 fields
|
|
316
|
+
scope,
|
|
317
|
+
entity_links: envelope.entities,
|
|
318
|
+
evidence_score: 0.8,
|
|
319
|
+
corroborations: 0,
|
|
320
|
+
contradiction_refs: [],
|
|
321
|
+
sensitivity,
|
|
322
|
+
creator_scope: null,
|
|
323
|
+
origin: envelope.origin,
|
|
324
|
+
propagation_path: [],
|
|
325
|
+
};
|
|
326
|
+
|
|
327
|
+
// 6. Store
|
|
328
|
+
await this.deps.store.add(layerKey, unit);
|
|
329
|
+
result.accepted = 1;
|
|
330
|
+
|
|
331
|
+
// 7. Process edge updates
|
|
332
|
+
const edgeUpdates = (envelope.raw_metadata.edge_updates as EdgeUpdate[]) ?? [];
|
|
333
|
+
for (const update of edgeUpdates) {
|
|
334
|
+
try {
|
|
335
|
+
this.deps.graph.incrementEdgeWeight(
|
|
336
|
+
update.source_id, update.target_id, update.relation as any, update.weight_delta
|
|
337
|
+
);
|
|
338
|
+
} catch {
|
|
339
|
+
// Edge entities may not exist yet, skip
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
} catch (err: any) {
|
|
344
|
+
result.errors.push(err.message);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
return result;
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
async ingestBatch(envelopes: SignalEnvelope[]): Promise<IngestResult> {
|
|
351
|
+
const totals: IngestResult = { accepted: 0, skipped: 0, errors: [] };
|
|
352
|
+
for (const envelope of envelopes) {
|
|
353
|
+
const r = await this.ingest(envelope);
|
|
354
|
+
totals.accepted += r.accepted;
|
|
355
|
+
totals.skipped += r.skipped;
|
|
356
|
+
totals.errors.push(...r.errors);
|
|
357
|
+
}
|
|
358
|
+
return totals;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
- [ ] **Step 4: Run tests to verify they pass**
|
|
364
|
+
|
|
365
|
+
Run: `npx vitest run tests/lib/cortex/signals/pipeline.test.ts`
|
|
366
|
+
Expected: PASS (8 tests)
|
|
367
|
+
|
|
368
|
+
- [ ] **Step 5: Commit**
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
git add src/lib/cortex/signals/pipeline.ts tests/lib/cortex/signals/pipeline.test.ts
|
|
372
|
+
git commit -m "feat(cortex): add unified SignalPipeline for multi-source ingestion"
|
|
373
|
+
```
|
|
374
|
+
|
|
375
|
+
---
|
|
376
|
+
|
|
377
|
+
## Chunk 2: Core Adapters
|
|
378
|
+
|
|
379
|
+
### Task 3: Conversation adapter (wraps existing pipeline)
|
|
380
|
+
|
|
381
|
+
**Files:**
|
|
382
|
+
- Create: `src/lib/cortex/signals/adapters/conversation.ts`
|
|
383
|
+
|
|
384
|
+
- [ ] **Step 1: Implement conversation adapter**
|
|
385
|
+
|
|
386
|
+
This adapter wraps the existing `IngestionPipeline` to produce `SignalEnvelope` objects from Claude Code session transcripts. It does NOT replace the existing pipeline — it wraps it so conversations flow through the unified `SignalPipeline`.
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
// src/lib/cortex/signals/adapters/conversation.ts
|
|
390
|
+
import type { SignalAdapter, SignalEnvelope } from '../types';
|
|
391
|
+
|
|
392
|
+
/**
|
|
393
|
+
* Conversation adapter — wraps the learn hook's output format.
|
|
394
|
+
* This is a "pull" adapter: it doesn't actively extract.
|
|
395
|
+
* Instead, the learn hook POSTs to the knowledge API, and this adapter
|
|
396
|
+
* can be used to convert raw session messages into SignalEnvelopes
|
|
397
|
+
* for batch processing.
|
|
398
|
+
*/
|
|
399
|
+
export class ConversationAdapter implements SignalAdapter {
|
|
400
|
+
name = 'conversation';
|
|
401
|
+
schedule = 'realtime' as const;
|
|
402
|
+
|
|
403
|
+
async *extract(): AsyncIterable<SignalEnvelope> {
|
|
404
|
+
// No-op for the conversation adapter.
|
|
405
|
+
// Conversations are ingested in real-time via the learn hook → knowledge API.
|
|
406
|
+
// This adapter exists to satisfy the interface and for future batch reprocessing.
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
async healthCheck(): Promise<boolean> {
|
|
410
|
+
return true;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Convert a raw Q&A pair into a SignalEnvelope (used by learn hook).
|
|
415
|
+
*/
|
|
416
|
+
static fromQA(question: string, answer: string, sessionId: string, type: string = 'conversation'): SignalEnvelope {
|
|
417
|
+
return {
|
|
418
|
+
text: `Q: ${question}\nA: ${answer}`,
|
|
419
|
+
origin: {
|
|
420
|
+
source_type: 'conversation',
|
|
421
|
+
source_ref: sessionId,
|
|
422
|
+
creator_entity_id: 'person-default-user',
|
|
423
|
+
},
|
|
424
|
+
entities: [],
|
|
425
|
+
suggested_type: type as any,
|
|
426
|
+
suggested_sensitivity: 'internal',
|
|
427
|
+
raw_metadata: { session_id: sessionId },
|
|
428
|
+
};
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
- [ ] **Step 2: Commit**
|
|
434
|
+
|
|
435
|
+
```bash
|
|
436
|
+
git add src/lib/cortex/signals/adapters/conversation.ts
|
|
437
|
+
git commit -m "feat(cortex): add conversation signal adapter"
|
|
438
|
+
```
|
|
439
|
+
|
|
440
|
+
---
|
|
441
|
+
|
|
442
|
+
### Task 4: Git history adapter
|
|
443
|
+
|
|
444
|
+
**Files:**
|
|
445
|
+
- Create: `src/lib/cortex/signals/adapters/git.ts`
|
|
446
|
+
- Create: `tests/lib/cortex/signals/adapters/git.test.ts`
|
|
447
|
+
|
|
448
|
+
- [ ] **Step 1: Write failing tests**
|
|
449
|
+
|
|
450
|
+
```typescript
|
|
451
|
+
// tests/lib/cortex/signals/adapters/git.test.ts
|
|
452
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
453
|
+
import { GitAdapter, parseGitLog } from '@/lib/cortex/signals/adapters/git';
|
|
454
|
+
import type { SignalEnvelope } from '@/lib/cortex/signals/types';
|
|
455
|
+
|
|
456
|
+
describe('parseGitLog', () => {
|
|
457
|
+
it('parses a commit into a SignalEnvelope', () => {
|
|
458
|
+
const logEntry = {
|
|
459
|
+
sha: 'abc123def',
|
|
460
|
+
author: 'alice@acme.com',
|
|
461
|
+
authorName: 'Alice Smith',
|
|
462
|
+
date: '2026-03-15T10:00:00Z',
|
|
463
|
+
message: 'fix(auth): increase connection pool to handle concurrent load\n\nThe default pool of 10 was exhausted under peak traffic.',
|
|
464
|
+
files: ['src/services/auth/pool.ts', 'config/auth.yaml'],
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
const envelopes = parseGitLog(logEntry);
|
|
468
|
+
expect(envelopes.length).toBeGreaterThanOrEqual(1);
|
|
469
|
+
|
|
470
|
+
const main = envelopes[0];
|
|
471
|
+
expect(main.origin.source_type).toBe('git_commit');
|
|
472
|
+
expect(main.origin.source_ref).toBe('abc123def');
|
|
473
|
+
expect(main.suggested_type).toBe('error_fix'); // "fix" in message
|
|
474
|
+
expect(main.text).toContain('increase connection pool');
|
|
475
|
+
expect(main.raw_metadata.file_refs).toEqual(['src/services/auth/pool.ts', 'config/auth.yaml']);
|
|
476
|
+
});
|
|
477
|
+
|
|
478
|
+
it('classifies refactor commits as decisions', () => {
|
|
479
|
+
const logEntry = {
|
|
480
|
+
sha: 'def456',
|
|
481
|
+
author: 'bob@acme.com',
|
|
482
|
+
authorName: 'Bob',
|
|
483
|
+
date: '2026-03-15T11:00:00Z',
|
|
484
|
+
message: 'refactor: migrate auth from Express to Fastify',
|
|
485
|
+
files: ['src/server.ts'],
|
|
486
|
+
};
|
|
487
|
+
|
|
488
|
+
const envelopes = parseGitLog(logEntry);
|
|
489
|
+
expect(envelopes[0].suggested_type).toBe('decision');
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it('classifies generic commits as context', () => {
|
|
493
|
+
const logEntry = {
|
|
494
|
+
sha: 'ghi789',
|
|
495
|
+
author: 'charlie@acme.com',
|
|
496
|
+
authorName: 'Charlie',
|
|
497
|
+
date: '2026-03-15T12:00:00Z',
|
|
498
|
+
message: 'update dependencies',
|
|
499
|
+
files: ['package.json'],
|
|
500
|
+
};
|
|
501
|
+
|
|
502
|
+
const envelopes = parseGitLog(logEntry);
|
|
503
|
+
expect(envelopes[0].suggested_type).toBe('context');
|
|
504
|
+
});
|
|
505
|
+
|
|
506
|
+
it('includes edge updates for author TOUCHES files', () => {
|
|
507
|
+
const logEntry = {
|
|
508
|
+
sha: 'jkl012',
|
|
509
|
+
author: 'alice@acme.com',
|
|
510
|
+
authorName: 'Alice Smith',
|
|
511
|
+
date: '2026-03-15T13:00:00Z',
|
|
512
|
+
message: 'feat: add new endpoint',
|
|
513
|
+
files: ['src/api/users.ts'],
|
|
514
|
+
};
|
|
515
|
+
|
|
516
|
+
const envelopes = parseGitLog(logEntry);
|
|
517
|
+
const edgeUpdates = envelopes[0].raw_metadata.edge_updates as any[];
|
|
518
|
+
expect(edgeUpdates).toBeDefined();
|
|
519
|
+
expect(edgeUpdates.length).toBeGreaterThanOrEqual(1);
|
|
520
|
+
expect(edgeUpdates[0].relation).toBe('touches');
|
|
521
|
+
});
|
|
522
|
+
|
|
523
|
+
it('skips merge commits', () => {
|
|
524
|
+
const logEntry = {
|
|
525
|
+
sha: 'mno345',
|
|
526
|
+
author: 'alice@acme.com',
|
|
527
|
+
authorName: 'Alice',
|
|
528
|
+
date: '2026-03-15T14:00:00Z',
|
|
529
|
+
message: 'Merge branch \'feature/foo\' into main',
|
|
530
|
+
files: [],
|
|
531
|
+
};
|
|
532
|
+
|
|
533
|
+
const envelopes = parseGitLog(logEntry);
|
|
534
|
+
expect(envelopes).toHaveLength(0);
|
|
535
|
+
});
|
|
536
|
+
});
|
|
537
|
+
```
|
|
538
|
+
|
|
539
|
+
- [ ] **Step 2: Implement git adapter**
|
|
540
|
+
|
|
541
|
+
```typescript
|
|
542
|
+
// src/lib/cortex/signals/adapters/git.ts
|
|
543
|
+
import type { SignalAdapter, SignalEnvelope, EdgeUpdate } from '../types';
|
|
544
|
+
import { slugify } from '../../graph/types';
|
|
545
|
+
|
|
546
|
+
export interface GitLogEntry {
|
|
547
|
+
sha: string;
|
|
548
|
+
author: string; // email
|
|
549
|
+
authorName: string;
|
|
550
|
+
date: string; // ISO timestamp
|
|
551
|
+
message: string;
|
|
552
|
+
files: string[];
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
const FIX_PATTERNS = [/^fix[:(]/, /\bfix\b/i, /\bbug\b/i, /\bhotfix\b/i];
|
|
556
|
+
const DECISION_PATTERNS = [/^refactor[:(]/, /\bmigrat/i, /\bswitch\s+to\b/i, /\breplace\b.*\bwith\b/i, /^feat[:(]/];
|
|
557
|
+
const MERGE_PATTERN = /^Merge\s+(branch|pull\s+request|remote)/i;
|
|
558
|
+
|
|
559
|
+
/**
|
|
560
|
+
* Parse a git log entry into SignalEnvelopes.
|
|
561
|
+
*/
|
|
562
|
+
export function parseGitLog(entry: GitLogEntry): SignalEnvelope[] {
|
|
563
|
+
// Skip merge commits
|
|
564
|
+
if (MERGE_PATTERN.test(entry.message)) return [];
|
|
565
|
+
|
|
566
|
+
// Skip very short messages
|
|
567
|
+
const body = entry.message.trim();
|
|
568
|
+
if (body.length < 10) return [];
|
|
569
|
+
|
|
570
|
+
// Classify commit type
|
|
571
|
+
let suggestedType: string = 'context';
|
|
572
|
+
if (FIX_PATTERNS.some(p => p.test(body))) suggestedType = 'error_fix';
|
|
573
|
+
else if (DECISION_PATTERNS.some(p => p.test(body))) suggestedType = 'decision';
|
|
574
|
+
|
|
575
|
+
const authorSlug = slugify(entry.authorName);
|
|
576
|
+
const creatorEntityId = `person-${authorSlug}`;
|
|
577
|
+
|
|
578
|
+
// Build edge updates: author TOUCHES each file
|
|
579
|
+
const edgeUpdates: EdgeUpdate[] = entry.files.map(file => ({
|
|
580
|
+
source_id: creatorEntityId,
|
|
581
|
+
target_id: `module-${slugify(file)}`,
|
|
582
|
+
relation: 'touches',
|
|
583
|
+
weight_delta: 0.05,
|
|
584
|
+
}));
|
|
585
|
+
|
|
586
|
+
const envelope: SignalEnvelope = {
|
|
587
|
+
text: body,
|
|
588
|
+
origin: {
|
|
589
|
+
source_type: 'git_commit',
|
|
590
|
+
source_ref: entry.sha,
|
|
591
|
+
creator_entity_id: creatorEntityId,
|
|
592
|
+
},
|
|
593
|
+
entities: [],
|
|
594
|
+
suggested_type: suggestedType as any,
|
|
595
|
+
suggested_sensitivity: 'internal',
|
|
596
|
+
raw_metadata: {
|
|
597
|
+
file_refs: entry.files,
|
|
598
|
+
edge_updates: edgeUpdates,
|
|
599
|
+
author_email: entry.author,
|
|
600
|
+
commit_date: entry.date,
|
|
601
|
+
},
|
|
602
|
+
};
|
|
603
|
+
|
|
604
|
+
return [envelope];
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Git adapter — extracts knowledge from git history.
|
|
609
|
+
* Uses `git log` to scan recent commits.
|
|
610
|
+
*/
|
|
611
|
+
export class GitAdapter implements SignalAdapter {
|
|
612
|
+
name = 'git';
|
|
613
|
+
schedule = 'polling' as const;
|
|
614
|
+
|
|
615
|
+
constructor(private repoPath: string, private sinceDate?: string) {}
|
|
616
|
+
|
|
617
|
+
async *extract(): AsyncIterable<SignalEnvelope> {
|
|
618
|
+
const { execSync } = await import('child_process');
|
|
619
|
+
const since = this.sinceDate ?? new Date(Date.now() - 86400000).toISOString(); // default: last 24h
|
|
620
|
+
|
|
621
|
+
try {
|
|
622
|
+
const log = execSync(
|
|
623
|
+
`git log --since="${since}" --format="%H|%ae|%an|%aI|%s" --name-only`,
|
|
624
|
+
{ cwd: this.repoPath, encoding: 'utf-8', timeout: 10000 }
|
|
625
|
+
);
|
|
626
|
+
|
|
627
|
+
const entries = this.parseLogOutput(log);
|
|
628
|
+
for (const entry of entries) {
|
|
629
|
+
for (const envelope of parseGitLog(entry)) {
|
|
630
|
+
yield envelope;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
} catch {
|
|
634
|
+
// Git not available or not a repo, yield nothing
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
|
|
638
|
+
async healthCheck(): Promise<boolean> {
|
|
639
|
+
try {
|
|
640
|
+
const { execSync } = await import('child_process');
|
|
641
|
+
execSync('git rev-parse HEAD', { cwd: this.repoPath, encoding: 'utf-8', timeout: 5000 });
|
|
642
|
+
return true;
|
|
643
|
+
} catch {
|
|
644
|
+
return false;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
private parseLogOutput(log: string): GitLogEntry[] {
|
|
649
|
+
const entries: GitLogEntry[] = [];
|
|
650
|
+
const lines = log.split('\n');
|
|
651
|
+
let current: GitLogEntry | null = null;
|
|
652
|
+
|
|
653
|
+
for (const line of lines) {
|
|
654
|
+
if (line.includes('|') && line.split('|').length >= 5) {
|
|
655
|
+
if (current) entries.push(current);
|
|
656
|
+
const [sha, author, authorName, date, ...messageParts] = line.split('|');
|
|
657
|
+
current = {
|
|
658
|
+
sha, author, authorName, date,
|
|
659
|
+
message: messageParts.join('|'),
|
|
660
|
+
files: [],
|
|
661
|
+
};
|
|
662
|
+
} else if (line.trim() && current) {
|
|
663
|
+
current.files.push(line.trim());
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
if (current) entries.push(current);
|
|
668
|
+
return entries;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
```
|
|
672
|
+
|
|
673
|
+
- [ ] **Step 3: Run tests, commit**
|
|
674
|
+
|
|
675
|
+
Run: `npx vitest run tests/lib/cortex/signals/adapters/git.test.ts`
|
|
676
|
+
|
|
677
|
+
```bash
|
|
678
|
+
git commit -m "feat(cortex): add git history signal adapter"
|
|
679
|
+
```
|
|
680
|
+
|
|
681
|
+
---
|
|
682
|
+
|
|
683
|
+
### Task 5: Document adapter
|
|
684
|
+
|
|
685
|
+
**Files:**
|
|
686
|
+
- Create: `src/lib/cortex/signals/adapters/document.ts`
|
|
687
|
+
- Create: `tests/lib/cortex/signals/adapters/document.test.ts`
|
|
688
|
+
|
|
689
|
+
- [ ] **Step 1: Write failing tests**
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
// tests/lib/cortex/signals/adapters/document.test.ts
|
|
693
|
+
import { describe, it, expect } from 'vitest';
|
|
694
|
+
import { parseDocument, classifyDocument } from '@/lib/cortex/signals/adapters/document';
|
|
695
|
+
|
|
696
|
+
describe('classifyDocument', () => {
|
|
697
|
+
it('classifies ADR files as decisions', () => {
|
|
698
|
+
expect(classifyDocument('docs/adr/001-use-postgres.md')).toBe('decision');
|
|
699
|
+
expect(classifyDocument('docs/ADR-002.md')).toBe('decision');
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
it('classifies runbook files as pattern', () => {
|
|
703
|
+
expect(classifyDocument('docs/runbooks/deploy-production.md')).toBe('pattern');
|
|
704
|
+
});
|
|
705
|
+
|
|
706
|
+
it('classifies README as context', () => {
|
|
707
|
+
expect(classifyDocument('README.md')).toBe('context');
|
|
708
|
+
expect(classifyDocument('docs/getting-started.md')).toBe('context');
|
|
709
|
+
});
|
|
710
|
+
});
|
|
711
|
+
|
|
712
|
+
describe('parseDocument', () => {
|
|
713
|
+
it('creates envelope from document content', () => {
|
|
714
|
+
const envelope = parseDocument({
|
|
715
|
+
path: 'docs/adr/001-use-postgres.md',
|
|
716
|
+
content: '# ADR-001: Use PostgreSQL\n\nWe decided to use PostgreSQL for all new services due to its reliability and JSON support.',
|
|
717
|
+
});
|
|
718
|
+
|
|
719
|
+
expect(envelope.origin.source_type).toBe('document');
|
|
720
|
+
expect(envelope.origin.source_ref).toBe('docs/adr/001-use-postgres.md');
|
|
721
|
+
expect(envelope.suggested_type).toBe('decision');
|
|
722
|
+
expect(envelope.text).toContain('PostgreSQL');
|
|
723
|
+
expect(envelope.suggested_sensitivity).toBe('internal');
|
|
724
|
+
});
|
|
725
|
+
|
|
726
|
+
it('truncates very long documents', () => {
|
|
727
|
+
const longContent = 'x'.repeat(10000);
|
|
728
|
+
const envelope = parseDocument({
|
|
729
|
+
path: 'docs/guide.md',
|
|
730
|
+
content: longContent,
|
|
731
|
+
});
|
|
732
|
+
expect(envelope.text.length).toBeLessThanOrEqual(4000);
|
|
733
|
+
});
|
|
734
|
+
|
|
735
|
+
it('sets higher authority via raw_metadata', () => {
|
|
736
|
+
const envelope = parseDocument({
|
|
737
|
+
path: 'docs/adr/001.md',
|
|
738
|
+
content: 'ADR content',
|
|
739
|
+
});
|
|
740
|
+
expect(envelope.raw_metadata.authority_boost).toBe(true);
|
|
741
|
+
});
|
|
742
|
+
});
|
|
743
|
+
```
|
|
744
|
+
|
|
745
|
+
- [ ] **Step 2: Implement document adapter**
|
|
746
|
+
|
|
747
|
+
```typescript
|
|
748
|
+
// src/lib/cortex/signals/adapters/document.ts
|
|
749
|
+
import type { SignalAdapter, SignalEnvelope } from '../types';
|
|
750
|
+
import type { KnowledgeType } from '../../knowledge/types';
|
|
751
|
+
|
|
752
|
+
const MAX_DOC_LENGTH = 4000;
|
|
753
|
+
|
|
754
|
+
const DOC_TYPE_PATTERNS: [RegExp, KnowledgeType][] = [
|
|
755
|
+
[/\badr[s]?\b/i, 'decision'],
|
|
756
|
+
[/\bADR[-_]/i, 'decision'],
|
|
757
|
+
[/\brunbook/i, 'pattern'],
|
|
758
|
+
[/\bplaybook/i, 'pattern'],
|
|
759
|
+
[/\bREADME/i, 'context'],
|
|
760
|
+
[/\bguide/i, 'context'],
|
|
761
|
+
[/\bchangelog/i, 'summary'],
|
|
762
|
+
];
|
|
763
|
+
|
|
764
|
+
export function classifyDocument(filepath: string): KnowledgeType {
|
|
765
|
+
for (const [pattern, type] of DOC_TYPE_PATTERNS) {
|
|
766
|
+
if (pattern.test(filepath)) return type;
|
|
767
|
+
}
|
|
768
|
+
return 'context';
|
|
769
|
+
}
|
|
770
|
+
|
|
771
|
+
export interface DocumentInput {
|
|
772
|
+
path: string;
|
|
773
|
+
content: string;
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
export function parseDocument(input: DocumentInput): SignalEnvelope {
|
|
777
|
+
const type = classifyDocument(input.path);
|
|
778
|
+
const text = input.content.length > MAX_DOC_LENGTH
|
|
779
|
+
? input.content.slice(0, MAX_DOC_LENGTH)
|
|
780
|
+
: input.content;
|
|
781
|
+
|
|
782
|
+
return {
|
|
783
|
+
text,
|
|
784
|
+
origin: {
|
|
785
|
+
source_type: 'document',
|
|
786
|
+
source_ref: input.path,
|
|
787
|
+
creator_entity_id: 'person-default-user',
|
|
788
|
+
},
|
|
789
|
+
entities: [],
|
|
790
|
+
suggested_type: type,
|
|
791
|
+
suggested_sensitivity: 'internal',
|
|
792
|
+
raw_metadata: {
|
|
793
|
+
file_path: input.path,
|
|
794
|
+
authority_boost: true, // documents have higher authority
|
|
795
|
+
},
|
|
796
|
+
};
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
/**
|
|
800
|
+
* Document adapter — scans docs directories for markdown files.
|
|
801
|
+
*/
|
|
802
|
+
export class DocumentAdapter implements SignalAdapter {
|
|
803
|
+
name = 'document';
|
|
804
|
+
schedule = 'polling' as const;
|
|
805
|
+
|
|
806
|
+
constructor(private docPaths: string[]) {}
|
|
807
|
+
|
|
808
|
+
async *extract(): AsyncIterable<SignalEnvelope> {
|
|
809
|
+
const fs = await import('fs');
|
|
810
|
+
const path = await import('path');
|
|
811
|
+
|
|
812
|
+
for (const docDir of this.docPaths) {
|
|
813
|
+
try {
|
|
814
|
+
const files = this.walkDir(docDir, fs, path);
|
|
815
|
+
for (const file of files) {
|
|
816
|
+
if (!file.endsWith('.md')) continue;
|
|
817
|
+
try {
|
|
818
|
+
const content = fs.readFileSync(file, 'utf-8');
|
|
819
|
+
yield parseDocument({ path: file, content });
|
|
820
|
+
} catch {
|
|
821
|
+
// File not readable, skip
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
} catch {
|
|
825
|
+
// Directory not accessible, skip
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
async healthCheck(): Promise<boolean> {
|
|
831
|
+
const fs = await import('fs');
|
|
832
|
+
return this.docPaths.some(p => {
|
|
833
|
+
try { return fs.statSync(p).isDirectory(); } catch { return false; }
|
|
834
|
+
});
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
private walkDir(dir: string, fs: any, path: any): string[] {
|
|
838
|
+
const results: string[] = [];
|
|
839
|
+
try {
|
|
840
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
841
|
+
for (const entry of entries) {
|
|
842
|
+
const full = path.join(dir, entry.name);
|
|
843
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
844
|
+
results.push(...this.walkDir(full, fs, path));
|
|
845
|
+
} else if (entry.isFile()) {
|
|
846
|
+
results.push(full);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
} catch { /* not accessible */ }
|
|
850
|
+
return results;
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
- [ ] **Step 3: Run tests, commit**
|
|
856
|
+
|
|
857
|
+
Run: `npx vitest run tests/lib/cortex/signals/adapters/document.test.ts`
|
|
858
|
+
|
|
859
|
+
```bash
|
|
860
|
+
git commit -m "feat(cortex): add document signal adapter"
|
|
861
|
+
```
|
|
862
|
+
|
|
863
|
+
---
|
|
864
|
+
|
|
865
|
+
## Chunk 3: Integration and Barrel Export
|
|
866
|
+
|
|
867
|
+
### Task 6: Barrel export and CortexInstance integration
|
|
868
|
+
|
|
869
|
+
**Files:**
|
|
870
|
+
- Create: `src/lib/cortex/signals/index.ts`
|
|
871
|
+
- Modify: `src/lib/cortex/index.ts`
|
|
872
|
+
|
|
873
|
+
- [ ] **Step 1: Create barrel export**
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
// src/lib/cortex/signals/index.ts
|
|
877
|
+
export { SignalPipeline } from './pipeline';
|
|
878
|
+
export type { SignalPipelineDeps } from './pipeline';
|
|
879
|
+
export { ConversationAdapter } from './adapters/conversation';
|
|
880
|
+
export { GitAdapter, parseGitLog } from './adapters/git';
|
|
881
|
+
export type { GitLogEntry } from './adapters/git';
|
|
882
|
+
export { DocumentAdapter, parseDocument, classifyDocument } from './adapters/document';
|
|
883
|
+
export type { DocumentInput } from './adapters/document';
|
|
884
|
+
export type { SignalEnvelope, SignalAdapter, IngestResult, EdgeUpdate } from './types';
|
|
885
|
+
```
|
|
886
|
+
|
|
887
|
+
- [ ] **Step 2: Add SignalPipeline to CortexInstance**
|
|
888
|
+
|
|
889
|
+
Read `src/lib/cortex/index.ts`. Add:
|
|
890
|
+
|
|
891
|
+
1. Import: `import { SignalPipeline } from './signals/pipeline';`
|
|
892
|
+
2. Add `signalPipeline?: SignalPipeline` to CortexInstance interface
|
|
893
|
+
3. In getCortex(), after graph/resolver initialization:
|
|
894
|
+
|
|
895
|
+
```typescript
|
|
896
|
+
const signalPipeline = new SignalPipeline({ store, embedding, graph, resolver });
|
|
897
|
+
```
|
|
898
|
+
|
|
899
|
+
4. Include in instance object.
|
|
900
|
+
|
|
901
|
+
- [ ] **Step 3: Run full test suite**
|
|
902
|
+
|
|
903
|
+
Run: `npx vitest run tests/lib/cortex/`
|
|
904
|
+
|
|
905
|
+
- [ ] **Step 4: Commit**
|
|
906
|
+
|
|
907
|
+
```bash
|
|
908
|
+
git add src/lib/cortex/signals/index.ts src/lib/cortex/index.ts
|
|
909
|
+
git commit -m "feat(cortex): add signal module barrel export and CortexInstance integration"
|
|
910
|
+
```
|
|
911
|
+
|
|
912
|
+
---
|
|
913
|
+
|
|
914
|
+
## Summary
|
|
915
|
+
|
|
916
|
+
| Task | Component | Tests | Status |
|
|
917
|
+
|------|-----------|-------|--------|
|
|
918
|
+
| 1 | Signal types | — | |
|
|
919
|
+
| 2 | Unified SignalPipeline | 8 | |
|
|
920
|
+
| 3 | Conversation adapter | — | |
|
|
921
|
+
| 4 | Git history adapter | 5 | |
|
|
922
|
+
| 5 | Document adapter | 6 | |
|
|
923
|
+
| 6 | Barrel export + integration | regression | |
|
|
924
|
+
|
|
925
|
+
**Total: 6 tasks, ~19 new tests, 3 chunks**
|
|
926
|
+
|
|
927
|
+
**Deferred adapters** (require external APIs/webhooks — implement when infrastructure is ready):
|
|
928
|
+
- PR Review adapter (GitHub API)
|
|
929
|
+
- Test Signal adapter (CI pipeline webhook)
|
|
930
|
+
- Deployment adapter (deploy system webhook)
|
|
931
|
+
- Behavioral Inference adapter (daily cron analyzing accumulated signals)
|
|
932
|
+
|
|
933
|
+
Each deferred adapter = implement `SignalAdapter` interface + test, zero changes to SignalPipeline.
|