cognitive-fabric 0.1.0__tar.gz

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.
Files changed (148) hide show
  1. cognitive_fabric-0.1.0/.dockerignore +69 -0
  2. cognitive_fabric-0.1.0/.env.example +47 -0
  3. cognitive_fabric-0.1.0/.github/dependabot.yml +76 -0
  4. cognitive_fabric-0.1.0/.github/workflows/ci.yml +285 -0
  5. cognitive_fabric-0.1.0/.github/workflows/live-provider-smoke.yml +73 -0
  6. cognitive_fabric-0.1.0/.github/workflows/release.yml +140 -0
  7. cognitive_fabric-0.1.0/.gitignore +78 -0
  8. cognitive_fabric-0.1.0/Dockerfile +54 -0
  9. cognitive_fabric-0.1.0/Dockerfile.dev +35 -0
  10. cognitive_fabric-0.1.0/LICENSE +201 -0
  11. cognitive_fabric-0.1.0/NOTICE +29 -0
  12. cognitive_fabric-0.1.0/PKG-INFO +274 -0
  13. cognitive_fabric-0.1.0/README.md +233 -0
  14. cognitive_fabric-0.1.0/docker-compose.yml +118 -0
  15. cognitive_fabric-0.1.0/docs/SECURITY.md +50 -0
  16. cognitive_fabric-0.1.0/docs/api/cli.md +455 -0
  17. cognitive_fabric-0.1.0/docs/api/schemas.md +372 -0
  18. cognitive_fabric-0.1.0/docs/api/tools.md +980 -0
  19. cognitive_fabric-0.1.0/docs/architecture.md +286 -0
  20. cognitive_fabric-0.1.0/docs/examples/basic-usage.md +440 -0
  21. cognitive_fabric-0.1.0/docs/fabric.md +75 -0
  22. cognitive_fabric-0.1.0/docs/guides/configuration.md +306 -0
  23. cognitive_fabric-0.1.0/docs/guides/development.md +502 -0
  24. cognitive_fabric-0.1.0/docs/guides/getting-started.md +217 -0
  25. cognitive_fabric-0.1.0/docs/guides/memory-optimizer.md +403 -0
  26. cognitive_fabric-0.1.0/docs/index.md +77 -0
  27. cognitive_fabric-0.1.0/pyproject.toml +149 -0
  28. cognitive_fabric-0.1.0/scripts/live_provider_smoke.py +133 -0
  29. cognitive_fabric-0.1.0/scripts/start_server.py +23 -0
  30. cognitive_fabric-0.1.0/src/cognitive_fabric/__init__.py +7 -0
  31. cognitive_fabric-0.1.0/src/cognitive_fabric/_version.py +20 -0
  32. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/__init__.py +7 -0
  33. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/__init__.py +19 -0
  34. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/agent.py +367 -0
  35. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/base.py +129 -0
  36. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/context_builder.py +319 -0
  37. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/mcp_sampling.py +407 -0
  38. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/prompt_manager.py +298 -0
  39. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/services/__init__.py +17 -0
  40. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/services/analysis.py +290 -0
  41. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/services/execution.py +416 -0
  42. cognitive_fabric-0.1.0/src/cognitive_fabric/agents/memory_optimizer/services/planning.py +303 -0
  43. cognitive_fabric-0.1.0/src/cognitive_fabric/cli/__init__.py +5 -0
  44. cognitive_fabric-0.1.0/src/cognitive_fabric/cli/main.py +463 -0
  45. cognitive_fabric-0.1.0/src/cognitive_fabric/config.py +95 -0
  46. cognitive_fabric-0.1.0/src/cognitive_fabric/db/__init__.py +11 -0
  47. cognitive_fabric-0.1.0/src/cognitive_fabric/db/connection_manager.py +126 -0
  48. cognitive_fabric-0.1.0/src/cognitive_fabric/db/kuzu_client.py +269 -0
  49. cognitive_fabric-0.1.0/src/cognitive_fabric/db/query_executor.py +208 -0
  50. cognitive_fabric-0.1.0/src/cognitive_fabric/db/repository_factory.py +244 -0
  51. cognitive_fabric-0.1.0/src/cognitive_fabric/db/repository_provider.py +138 -0
  52. cognitive_fabric-0.1.0/src/cognitive_fabric/db/schema_manager.py +425 -0
  53. cognitive_fabric-0.1.0/src/cognitive_fabric/fabric/__init__.py +6 -0
  54. cognitive_fabric-0.1.0/src/cognitive_fabric/fabric/ingestor.py +126 -0
  55. cognitive_fabric-0.1.0/src/cognitive_fabric/fabric/vector_store.py +239 -0
  56. cognitive_fabric-0.1.0/src/cognitive_fabric/llm.py +239 -0
  57. cognitive_fabric-0.1.0/src/cognitive_fabric/main.py +35 -0
  58. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/__init__.py +12 -0
  59. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/__init__.py +48 -0
  60. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/analyze.py +165 -0
  61. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/associate.py +169 -0
  62. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/bulk_import.py +137 -0
  63. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/context.py +113 -0
  64. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/delete.py +477 -0
  65. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/detect.py +145 -0
  66. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/entity.py +195 -0
  67. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/fabric.py +213 -0
  68. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/introspect.py +224 -0
  69. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/memory_bank.py +90 -0
  70. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/memory_optimizer.py +577 -0
  71. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/query.py +159 -0
  72. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/handlers/search.py +416 -0
  73. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/server.py +319 -0
  74. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/tool_context.py +91 -0
  75. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/tool_registry.py +131 -0
  76. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/tools/__init__.py +13 -0
  77. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/tools/base.py +85 -0
  78. cognitive_fabric-0.1.0/src/cognitive_fabric/mcp/tools/definitions.py +929 -0
  79. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/__init__.py +29 -0
  80. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/base.py +216 -0
  81. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/component/__init__.py +11 -0
  82. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/component/algorithms.py +224 -0
  83. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/component/crud.py +302 -0
  84. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/component/graph.py +257 -0
  85. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/component_repo.py +30 -0
  86. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/context_repo.py +483 -0
  87. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/decision_repo.py +375 -0
  88. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/file_repo.py +509 -0
  89. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/graph_projection.py +116 -0
  90. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/metadata_repo.py +364 -0
  91. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/repository_repo.py +241 -0
  92. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/requirement_repo.py +183 -0
  93. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/rule_repo.py +449 -0
  94. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/symbol_repo.py +171 -0
  95. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/tag_repo.py +518 -0
  96. cognitive_fabric-0.1.0/src/cognitive_fabric/repositories/trace_repo.py +140 -0
  97. cognitive_fabric-0.1.0/src/cognitive_fabric/services/__init__.py +11 -0
  98. cognitive_fabric-0.1.0/src/cognitive_fabric/services/core/__init__.py +19 -0
  99. cognitive_fabric-0.1.0/src/cognitive_fabric/services/core/interfaces.py +316 -0
  100. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/__init__.py +21 -0
  101. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/context.py +252 -0
  102. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/data_fabric.py +159 -0
  103. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/dream.py +193 -0
  104. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/entity.py +743 -0
  105. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/graph_analysis.py +437 -0
  106. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/graph_query.py +553 -0
  107. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/memory_bank.py +337 -0
  108. cognitive_fabric-0.1.0/src/cognitive_fabric/services/domain/metadata.py +177 -0
  109. cognitive_fabric-0.1.0/src/cognitive_fabric/services/memory_service.py +280 -0
  110. cognitive_fabric-0.1.0/src/cognitive_fabric/services/service_container.py +357 -0
  111. cognitive_fabric-0.1.0/src/cognitive_fabric/services/snapshot_service.py +463 -0
  112. cognitive_fabric-0.1.0/src/cognitive_fabric/types/__init__.py +81 -0
  113. cognitive_fabric-0.1.0/src/cognitive_fabric/types/entities.py +386 -0
  114. cognitive_fabric-0.1.0/src/cognitive_fabric/types/mcp_types.py +202 -0
  115. cognitive_fabric-0.1.0/src/cognitive_fabric/types/optimization.py +355 -0
  116. cognitive_fabric-0.1.0/src/cognitive_fabric/utils/__init__.py +18 -0
  117. cognitive_fabric-0.1.0/src/cognitive_fabric/utils/id_utils.py +103 -0
  118. cognitive_fabric-0.1.0/src/cognitive_fabric/utils/logger.py +81 -0
  119. cognitive_fabric-0.1.0/src/cognitive_fabric/utils/mutex.py +75 -0
  120. cognitive_fabric-0.1.0/src/cognitive_fabric/utils/path_utils.py +151 -0
  121. cognitive_fabric-0.1.0/src/cognitive_fabric/utils/security.py +183 -0
  122. cognitive_fabric-0.1.0/tests/__init__.py +1 -0
  123. cognitive_fabric-0.1.0/tests/conftest.py +192 -0
  124. cognitive_fabric-0.1.0/tests/e2e/__init__.py +1 -0
  125. cognitive_fabric-0.1.0/tests/e2e/test_http_transport.py +132 -0
  126. cognitive_fabric-0.1.0/tests/e2e/test_mcp_server.py +226 -0
  127. cognitive_fabric-0.1.0/tests/e2e/tools_snapshot.json +1012 -0
  128. cognitive_fabric-0.1.0/tests/integration/__init__.py +1 -0
  129. cognitive_fabric-0.1.0/tests/integration/test_fabric_handler.py +196 -0
  130. cognitive_fabric-0.1.0/tests/integration/test_fabric_repos.py +188 -0
  131. cognitive_fabric-0.1.0/tests/integration/test_fabric_schema.py +24 -0
  132. cognitive_fabric-0.1.0/tests/integration/test_fabric_services.py +334 -0
  133. cognitive_fabric-0.1.0/tests/integration/test_kuzu_operations.py +289 -0
  134. cognitive_fabric-0.1.0/tests/integration/test_optimizer_parity.py +483 -0
  135. cognitive_fabric-0.1.0/tests/integration/test_repos_extra.py +154 -0
  136. cognitive_fabric-0.1.0/tests/integration/test_services.py +117 -0
  137. cognitive_fabric-0.1.0/tests/integration/test_snapshot_service.py +385 -0
  138. cognitive_fabric-0.1.0/tests/integration/test_vector_store.py +98 -0
  139. cognitive_fabric-0.1.0/tests/integration/test_wire_contract.py +246 -0
  140. cognitive_fabric-0.1.0/tests/unit/__init__.py +1 -0
  141. cognitive_fabric-0.1.0/tests/unit/test_db_path_env.py +97 -0
  142. cognitive_fabric-0.1.0/tests/unit/test_docs_consistency.py +755 -0
  143. cognitive_fabric-0.1.0/tests/unit/test_logging.py +120 -0
  144. cognitive_fabric-0.1.0/tests/unit/test_no_model_ids_in_source.py +176 -0
  145. cognitive_fabric-0.1.0/tests/unit/test_optimizer_limits.py +219 -0
  146. cognitive_fabric-0.1.0/tests/unit/test_path_confinement.py +97 -0
  147. cognitive_fabric-0.1.0/tests/unit/test_types.py +245 -0
  148. cognitive_fabric-0.1.0/tests/unit/test_version.py +75 -0
@@ -0,0 +1,69 @@
1
+ # Docker matches these against the path relative to the context root, so a bare
2
+ # name covers only the root -- `__pycache__` does not match `src/x/__pycache__`.
3
+ # Depth-capable patterns therefore carry `**/`. Patterns that are meaningfully
4
+ # root-only (.git, .venv, data) stay anchored.
5
+
6
+ # Git
7
+ .git
8
+ .gitignore
9
+
10
+ # Python
11
+ **/__pycache__
12
+ **/*.py[cod]
13
+ **/*$py.class
14
+ **/*.so
15
+ .Python
16
+ .venv
17
+ venv
18
+ ENV
19
+ env
20
+
21
+ # IDE
22
+ **/.vscode
23
+ **/.idea
24
+ **/*.swp
25
+ **/*.swo
26
+
27
+ # Testing
28
+ **/.pytest_cache
29
+ **/.coverage
30
+ **/htmlcov
31
+ **/.tox
32
+ **/.nox
33
+
34
+ # Type-checker and linter caches (61 MB of .mypy_cache here)
35
+ **/.mypy_cache
36
+ **/.ruff_cache
37
+ **/.cache
38
+
39
+ # Build
40
+ build
41
+ dist
42
+ **/*.egg-info
43
+ **/*.egg
44
+
45
+ # Documentation
46
+ docs/_build
47
+
48
+ # Local data
49
+ data/
50
+ **/*.db
51
+ **/*.kuzu
52
+ **/*.wal
53
+ .kuzumem/
54
+ .fabric_data/
55
+
56
+ # Misc
57
+ **/.DS_Store
58
+ **/*.log
59
+
60
+ # Secrets. .gitignore already covers `.env`, but this file did not -- and
61
+ # .dockerignore is what governs the build context, not .gitignore. An
62
+ # untracked .env is exactly what a broad COPY would pick up. Nested too, which
63
+ # is why this is `**/` rather than a bare name.
64
+ **/.env
65
+ **/.env.*
66
+ **/*.pem
67
+ **/*.key
68
+ **/*.p12
69
+ **/id_rsa*
@@ -0,0 +1,47 @@
1
+ # Cognitive-Fabric Configuration
2
+ #
3
+ # Every model id in this file is an example. No model is compiled into the
4
+ # package: a model id is a vendor, a price and a capability envelope chosen at
5
+ # install time, and it goes stale only when the provider retires the name. A
6
+ # provider is usable once you name a model for it, and the server says which
7
+ # setting is missing when you have not.
8
+
9
+ # Logging
10
+ COGNITIVE_FABRIC_LOG_LEVEL=INFO
11
+ COGNITIVE_FABRIC_LOG_JSON=true
12
+
13
+ # Database (required: the server has no default path)
14
+ # COGNITIVE_FABRIC_DB_PATH=/path/to/custom/db
15
+
16
+ # LLM Provider (openai or anthropic)
17
+ COGNITIVE_FABRIC_LLM_PROVIDER=openai
18
+
19
+ # OpenAI Configuration
20
+ OPENAI_API_KEY=sk-your-api-key-here
21
+ # Required. Must be a model the provider still lists -- it is checked against
22
+ # https://api.openai.com/v1/models before the first call.
23
+ COGNITIVE_FABRIC_OPENAI_MODEL=<a model id from https://platform.openai.com/docs/models>
24
+
25
+ # Anthropic Configuration (alternative)
26
+ # ANTHROPIC_API_KEY=sk-ant-your-api-key-here
27
+ # COGNITIVE_FABRIC_ANTHROPIC_MODEL=<a model id from https://docs.anthropic.com/en/docs/about-claude/models>
28
+
29
+ # Embeddings for the semantic layer. There is no default cloud embedding model:
30
+ # an embedding model fixes both the price per million tokens and the width of
31
+ # every vector already in the table, and vectors from two models are not
32
+ # comparable. The local backends keep their own defaults.
33
+ #
34
+ # fastembed local, ONNX, no torch (default)
35
+ # sentence-transformers local, needs torch
36
+ # openai cloud, model required
37
+ # COGNITIVE_FABRIC_FABRIC_EMBEDDING_PROVIDER=fastembed
38
+ # COGNITIVE_FABRIC_FABRIC_EMBEDDING_MODEL=<required when the provider is openai>
39
+
40
+ # Memory Optimizer
41
+ COGNITIVE_FABRIC_OPTIMIZER_DEFAULT_STRATEGY=conservative
42
+ COGNITIVE_FABRIC_OPTIMIZER_ENABLE_MCP_SAMPLING=true
43
+ COGNITIVE_FABRIC_OPTIMIZER_SNAPSHOT_FAILURE_POLICY=warn
44
+
45
+ # Cognitive Fabric (semantic layer)
46
+ # Where the LanceDB vector store is stored (defaults to ./.fabric_data)
47
+ # COGNITIVE_FABRIC_FABRIC_DATA_DIR=/path/to/fabric_data
@@ -0,0 +1,76 @@
1
+ # Dependabot is configured rather than switched off.
2
+ #
3
+ # Left at the default, this opens a PR per dependency per week, which in practice
4
+ # is a queue nobody can merge. This repository holds two dependencies deliberately
5
+ # (kuzu, mcp) and caps two more (the cloud SDKs), so an ungrouped config would
6
+ # spend most of its PRs proposing to lift exactly the pins that are there on
7
+ # purpose. One grouped PR per ecosystem per month is a size a person can review in
8
+ # one sitting.
9
+ #
10
+ # The `ignore` entries below restate decisions already written into
11
+ # pyproject.toml. If a pin is lifted there, lift it here too -- otherwise this file
12
+ # goes on suppressing updates nobody still means to suppress. Note that Dependabot
13
+ # reads this file only from the default branch, so changes here take effect at the
14
+ # next merge to `main`, not at the push.
15
+ #
16
+ # There are deliberately no `labels:` entries: requesting a label that does not
17
+ # exist in the repository gives you a PR that carries a "could not be found"
18
+ # warning and no labels at all. Create them first if you want them.
19
+
20
+ version: 2
21
+
22
+ updates:
23
+ # ------------------------------------------------------------------ Python deps
24
+ - package-ecosystem: "pip"
25
+ directory: "/"
26
+ schedule:
27
+ interval: "monthly"
28
+ commit-message:
29
+ prefix: "deps"
30
+ groups:
31
+ pip:
32
+ patterns: ["*"]
33
+ ignore:
34
+ # Pinned exactly in pyproject.toml. kuzu is archived upstream -- 0.11.3 is the
35
+ # final release -- and its file format is not stable before 1.0, so a bump can
36
+ # leave an existing graph unreadable. Lifting this pin needs a migration step,
37
+ # not a dependency bump.
38
+ - dependency-name: "kuzu"
39
+
40
+ # Capped below 2 in pyproject.toml: the 2.x SDK removed the low-level Server
41
+ # list_tools/call_tool decorators that the server is built on.
42
+ - dependency-name: "mcp"
43
+ versions: [">= 2.0.0"]
44
+
45
+ # The two cloud SDKs in the `[cloud]` extra are deliberately *not* ignored,
46
+ # though they are capped in pyproject.toml. An ignore would suppress the one
47
+ # PR that would show you a break: an SDK major rewrote the request types under
48
+ # its callers once already, and the failure arrived as a runtime TypeError on a
49
+ # user's machine. A major bump now opens a PR proposing to lift the cap, and
50
+ # the live-provider smoke workflow (Actions -> Live provider smoke -> Run
51
+ # workflow) is what says whether the new SDK still accepts the request this
52
+ # package builds. A green install proves nothing about the request shape.
53
+
54
+ # ----------------------------------------------------------------- GitHub Actions
55
+ - package-ecosystem: "github-actions"
56
+ directory: "/"
57
+ schedule:
58
+ interval: "monthly"
59
+ commit-message:
60
+ prefix: "ci"
61
+ groups:
62
+ github-actions:
63
+ patterns: ["*"]
64
+
65
+ # ------------------------------------------------------------------ Docker images
66
+ # The Dockerfile's base image is the only docker dependency here; nothing is held,
67
+ # so this is a plain grouped update.
68
+ - package-ecosystem: "docker"
69
+ directory: "/"
70
+ schedule:
71
+ interval: "monthly"
72
+ commit-message:
73
+ prefix: "docker"
74
+ groups:
75
+ docker:
76
+ patterns: ["*"]
@@ -0,0 +1,285 @@
1
+ # Continuous Integration - Run tests and linting
2
+ name: CI
3
+
4
+ on:
5
+ push:
6
+ branches:
7
+ - main
8
+ - master
9
+ # Deliberately no branch filter. With `branches: [main, master]` here, a pull
10
+ # request whose base is any other branch -- which is how a change is stacked on
11
+ # another unmerged change -- got no CI at all, and "no checks reported" in the
12
+ # PR list reads much like "checks passed". Every pull request runs CI.
13
+ pull_request:
14
+
15
+ jobs:
16
+ # The workflow files are the one thing here with no other gate, and release.yml
17
+ # is the part that runs rarely and matters most -- so a mistake in it is found
18
+ # at release time, by everyone. actionlint reads the workflows themselves:
19
+ # expression syntax, job-graph references, and the shell and Python inside each
20
+ # `run:` block, through shellcheck and pyflakes, both of which the pinned image
21
+ # below ships. Run against this tree it reported two SC2086 findings in
22
+ # release.yml (`>> $GITHUB_OUTPUT` unquoted); both are fixed.
23
+ actionlint:
24
+ name: Lint workflows
25
+ runs-on: ubuntu-latest
26
+ steps:
27
+ - name: Checkout repository
28
+ uses: actions/checkout@v4
29
+
30
+ - name: Run actionlint
31
+ uses: docker://rhysd/actionlint:1.7.12
32
+ with:
33
+ args: -color
34
+
35
+ lint:
36
+ name: Lint
37
+ runs-on: ubuntu-latest
38
+ steps:
39
+ - name: Checkout repository
40
+ uses: actions/checkout@v4
41
+
42
+ - name: Set up Python
43
+ uses: actions/setup-python@v5
44
+ with:
45
+ python-version: '3.11'
46
+
47
+ - name: Install dependencies
48
+ run: pip install -e '.[dev]'
49
+
50
+ # `scripts/` is included, not just `src/` and `tests/`. The smoke script is
51
+ # not part of the package but it is part of this repository, and code no
52
+ # gate reads is code that rots. This used to name that one file because
53
+ # scripts/start_server.py did not pass -- it imported `kuzumempy.main`,
54
+ # which does not exist; the tool-surface freeze fixed it to
55
+ # `cognitive_fabric.main`. Its remaining E402 is a deliberate path insert,
56
+ # marked as such.
57
+ - name: Run Ruff linter
58
+ run: ruff check src tests scripts
59
+
60
+ # mypy is deliberately not a gate here yet. It is configured
61
+ # (`strict = true` in pyproject.toml) but the tree does not satisfy it:
62
+ # 199 errors across 39 files as of this commit. A job that fails on every
63
+ # run says less than no job at all, so wire this in once the tree is
64
+ # clean — the configuration is already in place and only the step is
65
+ # missing. The command will be `mypy src`.
66
+
67
+ test:
68
+ name: Test (py${{ matrix.python-version }}, ${{ matrix.profile.name }})
69
+ runs-on: ubuntu-latest
70
+ needs: lint
71
+ strategy:
72
+ # Report every combination, rather than cancelling the others when one
73
+ # fails.
74
+ fail-fast: false
75
+ matrix:
76
+ # Every version the package installs on, and nothing beyond them. 3.14 is
77
+ # excluded by `requires-python`, because kuzu 0.11.3 ships no cp314 wheels
78
+ # for macOS or Windows; the comment there has the detail.
79
+ python-version: ['3.11', '3.12', '3.13']
80
+ # Two installs, because the failure worth catching here is not a red
81
+ # test -- it is a green run in which the optional half never ran. The
82
+ # vector-store suite is `pytest.importorskip("lancedb")` at module level,
83
+ # so under the minimal install it collapses to a single module skip and
84
+ # its four tests do not exist; the full install is where they run. The
85
+ # minimal install is where the *degraded* path -- semantic search
86
+ # reporting itself unavailable rather than raising -- is the only thing
87
+ # that can run, and so must. That pairing has to hold on both Python
88
+ # versions, because a green minimal run is exactly what a broken install
89
+ # of the full one looks like.
90
+ profile:
91
+ - name: minimal
92
+ extras: dev
93
+ - name: full
94
+ extras: dev,cloud,semantic
95
+
96
+ steps:
97
+ - name: Checkout repository
98
+ uses: actions/checkout@v4
99
+
100
+ - name: Set up Python
101
+ uses: actions/setup-python@v5
102
+ with:
103
+ python-version: ${{ matrix.python-version }}
104
+
105
+ # Every extra is installed somewhere here. An extra that does not resolve
106
+ # is a broken install for every user who asks for it, and nothing else in
107
+ # CI would notice. `cloud` is in the full profile only; no test imports
108
+ # either SDK (the LLM tests use duck-typed fakes), so it is installed to
109
+ # prove it installs, not to cover a hidden skip.
110
+ #
111
+ # `full` downloads fastembed's ONNX model on the first vector-store test,
112
+ # which is the price of the four tests actually running rather than
113
+ # skipping. It happens once per job.
114
+ - name: Install dependencies
115
+ run: pip install -e '.[${{ matrix.profile.extras }}]'
116
+
117
+ # `-rs` is not decoration. Without it a module-level skip leaves no line at
118
+ # all in the `-v` output, so "the vector-store tests skipped" and "the
119
+ # vector-store tests were never mentioned" read identically -- which is the
120
+ # whole defect this job pair exists for. The short summary is what names the
121
+ # reason, and the check below reads it.
122
+ - name: Run tests
123
+ env:
124
+ PROFILE: ${{ matrix.profile.name }}
125
+ run: |
126
+ set +e
127
+ pytest tests/unit/ tests/integration/ tests/e2e/ -v --tb=short -rs \
128
+ > /tmp/pytest.log 2>&1
129
+ PYTEST_RC=$?
130
+ set -e
131
+ cat /tmp/pytest.log
132
+ export PYTEST_RC
133
+ python3 - <<'PY'
134
+ import os
135
+ import pathlib
136
+ import re
137
+ import sys
138
+
139
+ rc = int(os.environ["PYTEST_RC"])
140
+ log = pathlib.Path("/tmp/pytest.log").read_text()
141
+
142
+ # A red run is its own message, and it is printed above.
143
+ if rc != 0:
144
+ sys.exit(rc)
145
+
146
+ profile = os.environ["PROFILE"]
147
+
148
+ # One line per test that actually ran, so this counts tests executed --
149
+ # not tests collected, and not "no failures".
150
+ ran = re.findall(
151
+ r"^tests/integration/test_vector_store\.py::\S+ PASSED", log, re.M
152
+ )
153
+ degraded = (
154
+ "TestTheDegradedSemanticPath::"
155
+ "test_semantic_search_reports_itself_unavailable PASSED"
156
+ )
157
+
158
+ if profile == "full":
159
+ # `full` installs the [semantic] extra, so the four tests are the
160
+ # point of the profile; anything fewer means part of them skipped.
161
+ if len(ran) != 4:
162
+ sys.exit(
163
+ f"the full profile installs [semantic], so all four "
164
+ f"vector-store tests must run; {len(ran)} did: {ran}"
165
+ )
166
+ print(f"full profile: {len(ran)}/4 vector-store tests ran")
167
+ else:
168
+ # `minimal` has no [semantic] extra: the module must have skipped,
169
+ # and the degraded path must have been asserted. A profile that
170
+ # merely skipped things has proved nothing.
171
+ if ran:
172
+ sys.exit(
173
+ f"the minimal profile does not install [semantic], so "
174
+ f"test_vector_store.py must not run, but {len(ran)} of its "
175
+ f"tests did: {ran}"
176
+ )
177
+ if "SKIPPED [1] tests/integration/test_vector_store.py:" not in log:
178
+ sys.exit(
179
+ "the vector-store module did not report itself skipped, so "
180
+ "nothing here says the missing extra is what is missing"
181
+ )
182
+ if degraded not in log:
183
+ sys.exit(
184
+ "semantic search was never asserted to degrade, in the one "
185
+ "profile where it is unavailable"
186
+ )
187
+ print("minimal profile: vector store skipped; degraded path asserted")
188
+ PY
189
+
190
+ docker:
191
+ # Builds the image but never publishes it. CF ships a Dockerfile and a
192
+ # docker-compose.yml, and until this job existed nothing had ever built
193
+ # either -- so a broken image would have been found by whoever tried it
194
+ # first, after release, rather than here.
195
+ name: Docker (production image)
196
+ runs-on: ubuntu-latest
197
+ needs: lint
198
+
199
+ steps:
200
+ - name: Checkout repository
201
+ uses: actions/checkout@v4
202
+
203
+ - name: Build the production image
204
+ run: docker build --file Dockerfile --tag cognitive-fabric:ci .
205
+
206
+ - name: Handshake over stdio
207
+ # Initialise, then let stdin close. This does need the database path the
208
+ # Dockerfile sets: the server builds its MemoryService during startup and
209
+ # exits without one, so it cannot answer `initialize` pathless. No key
210
+ # and no network are needed, which is still the point of checking here.
211
+ #
212
+ # stdout carries the JSON-RPC stream, so anything the server logs has to
213
+ # go to stderr. It does -- PrintLoggerFactory(file=sys.stderr) in
214
+ # utils/logger.py -- and the assertion below fails rather than skips if
215
+ # that ever stops being true.
216
+ run: |
217
+ printf '%s\n' \
218
+ '{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"ci-handshake","version":"0"}}}' \
219
+ | timeout 300 docker run -i --rm cognitive-fabric:ci \
220
+ > /tmp/handshake.jsonl 2> /tmp/handshake.err \
221
+ || echo "docker run exit: $?"
222
+ echo "--- container stderr (tail) ---"
223
+ tail -20 /tmp/handshake.err || true
224
+
225
+ - name: Assert the handshake
226
+ run: |
227
+ python3 - <<'PY'
228
+ import json, pathlib, sys
229
+
230
+ # Both pinned deliberately: the version this repo publishes, and the
231
+ # name a client sees. Without an explicit `version=` the MCP SDK falls
232
+ # back to the *mcp package's* version, which is how the handshake once
233
+ # advertised 1.30.0 for a 0.1.0 server.
234
+ EXPECTED_VERSION = "0.1.0"
235
+ EXPECTED_NAME = "cognitive-fabric"
236
+
237
+ raw = pathlib.Path("/tmp/handshake.jsonl").read_text()
238
+
239
+ # stdout carries the JSON-RPC stream and nothing else. A stray log line
240
+ # here corrupts the protocol, so it fails rather than being skipped.
241
+ messages = []
242
+ for n, line in enumerate(raw.splitlines(), 1):
243
+ if not line.strip():
244
+ continue
245
+ try:
246
+ messages.append(json.loads(line))
247
+ except json.JSONDecodeError as e:
248
+ sys.exit(f"stdout line {n} is not JSON: {line[:160]!r} ({e})")
249
+
250
+ infos = [
251
+ m["result"]["serverInfo"]
252
+ for m in messages
253
+ if isinstance(m.get("result"), dict) and "serverInfo" in m["result"]
254
+ ]
255
+ if not infos:
256
+ sys.exit(f"no serverInfo in {len(messages)} message(s): {messages!r}")
257
+
258
+ info = infos[0]
259
+ if info.get("name") != EXPECTED_NAME:
260
+ sys.exit(f"serverInfo.name is {info.get('name')!r}, expected {EXPECTED_NAME!r}")
261
+ if info.get("version") != EXPECTED_VERSION:
262
+ sys.exit(
263
+ f"serverInfo.version is {info.get('version')!r}, "
264
+ f"expected {EXPECTED_VERSION!r}"
265
+ )
266
+
267
+ print(f"stdout: {len(messages)} JSON-RPC message(s), 0 stray lines")
268
+ print(f"serverInfo: {info}")
269
+ PY
270
+
271
+ test-summary:
272
+ name: Test Summary
273
+ runs-on: ubuntu-latest
274
+ needs: test
275
+ if: always()
276
+
277
+ steps:
278
+ - name: Check test results
279
+ run: |
280
+ if [ "${{ needs.test.result }}" == "success" ]; then
281
+ echo "All tests passed!"
282
+ else
283
+ echo "Some tests failed"
284
+ exit 1
285
+ fi
@@ -0,0 +1,73 @@
1
+ # One real call per cloud provider, weekly and on demand.
2
+ #
3
+ # This is the only job in this repository that talks to a provider for real.
4
+ # Everything else runs offline, and the tests that cover request shape use fakes
5
+ # -- deliberately, because that is what makes them fast and deterministic, and
6
+ # also what makes them unable to answer the one question only a live call can:
7
+ # does the model the operator configured still exist, and does the provider still
8
+ # accept the request this package builds for it? Vendors retire model ids on
9
+ # their own schedule, and no amount of local testing notices.
10
+ #
11
+ # So it is deliberately not in the push and PR gate. It needs secrets, it spends a
12
+ # fraction of a cent, and it fails for reasons that have nothing to do with the
13
+ # commit under test -- a red X on every PR would train people to ignore it. It
14
+ # runs on `main` only, weekly and by hand, and its failures are a signal about the
15
+ # world rather than about a diff.
16
+ #
17
+ # Models come from repository *variables* (Settings -> Secrets and variables
18
+ # -> Actions -> Variables): OPENAI_MODEL, ANTHROPIC_MODEL. A model id is
19
+ # not a credential, and being able to read it in the settings page is
20
+ # what makes a failure diagnosable.
21
+ # Keys come from repository *secrets* (OPENAI_API_KEY, ANTHROPIC_API_KEY).
22
+ #
23
+ # A provider with no key or no model is reported as skipped, and the job stays
24
+ # green -- but a run where *nothing* was configured fails, because a smoke test
25
+ # that passes by testing nothing is worse than one that never ran.
26
+ name: Live provider smoke
27
+
28
+ on:
29
+ # Manual: Actions -> Live provider smoke -> Run workflow.
30
+ workflow_dispatch:
31
+ schedule:
32
+ # Mondays at 07:19 UTC. Off the hour on purpose: the top of the hour is when
33
+ # most of the world's scheduled jobs fire, and GitHub queues behind them.
34
+ - cron: '19 7 * * 1'
35
+
36
+ # Read-only: this job runs provider SDKs against a checked-out tree and writes
37
+ # nothing back.
38
+ permissions:
39
+ contents: read
40
+
41
+ jobs:
42
+ smoke:
43
+ name: One call per provider
44
+ runs-on: ubuntu-latest
45
+ # `main` only, including for a manual dispatch, which can otherwise be
46
+ # started from any branch. The point is to check what users have: a branch may
47
+ # name a model that is not the released default, and a green run there would
48
+ # say nothing about the published package. Scheduled runs are already pinned
49
+ # to the default branch.
50
+ if: github.ref == 'refs/heads/main'
51
+ steps:
52
+ - name: Checkout repository
53
+ uses: actions/checkout@v4
54
+
55
+ - name: Set up Python
56
+ uses: actions/setup-python@v5
57
+ with:
58
+ python-version: '3.11'
59
+
60
+ # `.[cloud]`: the two provider SDKs live in an extra, not in the core
61
+ # dependencies, so a local-first install does not download them. This job
62
+ # is the one that speaks to a provider for real, so it is the one that has
63
+ # to ask for them.
64
+ - name: Install dependencies
65
+ run: pip install -e '.[cloud]'
66
+
67
+ - name: One tiny call per provider
68
+ env:
69
+ OPENAI_API_KEY: ${{ secrets.OPENAI_API_KEY }}
70
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
71
+ OPENAI_MODEL: ${{ vars.OPENAI_MODEL }}
72
+ ANTHROPIC_MODEL: ${{ vars.ANTHROPIC_MODEL }}
73
+ run: python scripts/live_provider_smoke.py
@@ -0,0 +1,140 @@
1
+ # Release workflow - Publish to PyPI on release
2
+ #
3
+ # Mirrors docugraph's, with three things that are CF's rather than docugraph's:
4
+ # the project name, the Dockerfile at the repository root (docugraph's lives in
5
+ # docker/), and no `target:` on the image build -- CF's Dockerfile has a single
6
+ # stage, so naming one would fail.
7
+ #
8
+ # This runs when a *GitHub Release* is published, or on manual dispatch. Pushing
9
+ # a bare `git tag v0.1.0` does NOT trigger it: draft a release from the tag, or
10
+ # use the manual run below.
11
+ name: Release
12
+
13
+ on:
14
+ release:
15
+ types: [published]
16
+ workflow_dispatch:
17
+ inputs:
18
+ version:
19
+ description: 'Version to release (e.g., 0.1.0)'
20
+ required: true
21
+
22
+ jobs:
23
+ build:
24
+ name: Build Distribution
25
+ runs-on: ubuntu-latest
26
+
27
+ steps:
28
+ - name: Checkout repository
29
+ uses: actions/checkout@v4
30
+
31
+ - name: Set up Python
32
+ uses: actions/setup-python@v5
33
+ with:
34
+ python-version: '3.11'
35
+
36
+ - name: Install uv
37
+ uses: astral-sh/setup-uv@v4
38
+
39
+ - name: Install build dependencies
40
+ run: |
41
+ uv pip install --system build twine
42
+
43
+ - name: Build package
44
+ run: python -m build
45
+
46
+ - name: Check distribution
47
+ run: twine check dist/*
48
+
49
+ - name: Upload artifacts
50
+ uses: actions/upload-artifact@v4
51
+ with:
52
+ name: dist
53
+ path: dist/
54
+
55
+ publish-pypi:
56
+ name: Publish to PyPI
57
+ runs-on: ubuntu-latest
58
+ needs: build
59
+ environment:
60
+ name: pypi
61
+ url: https://pypi.org/project/cognitive-fabric/
62
+
63
+ permissions:
64
+ id-token: write # Required for trusted publishing
65
+
66
+ steps:
67
+ - name: Download artifacts
68
+ uses: actions/download-artifact@v4
69
+ with:
70
+ name: dist
71
+ path: dist/
72
+
73
+ - name: Publish to PyPI
74
+ uses: pypa/gh-action-pypi-publish@release/v1
75
+ # Uses trusted publishing - no API token needed
76
+ # Configure at: https://pypi.org/manage/project/cognitive-fabric/settings/publishing/
77
+
78
+ publish-docker:
79
+ name: Publish Release Docker Image
80
+ runs-on: ubuntu-latest
81
+ # Not `needs: build`. `publish-pypi` is gated on the `pypi` environment's
82
+ # required reviewers, so waiting on it means nothing reaches the registry
83
+ # before the release has been approved. Running straight off `build` would
84
+ # put an image on ghcr.io for a version that may never be published.
85
+ needs: [build, publish-pypi]
86
+ permissions:
87
+ contents: read
88
+ packages: write
89
+
90
+ env:
91
+ REGISTRY: ghcr.io
92
+
93
+ steps:
94
+ - name: Checkout repository
95
+ uses: actions/checkout@v4
96
+
97
+ # ghcr.io requires a lowercase repository path, and `github.repository`
98
+ # keeps the owner's case. Expressions have no lowercase function, so it
99
+ # is done in the shell.
100
+ - name: Resolve image name
101
+ id: image
102
+ run: echo "name=${REGISTRY}/${GITHUB_REPOSITORY,,}" >> "$GITHUB_OUTPUT"
103
+
104
+ # linux/amd64 only. Building arm64 means running it under emulation, which
105
+ # needs a QEMU step registered with binfmt -- the step that used to sit
106
+ # here. It is a post-release item: add both back once an arm64 image has
107
+ # been built and verified by hand. kuzu 0.11.3 does publish manylinux
108
+ # aarch64 wheels for cp311, so that build would install from a wheel
109
+ # rather than compiling the extension.
110
+ - name: Set up Docker Buildx
111
+ uses: docker/setup-buildx-action@v3
112
+
113
+ - name: Log in to Container Registry
114
+ uses: docker/login-action@v3
115
+ with:
116
+ registry: ${{ env.REGISTRY }}
117
+ username: ${{ github.actor }}
118
+ password: ${{ secrets.GITHUB_TOKEN }}
119
+
120
+ - name: Extract version
121
+ id: version
122
+ run: |
123
+ if [ "${{ github.event_name }}" == "release" ]; then
124
+ echo "version=${{ github.event.release.tag_name }}" >> "$GITHUB_OUTPUT"
125
+ else
126
+ echo "version=v${{ github.event.inputs.version }}" >> "$GITHUB_OUTPUT"
127
+ fi
128
+
129
+ - name: Build and push Docker image
130
+ uses: docker/build-push-action@v5
131
+ with:
132
+ context: .
133
+ file: ./Dockerfile
134
+ push: true
135
+ tags: |
136
+ ${{ steps.image.outputs.name }}:${{ steps.version.outputs.version }}
137
+ ${{ steps.image.outputs.name }}:latest
138
+ cache-from: type=gha
139
+ cache-to: type=gha,mode=max
140
+ platforms: linux/amd64