Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint by KylinMountain · Pull Request #159 · VectifyAI/OpenKB · GitHub
Skip to content

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint - #159

Merged
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding
Jul 2, 2026
Merged

Agent-first dev harness: AGENTS.md map, CI gate, golden principles, file-size lint#159
KylinMountain merged 11 commits into
mainfrom
harness/agent-dev-scaffolding

Conversation

@KylinMountain

Copy link
Copy Markdown
Collaborator

What

Development scaffolding that makes this repo reliable for agent-driven maintenance (inspired by OpenAI's Harness engineering post — humans steer, agents execute):

  • AGENTS.md (~45-line map, single source of truth) + CLAUDE.md (@AGENTS.md import) so both Codex- and Claude-family agents load the same repo map: module responsibilities, dev commands, hard invariants.
  • docs/golden-principles.md — mechanical rules that keep the codebase legible for future agent runs (boundary validation, shared utilities, atomic wiki writes, module size limit).
  • tests/test_file_size.py — hard-fail 800-line module gate with a grandfathered allowlist (cli.py, agent/compiler.py, agent/chat.py); failure messages carry remediation so the fix instructions land directly in agent context.
  • CI workflowruff check / ruff format --check / mypy openkb / pytest on push+PR, installed via uv sync --locked --extra dev so CI runs the exact uv.lock resolution (transitive deps included) instead of letting a bare pip install float them. Least-privilege token (contents: read, persist-credentials: false), concurrency cancellation, SHA-pinned actions.
  • pyproject.toml — pinned ruff/mypy/types-PyYAML in the dev extra; ruff E501 and mypy suppressions scoped per-file/per-module (not global), so every other file gets full enforcement; docs/ restructured so public dev docs are tracked while docs/internal/ stays local (default-closed allowlist in docs/.gitignore).
  • Repo-wide ruff format pass (mechanical; verified content-preserving).

Why

Every agent session was re-deriving the repo layout from scratch, code merges had zero mechanical gate, and agents replicate existing patterns — including bad ones — unless taste is encoded and enforced. Constraints are encoded once, then apply to every future change.

Notes for review

  • The mypy config keeps a global follow_imports = "skip": numpy's bundled stubs (reached transitively via pydantic) use PEP 695 syntax fatal to python_version = "3.10" runs, and a scoped override was experimentally confirmed not to prevent the parse. Per-module disable_error_code overrides cover the 5 modules with pre-existing untyped-LLM-JSON debt; ratchet plan documented inline.
  • n >= limit in the file-size gate matches the documented "under 800 lines" contract; line counting uses splitlines() so unusual line endings can't under-count.
  • New convention introduced by the locked CI install: dependency changes in pyproject.toml must be accompanied by uv lock (CI fails on a stale lockfile by design).
  • The checkout action pin comment said v4.2.2 but the SHA resolves to v4.1.7; comments corrected in both workflows (the pin itself is unchanged).

Gate status on this branch: ruff ✓ · format ✓ · mypy (40 files) ✓ · pytest 917 passed ✓ · uv lock --check

https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg

Flip the gitignore default so public dev docs (AGENTS.md map, golden
principles) can live under docs/, while design/spec history stays local
under docs/internal/.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Single source of truth is AGENTS.md; CLAUDE.md imports it so both Codex
and Claude Code load the same map.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
Add ruff==0.9.7 and mypy==1.15.0 to the dev extra, configure both in
pyproject.toml, and add .github/workflows/ci.yml (pinned action SHAs,
Python 3.12, matching publish.yml style) running ruff check, ruff
format --check, mypy, and pytest on push to main and on PRs.
Config choices to reach a green gate on a codebase with no prior
lint/type config:
- ruff: select = ["E", "F", "I"], ignore E501 (ruff format already
wraps code; remaining long lines are unsplittable string literals —
docstrings/help text/prompt templates). openkb/cli.py gets a
per-file-ignore for E402/I001 because it deliberately interleaves
imports with side-effecting setup code (warning filters, tracing
disable, an env var default) that must run in a specific order.
- mypy: lenient starting config (ignore_missing_imports,
check_untyped_defs=false) plus follow_imports="skip" (a transitive
pydantic->numpy stub uses PEP 695 `type` syntax that crashes mypy
under python_version="3.10") and disable_error_code for
union-attr/var-annotated/arg-type/return-value/operator/type-var/
dict-item, concentrated almost entirely in agent/compiler.py's
loosely-typed LLM-JSON handling. Ratcheting these back on is future
tech debt, not this task.
Also includes real (non-cosmetic) fixes found along the way: removed
unused imports/f-strings/local variables (F401/F541/F841), moved two
accidentally-misplaced imports in agent/linter.py and agent/query.py,
and moved two test-file imports that had drifted mid-file back to the
top (I001) in tests/test_compiler.py and tests/test_generator.py.
Ran `ruff format .` across the repo to establish a formatting
baseline; the bulk of this diff (94 files) is that reformat, verified
cosmetic-only (blank-line-after-docstring, re-wrapping under the new
100-char line-length) via an AST diff against openkb/cli.py, the
largest changed file.
Verified locally (not pushed — CI is not triggered by this commit):
ruff check . && ruff format --check . && mypy openkb && pytest all
exit 0; full suite (914 tests) passes.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
chat.py uses the tools indirectly via query.build_chat_agent, not directly.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
…unt)
- Resolve the package via openkb.__file__ instead of test-file path math,
and assert the scan is non-empty, so the gate fails loudly rather than
going silently vacuous if files move.
- Pass the relativize root explicitly, removing the try/except fallback
whose pkg-relative keys could never match the repo-relative allowlist.
- Count lines via splitlines() so bare-CR files cannot under-count.
- Flag files AT the limit (n >= limit) to match the documented
"under 800 lines" contract.
- Reword the failure message so external contributors get an instruction
they can actually fulfill (tech-debt.md is maintainer-local).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- E501 moves from a global ignore to per-file-ignores for the 5 source
files + 3 test files whose violations are long string literals; every
other file now gets the 100-column limit enforced.
- The global 8-code mypy disable_error_code becomes per-module overrides
(compiler, cli, lint, indexer, skill.workspace), restoring full
checking of those codes for the other 35 modules.
- Add types-PyYAML (exact pin) to the dev extra, eliminating all six
yaml [import-untyped] suppressions outright.
- Drop check_untyped_defs/warn_unused_ignores lines that restated mypy
defaults and read as active leniency choices.
- follow_imports = "skip" stays global: experimentally confirmed that a
scoped override cannot prevent the fatal numpy-stub parse.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- Replace bare `pip install -e .[dev]` with `uv sync --locked --extra dev`
so CI installs the exact locked resolution (transitive deps included)
instead of floating them on every run, honoring the exact-pin
supply-chain policy. Lock now includes the dev toolchain.
- setup-uv (pinned by SHA, uv 0.10.2) with enable-cache replaces the
uncached cold pip install.
- Add `permissions: contents: read` and `persist-credentials: false` so
dependency build code never sees a writable token.
- Add a concurrency group cancelling superseded runs on the same ref.
- Fix the checkout pin comment in both workflows: SHA 692973e3 is
v4.1.7, not v4.2.2.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
- docs/.gitignore: default-closed allowlist so a design doc accidentally
written outside docs/internal/ cannot be swept into a commit, matching
the guarantee the old blanket docs/ ignore provided.
- AGENTS.md: `uv sync` alone does not install the dev extra (pytest,
ruff, mypy); document `uv sync --extra dev`.
- golden-principles: annotate the tech-debt.md reference as
maintainer-local so external readers don't chase a gitignored path.
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
The old blanket docs/ ignore matched examples/docs/ at any depth; the
anchored docs/internal/ rule does not. Already-tracked PDFs on main are
unaffected (ignore rules only apply to untracked files).
Claude-Session: https://claude.ai/code/session_01UtbmJxjtw6FtP8fUXUKVtg
@KylinMountain
KylinMountain merged commit 4d9319d into mainJul 2, 2026
2 checks passed
@KylinMountain
KylinMountain deleted the harness/agent-dev-scaffolding branch July 2, 2026 06:23
gwokhou added a commit to gwokhou/OpenKB that referenced this pull request Jul 2, 2026
KylinMountain pushed a commit that referenced this pull request Jul 3, 2026
* Add serial add mutation coordinator
* Route serial add paths through coordinator
* fix(cloud-import): resolve final doc name under ingest lock
* test(add): cover coordinator conflict edge cases
* fix(indexer): roll back the PageIndex blob when indexing fails after col.add
Since the add mutation stopped eagerly snapshotting .openkb/files (it registers the new blob via track_new only on success), a blob written by col.add() leaks if index_long_document raises afterward: pageindex.db is rolled back by the snapshot but the blob file is not, and nothing reclaims it. Delete the doc on the failure path so the indexer owns cleanup of a half-applied add, restoring the .openkb/files rollback-surface guarantee.
* fix(cloud-import): surface real error instead of mislabeling snapshot prep
run_add_mutation handles snapshot/body failures itself and returns False, so the broad except only ever catches pre-mutation errors (name resolution, registry read, plan construction). Echo the real exception instead of the old 'Failed to prepare mutation snapshot' label, which hid the cause at DEBUG level.
* style: wrap long lines for ruff E501 (CI gate from #159)
* style: apply ruff format to add_coordinator.py and test_add_command.py
* refactor(cli): drop duplicate _cleanup_staging, reuse coordinator helper
cli._cleanup_staging duplicated add_coordinator._cleanup_staging_dirs (both guard None then shutil.rmtree with ignore_errors). Drop the cli copy and route its two pre-coordinator call sites through the coordinator helper.
* refactor(add_coordinator): drop unused rollback_error_message field
AddMutationPlan.rollback_error_message had zero callers — every construction site (cli.py x2, tests x6) used the default. Remove the field and inline the literal at its single read site.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KylinMountain