org-knowledge-layer 0.3.1__tar.gz → 0.4.1__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 (143) hide show
  1. org_knowledge_layer-0.4.1/.coverage +0 -0
  2. org_knowledge_layer-0.4.1/.github/dependabot.yml +32 -0
  3. org_knowledge_layer-0.4.1/.github/workflows/ci.yml +100 -0
  4. {org_knowledge_layer-0.3.1/src/okl/scaffold/ci → org_knowledge_layer-0.4.1/.github/workflows}/okl-verify.yml +34 -8
  5. org_knowledge_layer-0.4.1/.github/workflows/publish.yml +90 -0
  6. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/AGENTS.md +3 -2
  7. org_knowledge_layer-0.4.1/CHANGELOG.md +145 -0
  8. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/CLAUDE.md +3 -2
  9. org_knowledge_layer-0.3.1/README.md → org_knowledge_layer-0.4.1/PKG-INFO +127 -1
  10. org_knowledge_layer-0.3.1/PKG-INFO → org_knowledge_layer-0.4.1/README.md +93 -33
  11. org_knowledge_layer-0.4.1/ci/check-diagram-figures.sh +82 -0
  12. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/ci/okl-verify.yml +34 -8
  13. org_knowledge_layer-0.4.1/ci/review-agent.sh +133 -0
  14. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/decisions/2026-07-21-subject-tags-controlled-vocabulary.md +7 -0
  15. org_knowledge_layer-0.4.1/docs/okl-how-it-works.excalidraw +3053 -0
  16. org_knowledge_layer-0.4.1/docs/okl-how-it-works.svg +2 -0
  17. org_knowledge_layer-0.4.1/docs/okl-sixth-surface.excalidraw +3819 -0
  18. org_knowledge_layer-0.4.1/docs/okl-sixth-surface.svg +2 -0
  19. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/REPORT.md +68 -0
  20. org_knowledge_layer-0.4.1/evals/results/ab-20260901-1238.json +441 -0
  21. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/check-doc-orphans.sh +5 -1
  22. org_knowledge_layer-0.4.1/pyproject.toml +118 -0
  23. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/bootstrap.py +6 -0
  24. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/cli.py +124 -15
  25. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/client.py +68 -18
  26. org_knowledge_layer-0.4.1/src/okl/core.py +524 -0
  27. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/drift.py +18 -3
  28. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/mcp_server.py +6 -4
  29. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/MANIFEST.md +8 -0
  30. org_knowledge_layer-0.4.1/src/okl/scaffold/ci/dependabot.yml +17 -0
  31. org_knowledge_layer-0.4.1/src/okl/scaffold/ci/method-gates.yml +74 -0
  32. {org_knowledge_layer-0.3.1/.github/workflows → org_knowledge_layer-0.4.1/src/okl/scaffold/ci}/okl-verify.yml +34 -8
  33. org_knowledge_layer-0.4.1/src/okl/scaffold/ci/review-agent.sh +133 -0
  34. org_knowledge_layer-0.4.1/src/okl/scaffold/claude/agents/architecture-reviewer.md +41 -0
  35. org_knowledge_layer-0.4.1/src/okl/scaffold/claude/commands/seed-from-docs.md +123 -0
  36. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/check-canon-size.sh +11 -0
  37. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/check-diagram-pairs.sh +39 -0
  38. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/check-doc-orphans.sh +23 -0
  39. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/check-links.sh +41 -0
  40. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/check-retractions.sh +22 -0
  41. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/check-tombstones.sh +22 -0
  42. org_knowledge_layer-0.4.1/src/okl/scaffold/gates/run-gates.sh +33 -0
  43. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold_cmd.py +26 -7
  44. org_knowledge_layer-0.4.1/src/okl/seed.py +119 -0
  45. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/service.py +7 -3
  46. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/store.py +109 -17
  47. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/tests/test_okl.py +364 -1
  48. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/tests/test_scaffold.py +130 -2
  49. org_knowledge_layer-0.3.1/.claude/settings.local.json +0 -183
  50. org_knowledge_layer-0.3.1/.github/workflows/ci.yml +0 -22
  51. org_knowledge_layer-0.3.1/CHANGELOG.md +0 -69
  52. org_knowledge_layer-0.3.1/docs/okl-sixth-surface.excalidraw +0 -3819
  53. org_knowledge_layer-0.3.1/docs/okl-sixth-surface.svg +0 -2
  54. org_knowledge_layer-0.3.1/pyproject.toml +0 -49
  55. org_knowledge_layer-0.3.1/src/okl/core.py +0 -301
  56. org_knowledge_layer-0.3.1/src/okl/scaffold/ci/method-gates.yml +0 -32
  57. org_knowledge_layer-0.3.1/src/okl/seed.py +0 -55
  58. {org_knowledge_layer-0.3.1/src/okl/scaffold/claude → org_knowledge_layer-0.4.1/.claude}/agents/architecture-reviewer.md +0 -0
  59. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.claude/hooks/stop-okl-encode.sh +0 -0
  60. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.claude/hooks/userpromptsubmit-okl-check.sh +0 -0
  61. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.claude/settings.json +0 -0
  62. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  63. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  64. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.github/pull_request_template.md +0 -0
  65. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/.gitignore +0 -0
  66. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/CONTRIBUTING.md +0 -0
  67. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/LICENSE +0 -0
  68. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/SECURITY.md +0 -0
  69. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/DEPLOY.md +0 -0
  70. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/ab-results-chart.png +0 -0
  71. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/ab-results-chart.svg +0 -0
  72. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/decisions/2026-07-17-flat-retrieval-until-scale.md +0 -0
  73. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/posts/01-memory-that-outlives-the-run.md +0 -0
  74. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/posts/02-dont-let-a-step-grade-itself.md +0 -0
  75. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/docs/posts/03-enforcement-or-good-intentions.md +0 -0
  76. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/README.md +0 -0
  77. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/ab_harness.py +0 -0
  78. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/ab-20260829-2300.json +0 -0
  79. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/ab-20260829-2315.json +0 -0
  80. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/ab-20260830-0003.json +0 -0
  81. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/ab-20260830-0148.json +0 -0
  82. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/ab-20260901-0133.json +0 -0
  83. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/README.md +0 -0
  84. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/briefed-userpromptsubmit-lint.yml +0 -0
  85. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/control-lint.yml +0 -0
  86. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/hook.log +0 -0
  87. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/service-record-500.log +0 -0
  88. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/session-briefed-pretooluse.txt +0 -0
  89. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/session-briefed-userpromptsubmit.txt +0 -0
  90. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/results/e2e-20260830/session-control.txt +0 -0
  91. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/evals/tasks.jsonl +0 -0
  92. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/check-canon-size.sh +0 -0
  93. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/check-diagram-pairs.sh +0 -0
  94. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/check-links.sh +0 -0
  95. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/check-retractions.sh +0 -0
  96. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/check-tombstones.sh +0 -0
  97. {org_knowledge_layer-0.3.1/src/okl/scaffold → org_knowledge_layer-0.4.1}/gates/run-gates.sh +0 -0
  98. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/hooks/stop-okl-encode.sh +0 -0
  99. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/hooks/userpromptsubmit-okl-check.sh +0 -0
  100. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/dotnet-canon.json +0 -0
  101. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/dotnet-decisions.json +0 -0
  102. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/dotnet-defects.json +0 -0
  103. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/dotnet-review-surfaces.json +0 -0
  104. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/frontend-canon.json +0 -0
  105. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/geospatial-deeptime-defects.json +0 -0
  106. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/geospatial-defects.json +0 -0
  107. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/geospatial-enforcement-defects.json +0 -0
  108. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/geospatial-eval-defects.json +0 -0
  109. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/rag-defects.json +0 -0
  110. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/seed/react-defects.json +0 -0
  111. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/__init__.py +0 -0
  112. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/__main__.py +0 -0
  113. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/commands/check-rules.md +0 -0
  114. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/commands/feature-spec.md +0 -0
  115. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/commands/seed-from-codebase.md +0 -0
  116. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/rules/example-area.md +0 -0
  117. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +0 -0
  118. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/skills/encoding-loop/SKILL.md +0 -0
  119. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +0 -0
  120. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/evals/README.md +0 -0
  121. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/evals/cases.jsonl +0 -0
  122. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/evals/run_evals.py +0 -0
  123. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/hooks/hooks.json +0 -0
  124. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/hooks/stop-okl-encode.sh +0 -0
  125. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/hooks/userpromptsubmit-okl-check.sh +0 -0
  126. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/plugin/plugin.json +0 -0
  127. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/dotnet/README.md +0 -0
  128. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/dotnet/rules/architecture.md +0 -0
  129. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/dotnet/rules/messaging.md +0 -0
  130. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/dotnet/rules/performance-and-data.md +0 -0
  131. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/dotnet/rules/security.md +0 -0
  132. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/geospatial/README.md +0 -0
  133. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +0 -0
  134. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/python-rag/README.md +0 -0
  135. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +0 -0
  136. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/python-rag/rules/project-structure.md +0 -0
  137. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +0 -0
  138. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/react/README.md +0 -0
  139. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/profiles/react/rules/frontend.md +0 -0
  140. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/registries/RETRACTIONS.md +0 -0
  141. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/registries/tombstones.txt +0 -0
  142. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/root/CLAUDE.md +0 -0
  143. {org_knowledge_layer-0.3.1 → org_knowledge_layer-0.4.1}/src/okl/scaffold/root/METHOD.md +0 -0
Binary file
@@ -0,0 +1,32 @@
1
+ # Dependabot.
2
+ #
3
+ # This exists because of the SHA pinning, not despite it. Pinning actions to a commit
4
+ # closes the "a mutable tag moved under me" hole and opens a quieter one: the pin never
5
+ # updates, so a security fix in an action never arrives. Pinning without an update path
6
+ # is how a repo ends up on a two-year-old checkout action and calls it hardening.
7
+ # Dependabot rewrites the SHA and the trailing `# v5` comment together.
8
+ version: 2
9
+
10
+ updates:
11
+ - package-ecosystem: github-actions
12
+ directory: "/"
13
+ schedule:
14
+ interval: weekly
15
+ labels: ["dependencies", "ci"]
16
+ commit-message:
17
+ prefix: "ci"
18
+
19
+ - package-ecosystem: pip
20
+ directory: "/"
21
+ schedule:
22
+ interval: weekly
23
+ labels: ["dependencies"]
24
+ commit-message:
25
+ prefix: "deps"
26
+ # The gating tools are pinned exactly on purpose (a range lets a new release
27
+ # retro-fail a branch that changed nothing). Dependabot is the intended way to move
28
+ # those pins: a PR that runs the full suite against the new version before it lands,
29
+ # rather than a range that upgrades silently mid-branch.
30
+ groups:
31
+ dev-tooling:
32
+ patterns: ["pytest", "ruff", "mypy"]
@@ -0,0 +1,100 @@
1
+ # Repo CI — lints, type-checks and tests okl itself. (okl-verify.yml is different: it
2
+ # demonstrates the product's own drift/method gates, the workflow consumers copy.)
3
+ #
4
+ # Hardened per the org rule this repo's own store carries ("CI workflow baseline:
5
+ # pipefail, no persisted credentials, least-privilege permissions, concurrency groups —
6
+ # pin actions"). It satisfied none of the five until this commit, which is the rule's own
7
+ # symptom: an enforcement surface nothing triggers runs never.
8
+ name: ci
9
+
10
+ on:
11
+ push:
12
+ branches: [ "main" ]
13
+ pull_request:
14
+ branches: [ "main" ]
15
+
16
+ # Least privilege: this job reads code and reports status. It never pushes.
17
+ permissions:
18
+ contents: read
19
+
20
+ # One run per ref. A superseded push should stop burning minutes on a result nobody
21
+ # will read.
22
+ concurrency:
23
+ group: ci-${{ github.ref }}
24
+ cancel-in-progress: true
25
+
26
+ defaults:
27
+ run:
28
+ # Without pipefail a run block continues past a failed segment of a pipeline, so a
29
+ # green step can hide a red command.
30
+ shell: bash -euo pipefail {0}
31
+
32
+ jobs:
33
+ lint-and-test:
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ # Actions pinned to a commit SHA, not a tag: a tag is mutable, so `@v5` is an
37
+ # unpinned dependency with write access to the runner.
38
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
39
+ with:
40
+ # The diagram gate reads committed history (git show HEAD:...), not the
41
+ # checkout tree, so it needs more than the default shallow fetch.
42
+ fetch-depth: 0
43
+ # This job never pushes; leaving the token in .git/config lets any later step
44
+ # (or a compromised dependency) use it.
45
+ persist-credentials: false
46
+
47
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
48
+ with:
49
+ python-version: "3.12"
50
+
51
+ - name: Install (dev extras)
52
+ run: pip install -e ".[dev]"
53
+
54
+ - name: Lint — ruff (config in pyproject.toml)
55
+ run: ruff check .
56
+
57
+ - name: Types — mypy
58
+ run: mypy src/okl
59
+
60
+ - name: Tests (with the coverage ratchet)
61
+ run: pytest -q --cov=okl
62
+
63
+ - name: Diagram figures trace to committed receipts
64
+ run: ./ci/check-diagram-figures.sh
65
+
66
+ # The content audits okl ships to consumers, run against okl itself. They were
67
+ # shipped for weeks without this repo running any of them — the "enforcement
68
+ # surface nothing triggers" failure, one level up: a gate whose author has never
69
+ # run it on their own tree.
70
+ - name: Method gates (links, doc orphans, diagram pairs, tombstones, canon size)
71
+ run: bash gates/run-gates.sh
72
+
73
+ # Secret scan over the FULL history, not the working tree. okl writes a bearer
74
+ # token into .okl/config.json, and the .gitignore it now drops there is the only
75
+ # thing between that and a commit — this is the check for when that fails.
76
+ #
77
+ # The upstream gitleaks-action is deliberately not used: it requires a paid licence
78
+ # key for organization-owned repositories, so it would break for anyone who forks
79
+ # this into an org. The binary has no such condition, and pinning the release keeps
80
+ # a new detection rule from retro-failing a branch that changed nothing.
81
+ # The reviewer okl ships, run on okl. It was listed as a review surface with nothing
82
+ # triggering it — the "enforcement surface that only runs on-demand runs never"
83
+ # failure this repo's own store names. Off unless REVIEW_CMD is set, so it costs
84
+ # nothing until someone decides it should.
85
+ - name: Architecture review (off unless REVIEW_CMD is set)
86
+ if: github.event_name == 'pull_request'
87
+ env:
88
+ REVIEW_CMD: ${{ vars.REVIEW_CMD }}
89
+ ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
90
+ REVIEW_BASE_REF: origin/${{ github.base_ref }}
91
+ run: bash ci/review-agent.sh
92
+
93
+ - name: Secret scan — gitleaks
94
+ env:
95
+ GITLEAKS_VERSION: "8.30.1"
96
+ run: |
97
+ curl -sSfL -o /tmp/gitleaks.tar.gz \
98
+ "https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}/gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz"
99
+ tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks
100
+ /tmp/gitleaks git . --no-banner --redact --exit-code 1
@@ -16,16 +16,45 @@ on:
16
16
  push:
17
17
  branches: [main]
18
18
 
19
+ # Least privilege: this workflow reads code and reports a status. It never writes.
20
+ permissions:
21
+ contents: read
22
+
23
+ concurrency:
24
+ group: okl-verify-${{ github.ref }}
25
+ cancel-in-progress: true
26
+
27
+ defaults:
28
+ run:
29
+ # Without pipefail a run block continues past a failed segment of a pipeline, so a
30
+ # green step can hide a red command.
31
+ shell: bash -euo pipefail {0}
32
+
19
33
  jobs:
20
34
  okl-verify:
21
35
  runs-on: ubuntu-latest
36
+ env:
37
+ # Set at job level so every okl command below resolves the shared layer from the
38
+ # environment. This replaces an `okl connect --token` step, which wrote the bearer
39
+ # token into .okl/config.json on the runner — needless, since the client reads both
40
+ # variables directly, and one more place for a credential to end up.
41
+ # Unset secrets leave these empty, and okl falls back to the repo-local store.
42
+ OKL_SERVICE_URL: ${{ secrets.OKL_SERVICE_URL }}
43
+ OKL_TOKEN: ${{ secrets.OKL_TOKEN }}
22
44
  steps:
23
- - uses: actions/checkout@v4
45
+ # Actions are pinned to a commit SHA: a tag is mutable, so `@v5` is an unpinned
46
+ # dependency with access to your runner. If you copy this file into your own repo,
47
+ # add a .github/dependabot.yml with the `github-actions` ecosystem so these pins get
48
+ # security updates — a pin with no update path goes stale and that is its own risk.
49
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
24
50
  with:
25
- fetch-depth: 0 # drift needs history: it compares file mtimes/commits vs verified_at
26
- - uses: actions/setup-python@v5
51
+ fetch-depth: 0 # drift needs history: it compares governed-file commits vs verified_at
52
+ persist-credentials: false # this job never pushes
53
+
54
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
27
55
  with:
28
56
  python-version: "3.13"
57
+
29
58
  - name: Install okl
30
59
  # Detect by the package source, not the distribution name: the name changed once
31
60
  # (PyPI rejects "okl" as confusable) and a name-based check silently broke with it.
@@ -36,13 +65,10 @@ jobs:
36
65
  pip install org-knowledge-layer
37
66
  fi
38
67
 
39
- - name: Connect to the shared layer (optional — skipped when secrets are unset)
40
- env:
41
- OKL_SERVICE_URL: ${{ secrets.OKL_SERVICE_URL }}
42
- OKL_TOKEN: ${{ secrets.OKL_TOKEN }}
68
+ - name: Report which store the gate will run against
43
69
  run: |
44
70
  if [ -n "${OKL_SERVICE_URL:-}" ]; then
45
- okl connect "$OKL_SERVICE_URL" --token "$OKL_TOKEN"
71
+ echo "verifying against the shared layer at $OKL_SERVICE_URL"
46
72
  else
47
73
  echo "no OKL_SERVICE_URL secret — verifying against the repo-local store"
48
74
  fi
@@ -0,0 +1,90 @@
1
+ # Release to PyPI on a version tag, using Trusted Publishing.
2
+ #
3
+ # There is no API token anywhere in this workflow, and none stored in the repository.
4
+ # PyPI accepts the upload because GitHub vouches for it: the publish job mints a
5
+ # short-lived OIDC token that names this repository, this workflow file and this
6
+ # environment, and PyPI checks that against the trusted publisher it was told to expect.
7
+ # A leaked repository secret cannot publish okl because there is no such secret.
8
+ #
9
+ # ONE-TIME SETUP ON PYPI (this workflow does nothing until it is done):
10
+ # pypi.org → the org-knowledge-layer project → Publishing → Add a pending publisher
11
+ # Owner: emeraldleaf
12
+ # Repository: okl
13
+ # Workflow name: publish.yml
14
+ # Environment name: pypi
15
+ # Then create the `pypi` environment under repo Settings → Environments. Adding a
16
+ # required reviewer there makes every release a deliberate, approved act.
17
+ name: publish
18
+
19
+ on:
20
+ push:
21
+ tags: ["v*"]
22
+
23
+ permissions:
24
+ contents: read
25
+
26
+ jobs:
27
+ build:
28
+ runs-on: ubuntu-latest
29
+ steps:
30
+ - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5
31
+ with:
32
+ persist-credentials: false
33
+
34
+ - uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6
35
+ with:
36
+ python-version: "3.12"
37
+
38
+ # The tag is the release's claim about what version this is; pyproject.toml is the
39
+ # version that actually gets uploaded. When they disagree, PyPI takes pyproject's
40
+ # answer and the tag becomes a lie that is permanent — you cannot reuse a version
41
+ # number on PyPI, so the mistake is not fixable, only worked around.
42
+ - name: Tag must match the version in pyproject.toml
43
+ run: |
44
+ tag="${GITHUB_REF_NAME#v}"
45
+ pkg="$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")"
46
+ echo "tag=$tag pyproject=$pkg"
47
+ if [ "$tag" != "$pkg" ]; then
48
+ echo "::error::tag v$tag does not match pyproject version $pkg"
49
+ exit 1
50
+ fi
51
+
52
+ - name: Build
53
+ run: |
54
+ python -m pip install --upgrade build twine
55
+ python -m build
56
+ # `twine check` catches a malformed long_description, which PyPI rejects only
57
+ # after the upload has otherwise succeeded.
58
+ python -m twine check dist/*
59
+
60
+ - uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4
61
+ with:
62
+ name: dist
63
+ path: dist/
64
+
65
+ publish:
66
+ needs: build
67
+ runs-on: ubuntu-latest
68
+ # A named environment is what the PyPI trusted publisher is bound to, and it is where
69
+ # a required-reviewer rule can gate the release.
70
+ environment:
71
+ name: pypi
72
+ url: https://pypi.org/project/org-knowledge-layer/
73
+ permissions:
74
+ id-token: write # mint the OIDC token PyPI verifies
75
+ attestations: write # sign the artifacts
76
+ contents: read
77
+ steps:
78
+ - uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4
79
+ with:
80
+ name: dist
81
+ path: dist/
82
+
83
+ # Provenance: a signed statement that these exact files were built by this workflow
84
+ # from this commit. It is what lets someone verify the wheel on PyPI came from the
85
+ # source they are reading, rather than trusting that it did.
86
+ - uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2
87
+ with:
88
+ subject-path: "dist/*"
89
+
90
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1
@@ -31,8 +31,9 @@ okl drift # rules whose governed source changed after v
31
31
  ## Rules that are enforced (and why)
32
32
 
33
33
  - **Mirror files are byte-identical, test-enforced**: `ci/okl-verify.yml` ==
34
- `.github/workflows/okl-verify.yml` == `src/okl/scaffold/ci/okl-verify.yml`, and
35
- `hooks/*.sh` == `src/okl/scaffold/hooks/*.sh`. The scaffold copies are what consumers
34
+ `.github/workflows/okl-verify.yml` == `src/okl/scaffold/ci/okl-verify.yml`,
35
+ `hooks/*.sh` == `src/okl/scaffold/hooks/*.sh`, and
36
+ `gates/*.sh` == `src/okl/scaffold/gates/*.sh`. The scaffold copies are what consumers
36
37
  receive; the repo copies are the dogfood. Edit ONE, copy to the others in the same
37
38
  change — `tests/test_scaffold.py::test_mirror_files_identical` fails otherwise.
38
39
  - **ruff `E702` is ignored deliberately** (semicolon one-liners): the tests use a
@@ -0,0 +1,145 @@
1
+ # Changelog
2
+
3
+ ## 0.4.1
4
+
5
+ ### Security — affects the CI workflow already in your repo
6
+
7
+ - **The shipped CI verifier no longer writes your bearer token to disk.** It ran
8
+ `okl connect --token`, which persisted `OKL_TOKEN` into `.okl/config.json` on the runner
9
+ for no reason: the client reads `OKL_SERVICE_URL` and `OKL_TOKEN` from the environment
10
+ directly, so setting them at job level does the same work with the credential never
11
+ touching the filesystem. Re-run `okl init` (or `okl scaffold`) to pick up the new file.
12
+ - **Both shipped workflows are hardened.** `okl-verify.yml` and `method-gates.yml` now
13
+ declare least-privilege `permissions`, a concurrency group, `pipefail`, and
14
+ `persist-credentials: false`, and pin their actions to commit SHAs rather than mutable
15
+ tags. They satisfied none of that before.
16
+ - `.github/dependabot.yml` ships with the kit, because pinning to a SHA without an update
17
+ path is how a repo ends up on a two-year-old action and calls it hardening.
18
+
19
+ ### Added
20
+
21
+ - **Opt-in architecture review in CI.** `ci/review-agent.sh` runs the reviewer over a PR
22
+ diff and fails on must-fix findings. **Off unless you set the `REVIEW_CMD` repository
23
+ variable** to a CLI that reads a prompt on stdin — `claude -p` (authenticates with an
24
+ existing Claude Code login, no separate API key), `ollama run <model>` (local, free), or
25
+ any other. Unset, it prints one line and passes. It is the only gate in the kit that
26
+ calls a model, which is why it is the only one that is opt-in. Documented in the README
27
+ and the kit manifest.
28
+
29
+
30
+ ## 0.4.0
31
+
32
+ ### Behaviour changes worth reading before upgrading
33
+
34
+ - **Stack tags now filter exclusively.** A record naming a stack (`dotnet`, `react`,
35
+ `geospatial`, `python`, `python-rag`) is only shown to a repo that declared that stack
36
+ in its `interests`. Previously any shared tag let it through, so a rule tagged
37
+ `dotnet,method` reached every repo interested in `method` — a subject 75 records carry.
38
+ **If you declare `interests`, expect fewer records after upgrading.** That is the point,
39
+ but it is a change in what your briefings contain. Repos that declare no interests are
40
+ unaffected, and untagged records still always pass.
41
+ - **`symptom` and `fix` are now searchable.** They were not, which meant a record written
42
+ the way the docs tell you to write it — short title, the distinguishing words in the
43
+ symptom — could not be retrieved at all. Existing stores rebuild their index on first
44
+ open; you do not need to re-record anything.
45
+
46
+ ### Added
47
+
48
+ - **`okl dedup`** reports near-duplicate records for review. Lexical and explainable:
49
+ per-field weighted Jaccard over title, symptom and fix, IDF-weighted from your own
50
+ corpus. It never merges or drops anything — the measured score bands for true
51
+ paraphrases and for distinct-but-related records overlap, so the call is a person's.
52
+ The same check runs as an advisory when importing an agent-proposed pack.
53
+ - **`/seed-from-docs`** mines the specs, ADRs and rules files you already wrote into typed
54
+ records. Built around one distinction: a record is a standing instruction that outlives
55
+ the work item it came from, so "deep offsets use keyset pagination" belongs and "add
56
+ pagination to /orders this sprint" does not.
57
+ - A pack declaring `_proposed_by` is refused unless every node carries a `found_by`
58
+ citation, so the rule the seeding commands state is enforced at import rather than
59
+ remembered by a reviewer.
60
+
61
+ ### Fixed
62
+
63
+ - `Client._remote_url` validates the URL scheme once. A `service_url` from config could
64
+ name `file://`, turning a remote read into a local file read.
65
+ - `OKLUnreachable` is now `OKLUnreachableError`, with the old name kept as an alias so
66
+ existing `except` clauses still work.
67
+ - Drift timestamps are timezone-aware; `utcfromtimestamp` is deprecated from Python 3.12
68
+ and returned a naive datetime that read as local time when compared across machines.
69
+
70
+ ### Internal
71
+
72
+ - `_Backend` is a `typing.Protocol` whose docstring states the behavioural contract, with
73
+ one conformance test both backends run — the defect it guards (Postgres satisfying every
74
+ signature while running an unranked match) was invisible to signatures alone.
75
+ - mypy, and ruff widened from 6 rule families to 15 including security and complexity,
76
+ both wired into CI alongside a secret scan, the method gates and a coverage floor.
77
+
78
+
79
+ ## 0.3.1
80
+
81
+ ### Security
82
+
83
+ - **Setting `OKL_TOKEN` now also removes `/openapi.json`, `/docs` and `/redoc`.** They
84
+ were left serving 200 to anonymous callers by the 0.3.0 work that closed every data
85
+ route, because FastAPI mounts them itself — they are not handlers, so the per-handler
86
+ auth sweep could not reach them. They leak no records, but they publish the endpoint
87
+ list, every schema, and which routes want a credential. With no token set the
88
+ interactive docs remain available, since that case is a developer's laptop.
89
+
90
+ ## 0.3.0
91
+
92
+ Everything here came from running three things that had been written but never
93
+ executed: the MCP server, the Postgres backend, and a deployment.
94
+
95
+ ### Security
96
+
97
+ - **The service token now covers reads.** Previously `OKL_TOKEN` gated writes only, so
98
+ an unauthenticated `GET /nodes` returned the entire store — every recorded defect,
99
+ retired identifier and architecture decision. Every route now requires the token when
100
+ it is set, except `/health`, which is left open for schedulers and returns no record
101
+ content.
102
+ - **`okl connect --token` no longer commits your secret.** The token is stored in
103
+ cleartext in `.okl/config.json`, and a comment claimed the directory was gitignored
104
+ while nothing wrote a `.gitignore`. `okl init` and `okl connect` now write
105
+ `.okl/.gitignore`.
106
+ - **A rejected check no longer reports success.** A 401 surfaced as `ValueError` rather
107
+ than `OKLUnreachable`, so an unauthorized `okl check` exited 0 with a traceback — which
108
+ a pre-task hook reads as "no rules apply". It now fails closed with exit 2, as does
109
+ every other command, via a backstop in `main()`.
110
+
111
+ **Breaking:** if you run a service with `OKL_TOKEN` set, clients must upgrade too.
112
+ Clients older than 0.3.0 send no credential on `GET` requests and will get 401s from
113
+ `okl drift` and the recurrence metric. Upgrade the service and its clients together, or
114
+ unset `OKL_TOKEN` during the rollover.
115
+
116
+ ### Fixed
117
+
118
+ - `uvicorn okl.service:app` served a module-level `None`: the process started, bound the
119
+ port, passed a port-liveness check and returned 500 to every request. The app is now
120
+ built lazily in a module `__getattr__`, so the standard ASGI entrypoint works while
121
+ importing the module still does not touch the database.
122
+ - The MCP server could not start under `mcp` 2.x, which renamed `FastMCP` to
123
+ `MCPServer` — and the error handler told you to install the extra you had just
124
+ installed. Both names are tried, and the real import error is reported.
125
+ - Every MCP `okl_record` call with `scope="repo"` failed. The repo default used
126
+ `setdefault`, which cannot replace an explicit `None`, and the MCP tools pass every
127
+ field explicitly.
128
+ - MCP validation errors raised as an opaque "Error executing tool". They now return the
129
+ complaint, so an agent that invents a tag is told the vocabulary.
130
+
131
+ ### Added
132
+
133
+ - `docs/DEPLOY.md`: the shared-service deployment path, including a throwaway Postgres
134
+ for trying it locally and what each failure mode looks like. Every command in it was
135
+ run against a real Postgres and a real service.
136
+ - Tests covering the live MCP server, the ASGI entrypoint, service auth on reads, the
137
+ fail-closed 401, and the config `.gitignore`.
138
+ - The Postgres/SQLite parity test now runs in a scratch schema it creates and drops. The
139
+ first version opened with `DELETE FROM node` against whatever `OKL_TEST_POSTGRES_URL`
140
+ pointed at, which would have destroyed the store of anyone who set it to their real
141
+ service.
142
+
143
+ ## 0.2.0 and earlier
144
+
145
+ See the git history.
@@ -31,8 +31,9 @@ okl drift # rules whose governed source changed after v
31
31
  ## Rules that are enforced (and why)
32
32
 
33
33
  - **Mirror files are byte-identical, test-enforced**: `ci/okl-verify.yml` ==
34
- `.github/workflows/okl-verify.yml` == `src/okl/scaffold/ci/okl-verify.yml`, and
35
- `hooks/*.sh` == `src/okl/scaffold/hooks/*.sh`. The scaffold copies are what consumers
34
+ `.github/workflows/okl-verify.yml` == `src/okl/scaffold/ci/okl-verify.yml`,
35
+ `hooks/*.sh` == `src/okl/scaffold/hooks/*.sh`, and
36
+ `gates/*.sh` == `src/okl/scaffold/gates/*.sh`. The scaffold copies are what consumers
36
37
  receive; the repo copies are the dogfood. Edit ONE, copy to the others in the same
37
38
  change — `tests/test_scaffold.py::test_mirror_files_identical` fails otherwise.
38
39
  - **ruff `E702` is ignored deliberately** (semicolon one-liners): the tests use a
@@ -1,3 +1,37 @@
1
+ Metadata-Version: 2.5
2
+ Name: org-knowledge-layer
3
+ Version: 0.4.1
4
+ Summary: Org Knowledge Layer — an installable sixth surface that carries encoded engineering lessons across repos.
5
+ Author: Joshua Dell
6
+ License: MIT
7
+ License-File: LICENSE
8
+ Keywords: agentic-coding,ai-engineering,encoding-loop,knowledge-layer,mcp
9
+ Requires-Python: >=3.10
10
+ Provides-Extra: all
11
+ Requires-Dist: fastapi>=0.110; extra == 'all'
12
+ Requires-Dist: mcp>=1.2; extra == 'all'
13
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'all'
14
+ Requires-Dist: pydantic>=2; extra == 'all'
15
+ Requires-Dist: uvicorn>=0.29; extra == 'all'
16
+ Provides-Extra: dev
17
+ Requires-Dist: fastapi>=0.110; extra == 'dev'
18
+ Requires-Dist: httpx>=0.27; extra == 'dev'
19
+ Requires-Dist: mypy==2.3.1; extra == 'dev'
20
+ Requires-Dist: pydantic>=2; extra == 'dev'
21
+ Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
22
+ Requires-Dist: pytest==8.4.2; extra == 'dev'
23
+ Requires-Dist: ruff==0.16.5; extra == 'dev'
24
+ Requires-Dist: uvicorn>=0.29; extra == 'dev'
25
+ Provides-Extra: mcp
26
+ Requires-Dist: mcp>=1.2; extra == 'mcp'
27
+ Provides-Extra: postgres
28
+ Requires-Dist: psycopg[binary]>=3.1; extra == 'postgres'
29
+ Provides-Extra: service
30
+ Requires-Dist: fastapi>=0.110; extra == 'service'
31
+ Requires-Dist: pydantic>=2; extra == 'service'
32
+ Requires-Dist: uvicorn>=0.29; extra == 'service'
33
+ Description-Content-Type: text/markdown
34
+
1
35
  # okl — a shared knowledge layer for AI-assisted engineering
2
36
 
3
37
  > A small database of the specific lessons a codebase has learned — the bugs it
@@ -46,6 +80,17 @@ Two things ship in the package. They are not coequal:
46
80
  retrieved into an agent's context before a task, and go stale loudly when the code
47
81
  they describe moves on. Everything measured in [evals/REPORT.md](evals/REPORT.md)
48
82
  measures this.
83
+
84
+ It is worth being precise about what that store fills up with, because "lessons a
85
+ codebase has learned" invites the picture of a bug database. In the 161-record corpus
86
+ in [seed/](seed/) it is mostly not that: **90 Rules, 20 Decisions and 7 Gates against
87
+ 34 Defects** — conventions the code follows and trade-offs already settled, not a
88
+ ledger of things that broke. Count it yourself:
89
+
90
+ ```bash
91
+ python3 -c "import json,glob,collections; c=collections.Counter(
92
+ n['type'] for f in glob.glob('seed/*.json') for n in json.load(open(f))['nodes']); print(c)"
93
+ ```
49
94
  - **`okl scaffold`** is a starter kit for the in-repo discipline the store assumes: a
50
95
  lean canon file, mechanical gates, registries, a review agent, and an eval harness.
51
96
  It is useful on its own and it has never been measured. Use it to get a new repo to
@@ -163,6 +208,8 @@ with receipts, not a benchmark.
163
208
 
164
209
  ## How it works
165
210
 
211
+ <img src="docs/okl-how-it-works.svg" alt="One repo records a lesson; every other repo is briefed on it before its next task. The store is typed, scoped and tagged; every verification stamp carries the check that earned it." width="100%">
212
+
166
213
  ### The mental model
167
214
 
168
215
  `okl` stores small, typed **notes** and the **links** between them.
@@ -258,6 +305,85 @@ pip install "org-knowledge-layer[mcp]" # MCP server for Claude Code / Cur
258
305
  pip install "org-knowledge-layer[all]"
259
306
  ```
260
307
 
308
+ ## What it costs, and how to turn it down
309
+
310
+ Installing okl is not free. It is worth knowing exactly what you are signing up for
311
+ before you wire it into every prompt, and every number below was measured on this repo's
312
+ own 199-record store rather than estimated.
313
+
314
+ **Per prompt, once the hook is installed:**
315
+
316
+ | | |
317
+ |---|---|
318
+ | Latency | **~0.11s** — one local SQLite query, no network in local mode |
319
+ | Context | **~2,300 tokens** at the default `--limit 12`, down to **~250** at `--format actions --limit 3` |
320
+
321
+ **Per session:** the Stop hook interrupts once at the end to ask what was learned. It
322
+ blocks the first stop only, and answering it is the whole write side of the loop.
323
+
324
+ **In your repo:** `okl init` writes `.okl/` (config, the local database, a `.gitignore`
325
+ covering both) and, if `.claude/` exists, two hook scripts plus their registration. It
326
+ also installs `.github/workflows/okl-verify.yml`, which runs the drift gate on every PR.
327
+ `okl scaffold` is separate and optional — nothing installs it unless you ask.
328
+
329
+ ### The knobs, cheapest first
330
+
331
+ ```bash
332
+ okl check --task "..." --format actions # imperatives only, ~60% smaller
333
+ okl check --task "..." --limit 3 # fewer records; the briefing says how many it trimmed
334
+ okl init --interests "python,security" # drop records tagged for stacks you do not use
335
+ ```
336
+
337
+ - **`--format actions`** is the single biggest saving and loses the least: you keep every
338
+ "when you see X → do Y" and drop the explanatory prose.
339
+ - **`--limit N`** caps how many records are drawn on. The briefing always reports what it
340
+ trimmed, so a short briefing can never quietly hide a miss.
341
+ - **`interests`** is the one to reach for on a mature shared store. Stack tags filter
342
+ exclusively — declaring `python` means records tagged `dotnet` stay out even when they
343
+ share a subject tag with something you asked for.
344
+ - **Scope records `repo:` rather than `org`** when a lesson is local. Org scope is a claim
345
+ that every project in the organization should see it, and it costs every project's
346
+ budget to be wrong about that.
347
+
348
+ ### Turning parts off
349
+
350
+ The hooks are registered in `.claude/settings.json`; delete the entry to stop one firing.
351
+ The pre-task hook is the read side and the Stop hook is the write side, and they are
352
+ independent — running the read without the write is a reasonable way to start.
353
+
354
+ Nothing is load-bearing on the hooks: `okl check` and `okl record` work from the terminal,
355
+ from CI, and through the MCP server whether or not any hook is installed.
356
+
357
+ To remove okl from a repo entirely, delete `.okl/`, the two hook scripts and their entries
358
+ in `.claude/settings.json`, and `.github/workflows/okl-verify.yml`. Nothing else was
359
+ written, and nothing outside that repo was touched.
360
+
361
+ ### Architecture review in CI (off by default)
362
+
363
+ The kit ships a reviewer that reads a PR diff against your encoded rules and fails the
364
+ build on a must-fix finding. It is **off unless you ask for it**, and it is not tied to any
365
+ vendor. Set the `REVIEW_CMD` repository variable to any CLI that reads a prompt on stdin:
366
+
367
+ ```bash
368
+ gh variable set REVIEW_CMD --body "claude -p --model sonnet" # your existing Claude Code login
369
+ gh variable set REVIEW_CMD --body "ollama run qwen2.5-coder" # local model, no API cost
370
+ gh variable set REVIEW_CMD --body "llm -m gpt-4o" # any other CLI
371
+ ```
372
+
373
+ Two things worth knowing:
374
+
375
+ - **`claude -p` needs no separate API key.** It authenticates with the Claude Code login you
376
+ already have, so if you use Claude Code there is nothing else to configure and no second
377
+ bill. Verified headless with `ANTHROPIC_API_KEY` unset.
378
+ - **Locally you do not need this at all.** The reviewer is a subagent
379
+ (`.claude/agents/architecture-reviewer.md`); ask your agent to run it on your changes and
380
+ it costs nothing beyond the session you are already in. The CI job exists for the case
381
+ where no human and no agent is in the loop — a PR nobody reviewed.
382
+
383
+ Unset, the step prints one line saying it is off and exits 0. Every other gate in the kit is
384
+ deterministic and free; this is the only one that calls a model, which is why it is the only
385
+ one that is opt-in.
386
+
261
387
  ## Wire a repo
262
388
 
263
389
  ```bash
@@ -371,7 +497,7 @@ okl metric # recurrence-after-arming: defect classes that came back in
371
497
 
372
498
  ## Subagents and small context budgets
373
499
 
374
- A full briefing costs roughly **4,400 tokens** — fine for a main session with a large
500
+ A full briefing costs roughly **2,300 tokens** — fine for a main session with a large
375
501
  window, punishing for a subagent working in a few thousand. That asymmetry matters
376
502
  because subagents are exactly where org rules get lost: a focused worker handling one
377
503
  subtask has the least context and the most need for "here is the mistake this codebase