org-knowledge-layer 0.4.0__tar.gz → 0.5.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 (146) hide show
  1. {org_knowledge_layer-0.4.0/src/okl/scaffold → org_knowledge_layer-0.5.0/.claude}/hooks/userpromptsubmit-okl-check.sh +4 -2
  2. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.coverage +0 -0
  3. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.github/workflows/ci.yml +12 -0
  4. {org_knowledge_layer-0.4.0/ci → org_knowledge_layer-0.5.0/.github/workflows}/okl-verify.yml +34 -8
  5. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.gitignore +2 -0
  6. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/AGENTS.md +8 -1
  7. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/CHANGELOG.md +63 -0
  8. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/CLAUDE.md +8 -1
  9. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/PKG-INFO +42 -2
  10. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/README.md +40 -0
  11. {org_knowledge_layer-0.4.0/src/okl/scaffold → org_knowledge_layer-0.5.0}/ci/okl-verify.yml +34 -8
  12. org_knowledge_layer-0.5.0/ci/review-agent.sh +133 -0
  13. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/decisions/2026-07-21-subject-tags-controlled-vocabulary.md +41 -0
  14. org_knowledge_layer-0.5.0/docs/okl-retrieval-pipeline.svg +89 -0
  15. org_knowledge_layer-0.5.0/docs/posts/04-the-elegant-change-was-the-wrong-one.md +55 -0
  16. org_knowledge_layer-0.5.0/docs/render_pipeline_diagram.py +287 -0
  17. org_knowledge_layer-0.5.0/evals/REPORT.md +838 -0
  18. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/ab_harness.py +134 -6
  19. org_knowledge_layer-0.5.0/evals/preflight.py +94 -0
  20. org_knowledge_layer-0.5.0/evals/results/ab-20260902-0538.json +439 -0
  21. org_knowledge_layer-0.5.0/evals/results/ab-20260902-1216.json +27 -0
  22. org_knowledge_layer-0.5.0/evals/results/ab-20260903-0157.json +441 -0
  23. org_knowledge_layer-0.5.0/evals/results/ab-20260903-0308.json +441 -0
  24. org_knowledge_layer-0.5.0/evals/results/ab-20260903-1214.json +441 -0
  25. org_knowledge_layer-0.5.0/evals/results/ab-20260903-1323.json +443 -0
  26. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/tasks.jsonl +1 -1
  27. {org_knowledge_layer-0.4.0/src/okl/scaffold → org_knowledge_layer-0.5.0}/hooks/stop-okl-encode.sh +11 -3
  28. {org_knowledge_layer-0.4.0/.claude → org_knowledge_layer-0.5.0}/hooks/userpromptsubmit-okl-check.sh +4 -2
  29. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/pyproject.toml +2 -2
  30. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/dotnet-canon.json +8 -0
  31. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/dotnet-decisions.json +11 -0
  32. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/dotnet-defects.json +4 -2
  33. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/frontend-canon.json +2 -0
  34. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/geospatial-deeptime-defects.json +2 -1
  35. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/geospatial-defects.json +8 -4
  36. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/geospatial-enforcement-defects.json +2 -1
  37. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/react-defects.json +4 -2
  38. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/cli.py +53 -4
  39. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/client.py +28 -8
  40. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/core.py +35 -16
  41. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/MANIFEST.md +8 -0
  42. org_knowledge_layer-0.5.0/src/okl/scaffold/ci/dependabot.yml +17 -0
  43. org_knowledge_layer-0.5.0/src/okl/scaffold/ci/method-gates.yml +74 -0
  44. {org_knowledge_layer-0.4.0/.github/workflows → org_knowledge_layer-0.5.0/src/okl/scaffold/ci}/okl-verify.yml +34 -8
  45. org_knowledge_layer-0.5.0/src/okl/scaffold/ci/review-agent.sh +133 -0
  46. org_knowledge_layer-0.5.0/src/okl/scaffold/claude/agents/architecture-reviewer.md +41 -0
  47. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/seed-from-codebase.md +1 -1
  48. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/seed-from-docs.md +1 -1
  49. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0/src/okl/scaffold}/hooks/stop-okl-encode.sh +11 -3
  50. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0/src/okl/scaffold}/hooks/userpromptsubmit-okl-check.sh +4 -2
  51. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold_cmd.py +5 -0
  52. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/store.py +66 -10
  53. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/tests/test_okl.py +331 -36
  54. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/tests/test_scaffold.py +205 -0
  55. org_knowledge_layer-0.4.0/evals/REPORT.md +0 -369
  56. org_knowledge_layer-0.4.0/src/okl/scaffold/ci/method-gates.yml +0 -32
  57. {org_knowledge_layer-0.4.0/src/okl/scaffold/claude → org_knowledge_layer-0.5.0/.claude}/agents/architecture-reviewer.md +0 -0
  58. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.claude/hooks/stop-okl-encode.sh +0 -0
  59. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.claude/settings.json +0 -0
  60. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  61. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  62. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.github/dependabot.yml +0 -0
  63. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.github/pull_request_template.md +0 -0
  64. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/.github/workflows/publish.yml +0 -0
  65. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/CONTRIBUTING.md +0 -0
  66. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/LICENSE +0 -0
  67. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/SECURITY.md +0 -0
  68. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/ci/check-diagram-figures.sh +0 -0
  69. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/DEPLOY.md +0 -0
  70. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/ab-results-chart.png +0 -0
  71. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/ab-results-chart.svg +0 -0
  72. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/decisions/2026-07-17-flat-retrieval-until-scale.md +0 -0
  73. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/okl-how-it-works.excalidraw +0 -0
  74. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/okl-how-it-works.svg +0 -0
  75. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/okl-sixth-surface.excalidraw +0 -0
  76. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/okl-sixth-surface.svg +0 -0
  77. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/posts/01-memory-that-outlives-the-run.md +0 -0
  78. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/posts/02-dont-let-a-step-grade-itself.md +0 -0
  79. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/docs/posts/03-enforcement-or-good-intentions.md +0 -0
  80. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/README.md +0 -0
  81. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/ab-20260829-2300.json +0 -0
  82. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/ab-20260829-2315.json +0 -0
  83. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/ab-20260830-0003.json +0 -0
  84. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/ab-20260830-0148.json +0 -0
  85. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/ab-20260901-0133.json +0 -0
  86. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/ab-20260901-1238.json +0 -0
  87. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/README.md +0 -0
  88. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/briefed-userpromptsubmit-lint.yml +0 -0
  89. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/control-lint.yml +0 -0
  90. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/hook.log +0 -0
  91. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/service-record-500.log +0 -0
  92. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/session-briefed-pretooluse.txt +0 -0
  93. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/session-briefed-userpromptsubmit.txt +0 -0
  94. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/evals/results/e2e-20260830/session-control.txt +0 -0
  95. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/check-canon-size.sh +0 -0
  96. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/check-diagram-pairs.sh +0 -0
  97. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/check-doc-orphans.sh +0 -0
  98. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/check-links.sh +0 -0
  99. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/check-retractions.sh +0 -0
  100. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/check-tombstones.sh +0 -0
  101. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/gates/run-gates.sh +0 -0
  102. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/dotnet-review-surfaces.json +0 -0
  103. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/geospatial-eval-defects.json +0 -0
  104. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/seed/rag-defects.json +0 -0
  105. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/__init__.py +0 -0
  106. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/__main__.py +0 -0
  107. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/bootstrap.py +0 -0
  108. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/drift.py +0 -0
  109. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/mcp_server.py +0 -0
  110. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/check-rules.md +0 -0
  111. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/commands/feature-spec.md +0 -0
  112. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/rules/example-area.md +0 -0
  113. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +0 -0
  114. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/skills/encoding-loop/SKILL.md +0 -0
  115. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +0 -0
  116. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/evals/README.md +0 -0
  117. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/evals/cases.jsonl +0 -0
  118. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/evals/run_evals.py +0 -0
  119. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-canon-size.sh +0 -0
  120. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-diagram-pairs.sh +0 -0
  121. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-doc-orphans.sh +0 -0
  122. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-links.sh +0 -0
  123. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-retractions.sh +0 -0
  124. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/check-tombstones.sh +0 -0
  125. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/gates/run-gates.sh +0 -0
  126. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/hooks/hooks.json +0 -0
  127. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/plugin/plugin.json +0 -0
  128. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/README.md +0 -0
  129. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/architecture.md +0 -0
  130. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/messaging.md +0 -0
  131. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/performance-and-data.md +0 -0
  132. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/dotnet/rules/security.md +0 -0
  133. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/geospatial/README.md +0 -0
  134. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +0 -0
  135. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/README.md +0 -0
  136. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +0 -0
  137. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/rules/project-structure.md +0 -0
  138. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +0 -0
  139. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/react/README.md +0 -0
  140. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/profiles/react/rules/frontend.md +0 -0
  141. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/registries/RETRACTIONS.md +0 -0
  142. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/registries/tombstones.txt +0 -0
  143. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/root/CLAUDE.md +0 -0
  144. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/scaffold/root/METHOD.md +0 -0
  145. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/seed.py +0 -0
  146. {org_knowledge_layer-0.4.0 → org_knowledge_layer-0.5.0}/src/okl/service.py +0 -0
@@ -36,8 +36,10 @@ resolve_okl() {
36
36
  if ! OKL=$(resolve_okl); then
37
37
  [ "${OKL_OFFLINE:-0}" = "1" ] && exit 0
38
38
  echo "okl NOT FOUND — blocking (a check that can't run must not pass as clean)." >&2
39
- echo "Install it (pip install okl), set OKL_BIN, or re-run 'okl init' from a shell where it" >&2
40
- echo "works (pins okl_bin into .okl/config.json). OKL_OFFLINE=1 proceeds without the layer." >&2
39
+ echo "Install it: pip install org-knowledge-layer. NOT 'pip install okl' — PyPI refuses that" >&2
40
+ echo "name as confusable, so it fails and looks like the tool does not exist." >&2
41
+ echo "Or set OKL_BIN, or re-run 'okl init' from a shell where okl works (that pins okl_bin" >&2
42
+ echo "into .okl/config.json). OKL_OFFLINE=1 proceeds without the layer." >&2
41
43
  exit 2
42
44
  fi
43
45
 
@@ -78,6 +78,18 @@ jobs:
78
78
  # key for organization-owned repositories, so it would break for anyone who forks
79
79
  # this into an org. The binary has no such condition, and pinning the release keeps
80
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
+
81
93
  - name: Secret scan — gitleaks
82
94
  env:
83
95
  GITLEAKS_VERSION: "8.30.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
@@ -19,3 +19,5 @@ e2e/
19
19
 
20
20
  #Ignore vscode AI rules
21
21
  .github/instructions/codacy.instructions.md
22
+ okl.db-wal
23
+ okl.db-shm
@@ -9,7 +9,14 @@ The hooks fire on your own session: `UserPromptSubmit` injects the store's brief
9
9
  your context before you start; `Stop` blocks your first stop to ask what was learned.
10
10
  Answer it honestly — record with a deliberate scope (`org` spreads to every repo,
11
11
  `repo` stays local) and tags from the closed vocabulary in `store.KNOWN_TAGS`. Growing
12
- that vocabulary is an edit to the set plus a note in the tags ADR, never an ad-hoc tag.
12
+ that vocabulary is a deliberate act, never an ad-hoc tag: `okl record --type Vocabulary
13
+ --scope org --title <tag>` declares one for THIS store (a Go or Rust team needs no fork),
14
+ and editing `KNOWN_TAGS` changes the floor every store ships with.
15
+
16
+ - **`applies_to` is where a lesson is TRUE; tags are where it was FOUND.** Leave it unset
17
+ unless the lesson is false or meaningless off that stack — unset reaches every repo and
18
+ is the safe default, while a wrong value hides the record silently. Never derive it from
19
+ a tag: that is the reverted §4d defect, which measured worse than no filter at all.
13
20
 
14
21
  - **Verification is evidence-based**: `okl verify <id> --run "<check>" --expect "<signal>"`.
15
22
  Never re-record with `--verified` to clear drift — run the check.
@@ -1,5 +1,68 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.5.0
4
+
5
+ ### The vocabulary is no longer fixed in the package
6
+
7
+ - **A store can declare its own subject tags.** `KNOWN_TAGS` remains, but as the *floor*
8
+ every store ships with rather than the whole vocabulary. Widen it by recording a
9
+ `Vocabulary` node, whose title is the tag:
10
+
11
+ ```bash
12
+ okl record --type Vocabulary --scope org --title rust --body "Rust services."
13
+ okl record --type Rule --scope org --tags rust --title "Prefer ? over unwrap in handlers"
14
+ ```
15
+
16
+ Before this, a Go, Rust or PowerShell team could not file a lesson under its own language
17
+ without forking the package, which is a hard stop on adoption for a tool whose claim is
18
+ that a lesson learned in one place reaches another. What the closed vocabulary was *for*
19
+ survives: it catches the typo that files a record where nobody looks, and closed-per-store
20
+ still does, so `rustt` is refused in a store that declared `rust`. What changes is who may
21
+ grow it. `STACK_TAGS`, which gates `applies_to`, is deliberately not extended.
22
+
23
+ - **`frontend` and `prose` added to the floor.** Neither is specific to one org: any repo
24
+ that renders markup has frontend lessons, and any repo that governs its writing has prose
25
+ ones. `react` was the nearest existing tag for the first and is wrong, being a stack tag.
26
+
27
+ ### Fixed
28
+
29
+ - **The hooks no longer tell you to run `pip install okl`, which cannot work.** PyPI refuses
30
+ `okl` as confusable, so the distribution is `org-knowledge-layer` and `okl` is only the
31
+ console script. The wrong name was in the `UserPromptSubmit` hook's not-found message,
32
+ which fires *exactly* when someone has no working install and sent them to a 404 that
33
+ reads as "this tool was never published." `okl init` stamps that hook into every repo it
34
+ touches, so the error propagated with adoption. Re-run `okl init` to pick up the new file.
35
+ - `cli.py`'s MCP hint said `pip install okl[mcp]`; now `pip install 'org-knowledge-layer[mcp]'`,
36
+ quoted because zsh globs unquoted brackets.
37
+
38
+
39
+ ## 0.4.1
40
+
41
+ ### Security — affects the CI workflow already in your repo
42
+
43
+ - **The shipped CI verifier no longer writes your bearer token to disk.** It ran
44
+ `okl connect --token`, which persisted `OKL_TOKEN` into `.okl/config.json` on the runner
45
+ for no reason: the client reads `OKL_SERVICE_URL` and `OKL_TOKEN` from the environment
46
+ directly, so setting them at job level does the same work with the credential never
47
+ touching the filesystem. Re-run `okl init` (or `okl scaffold`) to pick up the new file.
48
+ - **Both shipped workflows are hardened.** `okl-verify.yml` and `method-gates.yml` now
49
+ declare least-privilege `permissions`, a concurrency group, `pipefail`, and
50
+ `persist-credentials: false`, and pin their actions to commit SHAs rather than mutable
51
+ tags. They satisfied none of that before.
52
+ - `.github/dependabot.yml` ships with the kit, because pinning to a SHA without an update
53
+ path is how a repo ends up on a two-year-old action and calls it hardening.
54
+
55
+ ### Added
56
+
57
+ - **Opt-in architecture review in CI.** `ci/review-agent.sh` runs the reviewer over a PR
58
+ diff and fails on must-fix findings. **Off unless you set the `REVIEW_CMD` repository
59
+ variable** to a CLI that reads a prompt on stdin — `claude -p` (authenticates with an
60
+ existing Claude Code login, no separate API key), `ollama run <model>` (local, free), or
61
+ any other. Unset, it prints one line and passes. It is the only gate in the kit that
62
+ calls a model, which is why it is the only one that is opt-in. Documented in the README
63
+ and the kit manifest.
64
+
65
+
3
66
  ## 0.4.0
4
67
 
5
68
  ### Behaviour changes worth reading before upgrading
@@ -9,7 +9,14 @@ The hooks fire on your own session: `UserPromptSubmit` injects the store's brief
9
9
  your context before you start; `Stop` blocks your first stop to ask what was learned.
10
10
  Answer it honestly — record with a deliberate scope (`org` spreads to every repo,
11
11
  `repo` stays local) and tags from the closed vocabulary in `store.KNOWN_TAGS`. Growing
12
- that vocabulary is an edit to the set plus a note in the tags ADR, never an ad-hoc tag.
12
+ that vocabulary is a deliberate act, never an ad-hoc tag: `okl record --type Vocabulary
13
+ --scope org --title <tag>` declares one for THIS store (a Go or Rust team needs no fork),
14
+ and editing `KNOWN_TAGS` changes the floor every store ships with.
15
+
16
+ - **`applies_to` is where a lesson is TRUE; tags are where it was FOUND.** Leave it unset
17
+ unless the lesson is false or meaningless off that stack — unset reaches every repo and
18
+ is the safe default, while a wrong value hides the record silently. Never derive it from
19
+ a tag: that is the reverted §4d defect, which measured worse than no filter at all.
13
20
 
14
21
  - **Verification is evidence-based**: `okl verify <id> --run "<check>" --expect "<signal>"`.
15
22
  Never re-record with `--verified` to clear drift — run the check.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: org-knowledge-layer
3
- Version: 0.4.0
3
+ Version: 0.5.0
4
4
  Summary: Org Knowledge Layer — an installable sixth surface that carries encoded engineering lessons across repos.
5
5
  Author: Joshua Dell
6
6
  License: MIT
@@ -19,7 +19,7 @@ Requires-Dist: httpx>=0.27; extra == 'dev'
19
19
  Requires-Dist: mypy==2.3.1; extra == 'dev'
20
20
  Requires-Dist: pydantic>=2; extra == 'dev'
21
21
  Requires-Dist: pytest-cov==7.1.0; extra == 'dev'
22
- Requires-Dist: pytest==8.4.2; extra == 'dev'
22
+ Requires-Dist: pytest==9.1.1; extra == 'dev'
23
23
  Requires-Dist: ruff==0.16.5; extra == 'dev'
24
24
  Requires-Dist: uvicorn>=0.29; extra == 'dev'
25
25
  Provides-Extra: mcp
@@ -210,6 +210,20 @@ with receipts, not a benchmark.
210
210
 
211
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
212
 
213
+ ### How a briefing is actually built
214
+
215
+ `okl check` is a retrieval pipeline: seven stages turn a task sentence and the whole corpus
216
+ into the twelve records an agent reads. Two stages can drop a record, and only one of them
217
+ is entitled to — the distinction that this project got wrong once and measured its way out
218
+ of ([REPORT §4d](evals/REPORT.md)).
219
+
220
+ <img src="docs/okl-retrieval-pipeline.svg" alt="Seven stages: the corpus, a BM25 fetch of three times the limit, a scope gate, the exclusive applies_to filter, the inclusive tags-times-interests filter, a top-12 cutoff, bucketing by type, and rendering. Each stage shows how many records survive it." width="100%">
221
+
222
+ Every count in that diagram is **traced**, not transcribed — `docs/render_pipeline_diagram.py`
223
+ runs the real `store.search` and `core._in_scope` against a store seeded from `seed/`, and
224
+ `test_pipeline_diagram_is_current` fails if the committed render is not what the generator
225
+ produces today. Change a BM25 weight or a filter predicate and the diagram goes red with it.
226
+
213
227
  ### The mental model
214
228
 
215
229
  `okl` stores small, typed **notes** and the **links** between them.
@@ -358,6 +372,32 @@ To remove okl from a repo entirely, delete `.okl/`, the two hook scripts and the
358
372
  in `.claude/settings.json`, and `.github/workflows/okl-verify.yml`. Nothing else was
359
373
  written, and nothing outside that repo was touched.
360
374
 
375
+ ### Architecture review in CI (off by default)
376
+
377
+ The kit ships a reviewer that reads a PR diff against your encoded rules and fails the
378
+ build on a must-fix finding. It is **off unless you ask for it**, and it is not tied to any
379
+ vendor. Set the `REVIEW_CMD` repository variable to any CLI that reads a prompt on stdin:
380
+
381
+ ```bash
382
+ gh variable set REVIEW_CMD --body "claude -p --model sonnet" # your existing Claude Code login
383
+ gh variable set REVIEW_CMD --body "ollama run qwen2.5-coder" # local model, no API cost
384
+ gh variable set REVIEW_CMD --body "llm -m gpt-4o" # any other CLI
385
+ ```
386
+
387
+ Two things worth knowing:
388
+
389
+ - **`claude -p` needs no separate API key.** It authenticates with the Claude Code login you
390
+ already have, so if you use Claude Code there is nothing else to configure and no second
391
+ bill. Verified headless with `ANTHROPIC_API_KEY` unset.
392
+ - **Locally you do not need this at all.** The reviewer is a subagent
393
+ (`.claude/agents/architecture-reviewer.md`); ask your agent to run it on your changes and
394
+ it costs nothing beyond the session you are already in. The CI job exists for the case
395
+ where no human and no agent is in the loop — a PR nobody reviewed.
396
+
397
+ Unset, the step prints one line saying it is off and exits 0. Every other gate in the kit is
398
+ deterministic and free; this is the only one that calls a model, which is why it is the only
399
+ one that is opt-in.
400
+
361
401
  ## Wire a repo
362
402
 
363
403
  ```bash
@@ -176,6 +176,20 @@ with receipts, not a benchmark.
176
176
 
177
177
  <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%">
178
178
 
179
+ ### How a briefing is actually built
180
+
181
+ `okl check` is a retrieval pipeline: seven stages turn a task sentence and the whole corpus
182
+ into the twelve records an agent reads. Two stages can drop a record, and only one of them
183
+ is entitled to — the distinction that this project got wrong once and measured its way out
184
+ of ([REPORT §4d](evals/REPORT.md)).
185
+
186
+ <img src="docs/okl-retrieval-pipeline.svg" alt="Seven stages: the corpus, a BM25 fetch of three times the limit, a scope gate, the exclusive applies_to filter, the inclusive tags-times-interests filter, a top-12 cutoff, bucketing by type, and rendering. Each stage shows how many records survive it." width="100%">
187
+
188
+ Every count in that diagram is **traced**, not transcribed — `docs/render_pipeline_diagram.py`
189
+ runs the real `store.search` and `core._in_scope` against a store seeded from `seed/`, and
190
+ `test_pipeline_diagram_is_current` fails if the committed render is not what the generator
191
+ produces today. Change a BM25 weight or a filter predicate and the diagram goes red with it.
192
+
179
193
  ### The mental model
180
194
 
181
195
  `okl` stores small, typed **notes** and the **links** between them.
@@ -324,6 +338,32 @@ To remove okl from a repo entirely, delete `.okl/`, the two hook scripts and the
324
338
  in `.claude/settings.json`, and `.github/workflows/okl-verify.yml`. Nothing else was
325
339
  written, and nothing outside that repo was touched.
326
340
 
341
+ ### Architecture review in CI (off by default)
342
+
343
+ The kit ships a reviewer that reads a PR diff against your encoded rules and fails the
344
+ build on a must-fix finding. It is **off unless you ask for it**, and it is not tied to any
345
+ vendor. Set the `REVIEW_CMD` repository variable to any CLI that reads a prompt on stdin:
346
+
347
+ ```bash
348
+ gh variable set REVIEW_CMD --body "claude -p --model sonnet" # your existing Claude Code login
349
+ gh variable set REVIEW_CMD --body "ollama run qwen2.5-coder" # local model, no API cost
350
+ gh variable set REVIEW_CMD --body "llm -m gpt-4o" # any other CLI
351
+ ```
352
+
353
+ Two things worth knowing:
354
+
355
+ - **`claude -p` needs no separate API key.** It authenticates with the Claude Code login you
356
+ already have, so if you use Claude Code there is nothing else to configure and no second
357
+ bill. Verified headless with `ANTHROPIC_API_KEY` unset.
358
+ - **Locally you do not need this at all.** The reviewer is a subagent
359
+ (`.claude/agents/architecture-reviewer.md`); ask your agent to run it on your changes and
360
+ it costs nothing beyond the session you are already in. The CI job exists for the case
361
+ where no human and no agent is in the loop — a PR nobody reviewed.
362
+
363
+ Unset, the step prints one line saying it is off and exits 0. Every other gate in the kit is
364
+ deterministic and free; this is the only one that calls a model, which is why it is the only
365
+ one that is opt-in.
366
+
327
367
  ## Wire a repo
328
368
 
329
369
  ```bash
@@ -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,133 @@
1
+ #!/usr/bin/env bash
2
+ # review-agent.sh — run the architecture reviewer over a PR diff, headless.
3
+ #
4
+ # Closes the gap the store names as "an enforcement surface that only runs on-demand runs
5
+ # never": .claude/agents/architecture-reviewer.md is listed as a review surface but nothing
6
+ # triggers it, so in practice it runs when someone remembers, which is never.
7
+ #
8
+ # OFF BY DEFAULT, AND VENDOR-NEUTRAL. Set REVIEW_CMD to any CLI that reads a prompt on
9
+ # stdin and writes the model's reply to stdout:
10
+ #
11
+ # REVIEW_CMD="claude -p --model sonnet"
12
+ # REVIEW_CMD="llm -m gpt-4o"
13
+ # REVIEW_CMD="ollama run qwen2.5-coder" # local, no API cost at all
14
+ #
15
+ # Unset, the step soft-passes and says how to switch it on. okl is harness-agnostic —
16
+ # hard-coding one vendor's CLI here would contradict that, and would hand every consumer
17
+ # an API bill they never asked for. This mirrors GENERATOR_CMD/JUDGE_CMD in evals/.
18
+ #
19
+ # Self-owned on purpose: no third-party review SaaS holds a token for your source, and the
20
+ # reviewer reads YOUR encoded rules (it calls `okl check` itself) rather than generic advice.
21
+ set -uo pipefail
22
+ cd "$(dirname "$0")/.."
23
+
24
+ # Located, not assumed: `okl scaffold --claude-dir` lets a repo name that directory
25
+ # something other than .claude, and a hard-coded path silently skips the review in exactly
26
+ # those repos — a gate that quietly does nothing is the failure this whole file exists to
27
+ # fix. AGENT_FILE overrides for anything unusual.
28
+ AGENT="${AGENT_FILE:-}"
29
+ if [ -z "$AGENT" ]; then
30
+ AGENT="$(find . -maxdepth 3 -path ./.git -prune -o \
31
+ -name architecture-reviewer.md -print 2>/dev/null | head -1)"
32
+ fi
33
+ BASE="${REVIEW_BASE_REF:-origin/main}"
34
+
35
+ if [ -z "${REVIEW_CMD:-}" ]; then
36
+ echo "review-agent: REVIEW_CMD not set — skipping (soft pass)."
37
+ echo " Set it to any CLI that takes a prompt on stdin to turn this into a blocking gate,"
38
+ echo " e.g. REVIEW_CMD=\"claude -p --model sonnet\" or REVIEW_CMD=\"ollama run qwen2.5-coder\"."
39
+ exit 0
40
+ fi
41
+ if [ ! -f "$AGENT" ]; then
42
+ echo "review-agent: no $AGENT — skipping (the reviewer ships with \`okl scaffold\`)."
43
+ exit 0
44
+ fi
45
+ # shellcheck disable=SC2086 — REVIEW_CMD is a command line, so word splitting is intended
46
+ if ! command -v ${REVIEW_CMD%% *} >/dev/null 2>&1; then
47
+ echo "review-agent: '${REVIEW_CMD%% *}' from REVIEW_CMD is not on PATH — skipping (soft pass)." >&2
48
+ exit 0
49
+ fi
50
+
51
+ diff_text="$(git diff "$BASE"...HEAD 2>/dev/null)"
52
+ if [ -z "$diff_text" ]; then
53
+ echo "review-agent: no diff against $BASE — nothing to review."
54
+ exit 0
55
+ fi
56
+
57
+ # Truncate: a very large diff would blow the context budget and produce a worse review than
58
+ # a focused one. Reviewing the first N lines and saying so beats silently reviewing a
59
+ # truncated diff as though it were whole.
60
+ max_lines=4000
61
+ total_lines=$(printf '%s' "$diff_text" | wc -l | tr -d ' ')
62
+ if [ "$total_lines" -gt "$max_lines" ]; then
63
+ diff_text="$(printf '%s' "$diff_text" | head -n "$max_lines")"
64
+ echo "review-agent: diff is $total_lines lines; reviewing the first $max_lines."
65
+ fi
66
+
67
+ # The checklist lives in the agent file — one source of truth for the rules. Only the OUTPUT
68
+ # CONTRACT is supplied here, because the agent is written for interactive use and returns
69
+ # prose, which CI cannot act on.
70
+ prompt="$(cat <<EOF
71
+ You are the architecture reviewer defined below. Apply its checklist to the diff.
72
+
73
+ $(cat "$AGENT")
74
+
75
+ --- DIFF UNDER REVIEW ---
76
+ $diff_text
77
+ --- END DIFF ---
78
+
79
+ Reply with STRICT JSON and nothing else:
80
+ {"findings":[{"severity":"must-fix|consider","file":"path","line":0,"rule":"which checklist item","finding":"one sentence"}]}
81
+
82
+ Rules for your reply:
83
+ - "must-fix" is reserved for a violation of an encoded rule: a restated retraction, a
84
+ resurrected tombstoned identifier, a speculative abstraction, an assert-from-memory claim.
85
+ Style preferences and suggestions are "consider".
86
+ - Only report what this diff introduces. Pre-existing issues are out of scope.
87
+ - An empty findings list is a valid and common answer. Do not invent findings to seem useful.
88
+ EOF
89
+ )"
90
+
91
+ echo "review-agent: reviewing $(printf '%s' "$diff_text" | wc -l | tr -d ' ') lines of diff against $BASE"
92
+ # CLEAN ROOM. Run the reviewer from a temp directory, never the repo. If REVIEW_CMD is a
93
+ # coding agent, this repo's own hooks would otherwise fire on it: the UserPromptSubmit hook
94
+ # injects a briefing into the review, and the Stop hook answers instead of the reviewer.
95
+ # Reproduced while verifying this script — a smoke call in the repo cwd came back answering
96
+ # the Stop hook rather than the question, which is the same failure evals/ documents.
97
+ clean="$(mktemp -d)"
98
+ raw="$(cd "$clean" && printf '%s' "$prompt" | $REVIEW_CMD 2>/dev/null)"
99
+ rmdir "$clean" 2>/dev/null || true
100
+
101
+ # Strip any markdown fence the model adds around the JSON.
102
+ json="$(printf '%s' "$raw" | sed -e 's/^```json//' -e 's/^```//' -e '/^```$/d')"
103
+
104
+ if ! printf '%s' "$json" | python3 -c "import json,sys; json.load(sys.stdin)" 2>/dev/null; then
105
+ # Fail OPEN on an unparseable reply, loudly. A reviewer that cannot be understood has not
106
+ # found anything; blocking every merge on a malformed model response would train people to
107
+ # bypass the gate, which is worse than the gate not firing.
108
+ echo "review-agent: could not parse the reviewer's reply as JSON — not blocking." >&2
109
+ printf '%s\n' "$raw" | head -20 >&2
110
+ exit 0
111
+ fi
112
+
113
+ printf '%s' "$json" | python3 -c '
114
+ import json, sys
115
+
116
+ # %-formatting rather than f-strings: this is embedded in a shell single-quoted string, so
117
+ # the keys need double quotes, and a backslash inside an f-string EXPRESSION is a SyntaxError
118
+ # before Python 3.12 — which this must survive, since it runs on whatever interpreter the
119
+ # consumer has. The first version hit exactly that and exited 1 on every run. A gate that
120
+ # always blocks looks identical to a gate that works until someone notices nothing has ever
121
+ # passed it.
122
+ findings = json.load(sys.stdin).get("findings", [])
123
+ must = [f for f in findings if f.get("severity") == "must-fix"]
124
+ for f in findings:
125
+ mark = "MUST-FIX" if f.get("severity") == "must-fix" else "consider"
126
+ print(" [%s] %s:%s %s" % (mark, f.get("file"), f.get("line"), f.get("rule")))
127
+ print(" %s" % f.get("finding"))
128
+ if not findings:
129
+ print(" no findings")
130
+ print()
131
+ print("review-agent: %d must-fix, %d to consider" % (len(must), len(findings) - len(must)))
132
+ sys.exit(1 if must else 0)
133
+ '
@@ -57,3 +57,44 @@ seed-file comments ("eval-integrity lessons are org-scoped") and the scaffold's
57
57
  pipeline, not a language. The distinction the vocabulary already draws (stacks vs subjects)
58
58
  did not have a slot for "the language this is written in", and adding one is cheaper than
59
59
  overloading a stack tag whose meaning other repos depend on.
60
+ - 2026-09-03: **the "revisit when" condition above was met, and the vocabulary is now
61
+ extensible per store.** `KNOWN_TAGS` remains, but as the *floor* every store ships with
62
+ rather than the whole vocabulary: a store widens it by recording `Vocabulary` nodes,
63
+ whose title is the tag, and `Store.add_node` validates against floor ∪ declared.
64
+
65
+ The trigger was a language-support audit. Everything else in okl is language-agnostic —
66
+ drift asks git about path globs, verification runs whatever command you hand it, the
67
+ store holds text — and a Rust repo was driven end to end successfully except for one
68
+ thing: `okl record --tags rust` was refused, and the only remedy was to fork the package.
69
+ That is a hard stop on adoption for a tool whose whole claim is that a lesson learned in
70
+ one place reaches another.
71
+
72
+ What the closed vocabulary was *for* survives intact. It exists to catch the typo that
73
+ files a record where nobody looks, and closed-per-store still does: `rustt` is refused in
74
+ a store that declared `rust`. What changes is who is entitled to grow it — the org that
75
+ owns the store, rather than whoever can merge to this package.
76
+
77
+ A `Vocabulary` node is validated against the floor only. Otherwise the first declaration
78
+ in a fresh store would require the tag it is declaring.
79
+
80
+ **Not extended:** `STACK_TAGS`, which gates `applies_to`. Declaring `rust` makes it usable
81
+ as a subject tag, not as an applicability value. That is a narrower and rarer need — it
82
+ only matters for a lesson that is genuinely false off-stack — and §4h measured that the
83
+ filter `applies_to` feeds changes 0% of delivered briefing slots today. Extending the
84
+ exclusive mechanism without a measurement is exactly what §4d punished.
85
+ - 2026-09-17: `frontend` and `prose` added **to the floor**, when `emeraldleaf-dev` joined
86
+ the layer and every one of its records was rejected: markup and CSS defects, and rules
87
+ about writing. `react` was the nearest existing tag for the first group and is wrong for
88
+ the same reason `python-rag` was wrong for `python`. It is a stack tag, and that repo is a
89
+ hand-rolled Astro site with no UI framework. `frontend` names the subject those lessons are
90
+ actually about: markup whitespace semantics, CSS layout and responsive behaviour, browser
91
+ rendering. `prose` had no near-miss at all. Writing is governed in these repos the way code
92
+ is, with an em-dash budget and a claims-must-be-supported rule behind mechanical gates, and
93
+ a rule with a gate behind it is a rule the layer should carry.
94
+
95
+ The floor rather than a per-store `Vocabulary` declaration, now that the amendment above
96
+ makes both available. Neither tag is specific to one org: any repo that renders markup has
97
+ frontend lessons, and any repo that governs its writing has prose ones. The floor is for
98
+ subjects every store would otherwise have to declare for itself, which is the same reason
99
+ `messaging` and `python` are in it. Per-store declaration is for what an org's own stack
100
+ needs and nobody else's.