observed-knowledge-ledger 0.6.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 (150) hide show
  1. observed_knowledge_ledger-0.6.0/.claude/agent-memory/architecture-reviewer/MEMORY.md +1 -0
  2. observed_knowledge_ledger-0.6.0/.claude/agent-memory/architecture-reviewer/review_findings_log.md +15 -0
  3. observed_knowledge_ledger-0.6.0/.claude/agents/architecture-reviewer.md +41 -0
  4. observed_knowledge_ledger-0.6.0/.claude/hooks/stop-okl-encode.sh +78 -0
  5. observed_knowledge_ledger-0.6.0/.claude/hooks/userpromptsubmit-okl-check.sh +70 -0
  6. observed_knowledge_ledger-0.6.0/.claude/settings.json +24 -0
  7. observed_knowledge_ledger-0.6.0/.coverage +0 -0
  8. observed_knowledge_ledger-0.6.0/.github/ISSUE_TEMPLATE/bug_report.md +21 -0
  9. observed_knowledge_ledger-0.6.0/.github/ISSUE_TEMPLATE/feature_request.md +15 -0
  10. observed_knowledge_ledger-0.6.0/.github/dependabot.yml +32 -0
  11. observed_knowledge_ledger-0.6.0/.github/pull_request_template.md +15 -0
  12. observed_knowledge_ledger-0.6.0/.github/workflows/ci.yml +100 -0
  13. observed_knowledge_ledger-0.6.0/.github/workflows/okl-verify.yml +120 -0
  14. observed_knowledge_ledger-0.6.0/.github/workflows/publish.yml +99 -0
  15. observed_knowledge_ledger-0.6.0/.gitignore +23 -0
  16. observed_knowledge_ledger-0.6.0/AGENTS.md +74 -0
  17. observed_knowledge_ledger-0.6.0/CHANGELOG.md +248 -0
  18. observed_knowledge_ledger-0.6.0/CLAUDE.md +74 -0
  19. observed_knowledge_ledger-0.6.0/CONTRIBUTING.md +111 -0
  20. observed_knowledge_ledger-0.6.0/LICENSE +21 -0
  21. observed_knowledge_ledger-0.6.0/PKG-INFO +791 -0
  22. observed_knowledge_ledger-0.6.0/README.md +757 -0
  23. observed_knowledge_ledger-0.6.0/SECURITY.md +56 -0
  24. observed_knowledge_ledger-0.6.0/ci/check-diagram-figures.sh +82 -0
  25. observed_knowledge_ledger-0.6.0/ci/okl-verify.yml +120 -0
  26. observed_knowledge_ledger-0.6.0/ci/review-agent.sh +133 -0
  27. observed_knowledge_ledger-0.6.0/docs/DEPLOY.md +171 -0
  28. observed_knowledge_ledger-0.6.0/docs/ab-results-chart.png +0 -0
  29. observed_knowledge_ledger-0.6.0/docs/ab-results-chart.svg +27 -0
  30. observed_knowledge_ledger-0.6.0/docs/decisions/2026-07-17-flat-retrieval-until-scale.md +55 -0
  31. observed_knowledge_ledger-0.6.0/docs/decisions/2026-07-21-subject-tags-controlled-vocabulary.md +100 -0
  32. observed_knowledge_ledger-0.6.0/docs/okl-how-it-works.excalidraw +3053 -0
  33. observed_knowledge_ledger-0.6.0/docs/okl-how-it-works.svg +2 -0
  34. observed_knowledge_ledger-0.6.0/docs/okl-retrieval-pipeline.svg +89 -0
  35. observed_knowledge_ledger-0.6.0/docs/okl-sixth-surface.excalidraw +3819 -0
  36. observed_knowledge_ledger-0.6.0/docs/okl-sixth-surface.svg +2 -0
  37. observed_knowledge_ledger-0.6.0/docs/posts/01-memory-that-outlives-the-run.md +65 -0
  38. observed_knowledge_ledger-0.6.0/docs/posts/02-dont-let-a-step-grade-itself.md +45 -0
  39. observed_knowledge_ledger-0.6.0/docs/posts/03-enforcement-or-good-intentions.md +37 -0
  40. observed_knowledge_ledger-0.6.0/docs/posts/04-the-elegant-change-was-the-wrong-one.md +55 -0
  41. observed_knowledge_ledger-0.6.0/docs/render_pipeline_diagram.py +287 -0
  42. observed_knowledge_ledger-0.6.0/evals/README.md +49 -0
  43. observed_knowledge_ledger-0.6.0/evals/REPORT.md +838 -0
  44. observed_knowledge_ledger-0.6.0/evals/ab_harness.py +324 -0
  45. observed_knowledge_ledger-0.6.0/evals/preflight.py +94 -0
  46. observed_knowledge_ledger-0.6.0/evals/results/ab-20260829-2300.json +41 -0
  47. observed_knowledge_ledger-0.6.0/evals/results/ab-20260829-2315.json +137 -0
  48. observed_knowledge_ledger-0.6.0/evals/results/ab-20260830-0003.json +441 -0
  49. observed_knowledge_ledger-0.6.0/evals/results/ab-20260830-0148.json +441 -0
  50. observed_knowledge_ledger-0.6.0/evals/results/ab-20260901-0133.json +441 -0
  51. observed_knowledge_ledger-0.6.0/evals/results/ab-20260901-1238.json +441 -0
  52. observed_knowledge_ledger-0.6.0/evals/results/ab-20260902-0538.json +439 -0
  53. observed_knowledge_ledger-0.6.0/evals/results/ab-20260902-1216.json +27 -0
  54. observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-0157.json +441 -0
  55. observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-0308.json +441 -0
  56. observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-1214.json +441 -0
  57. observed_knowledge_ledger-0.6.0/evals/results/ab-20260903-1323.json +443 -0
  58. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/README.md +23 -0
  59. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/briefed-userpromptsubmit-lint.yml +27 -0
  60. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/control-lint.yml +22 -0
  61. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/hook.log +3 -0
  62. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/service-record-500.log +47 -0
  63. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/session-briefed-pretooluse.txt +3 -0
  64. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/session-briefed-userpromptsubmit.txt +7 -0
  65. observed_knowledge_ledger-0.6.0/evals/results/e2e-20260830/session-control.txt +3 -0
  66. observed_knowledge_ledger-0.6.0/evals/tasks.jsonl +8 -0
  67. observed_knowledge_ledger-0.6.0/gates/check-canon-size.sh +11 -0
  68. observed_knowledge_ledger-0.6.0/gates/check-diagram-pairs.sh +39 -0
  69. observed_knowledge_ledger-0.6.0/gates/check-doc-orphans.sh +23 -0
  70. observed_knowledge_ledger-0.6.0/gates/check-links.sh +41 -0
  71. observed_knowledge_ledger-0.6.0/gates/check-retractions.sh +22 -0
  72. observed_knowledge_ledger-0.6.0/gates/check-tombstones.sh +22 -0
  73. observed_knowledge_ledger-0.6.0/gates/run-gates.sh +33 -0
  74. observed_knowledge_ledger-0.6.0/hooks/stop-okl-encode.sh +93 -0
  75. observed_knowledge_ledger-0.6.0/hooks/userpromptsubmit-okl-check.sh +70 -0
  76. observed_knowledge_ledger-0.6.0/okl-drift.json +105 -0
  77. observed_knowledge_ledger-0.6.0/packaging/org-knowledge-layer/README.md +14 -0
  78. observed_knowledge_ledger-0.6.0/packaging/org-knowledge-layer/pyproject.toml +37 -0
  79. observed_knowledge_ledger-0.6.0/pyproject.toml +118 -0
  80. observed_knowledge_ledger-0.6.0/seed/dotnet-canon.json +497 -0
  81. observed_knowledge_ledger-0.6.0/seed/dotnet-decisions.json +339 -0
  82. observed_knowledge_ledger-0.6.0/seed/dotnet-defects.json +135 -0
  83. observed_knowledge_ledger-0.6.0/seed/dotnet-review-surfaces.json +147 -0
  84. observed_knowledge_ledger-0.6.0/seed/frontend-canon.json +118 -0
  85. observed_knowledge_ledger-0.6.0/seed/geospatial-deeptime-defects.json +60 -0
  86. observed_knowledge_ledger-0.6.0/seed/geospatial-defects.json +158 -0
  87. observed_knowledge_ledger-0.6.0/seed/geospatial-enforcement-defects.json +122 -0
  88. observed_knowledge_ledger-0.6.0/seed/geospatial-eval-defects.json +25 -0
  89. observed_knowledge_ledger-0.6.0/seed/rag-defects.json +120 -0
  90. observed_knowledge_ledger-0.6.0/seed/react-defects.json +47 -0
  91. observed_knowledge_ledger-0.6.0/src/okl/__init__.py +12 -0
  92. observed_knowledge_ledger-0.6.0/src/okl/__main__.py +8 -0
  93. observed_knowledge_ledger-0.6.0/src/okl/bootstrap.py +89 -0
  94. observed_knowledge_ledger-0.6.0/src/okl/cli.py +1009 -0
  95. observed_knowledge_ledger-0.6.0/src/okl/client.py +279 -0
  96. observed_knowledge_ledger-0.6.0/src/okl/coexist.py +148 -0
  97. observed_knowledge_ledger-0.6.0/src/okl/core.py +609 -0
  98. observed_knowledge_ledger-0.6.0/src/okl/drift.py +278 -0
  99. observed_knowledge_ledger-0.6.0/src/okl/mcp_server.py +112 -0
  100. observed_knowledge_ledger-0.6.0/src/okl/scaffold/MANIFEST.md +67 -0
  101. observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/dependabot.yml +17 -0
  102. observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/method-gates.yml +74 -0
  103. observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/okl-verify.yml +120 -0
  104. observed_knowledge_ledger-0.6.0/src/okl/scaffold/ci/review-agent.sh +133 -0
  105. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/agents/architecture-reviewer.md +41 -0
  106. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/check-rules.md +24 -0
  107. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/feature-spec.md +37 -0
  108. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/seed-from-codebase.md +90 -0
  109. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/commands/seed-from-docs.md +123 -0
  110. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/rules/example-area.md +22 -0
  111. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/skills/RECOMMENDED-COMPANIONS.md +40 -0
  112. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/skills/encoding-loop/SKILL.md +54 -0
  113. observed_knowledge_ledger-0.6.0/src/okl/scaffold/claude/skills/verify-before-claiming/SKILL.md +56 -0
  114. observed_knowledge_ledger-0.6.0/src/okl/scaffold/evals/README.md +32 -0
  115. observed_knowledge_ledger-0.6.0/src/okl/scaffold/evals/cases.jsonl +1 -0
  116. observed_knowledge_ledger-0.6.0/src/okl/scaffold/evals/run_evals.py +109 -0
  117. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-canon-size.sh +11 -0
  118. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-diagram-pairs.sh +39 -0
  119. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-doc-orphans.sh +23 -0
  120. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-links.sh +41 -0
  121. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-retractions.sh +22 -0
  122. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/check-tombstones.sh +22 -0
  123. observed_knowledge_ledger-0.6.0/src/okl/scaffold/gates/run-gates.sh +33 -0
  124. observed_knowledge_ledger-0.6.0/src/okl/scaffold/hooks/hooks.json +16 -0
  125. observed_knowledge_ledger-0.6.0/src/okl/scaffold/hooks/stop-okl-encode.sh +93 -0
  126. observed_knowledge_ledger-0.6.0/src/okl/scaffold/hooks/userpromptsubmit-okl-check.sh +70 -0
  127. observed_knowledge_ledger-0.6.0/src/okl/scaffold/plugin/plugin.json +10 -0
  128. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/README.md +12 -0
  129. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/architecture.md +55 -0
  130. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/messaging.md +31 -0
  131. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/performance-and-data.md +36 -0
  132. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/dotnet/rules/security.md +42 -0
  133. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/geospatial/README.md +6 -0
  134. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/geospatial/rules/geospatial-ml.md +38 -0
  135. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/README.md +13 -0
  136. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/rules/fastapi-backend.md +37 -0
  137. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/rules/project-structure.md +28 -0
  138. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/python-rag/rules/rag-pipeline.md +73 -0
  139. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/react/README.md +18 -0
  140. observed_knowledge_ledger-0.6.0/src/okl/scaffold/profiles/react/rules/frontend.md +57 -0
  141. observed_knowledge_ledger-0.6.0/src/okl/scaffold/registries/RETRACTIONS.md +19 -0
  142. observed_knowledge_ledger-0.6.0/src/okl/scaffold/registries/tombstones.txt +7 -0
  143. observed_knowledge_ledger-0.6.0/src/okl/scaffold/root/CLAUDE.md +55 -0
  144. observed_knowledge_ledger-0.6.0/src/okl/scaffold/root/METHOD.md +64 -0
  145. observed_knowledge_ledger-0.6.0/src/okl/scaffold_cmd.py +129 -0
  146. observed_knowledge_ledger-0.6.0/src/okl/seed.py +119 -0
  147. observed_knowledge_ledger-0.6.0/src/okl/service.py +186 -0
  148. observed_knowledge_ledger-0.6.0/src/okl/store.py +619 -0
  149. observed_knowledge_ledger-0.6.0/tests/test_okl.py +2133 -0
  150. observed_knowledge_ledger-0.6.0/tests/test_scaffold.py +877 -0
@@ -0,0 +1 @@
1
+ - [Review findings log](review_findings_log.md) — recurring finding classes with counts; promote to a gate at 3
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: review-findings-log
3
+ description: Recurring architecture-review finding classes in okl, with counts, to decide when to promote one to a mechanical gate
4
+ metadata:
5
+ type: project
6
+ ---
7
+
8
+ Tally of finding classes seen in okl reviews. At 3 occurrences, propose a gate via encoding-loop.
9
+
10
+ - **_Backend Protocol method added without a behavioural clause in its docstring** — 1 (PR #35, `edges()`, 2026-09-24). Related record: "A port is defined by behaviour, not by its signatures".
11
+ - **Old and new computation of one metric both served (two sources of truth)** — 1 (PR #35: `recurrence_after_arming` SQL kept beside `core.recurrence_report`).
12
+ - **Postgres-backend change reaches CI untested** — 1 (PR #35). CI has no Postgres service; `test_postgres_backend_conformance` is skipped unless OKL_TEST_POSTGRES_URL is set.
13
+
14
+ **Why:** the agent brief asks to promote a finding class to a gate at its third sighting.
15
+ **How to apply:** bump the count when a class recurs; at 3, recommend a gate (e.g. a test that every _Backend method is named in the Protocol docstring).
@@ -0,0 +1,41 @@
1
+ ---
2
+ name: architecture-reviewer
3
+ description: Reviews a diff or a proposed change against this repo's encoded rules and the org knowledge layer. Invoke on non-pattern-conforming changes (new bounded context, novel dependency, security-model change, multi-step refactor) and before merging anything touching published results. Returns a pattern-checklist verdict, not a rewrite.
4
+ tools: Read, Grep, Glob, Bash
5
+ model: inherit
6
+ memory: project # persists to .claude/agent-memory/architecture-reviewer/ (committed, team-shared)
7
+ skills:
8
+ - encoding-loop
9
+ ---
10
+
11
+ # Architecture reviewer
12
+
13
+ You are a reviewer, not an implementer. You read the change and judge it against the encoded body —
14
+ you do not rewrite it. Your output is a checklist verdict with specific file:line findings.
15
+
16
+ ## Before you start
17
+ 1. Run `okl check --task "<one-line summary of the change under review>"` and treat the returned
18
+ armed gates, retractions, tombstones, and THREAT prior-art as binding review criteria.
19
+ 2. Read `.claude/rules/` entries whose `paths:` match the changed files.
20
+
21
+ ## Pattern checklist (extend as the repo encodes new rules)
22
+ - **Smallest-surface / speculative coupling** — is there an abstraction (interface, factory, layer)
23
+ with exactly one implementation and no test substitution and no concrete second impl on the
24
+ roadmap? If so, flag it as speculative — the concrete class should be used directly.
25
+ - **Assert-from-memory** — does any spec/config/scaffold claim something is "correct"/"valid"
26
+ without a mechanical `validate` step? Flag it; this is the #14-class defect.
27
+ - **Retracted claim restated** — does any prose restate a claim in `registries/RETRACTIONS.md`
28
+ without also retracting it? Fail.
29
+ - **Resurrected identifier** — does the diff reintroduce anything in `registries/tombstones.txt`? Fail.
30
+ - **Missing gate receipt** — does a fix reference a defect class that has a gate, without the gate
31
+ running in CI on this change?
32
+ - **Report-the-result** — if this change is a "fix" that confirms a hypothesis, was the control run?
33
+
34
+ <!-- <<FILL: STACK-SPECIFIC REVIEW CHECKS>>
35
+ Add checks specific to this stack (e.g. IDOR predicate in the SQL Where clause; N+1 queries;
36
+ async-over-sync; mass-assignment of server-controlled fields). Keep each as one scan rule. -->
37
+
38
+ ## Memory
39
+ Use your persistent memory dir to accumulate this repo's recurring findings and architectural
40
+ decisions across reviews. When you see the same finding class a third time, propose promoting it to
41
+ a mechanical gate (surface 5) via the encoding-loop skill.
@@ -0,0 +1,78 @@
1
+ #!/usr/bin/env bash
2
+ # Stop hook — the write-side mechanical catch for the encoding loop.
3
+ #
4
+ # The read side (okl check) is enforced by the PreToolUse hook; nothing enforced the WRITE
5
+ # side, so a session could end without recording what it learned ("a merged fix without the
6
+ # rule is a half-finished job"). This hook asks the question at the ship moment, once:
7
+ # if the session changed the working tree, block the first stop (exit 2) with a prompt to
8
+ # either `okl record` the lesson or state that there is none. It never fires twice in one
9
+ # session (marker file) and never loops (stop_hook_active guard).
10
+ set -uo pipefail
11
+
12
+ # Same resolver as pretooluse-okl-check.sh (env → pinned config → PATH → python3 -m okl);
13
+ # the reminder is best-effort, so an unresolvable okl silently disables it rather than blocking.
14
+ resolve_okl() {
15
+ if [ -n "${OKL_BIN:-}" ]; then printf '%s' "$OKL_BIN"; return 0; fi
16
+ local d="$PWD"
17
+ while [ "$d" != "/" ]; do
18
+ if [ -f "$d/.okl/config.json" ]; then
19
+ local bin
20
+ bin=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("okl_bin") or "")' \
21
+ "$d/.okl/config.json" 2>/dev/null || true)
22
+ if [ -n "$bin" ]; then printf '%s' "$bin"; return 0; fi
23
+ break
24
+ fi
25
+ d=$(dirname "$d")
26
+ done
27
+ if command -v okl >/dev/null 2>&1; then printf '%s' "okl"; return 0; fi
28
+ if python3 -c "import okl" >/dev/null 2>&1; then printf '%s' "python3 -m okl"; return 0; fi
29
+ return 1
30
+ }
31
+
32
+ OKL=$(resolve_okl) || exit 0
33
+
34
+ payload=$(cat 2>/dev/null || true)
35
+ parsed=$(printf '%s' "$payload" | python3 -c '
36
+ import json, sys
37
+ try:
38
+ d = json.load(sys.stdin)
39
+ except Exception:
40
+ d = {}
41
+ print(d.get("session_id", ""))
42
+ print("true" if d.get("stop_hook_active") else "false")
43
+ ' 2>/dev/null) || parsed=""
44
+ session_id=$(printf '%s\n' "$parsed" | sed -n 1p)
45
+ stop_hook_active=$(printf '%s\n' "$parsed" | sed -n 2p)
46
+ [ -n "$stop_hook_active" ] || stop_hook_active="false"
47
+
48
+ # Never loop: if we already blocked once and Claude is stopping again, let it stop.
49
+ [ "${stop_hook_active}" = "true" ] && exit 0
50
+
51
+ # Only fire when the session plausibly did work: uncommitted changes, or a commit in the
52
+ # last hour (covers commit-then-stop sessions).
53
+ changed=0
54
+ if [ -n "$(git status --porcelain 2>/dev/null)" ]; then
55
+ changed=1
56
+ elif last=$(git log -1 --format=%ct 2>/dev/null); then
57
+ now=$(date +%s)
58
+ [ $((now - last)) -lt 3600 ] && changed=1
59
+ fi
60
+ [ "$changed" = "1" ] || exit 0
61
+
62
+ # Once per session (fall back to a repo-scoped marker when no session id is provided).
63
+ marker="${TMPDIR:-/tmp}/okl-encode-reminder-${session_id:-$(pwd | cksum | cut -d' ' -f1)}"
64
+ [ -e "$marker" ] && exit 0
65
+ touch "$marker" 2>/dev/null || true
66
+
67
+ cat >&2 <<'MSG'
68
+ ENCODING LOOP — before this session ends: did it surface a lesson worth keeping?
69
+ A non-obvious failure mode, a rule discovered the hard way, a decision that shouldn't be
70
+ silently reversed? If yes, record it now (choose the scope deliberately — 'org' spreads
71
+ to every repo, 'repo' stays local — and tag the subject):
72
+
73
+ okl record --type Defect|Rule|Decision --scope org|repo --tags "<subjects>" \
74
+ --title "..." --symptom "..." --body "cause: ..." --fix "..."
75
+
76
+ If the session genuinely learned nothing durable, state that explicitly and finish.
77
+ MSG
78
+ exit 2
@@ -0,0 +1,70 @@
1
+ #!/usr/bin/env bash
2
+ # UserPromptSubmit hook — inject the org's relevant lessons into the model's context
3
+ # BEFORE it starts the task. This event is the only correct one for delivery: its stdout
4
+ # (exit 0) is added to Claude's context, and its stdin carries the actual prompt text, so
5
+ # the briefing is retrieved for the task the user really asked for.
6
+ #
7
+ # (The earlier PreToolUse version fired on every edit and printed the briefing to a channel
8
+ # the model never sees — PreToolUse exit-0 stdout goes to the transcript only. Discovered by
9
+ # an end-to-end test: hook fired, briefing correct, defect reproduced anyway.)
10
+ #
11
+ # FAILS CLOSED via exit 2: if the knowledge layer is unreachable, the prompt is blocked
12
+ # rather than letting the agent proceed blind. A check that reports "clean" while broken is
13
+ # worse than no check.
14
+ set -uo pipefail
15
+
16
+ # Resolve how to invoke okl (env → pinned config → PATH → python3 -m okl); hooks run in
17
+ # whatever environment the harness spawns, which often lacks the venv/pipx bin dir.
18
+ resolve_okl() {
19
+ if [ -n "${OKL_BIN:-}" ]; then printf '%s' "$OKL_BIN"; return 0; fi
20
+ local d="$PWD"
21
+ while [ "$d" != "/" ]; do
22
+ if [ -f "$d/.okl/config.json" ]; then
23
+ local bin
24
+ bin=$(python3 -c 'import json,sys; print(json.load(open(sys.argv[1])).get("okl_bin") or "")' \
25
+ "$d/.okl/config.json" 2>/dev/null || true)
26
+ if [ -n "$bin" ]; then printf '%s' "$bin"; return 0; fi
27
+ break
28
+ fi
29
+ d=$(dirname "$d")
30
+ done
31
+ if command -v okl >/dev/null 2>&1; then printf '%s' "okl"; return 0; fi
32
+ if python3 -c "import okl" >/dev/null 2>&1; then printf '%s' "python3 -m okl"; return 0; fi
33
+ return 1
34
+ }
35
+
36
+ if ! OKL=$(resolve_okl); then
37
+ [ "${OKL_OFFLINE:-0}" = "1" ] && exit 0
38
+ echo "okl NOT FOUND — blocking (a check that can't run must not pass as clean)." >&2
39
+ echo "Install it: pip install observed-knowledge-ledger. 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
43
+ exit 2
44
+ fi
45
+
46
+ # The task is the prompt itself (stdin JSON: {"prompt": "..."}); OKL_TASK overrides;
47
+ # last-commit-message is only the fallback of last resort.
48
+ payload=$(cat 2>/dev/null || true)
49
+ prompt=$(printf '%s' "$payload" | python3 -c '
50
+ import json, sys
51
+ try:
52
+ print((json.load(sys.stdin).get("prompt") or "").strip()[:2000])
53
+ except Exception:
54
+ print("")
55
+ ' 2>/dev/null || true)
56
+ TASK="${OKL_TASK:-${prompt:-$(git log -1 --pretty=%s 2>/dev/null || echo 'general work')}}"
57
+
58
+ # $OKL unquoted on purpose: it may be a command + args ("python3 -m okl").
59
+ if out=$($OKL check --task "$TASK" --format agent 2>/dev/null); then
60
+ printf '%s\n' "$out" # stdout → the model's context
61
+ exit 0
62
+ fi
63
+
64
+ if [ "${OKL_OFFLINE:-0}" = "1" ]; then
65
+ echo "OKL offline (OKL_OFFLINE=1 acknowledged) — proceeding without the layer." >&2
66
+ exit 0
67
+ fi
68
+ echo "OKL UNREACHABLE — blocking this prompt. A check that reports 'clean' while broken is worse than no check." >&2
69
+ echo "Fix connectivity, or set OKL_OFFLINE=1 to explicitly proceed without the org knowledge layer." >&2
70
+ exit 2
@@ -0,0 +1,24 @@
1
+ {
2
+ "hooks": {
3
+ "Stop": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/stop-okl-encode.sh"
9
+ }
10
+ ]
11
+ }
12
+ ],
13
+ "UserPromptSubmit": [
14
+ {
15
+ "hooks": [
16
+ {
17
+ "type": "command",
18
+ "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/userpromptsubmit-okl-check.sh"
19
+ }
20
+ ]
21
+ }
22
+ ]
23
+ }
24
+ }
@@ -0,0 +1,21 @@
1
+ ---
2
+ name: Bug report
3
+ about: Something behaves differently than documented
4
+ labels: bug
5
+ ---
6
+
7
+ **What you ran** (the exact command, and `okl --help` version if relevant)
8
+
9
+ **What you expected**
10
+
11
+ **What actually happened** (paste the real output, not a summary)
12
+
13
+ **Environment**
14
+ - OS:
15
+ - Python:
16
+ - Installed via: `pip install observed-knowledge-ledger` / `pip install -e .`
17
+ - Mode: local store / shared service
18
+
19
+ **Especially useful:** if the tool reported success it had not earned (a check that
20
+ passed without running, a stamp without evidence, a hook that fired but changed
21
+ nothing), say so explicitly. That class of defect is this project's subject.
@@ -0,0 +1,15 @@
1
+ ---
2
+ name: Proposal
3
+ about: Suggest a change to how the loop works
4
+ labels: proposal
5
+ ---
6
+
7
+ **The symptom you hit** (what went wrong in real use, not the feature you want)
8
+
9
+ **Why the current design does not cover it**
10
+
11
+ **What you would change**
12
+
13
+ **How you would know it worked** — the check, measurement, or test that would show it.
14
+ Proposals that name their own evidence get adopted faster; see `evals/REPORT.md` for
15
+ the standard this repo holds itself to.
@@ -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,15 @@
1
+ ## What changed
2
+
3
+ ## Why
4
+
5
+ ## What you ran to check it
6
+
7
+ <!-- Paste real output. `pytest -q`, `ruff check .`, and `okl drift` at minimum. -->
8
+
9
+ ## Checklist
10
+
11
+ - [ ] `pytest -q` green
12
+ - [ ] `ruff check .` clean
13
+ - [ ] `okl drift` green, or affected records re-verified with `okl verify --run`
14
+ - [ ] Mirror files still byte-identical (`ci/`, `.github/workflows/`, `hooks/` and their `src/okl/scaffold/` twins)
15
+ - [ ] Any number quoted has a committed receipt in `evals/results/`
@@ -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@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
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@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
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
@@ -0,0 +1,120 @@
1
+ # Installed by `okl init` into .github/workflows/okl-verify.yml (also stamped by `okl scaffold`).
2
+ #
3
+ # The knowledge layer's CI verifier — real checks, not placeholders:
4
+ # 1. `okl drift --gate` — fails the build when a rule's governed files changed after the
5
+ # rule was last verified (a stale rule is a rule nobody re-checked).
6
+ # 2. `gates/run-gates.sh` — the repo's mechanical method gates, when the scaffold is present.
7
+ #
8
+ # Shared-layer connection is optional: without OKL_SERVICE_URL the drift gate reads the
9
+ # committed snapshot okl-drift.json (`okl export --drift`; `okl verify` keeps it current),
10
+ # which also covers fork PRs, since they get no secrets. Configure the secrets to verify against the org layer and to
11
+ # let gate scripts emit receipts (`okl link <gate_id> VERIFIED_ON <defect_id>`) — emit those
12
+ # from inside the gate that proved itself against real drift, where the ids are known.
13
+ name: okl-verify
14
+
15
+ on:
16
+ pull_request:
17
+ push:
18
+ branches: [main]
19
+
20
+ # Least privilege: this workflow reads code and reports a status. It never writes.
21
+ permissions:
22
+ contents: read
23
+
24
+ concurrency:
25
+ group: okl-verify-${{ github.ref }}
26
+ cancel-in-progress: true
27
+
28
+ defaults:
29
+ run:
30
+ # Without pipefail a run block continues past a failed segment of a pipeline, so a
31
+ # green step can hide a red command.
32
+ shell: bash -euo pipefail {0}
33
+
34
+ jobs:
35
+ okl-verify:
36
+ runs-on: ubuntu-latest
37
+ env:
38
+ # Set at job level so every okl command below resolves the shared layer from the
39
+ # environment. This replaces an `okl connect --token` step, which wrote the bearer
40
+ # token into .okl/config.json on the runner — needless, since the client reads both
41
+ # variables directly, and one more place for a credential to end up.
42
+ # Unset secrets leave these empty, and okl falls back to the repo-local store.
43
+ OKL_SERVICE_URL: ${{ secrets.OKL_SERVICE_URL }}
44
+ OKL_TOKEN: ${{ secrets.OKL_TOKEN }}
45
+ steps:
46
+ # Actions are pinned to a commit SHA: a tag is mutable, so `@v5` is an unpinned
47
+ # dependency with access to your runner. If you copy this file into your own repo,
48
+ # add a .github/dependabot.yml with the `github-actions` ecosystem so these pins get
49
+ # security updates — a pin with no update path goes stale and that is its own risk.
50
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
51
+ with:
52
+ fetch-depth: 0 # drift needs history: it compares governed-file commits vs verified_at
53
+ persist-credentials: false # this job never pushes
54
+
55
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
56
+ with:
57
+ python-version: "3.13"
58
+
59
+ - name: Install okl
60
+ # Detect by the package source, not the distribution name: the name changed once
61
+ # (PyPI rejects "okl" as confusable) and a name-based check silently broke with it.
62
+ run: |
63
+ if [ -f src/okl/cli.py ]; then
64
+ pip install -e .
65
+ else
66
+ pip install observed-knowledge-ledger
67
+ fi
68
+
69
+ - name: Report which store the gate will run against
70
+ run: |
71
+ if [ -n "${OKL_SERVICE_URL:-}" ]; then
72
+ echo "verifying against the shared layer at $OKL_SERVICE_URL"
73
+ elif git ls-files --error-unmatch okl-drift.json >/dev/null 2>&1; then
74
+ echo "no OKL_SERVICE_URL secret — verifying against the committed okl-drift.json"
75
+ else
76
+ echo "no OKL_SERVICE_URL secret and no committed okl-drift.json — nothing to verify against"
77
+ fi
78
+
79
+ - name: Drift gate — rules whose governed code changed after last verification
80
+ # The exit code is the verdict: 0 checked and clean, 1 drift found, 2 did not run.
81
+ # "Did not run" is the case this step used to report as a pass: with no store in CI
82
+ # (okl init gitignores it) the gate checked nothing and printed OK. It now says so,
83
+ # as a warning annotation on the PR rather than a failure, because an unseeded
84
+ # store is a setup gap, not a defect in the change under review -- and failing on it
85
+ # would block every pull request from the moment okl is installed. To make this
86
+ # step a real check, give CI a store: commit `okl export --drift`, or set the
87
+ # OKL_SERVICE_URL secret. A committed snapshot IS a store for this purpose, so once
88
+ # one exists, exit 2 fails like any other configured store.
89
+ run: |
90
+ snapshot=""
91
+ if [ -z "${OKL_SERVICE_URL:-}${OKL_DATABASE_URL:-}" ] \
92
+ && git ls-files --error-unmatch okl-drift.json >/dev/null 2>&1; then
93
+ snapshot="okl-drift.json"
94
+ fi
95
+ set +e
96
+ if [ -n "$snapshot" ]; then
97
+ okl drift --gate --snapshot "$snapshot"
98
+ else
99
+ okl drift --gate
100
+ fi
101
+ rc=$?
102
+ set -e
103
+ # Exit 2 means "did not run", which has two causes that deserve opposite outcomes.
104
+ # If this job was never given a store, it is the setup gap above: warn. If a store
105
+ # WAS configured and okl still did not run -- unreachable, refused, or holding no
106
+ # rule for this repo -- something broke, and passing would hide an outage behind
107
+ # a green check. The job knows which it is; okl's exit code alone cannot say.
108
+ if [ "$rc" -eq 2 ] && [ -z "${OKL_SERVICE_URL:-}${OKL_DATABASE_URL:-}${snapshot}" ]; then
109
+ echo "::warning title=Drift not checked::No knowledge store is available to this job, so no rule was checked for drift. This step passing is NOT an all-clear. Commit \`okl export --drift\` to give CI one."
110
+ elif [ "$rc" -ne 0 ]; then
111
+ exit "$rc"
112
+ fi
113
+
114
+ - name: Method gates (when the scaffold kit is present)
115
+ run: |
116
+ if [ -x gates/run-gates.sh ]; then
117
+ bash gates/run-gates.sh
118
+ else
119
+ echo "no gates/run-gates.sh — method-kit gates not installed (okl scaffold adds them)"
120
+ fi
@@ -0,0 +1,99 @@
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). The project was
10
+ # renamed from org-knowledge-layer in 0.6.0: the NEW name needs its own pending publisher
11
+ # below, and the old project keeps the one it already has (it publishes the redirect):
12
+ # pypi.org → the observed-knowledge-ledger project → Publishing → Add a pending publisher
13
+ # Owner: emeraldleaf
14
+ # Repository: okl
15
+ # Workflow name: publish.yml
16
+ # Environment name: pypi
17
+ # Then create the `pypi` environment under repo Settings → Environments. Adding a
18
+ # required reviewer there makes every release a deliberate, approved act.
19
+ name: publish
20
+
21
+ on:
22
+ push:
23
+ tags: ["v*"]
24
+
25
+ permissions:
26
+ contents: read
27
+
28
+ jobs:
29
+ build:
30
+ runs-on: ubuntu-latest
31
+ steps:
32
+ - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
33
+ with:
34
+ persist-credentials: false
35
+
36
+ - uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
37
+ with:
38
+ python-version: "3.12"
39
+
40
+ # The tag is the release's claim about what version this is; pyproject.toml is the
41
+ # version that actually gets uploaded. When they disagree, PyPI takes pyproject's
42
+ # answer and the tag becomes a lie that is permanent — you cannot reuse a version
43
+ # number on PyPI, so the mistake is not fixable, only worked around.
44
+ - name: Tag must match the version in pyproject.toml
45
+ run: |
46
+ tag="${GITHUB_REF_NAME#v}"
47
+ pkg="$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('pyproject.toml').read_text())['project']['version'])")"
48
+ echo "tag=$tag pyproject=$pkg"
49
+ if [ "$tag" != "$pkg" ]; then
50
+ echo "::error::tag v$tag does not match pyproject version $pkg"
51
+ exit 1
52
+ fi
53
+
54
+ - name: Build
55
+ run: |
56
+ python -m pip install --upgrade build twine
57
+ python -m build
58
+ # The old distribution name's final release (packaging/org-knowledge-layer):
59
+ # built only on the tag that matches its version, so it is published exactly
60
+ # once. Its PyPI project already trusts this workflow.
61
+ shim="$(python -c "import tomllib,pathlib; print(tomllib.loads(pathlib.Path('packaging/org-knowledge-layer/pyproject.toml').read_text())['project']['version'])")"
62
+ if [ "${GITHUB_REF_NAME#v}" = "$shim" ]; then
63
+ python -m build packaging/org-knowledge-layer --outdir dist/
64
+ fi
65
+ # `twine check` catches a malformed long_description, which PyPI rejects only
66
+ # after the upload has otherwise succeeded.
67
+ python -m twine check dist/*
68
+
69
+ - uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
70
+ with:
71
+ name: dist
72
+ path: dist/
73
+
74
+ publish:
75
+ needs: build
76
+ runs-on: ubuntu-latest
77
+ # A named environment is what the PyPI trusted publisher is bound to, and it is where
78
+ # a required-reviewer rule can gate the release.
79
+ environment:
80
+ name: pypi
81
+ url: https://pypi.org/project/observed-knowledge-ledger/
82
+ permissions:
83
+ id-token: write # mint the OIDC token PyPI verifies
84
+ attestations: write # sign the artifacts
85
+ contents: read
86
+ steps:
87
+ - uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
88
+ with:
89
+ name: dist
90
+ path: dist/
91
+
92
+ # Provenance: a signed statement that these exact files were built by this workflow
93
+ # from this commit. It is what lets someone verify the wheel on PyPI came from the
94
+ # source they are reading, rather than trusting that it did.
95
+ - uses: actions/attest-build-provenance@4d101475d8b20a2381f78447822ac1eab6504dd8 # v4.2.2
96
+ with:
97
+ subject-path: "dist/*"
98
+
99
+ - uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # release/v1