refactor(agents): migrate .agent/ planning tree to .agents/
Standardize on the .agents/ directory (shared with project skills): phases/, user_stories/, reports/, screenshots/, validate.sh, and phase-sessions/ + pipeline.log all move to .agents/ (git mv preserves history; runtime artifacts move alongside). Updates every reference in AGENTS.md, README.md, .gitignore, app docstrings, and test story headers. Historical KB content in data/ and the runtime pipeline.log transcript are left untouched.
This commit is contained in:
+11
@@ -0,0 +1,11 @@
|
||||
All green and scoped. Those other modified files are pre-existing uncommitted work (owner 2026-08-28 rework from earlier phases), untouched by me.
|
||||
|
||||
**Task 01 complete — settings + safe archive unpack utility**
|
||||
|
||||
- `app/config.py`: `upload_dir` (`~/bor-sources/uploads`, `BOR_UPLOAD_DIR`) + `upload_max_mb` (512, `BOR_UPLOAD_MAX_MB`) with fail-loud `<= 0` validator; `.env.example` documents both beside `BOR_SOURCES_DIR`.
|
||||
- `app/rag/archive_upload.py` (new, no FastAPI/DB): `ArchiveUploadError`, longest-first `ARCHIVE_SUFFIXES`, `archive_source_name` (case-insensitive suffix strip, stem case preserved, rejects separators/control chars/`.`/`..`/empty + bare-suffix stems like `tar.gz`), `unpack_archive` (content-sniffed zip/tar; rejects absolute/`..`/escape-resolving members, zip symlink + non-regular entries, tar device/FIFO, escaping symlink/hardlink targets; byte-capped streaming; full cleanup of partial target on any failure), `swap_in` (rename to `.old-<hex>` sibling → rename in → delete; best-effort restore + re-raise on failure).
|
||||
- `tests/unit/test_archive_upload.py` (new, 61 tests): full derivation matrix, byte-identical valid zip/tar.gz/plain-tar extracts, every guard raising with no partial target, cap exact/accumulate/corrupt cases, swap fresh/replace/restore-on-failure/double-failure, settings defaults/overrides/validator.
|
||||
- Tests: `uv run pytest tests/unit/test_archive_upload.py -v` → 61 passed; `uv run pytest tests/unit tests/integration` → 855 passed; `uv run pytest --cov=app` → new module 100%, TOTAL 99% (>90%).
|
||||
- Lint: `uv run ruff check .` clean; `uv run pyright` → 0 errors.
|
||||
- Notable: CPython `writestr`/Windows zips carry no file-type mode bits — only modes with explicit non-regular type bits are rejected (name-decided otherwise); bare `tar.gz` (no dot) treated as empty stem per task matrix. No existing files touched beyond config + `.env.example`.
|
||||
- Next pending task: `.agents/phases/todo/49_archive_upload_sources/02_upload_api.md`.
|
||||
+62
@@ -0,0 +1,62 @@
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 25%]
|
||||
........................................................................ [ 33%]
|
||||
........................................................................ [ 42%]
|
||||
........................................................................ [ 50%]
|
||||
........................................................................ [ 58%]
|
||||
........................................................................ [ 67%]
|
||||
........................................................................ [ 75%]
|
||||
........................................................................ [ 84%]
|
||||
........................................................................ [ 92%]
|
||||
............................................................... [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
-----------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 22 0 100%
|
||||
app/api/chat.py 122 0 100%
|
||||
app/api/config.py 7 0 100%
|
||||
app/api/docs.py 23 0 100%
|
||||
app/api/git_sources.py 66 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 8 0 100%
|
||||
app/api/sync.py 81 0 100%
|
||||
app/config.py 79 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 20 0 100%
|
||||
app/core/caching.py 92 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 44 0 100%
|
||||
app/models.py 60 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 72 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 173 3 98%
|
||||
app/rag/llm.py 186 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 65 0 100%
|
||||
app/rag/retriever.py 94 3 97%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 99 0 100%
|
||||
-----------------------------------------------
|
||||
TOTAL 1899 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
All green. Task 02 complete.
|
||||
|
||||
**Implemented**
|
||||
- `pyproject.toml`: added `python-multipart>=0.0.9,<0.1` (FastAPI multipart parser); `uv lock` + `uv sync` clean (0.0.32)
|
||||
- `app/schemas.py`: new `UploadOut` (`source` + `files/added/updated/unchanged/pruned/errors/chunks` + `overview`)
|
||||
- `app/api/git_sources.py`: `POST /api/git-sources/upload` — format/name gate (422 naming accepted set), module-level in-flight flag (409, phase-32 spirit; loop-independent), 1 MiB-chunk stream with `upload_max_mb` cap (413 + temp cleanup), safe unpack to temp sibling (422, zero-entry = "the archive contains no files"), atomic `swap_in`, row upsert by `path` (existing row/`added_at` preserved; `_commit_new` 409 backstop), fail-fast `check_models` (503, `_sanitize_error` from `app.api.sync`), `import_sources(prune=True)` + change-gated `regenerate_overview`, per-upload INFO log line, 200 with sync-style counts. Existing GET/POST/DELETE handlers byte-identical (diff = docstring/imports/new route only)
|
||||
- `tests/integration/test_git_sources_upload.py`: 21 tests — anonymous 403; 422 formats/unsafe names; 409 (real in-flight via thread + flag seam); 413 + exact-cap boundary + no stray temps; zip-slip/tar-symlink/corrupt/swap-failure all 422 with previous folder+row+KB asserted intact; zero-entry 422; happy paths (`.tar.gz`/`.zip`/`.tar`/`.tgz`) incl. log line + `kind=local` row; re-upload in-place replace (one row, `added_at` unchanged, 2 pruned); non-A9 replace (valid, no overview); 503 sanitized with folder/row committed
|
||||
|
||||
**Results**
|
||||
- `uv run pytest tests/integration/test_git_sources_upload.py -v` → 21 passed
|
||||
- `uv run pytest tests/unit tests/integration` → all passed (exit 0; 76 passed across upload + `test_git_sources_api` + `test_sync_api` + `test_import_docs_git`)
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` → exit 0, **TOTAL 99%** (>90%); `app/api/git_sources.py` 100%
|
||||
- `uv run ruff check .` → clean; `uv run pyright` → 0 errors
|
||||
|
||||
**Decisions/deviations**
|
||||
- Plain bool flag instead of `asyncio.Lock` (task-allowed; TestClient runs separate loops per request)
|
||||
- Tests post multipart via httpx `files=` — with this httpx (0.28.1), a tuple in `data=` is sent urlencoded and never parses as `UploadFile`
|
||||
- Control-char filenames are percent-encoded by the multipart transport before reaching the app (verified live); that branch stays unit-covered in `test_archive_upload.py`
|
||||
- Bare-suffix names (`tar.gz`) pass the format gate and get task-01's "no usable source name" 422 (more precise than the formats message)
|
||||
|
||||
**Next pending task:** `.agents/phases/todo/49_archive_upload_sources/03_admin_page_upload.md`
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 32%]
|
||||
........................................................................ [ 41%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 73%]
|
||||
........................................................................ [ 82%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 98%]
|
||||
............ [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
-----------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 22 0 100%
|
||||
app/api/chat.py 122 0 100%
|
||||
app/api/config.py 7 0 100%
|
||||
app/api/docs.py 23 0 100%
|
||||
app/api/git_sources.py 142 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 8 0 100%
|
||||
app/api/sync.py 81 0 100%
|
||||
app/config.py 79 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 20 0 100%
|
||||
app/core/caching.py 92 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 44 0 100%
|
||||
app/models.py 60 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 72 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 173 3 98%
|
||||
app/rag/llm.py 186 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 65 0 100%
|
||||
app/rag/retriever.py 94 3 97%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 109 0 100%
|
||||
-----------------------------------------------
|
||||
TOTAL 1985 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
All green. Final report:
|
||||
|
||||
## Task 03 complete — Sources page: local form out, upload form in (+ phase-38 E2E rewrite)
|
||||
|
||||
**Implemented**
|
||||
- `git-sources.html`: `#local-source-form` removed; `#archive-upload-form` added (file input w/ accept set, never-stale button, `role=alert` error, `role=status` result line); hint + table caption updated; `<title>`/`<h1>` untouched.
|
||||
- `styles.css`: upload form card/button/file-input/result-line rules (existing palette, ≥44px, global 3px `:focus-visible`), mobile stack block updated.
|
||||
- `git-sources.js`: local-form refs + second `wireAddForm` removed; upload wiring (§7.4 lifecycle, FormData without manual Content-Type, sync-style counts via `fmtUploadResult` per `sources.js` convention, announce, `loadSources()`, file kept on failure).
|
||||
- `test_local_directory_sources.py` rewritten fully API-driven (phase-49 note in docstring); acceptance (Local badge, add/remove, Sync import/prune, gate/403s incl. upload route) preserved.
|
||||
- **Defect fixed (task 02 work):** upload handler held the request `db` session open across the scan → concurrent E2E `TRUNCATE` deadlocked (invisible 3-connection cycle, app + pytest hung forever; reproduced via py-spy/pg_locks). Now `db.close()` after the row upsert; regression test added (verified it fails without the fix: `LockNotAvailable`).
|
||||
|
||||
**Results**
|
||||
- `uv run pytest --cov=app --cov-report=term-missing` → 877 passed, TOTAL 99% (>90%).
|
||||
- `uv run ruff check . && uv run pyright` → clean.
|
||||
- `uv run pytest tests/e2e/test_local_directory_sources.py -v --no-cov` → 3 passed in isolation; regressions green in isolation: `test_git_sources_admin` (6), `test_nav_rename_sources` (4), `test_smoke` (3), `test_sync_button` (3), `test_import_documents` (3); no orphan processes.
|
||||
- Upload form verified live end-to-end via a throwaway E2E (deleted after): "3 added" → re-upload "1 added · 1 updated · 2 pruned", one row, 422 inline w/ file kept.
|
||||
|
||||
**Deviation:** task snippet `new FormData([["file", file]])` isn't a valid browser API — used `new FormData(uploadFormEl)` (same multipart contract, field `file`).
|
||||
|
||||
**Next pending task:** `.agents/phases/todo/49_archive_upload_sources/04_story_e2e_docs_commit.md`
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 32%]
|
||||
........................................................................ [ 41%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 73%]
|
||||
........................................................................ [ 82%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 98%]
|
||||
............. [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
-----------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 22 0 100%
|
||||
app/api/chat.py 122 0 100%
|
||||
app/api/config.py 7 0 100%
|
||||
app/api/docs.py 23 0 100%
|
||||
app/api/git_sources.py 143 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 8 0 100%
|
||||
app/api/sync.py 81 0 100%
|
||||
app/config.py 79 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 20 0 100%
|
||||
app/core/caching.py 92 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 44 0 100%
|
||||
app/models.py 60 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 72 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 173 3 98%
|
||||
app/rag/llm.py 186 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 65 0 100%
|
||||
app/rag/retriever.py 94 3 97%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 109 0 100%
|
||||
-----------------------------------------------
|
||||
TOTAL 1986 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
All gates green. Phase 49 is complete.
|
||||
|
||||
**Report**
|
||||
|
||||
- **Story E2E**: created `tests/e2e/test_archive_upload_sources.py` (5 tests: form swap, upload→scan→list with deterministic "Uploading…" in-flight state via request-hold, same-filename in-place re-upload with prune + no duplicate row + v2-only folder, 422 inline error + recovery, anonymous gate + 403); real `tarfile` archives `e2e-upload.tar.gz` v1/v2 with ALPHA/BETA/GAMMA sentinels; `BOR_UPLOAD_DIR` pointed at a host-inspectable scratch dir.
|
||||
- **README**: new *Archive upload sources* section (formats, naming rule, in-place replace + prune, `BOR_UPLOAD_DIR`/`BOR_UPLOAD_MAX_MB`, synchronous scan semantics), local-directory form removal + API-only note, UI bullet, sync note, two config-table rows (`.env.example` already documented both settings in task 01).
|
||||
- **Tests/lint/coverage**: `uv run pytest tests/e2e/test_archive_upload_sources.py -v --no-cov` → 5 passed (isolated, DB up); regression suites isolated → git_sources_admin 6, local_directory_sources 3, sync_button 3, import_documents 3, nav_rename_sources 4, smoke 3, shared_header 6 — all passed; `uv run pytest` → 877 passed; `--cov=app` → **99%** (>90%); `ruff check .` + `pyright` clean.
|
||||
- **Commit**: `03d2625 feat(sources): upload tarball/zipfile archives as sources — unpack, scan, and replace in place` (`--no-gpg-sign`) — exactly the 21 phase files incl. story file + force-added moved phase dir; `todo/` no longer lists 49.
|
||||
- **Notes**: one pyright fix in my new test (`text_content()` None guard); a small pre-existing in-flight hunk (`.sign-in-mobile` CSS) rode in via `styles.css`, disclosed in the commit note per the phase-48 precedent; pre-existing uncommitted in-flight rework (sources.js sync-button etc.) and old `todo/40–47` tracked deletions were left untouched — out of this phase's file list.
|
||||
- **Next pending task**: none — `todo/` is empty; phase 49 was the last phase.
|
||||
+63
@@ -0,0 +1,63 @@
|
||||
........................................................................ [ 8%]
|
||||
........................................................................ [ 16%]
|
||||
........................................................................ [ 24%]
|
||||
........................................................................ [ 32%]
|
||||
........................................................................ [ 41%]
|
||||
........................................................................ [ 49%]
|
||||
........................................................................ [ 57%]
|
||||
........................................................................ [ 65%]
|
||||
........................................................................ [ 73%]
|
||||
........................................................................ [ 82%]
|
||||
........................................................................ [ 90%]
|
||||
........................................................................ [ 98%]
|
||||
............. [100%]
|
||||
=============================== warnings summary ===============================
|
||||
.venv/lib/python3.13/site-packages/fastapi/testclient.py:1
|
||||
/var/home/ducoterra/Projects/Personal/brain_of_reese/.venv/lib/python3.13/site-packages/fastapi/testclient.py:1: StarletteDeprecationWarning: Using `httpx` with `starlette.testclient` is deprecated; install `httpx2` instead.
|
||||
from starlette.testclient import TestClient as TestClient # noqa
|
||||
|
||||
-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
|
||||
================================ tests coverage ================================
|
||||
_______________ coverage: platform linux, python 3.13.13-final-0 _______________
|
||||
|
||||
Name Stmts Miss Cover
|
||||
-----------------------------------------------
|
||||
app/__init__.py 1 0 100%
|
||||
app/api/__init__.py 0 0 100%
|
||||
app/api/auth.py 22 0 100%
|
||||
app/api/chat.py 122 0 100%
|
||||
app/api/config.py 7 0 100%
|
||||
app/api/docs.py 23 0 100%
|
||||
app/api/git_sources.py 143 0 100%
|
||||
app/api/health.py 10 0 100%
|
||||
app/api/steering.py 42 0 100%
|
||||
app/api/suggestions.py 8 0 100%
|
||||
app/api/sync.py 81 0 100%
|
||||
app/config.py 79 0 100%
|
||||
app/core/__init__.py 0 0 100%
|
||||
app/core/auth.py 20 0 100%
|
||||
app/core/caching.py 92 0 100%
|
||||
app/core/debugging.py 29 2 93%
|
||||
app/core/logging.py 13 0 100%
|
||||
app/db.py 21 0 100%
|
||||
app/main.py 44 0 100%
|
||||
app/models.py 60 0 100%
|
||||
app/rag/__init__.py 0 0 100%
|
||||
app/rag/agent.py 72 0 100%
|
||||
app/rag/archive_upload.py 128 0 100%
|
||||
app/rag/chunker.py 206 4 98%
|
||||
app/rag/git_sources.py 14 0 100%
|
||||
app/rag/importer.py 173 3 98%
|
||||
app/rag/llm.py 186 0 100%
|
||||
app/rag/overview.py 71 0 100%
|
||||
app/rag/prompts.py 65 0 100%
|
||||
app/rag/retriever.py 94 3 97%
|
||||
app/rag/suggestions.py 27 0 100%
|
||||
app/rag/summarizer.py 24 0 100%
|
||||
app/schemas.py 109 0 100%
|
||||
-----------------------------------------------
|
||||
TOTAL 1986 12 99%
|
||||
coverage gate: app/ 99% (>90%) OK
|
||||
All checks passed!
|
||||
0 errors, 0 warnings, 0 informations
|
||||
validation OK
|
||||
Reference in New Issue
Block a user