feat(corpus): convenção _* em todo o engine e reagrupamento por livro - #71
Conversation
kb.fsutil.iter_articles centraliza a semântica de artigo vivo (exclui _*, .* e symlink em qualquer nível) e é adotado por lint, heal, archive (órfãos e idade), update_index e stats — os cinco pontos que aplicavam a convenção cada um do seu jeito, ou não aplicavam. O furo mais grave era atual, não futuro: o heal sorteava os 1.027 summaries de _summaries/ e podia reescrevê-los ou deletar um como stub. Pós-reagrupamento, find_orphans marcaria _chapters/ inteiro como órfão e um kb archive inocente desfaria a reversibilidade do ADR-0018. De quebra: o teste de corrida do schema ganhou timeout no Barrier — sem ele, worker que falhasse antes do wait pendurava a suíte inteira em vez de falhar (aconteceu 3x hoje em runs de fundo). 1033 passed, ruff limpo, appeasement 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3FL25TLKVHDdn99GjtkxW
…olamento O heal era a última remoção destrutiva do engine: stub detectado sofria unlink com backup achatado dentro da própria wiki. Agora vai para archive/ com hierarquia e backup versionado (semântica move_to_archive) e a entrada do manifest vira archived — o guard de recompile não aponta para path movido. V7 mínimo do ADR-0018; a unificação do formato do .heal_backup legado fica registrada como dívida. O RED desta task expôs a terceira ocorrência do dia da mesma classe de incidente: teste sem isolamento moveu stubs de fixture para o archive DO VAULT REAL (e regenerou o _index.md real via update_index). Limpeza feita com aprovação do dono e fix estrutural no conftest: o piso autouse agora cobre ARCHIVE_DIR e kb.compile.WIKI_DIR, como STATE_DIR desde o incidente de 2026-07-29. O teste de corrida do schema perdeu o Barrier: exigir 8 threads simultâneas era refém da carga da máquina (falso vermelho sob load); a corrida natural do pool já reproduzia o bug original. 1034 passed (3 execuções), ruff limpo, appeasement 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3FL25TLKVHDdn99GjtkxW
…29 C3) kb regroup scan monta o plano wiki/<...> → wiki/_chapters/<livro>/ a partir do manifest (o critério do ADR-0018 é proveniência, não cosseno): grupos por book com slug sanitizado, summaries espelhados para _summaries/_chapters/, e os unresolved listados como pendência humana — artigo sem proveniência NUNCA é movido por inferência. kb regroup apply --book move UM livro por vez (commit por livro, rollback granular), reusando move_to_archive com raiz de contenção em _chapters/ — backup versionado, nunca unlink. Manifest repontado via update_article_path; _index.md e embeddings atualizados. O move real (C4) continua atrás do gate explícito do dono. 1042 passed, ruff limpo, appeasement 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3FL25TLKVHDdn99GjtkxW
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3FL25TLKVHDdn99GjtkxW
🤖 CodeAnt AI — Review Status
|
📝 WalkthroughWalkthroughThe change centralizes live article discovery, archives healing stubs, and adds manifest-based chapter regrouping. It introduces ChangesChapter regrouping
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant RegroupCLI
participant RegroupEngine
participant WikiManifest
participant WikiStorage
participant Indexes
RegroupCLI->>WikiManifest: Load manifest entries
RegroupCLI->>RegroupEngine: Build regroup plan
RegroupEngine->>WikiStorage: Find live articles and summaries
RegroupCLI->>RegroupEngine: Apply selected book
RegroupEngine->>WikiStorage: Move articles and summaries
RegroupEngine->>WikiManifest: Update moved paths
RegroupEngine->>Indexes: Refresh compiled and embedding indexes
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
| # Sem Barrier de propósito: exigir 8 threads simultâneas tornou o teste | ||
| # refém da carga da máquina (e um Barrier sem timeout chegou a pendurar a | ||
| # suíte). A corrida natural do pool reproduziu o bug original em 2 de 3 | ||
| # rodadas — suficiente para o RED, e o fix o torna determinístico. |
There was a problem hiding this comment.
Suggestion: The test is not deterministic despite the comment's claim: submitting eight tasks without a barrier does not guarantee that multiple connections observe the legacy schema before any migration commits, so a broken concurrent migration can pass depending on thread scheduling. Restore a bounded synchronization mechanism or otherwise coordinate the workers to force the intended race without allowing the suite to hang. [possible bug]
Severity Level: Major ⚠️
- ⚠️ CI may miss regressions in concurrent schema migration.
- ⚠️ Legacy study databases may fail only under real concurrency.
- ⚠️ The test's claim of deterministic coverage is not guaranteed.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** tests/unit/test_study_db.py
**Line:** 37:40
**Comment:**
*Possible Bug: The test is not deterministic despite the comment's claim: submitting eight tasks without a barrier does not guarantee that multiple connections observe the legacy schema before any migration commits, so a broken concurrent migration can pass depending on thread scheduling. Restore a bounded synchronization mechanism or otherwise coordinate the workers to force the intended race without allowing the suite to hang.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| resultado = move_to_archive([{"source": path, "dest": dest}], ARCHIVE_DIR) | ||
| if resultado and resultado[0]["action"] == "moved": | ||
| mark_archived(path) |
There was a problem hiding this comment.
Suggestion: O arquivo é movido antes de mark_archived(path) atualizar o manifest. Se essa gravação falhar, a exceção interrompe o heal depois que o artigo já foi removido da wiki, deixando o manifest apontando para o caminho antigo e inconsistente com o filesystem. Faça a atualização do manifest de forma transacional ou restaure o arquivo quando ela falhar. [incomplete implementation]
Severity Level: Major ⚠️
- ❌ Manifest pode continuar apontando para artigo movido.
- ⚠️ Falhas posteriores podem recriar ou perder o stub arquivado.
- ⚠️ O comando heal termina abruptamente após alteração parcial.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** kb/heal.py
**Line:** 105:107
**Comment:**
*Incomplete Implementation: O arquivo é movido antes de `mark_archived(path)` atualizar o manifest. Se essa gravação falhar, a exceção interrompe o heal depois que o artigo já foi removido da wiki, deixando o manifest apontando para o caminho antigo e inconsistente com o filesystem. Faça a atualização do manifest de forma transacional ou restaure o arquivo quando ela falhar.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fix| resultado = move_to_archive([{"source": path, "dest": dest}], ARCHIVE_DIR) | ||
| if resultado and resultado[0]["action"] == "moved": | ||
| mark_archived(path) | ||
| log.append({"file": path.name, "action": "archived_stub"}) |
There was a problem hiding this comment.
Suggestion: O stub arquivado não é adicionado à lista changed, que é a única lista usada no final para criar o commit. Assim, heal(..., no_commit=False) move o arquivo e altera o manifest localmente, mas não versiona nenhuma dessas mudanças; o comportamento documentado de --commit é quebrado para stubs. [api mismatch]
Severity Level: Major ⚠️
- ❌ `kb heal --commit` não versiona stubs arquivados.
- ⚠️ Movimentação e manifest ficam apenas como alterações locais.
- ⚠️ O histórico do corpus diverge do estado efetivo da wiki.(Use Cmd/Ctrl + Click for best experience)
Prompt for AI Agent 🤖
This is a comment left during a code review.
**Path:** kb/heal.py
**Line:** 108:108
**Comment:**
*Api Mismatch: O stub arquivado não é adicionado à lista `changed`, que é a única lista usada no final para criar o commit. Assim, `heal(..., no_commit=False)` move o arquivo e altera o manifest localmente, mas não versiona nenhuma dessas mudanças; o comportamento documentado de `--commit` é quebrado para stubs.
Validate the correctness of the flagged issue. If correct, How can I resolve this? If you propose a fix, implement it and please make it concise.
Once fix is implemented, also check other comments on the same PR, and ask user if the user wants to fix the rest of the comments as well. if said yes, then fetch all the comments validate the correctness and implement a minimal fixThere was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (2)
tests/unit/test_study_db.py (1)
37-49: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winMantenha uma corrida determinística com timeout.
O pool pode executar as migrações em série. Nesse caso, uma implementação sem proteção contra corrida também passa no teste.
Use uma barreira ou evento com timeout. Libere os workers juntos e falhe o teste se algum worker não alcançar o ponto de sincronização.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@tests/unit/test_study_db.py` around lines 37 - 49, Atualize o teste em migrar e no bloco ThreadPoolExecutor para sincronizar todos os workers por meio de uma Barreira ou Evento com timeout antes de executar a migração. Libere as threads simultaneamente e faça o teste falhar explicitamente caso algum worker não alcance o ponto de sincronização, preservando a validação dos resultados.kb/compile.py (1)
593-593: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winRemove noncritical local type hints.
Both sites use explicit type hints for local lists. The project guideline excludes this use case.
kb/compile.py#L593-L593: replacearticles: list[str] = []witharticles = [].kb/lint.py#L33-L33: replaceachados: list[str] = []withachados = [].As per coding guidelines, “Evite type hints explícitos, exceto quando forem críticos, especialmente em configuração e cliente.”
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@kb/compile.py` at line 593, Remove the noncritical local list type hints: in kb/compile.py lines 593-593, change the articles initialization to inferred typing, and in kb/lint.py lines 33-33, do the same for achados. No other behavior changes are needed.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@kb/heal.py`:
- Around line 104-110: Update the successful stub branch in the heal flow around
move_to_archive and mark_archived to add the source path, archive destination,
and manifest path to changed so commit() runs when only stubs are archived.
Preserve local writes by default and version these paths only when
no_commit=False. Add coverage for a stub-only run with no_commit=False.
In `@kb/regroup.py`:
- Around line 83-91: Em kb/regroup.py, no fluxo que processa summary_moves e
atualiza o índice, torne cada par artigo-summary atômico: faça rollback quando
qualquer etapa falhar e só atualize o manifest após a conclusão bem-sucedida do
par, deixando o estado recuperável. Em kb/cli.py, no fluxo de execução do
comando, impeça a criação de commit quando log contiver erros e retorne uma
falha sem versionar o estado parcial.
- Around line 35-49: Atualize a construção de por_artigo no fluxo de regroup
para detectar múltiplas entradas do manifest apontando para o mesmo artigo com
valores book diferentes, em vez de manter silenciosamente o primeiro via
setdefault(). Marque esses artigos como unresolved e impeça seu uso como destino
de movimentação até decisão humana, preservando o comportamento atual para
proveniência única.
- Around line 51-59: Atualize o planejamento em _slug_book e na construção de
plan.groups para preservar o caminho relativo de cada artigo no destino, ou
rejeitar colisões antes do move_to_archive(); aplique a mesma garantia aos
destinos de resumo em plan.summary_moves. Garanta que cada entrada do manifest
tenha um destino único e não permita que arquivos com o mesmo nome se
sobrescrevam.
In `@kb/stats.py`:
- Line 6: The get_article_summary traversal must use iter_articles(wiki_dir)
instead of wiki_dir.rglob("*.md") so symlinked Markdown files are excluded
consistently; update get_article_summary accordingly and add a stats test
covering symlink handling.
In `@tests/integration/test_heal_workflow.py`:
- Around line 35-36: Update the test containing the heal() call to accept
monkeypatch and override kb.config.ARCHIVE_DIR with wiki.parent / "archive"
before invoking heal(). Afterward, retain the archived_stub result assertion and
also verify that the moved stub exists in the isolated archive directory.
---
Nitpick comments:
In `@kb/compile.py`:
- Line 593: Remove the noncritical local list type hints: in kb/compile.py lines
593-593, change the articles initialization to inferred typing, and in
kb/lint.py lines 33-33, do the same for achados. No other behavior changes are
needed.
In `@tests/unit/test_study_db.py`:
- Around line 37-49: Atualize o teste em migrar e no bloco ThreadPoolExecutor
para sincronizar todos os workers por meio de uma Barreira ou Evento com timeout
antes de executar a migração. Libere as threads simultaneamente e faça o teste
falhar explicitamente caso algum worker não alcance o ponto de sincronização,
preservando a validação dos resultados.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 7df8e11c-01a9-4dee-b3ef-98bb32e5ced2
📒 Files selected for processing (22)
features/029-chapters-regroup/.statefeatures/029-chapters-regroup/CONTRACT.mdfeatures/029-chapters-regroup/PLAN.mdfeatures/029-chapters-regroup/REPORT.mdfeatures/029-chapters-regroup/SPEC.mdfeatures/029-chapters-regroup/TASKS.mdkb/archive.pykb/cli.pykb/compile.pykb/fsutil.pykb/graph.pykb/heal.pykb/lint.pykb/regroup.pykb/stats.pytests/conftest.pytests/integration/test_heal_workflow.pytests/integration/test_regroup_cli.pytests/unit/test_fsutil_articles.pytests/unit/test_heal.pytests/unit/test_regroup.pytests/unit/test_study_db.py
| summary_moves = [ | ||
| {"source": origem, "dest": destino} | ||
| for origem, destino in plan.summary_moves.get(book_slug, []) | ||
| ] | ||
| if summary_moves: | ||
| log += move_to_archive(summary_moves, wiki_dir / "_summaries" / "_chapters") | ||
| if any(entry["action"] == "moved" for entry in log): | ||
| update_index(no_commit=True) | ||
| refresh_embeddings_index() |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
Faça o reagrupamento por livro recuperável.
Uma falha em artigo ou summary deixa o livro em estado parcial. O comando também pode versionar esse estado antes de retornar erro.
kb/regroup.py#L83-L91: mova cada par artigo-summary de forma atômica ou faça rollback. Atualize o manifest somente após o par completo.kb/cli.py#L117-L126: não crie commit se o log contiver erros. Retorne uma falha com estado recuperável.
📍 Affects 2 files
kb/regroup.py#L83-L91(this comment)kb/cli.py#L117-L126
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@kb/regroup.py` around lines 83 - 91, Em kb/regroup.py, no fluxo que processa
summary_moves e atualiza o índice, torne cada par artigo-summary atômico: faça
rollback quando qualquer etapa falhar e só atualize o manifest após a conclusão
bem-sucedida do par, deixando o estado recuperável. Em kb/cli.py, no fluxo de
execução do comando, impeça a criação de commit quando log contiver erros e
retorne uma falha sem versionar o estado parcial.
| # 029 C2: stub é ARQUIVADO (move), nunca deletado | ||
| assert any(r["action"] == "archived_stub" for r in result) |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win
Isolate ARCHIVE_DIR in this test.
Line 33 calls heal(), which moves the stub to kb.config.ARCHIVE_DIR. The tmp_raw_wiki fixture changes WIKI_DIR, but it does not change ARCHIVE_DIR. This test can write test data into the configured local archive.
Receive monkeypatch, set kb.config.ARCHIVE_DIR to wiki.parent / "archive", and assert that the moved file exists there.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@tests/integration/test_heal_workflow.py` around lines 35 - 36, Update the
test containing the heal() call to accept monkeypatch and override
kb.config.ARCHIVE_DIR with wiki.parent / "archive" before invoking heal().
Afterward, retain the archived_stub result assertion and also verify that the
moved stub exists in the isolated archive directory.
There was a problem hiding this comment.
6 issues found across 22 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="tests/unit/test_study_db.py">
<violation number="1" location="tests/unit/test_study_db.py:37">
P2: Removing the Barrier makes this a non-deterministic regression test: the comment itself concedes the natural pool race reproduces the original bug only 2 of 3 runs, so on a fast machine it can false-green even when the duplicate-column race returns. Keep real concurrency pressure with a timeout-guarded Barrier (threading.Barrier(n, timeout=...)) instead of dropping synchronization, so the RED reliably catches a regression without risking a hang.</violation>
</file>
<file name="kb/regroup.py">
<violation number="1" location="kb/regroup.py:53">
P1: Articles with the same basename are silently merged during regroup, corrupting the manifest mapping and hiding the first article behind a backup. The plan should reject duplicate destinations before moving or generate a collision-free path while preserving each article.</violation>
<violation number="2" location="kb/regroup.py:79">
P1: A single move failure leaves the book partially applied: prior files and manifest/index updates remain, and `--commit` can persist them even though the command exits with an error. Preflight the complete article+summary batch or roll back every successful move and manifest update before returning an error.</violation>
</file>
<file name="features/029-chapters-regroup/TASKS.md">
<violation number="1" location="features/029-chapters-regroup/TASKS.md:15">
P3: C1's verify points to tests/unit/test_fsutil.py, which tests file-write helpers and never exercises iter_articles; the test that validates C1's live-article semantics is tests/unit/test_fsutil_articles.py. Update the verify to run tests/unit/test_fsutil_articles.py so the documented gate actually checks C1's behavior.</violation>
</file>
<file name="kb/heal.py">
<violation number="1" location="kb/heal.py:107">
P2: `kb heal --commit` não versiona os stubs arquivados nem a atualização do manifesto quando a amostra contém apenas stubs, porque esse novo caminho nunca popula `changed`; o fluxo deveria incluir essas mutações no commit (ou usar um mecanismo de commit que as cubra).</violation>
</file>
<file name="tests/conftest.py">
<violation number="1" location="tests/conftest.py:51">
P2: The added `kb.compile.WIKI_DIR` floor doesn't protect the heal path the comment names: heal.py binds `WIKI_DIR` from `kb.config` at import time (`from kb.config import WIKI_DIR`), so a wiki-less heal test still resolves the stub-move source to the real vault wiki (and the ARCHIVE_DIR patch only redirects the destination). Since `kb.config.WIKI_DIR` is also left unfloored, the structural floor is incomplete. Prefer flooring the module attributes heal/lint/search/router actually use (as tmp_wiki/tmp_raw_wiki already do), or at least `kb.config.WIKI_DIR`, rather than `kb.compile.WIKI_DIR` alone.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| {"source": origem, "dest": destino} | ||
| for origem, destino in plan.groups.get(book_slug, []) | ||
| ] | ||
| log = move_to_archive(moves, wiki_dir / "_chapters") |
There was a problem hiding this comment.
P1: A single move failure leaves the book partially applied: prior files and manifest/index updates remain, and --commit can persist them even though the command exits with an error. Preflight the complete article+summary batch or roll back every successful move and manifest update before returning an error.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/regroup.py, line 79:
<comment>A single move failure leaves the book partially applied: prior files and manifest/index updates remain, and `--commit` can persist them even though the command exits with an error. Preflight the complete article+summary batch or roll back every successful move and manifest update before returning an error.</comment>
<file context>
@@ -0,0 +1,92 @@
+ {"source": origem, "dest": destino}
+ for origem, destino in plan.groups.get(book_slug, [])
+ ]
+ log = move_to_archive(moves, wiki_dir / "_chapters")
+ for entry in log:
+ if entry["action"] == "moved":
</file context>
| continue | ||
| slug = _slug_book(book) | ||
| plan.book_names.setdefault(slug, book) | ||
| destino = wiki_dir / "_chapters" / slug / artigo.name |
There was a problem hiding this comment.
P1: Articles with the same basename are silently merged during regroup, corrupting the manifest mapping and hiding the first article behind a backup. The plan should reject duplicate destinations before moving or generate a collision-free path while preserving each article.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/regroup.py, line 53:
<comment>Articles with the same basename are silently merged during regroup, corrupting the manifest mapping and hiding the first article behind a backup. The plan should reject duplicate destinations before moving or generate a collision-free path while preserving each article.</comment>
<file context>
@@ -0,0 +1,92 @@
+ continue
+ slug = _slug_book(book)
+ plan.book_names.setdefault(slug, book)
+ destino = wiki_dir / "_chapters" / slug / artigo.name
+ plan.groups.setdefault(slug, []).append((artigo, destino))
+ summary = wiki_dir / "_summaries" / artigo.relative_to(wiki_dir)
</file context>
| legacy.close() | ||
|
|
||
| largada = threading.Barrier(8) | ||
| # Sem Barrier de propósito: exigir 8 threads simultâneas tornou o teste |
There was a problem hiding this comment.
P2: Removing the Barrier makes this a non-deterministic regression test: the comment itself concedes the natural pool race reproduces the original bug only 2 of 3 runs, so on a fast machine it can false-green even when the duplicate-column race returns. Keep real concurrency pressure with a timeout-guarded Barrier (threading.Barrier(n, timeout=...)) instead of dropping synchronization, so the RED reliably catches a regression without risking a hang.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_study_db.py, line 37:
<comment>Removing the Barrier makes this a non-deterministic regression test: the comment itself concedes the natural pool race reproduces the original bug only 2 of 3 runs, so on a fast machine it can false-green even when the duplicate-column race returns. Keep real concurrency pressure with a timeout-guarded Barrier (threading.Barrier(n, timeout=...)) instead of dropping synchronization, so the RED reliably catches a regression without risking a hang.</comment>
<file context>
@@ -35,11 +34,13 @@ def test_should_ensure_the_schema_when_connections_race_over_a_legacy_database(
legacy.close()
- largada = threading.Barrier(8)
+ # Sem Barrier de propósito: exigir 8 threads simultâneas tornou o teste
+ # refém da carga da máquina (e um Barrier sem timeout chegou a pendurar a
+ # suíte). A corrida natural do pool reproduziu o bug original em 2 de 3
</file context>
| dest = ARCHIVE_DIR / path.relative_to(WIKI_DIR) | ||
| resultado = move_to_archive([{"source": path, "dest": dest}], ARCHIVE_DIR) | ||
| if resultado and resultado[0]["action"] == "moved": | ||
| mark_archived(path) |
There was a problem hiding this comment.
P2: kb heal --commit não versiona os stubs arquivados nem a atualização do manifesto quando a amostra contém apenas stubs, porque esse novo caminho nunca popula changed; o fluxo deveria incluir essas mutações no commit (ou usar um mecanismo de commit que as cubra).
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/heal.py, line 107:
<comment>`kb heal --commit` não versiona os stubs arquivados nem a atualização do manifesto quando a amostra contém apenas stubs, porque esse novo caminho nunca popula `changed`; o fluxo deveria incluir essas mutações no commit (ou usar um mecanismo de commit que as cubra).</comment>
<file context>
@@ -98,9 +95,19 @@ def heal(
+ dest = ARCHIVE_DIR / path.relative_to(WIKI_DIR)
+ resultado = move_to_archive([{"source": path, "dest": dest}], ARCHIVE_DIR)
+ if resultado and resultado[0]["action"] == "moved":
+ mark_archived(path)
+ log.append({"file": path.name, "action": "archived_stub"})
+ else:
</file context>
| "kb.config.ARCHIVE_DIR", tmp_path_factory.mktemp("archive_piso"), raising=False | ||
| ) | ||
| monkeypatch.setattr( | ||
| "kb.compile.WIKI_DIR", tmp_path_factory.mktemp("wiki_piso"), raising=False |
There was a problem hiding this comment.
P2: The added kb.compile.WIKI_DIR floor doesn't protect the heal path the comment names: heal.py binds WIKI_DIR from kb.config at import time (from kb.config import WIKI_DIR), so a wiki-less heal test still resolves the stub-move source to the real vault wiki (and the ARCHIVE_DIR patch only redirects the destination). Since kb.config.WIKI_DIR is also left unfloored, the structural floor is incomplete. Prefer flooring the module attributes heal/lint/search/router actually use (as tmp_wiki/tmp_raw_wiki already do), or at least kb.config.WIKI_DIR, rather than kb.compile.WIKI_DIR alone.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/conftest.py, line 51:
<comment>The added `kb.compile.WIKI_DIR` floor doesn't protect the heal path the comment names: heal.py binds `WIKI_DIR` from `kb.config` at import time (`from kb.config import WIKI_DIR`), so a wiki-less heal test still resolves the stub-move source to the real vault wiki (and the ARCHIVE_DIR patch only redirects the destination). Since `kb.config.WIKI_DIR` is also left unfloored, the structural floor is incomplete. Prefer flooring the module attributes heal/lint/search/router actually use (as tmp_wiki/tmp_raw_wiki already do), or at least `kb.config.WIKI_DIR`, rather than `kb.compile.WIKI_DIR` alone.</comment>
<file context>
@@ -41,6 +41,15 @@ def _state_dir_never_points_at_real_vault(tmp_path_factory, monkeypatch):
+ "kb.config.ARCHIVE_DIR", tmp_path_factory.mktemp("archive_piso"), raising=False
+ )
+ monkeypatch.setattr(
+ "kb.compile.WIKI_DIR", tmp_path_factory.mktemp("wiki_piso"), raising=False
+ )
</file context>
| tag: AFK | ||
| vertical_slice: yes | ||
| behavior: "iter_articles único honra _*/.*/symlink e é adotado por lint, heal, archive, update_index e stats; heal deixa de poder tocar _summaries hoje." | ||
| verify: "python -m pytest tests/unit/test_fsutil.py tests/unit/test_lint.py tests/unit/test_heal.py tests/unit/test_archive.py tests/unit/test_stats.py -q" |
There was a problem hiding this comment.
P3: C1's verify points to tests/unit/test_fsutil.py, which tests file-write helpers and never exercises iter_articles; the test that validates C1's live-article semantics is tests/unit/test_fsutil_articles.py. Update the verify to run tests/unit/test_fsutil_articles.py so the documented gate actually checks C1's behavior.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At features/029-chapters-regroup/TASKS.md, line 15:
<comment>C1's verify points to tests/unit/test_fsutil.py, which tests file-write helpers and never exercises iter_articles; the test that validates C1's live-article semantics is tests/unit/test_fsutil_articles.py. Update the verify to run tests/unit/test_fsutil_articles.py so the documented gate actually checks C1's behavior.</comment>
<file context>
@@ -0,0 +1,60 @@
+ tag: AFK
+ vertical_slice: yes
+ behavior: "iter_articles único honra _*/.*/symlink e é adotado por lint, heal, archive, update_index e stats; heal deixa de poder tocar _summaries hoje."
+ verify: "python -m pytest tests/unit/test_fsutil.py tests/unit/test_lint.py tests/unit/test_heal.py tests/unit/test_archive.py tests/unit/test_stats.py -q"
+ state: passing
+```
</file context>
… regroup e piso completo - heal --commit versiona o stub arquivado (source, dest, backup e manifest entravam no move mas não no commit — 3 bots apontaram); mark_archived ganhou guarda para não abortar o heal com o arquivo já movido. - regroup: basename colidindo dentro do livro ganha destino desambiguado pelo diretório de origem (mover por cima criava backup silencioso do primeiro — cubic P1); proveniência de livro conflitante para o mesmo artigo vira unresolved (inferir um dos dois é chute); apply faz preflight do lote inteiro e aborta o livro antes de mover qualquer coisa se origem sumiu ou destino está ocupado. - conftest: o piso autouse agora cobre TODOS os module-globals de WIKI_DIR (heal/lint/search/router fazem from-import no load) — cobrir só kb.config e kb.compile deixava o heal resolvendo a wiki real. - teste da corrida de schema reescrito DETERMINÍSTICO, sem threads: as duas conexões leem o PRAGMA antes de qualquer ALTER, o interleaving exato do bug. Fecha a discordância dos bots (sem sync = falso verde; Barrier de 8 = falso vermelho sob carga) eliminando a aleatoriedade. - stats usa iter_articles (paridade de symlink); verify do C1 no TASKS.md apontava para o arquivo de teste errado. 1045 passed, ruff limpo, appeasement 0. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01S3FL25TLKVHDdn99GjtkxW
|
Review atendido em Corrigidos com teste RED antes do fix:
Sobre o teste da corrida de schema (CodeAnt + cubic P2, em direção oposta ao meu fix anterior): os dois lados tinham razão — sem sincronização o teste pode dar falso verde em máquina rápida; com Barrier de 8 threads deu falso vermelho sob carga (aconteceu hoje). Resolvi eliminando a aleatoriedade: o teste agora interleava as duas conexões deterministicamente (ambas leem o PRAGMA antes de qualquer ALTER — o interleaving exato do bug original). Sem threads, sem sorte, sem carga. Gates: |
There was a problem hiding this comment.
16 issues found across 22 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="kb/cli.py">
<violation number="1" location="kb/cli.py:117">
P1: A regroup that reports an error can still create a successful per-book commit containing only the moves that happened before the failure. The commit should be skipped whenever `log` contains an error, so the failed operation remains recoverable and is not presented as an applied book regroup.</violation>
</file>
<file name="kb/heal.py">
<violation number="1" location="kb/heal.py:112">
P3: Archived stubs are shown with the fallback `?` icon in `kb heal`, because the new `archived_stub` action is not handled by the CLI action map. Adding the new action to the CLI and public action contract would make the safe archive result clear to users.</violation>
<violation number="2" location="kb/heal.py:112">
P2: When a stub is archived, the manifest update is best-effort and its failure is swallowed: the file is already moved to archive/, but if `mark_archived` raises, the manifest entry stays `status: compiled` pointing at the now-moved wiki path and the log still reports `archived_stub` as a success. That re-introduces the exact recompile-guard/misclassification problem this change (029 C2) is meant to fix — the guard can still resolve to a path that no longer exists — while hiding it from anyone consuming the returned `log`. Consider treating a manifest failure as a distinct outcome (e.g., log `archived_stub_manifest_warning` or `archive_error`) and not claiming full success for `archived_stub` when the manifest wasn't updated, or roll the move back when the manifest write fails so state stays consistent.</violation>
<violation number="3" location="kb/heal.py:123">
P2: When archiving fails, `heal` returns `archive_error` without the failure detail, and `kb heal` exits successfully while printing only the fallback `?`; the stub remains live without an actionable cause. Preserving `resultado[0]["detail"]` and propagating the failure to the CLI/exit status would avoid silently unsuccessful cleanup.</violation>
</file>
<file name="kb/regroup.py">
<violation number="1" location="kb/regroup.py:72">
P2: When two chapters of the same book share a basename (e.g. `algorithms/intro.md` and `intro.md`), the article moves are correctly disambiguated by a directory prefix, but their summary mirrors are not: both map to `_summaries/_chapters/<slug>/intro.md`. During apply, `move_to_archive` then sees the destination already occupied on the second summary and silently renames the first to a `.v1.<ts>.md` versioned backup before overwriting — the exact 'backup silencioso do primeiro' data-reassociation problem the article path explicitly guards against. Apply the same prefix-based disambiguation to the summary destination (based on the article's relative directory) so summaries cannot collide within a book.</violation>
<violation number="2" location="kb/regroup.py:117">
P2: Aplicar um plano para um `wiki_dir` não global deixa o `_index.md` e o índice de embeddings do vault informado desatualizados, enquanto pode alterar o vault configurado. As rotinas de atualização precisam receber o mesmo diretório ou `apply_book` deve rejeitar essa combinação.</violation>
</file>
<file name="tests/integration/test_regroup_cli.py">
<violation number="1" location="tests/integration/test_regroup_cli.py:82">
P3: The git-clean assertion filters out `_index.md` lines, so it no longer verifies that the index was committed — if `regroup apply --commit` forgot to version `_index.md`, this test would still pass. Since `_index.md` is expected to be committed by the flow, drop the filter and let it be asserted like the other files, or assert it explicitly.</violation>
</file>
<file name="tests/conftest.py">
<violation number="1" location="tests/conftest.py:56">
P3: The comment asserts the floor covers 'ALL' WIKI_DIR module-globals, but kb.qa.WIKI_DIR (kb/qa.py:32, a module-level from-import) is not in the patch list — a test that exercises qa with only the autouse floor will still resolve qa against the real vault. qa currently only reads the wiki, so this is low-risk today, but add kb.qa.WIKI_DIR to the tuple so the structural floor matches its stated intent.</violation>
</file>
<file name="features/029-chapters-regroup/PLAN.md">
<violation number="1" location="features/029-chapters-regroup/PLAN.md:17">
P3: C1's description is inaccurate and will mislead future readers documenting the single-source-of-truth goal. `heal._sample_paths` does not exist (heal.py samples inline via random.sample), and `graph._e_artigo` does not delegate to `iter_articles` — it retains its own `_`/`.` predicate and lacks the symlink filter `iter_articles` applies, so the "uma semântica, um lugar" centralization is only partially realized. Suggest correcting the PLAN to reference the real sampling call site and noting the predicate/symlink divergence, or actually delegating `_e_artigo` to the helper.</violation>
</file>
<file name="features/029-chapters-regroup/CONTRACT.md">
<violation number="1" location="features/029-chapters-regroup/CONTRACT.md:14">
P3: Premise 1 claims the '7 furos' are the only ones and that the API honors the `_*` convention, but kb/api/articles.py:39 (the fingerprint rglob) only skips symlinks and does not exclude `_*`/`.*` paths, so files under `_chapters/` still get scanned/counted there. The actual index is built through graph.build_link_index (which honors `_*`), so there is no user-visible break — but the premise as recorded is inaccurate and RF-01's 'nenhum arquivo ... listado/contado' semantics isn't fully met at that site. Consider filtering `_`/`.` parts in the fingerprint too, or revising the premise/contract text to reflect this residual gap.</violation>
</file>
<file name="tests/unit/test_regroup.py">
<violation number="1" location="tests/unit/test_regroup.py:63">
P3: This test claims to validate that `status == 'archived'` entries are ignored, but the archived file `algorithms/arquivado.md` is never created on disk, so it's filtered out by the file-exists check and the archived-status branch stays untested. Create the archived file (or the file it points to) so the status filter is genuinely exercised, e.g. call `_artigo(wiki, 'algorithms/arquivado.md')` and keep the archived status, then assert it's excluded while the (now present) `.md` file would otherwise be picked up by iter_articles.</violation>
</file>
<file name="tests/unit/test_heal.py">
<violation number="1" location="tests/unit/test_heal.py:358">
P3: The `if "_index" not in linha` filter in test_should_commit_archived_stub_and_manifest_when_commit_enabled weakens the git-status assertion: heal's stub-archive path does not produce an _index.md, so the filter masks arbitrary uncommitted files containing '_index' instead of asserting the working tree is truly clean. Assert on the exact expected paths (or drop the filter) so the commit test verifies precisely the move + manifest are versioned.</violation>
<violation number="2" location="tests/unit/test_heal.py:455">
P3: The docstring for test_should_create_distinct_backups_when_same_stem_in_different_topics still describes the old delete-and-create-backup behavior ('Quando heal deleta ambos no mesmo run... deve criar dois backups distintos'), but the test now archives to archive/a/x.md and archive/b/x.md. Update the docstring to describe the archive-preserves-hierarchy behavior so it doesn't mislead future readers.</violation>
</file>
<file name="features/029-chapters-regroup/REPORT.md">
<violation number="1" location="features/029-chapters-regroup/REPORT.md:20">
P3: The final count of '124 unresolved' (and implied 851 with provenance) contradicts the feature's own baseline of 126 unresolved stated in SPEC.md, CONTRACT.md premise 3, and PLAN.md. The −2 delta implies 2 articles left the unresolved set during C4 but the report never documents that event, breaking the documentary-traceability standard the project follows. Reconcile the numbers against the baseline or add a note explaining the two resolved articles.</violation>
</file>
<file name="kb/stats.py">
<violation number="1" location="kb/stats.py:6">
P3: After this change `_is_ignored_article` is no longer called anywhere in production. Its only consumer, `get_article_summary`, now iterates over `kb.fsutil.iter_articles`, which encodes the exact same `_*`/`.*` predicate. A live `rg` shows the helper is referenced only by `tests/unit/test_fsutil_articles.py`. This leaves dead code in the module, and worse, the test drives a private implementation detail instead of the actual behavior (`get_article_summary`). I'd suggest deleting the helper and asserting `get_article_summary()`'s output for the populated fixture, which keeps a regression guard on the real production path.</violation>
</file>
<file name="kb/lint.py">
<violation number="1" location="kb/lint.py:34">
P3: Minor redundancy: `iter_articles` already returns a sorted sequence (`sorted(wiki_dir.rglob("*.md"))`), so wrapping it again in `sorted(..., key=lambda p: p.as_posix())` does a second, unnecessary sort. The two orderings (Path parts vs. posix string) can even differ for nested files, so the extra sort silently overrides the generator's ordering. Since the order here is only for deterministic output messages, iterating the result directly (or keeping just the `key=` sort) is cleaner.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| else: | ||
| typer.echo(f"erro: {entry['source']} — {entry.get('detail', '')}", err=True) | ||
| typer.echo(f"{len(moved)} arquivo(s) movidos para _chapters/{book}/") | ||
| if moved and not no_commit: |
There was a problem hiding this comment.
P1: A regroup that reports an error can still create a successful per-book commit containing only the moves that happened before the failure. The commit should be skipped whenever log contains an error, so the failed operation remains recoverable and is not presented as an applied book regroup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/cli.py, line 117:
<comment>A regroup that reports an error can still create a successful per-book commit containing only the moves that happened before the failure. The commit should be skipped whenever `log` contains an error, so the failed operation remains recoverable and is not presented as an applied book regroup.</comment>
<file context>
@@ -61,6 +63,67 @@
+ else:
+ typer.echo(f"erro: {entry['source']} — {entry.get('detail', '')}", err=True)
+ typer.echo(f"{len(moved)} arquivo(s) movidos para _chapters/{book}/")
+ if moved and not no_commit:
+ from kb.git import commit
+
</file context>
| if moved and not no_commit: | |
| if moved and not no_commit and not any( | |
| entry["action"] == "error" for entry in log | |
| ): |
| if MANIFEST_PATH.exists(): | ||
| changed.append(MANIFEST_PATH) | ||
| else: | ||
| log.append({"file": path.name, "action": "archive_error"}) |
There was a problem hiding this comment.
P2: When archiving fails, heal returns archive_error without the failure detail, and kb heal exits successfully while printing only the fallback ?; the stub remains live without an actionable cause. Preserving resultado[0]["detail"] and propagating the failure to the CLI/exit status would avoid silently unsuccessful cleanup.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/heal.py, line 123:
<comment>When archiving fails, `heal` returns `archive_error` without the failure detail, and `kb heal` exits successfully while printing only the fallback `?`; the stub remains live without an actionable cause. Preserving `resultado[0]["detail"]` and propagating the failure to the CLI/exit status would avoid silently unsuccessful cleanup.</comment>
<file context>
@@ -98,9 +96,31 @@ def heal(
+ if MANIFEST_PATH.exists():
+ changed.append(MANIFEST_PATH)
+ else:
+ log.append({"file": path.name, "action": "archive_error"})
continue
</file context>
| if summary_moves: | ||
| log += move_to_archive(summary_moves, wiki_dir / "_summaries" / "_chapters") | ||
| if any(entry["action"] == "moved" for entry in log): | ||
| update_index(no_commit=True) |
There was a problem hiding this comment.
P2: Aplicar um plano para um wiki_dir não global deixa o _index.md e o índice de embeddings do vault informado desatualizados, enquanto pode alterar o vault configurado. As rotinas de atualização precisam receber o mesmo diretório ou apply_book deve rejeitar essa combinação.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/regroup.py, line 117:
<comment>Aplicar um plano para um `wiki_dir` não global deixa o `_index.md` e o índice de embeddings do vault informado desatualizados, enquanto pode alterar o vault configurado. As rotinas de atualização precisam receber o mesmo diretório ou `apply_book` deve rejeitar essa combinação.</comment>
<file context>
@@ -0,0 +1,119 @@
+ if summary_moves:
+ log += move_to_archive(summary_moves, wiki_dir / "_summaries" / "_chapters")
+ if any(entry["action"] == "moved" for entry in log):
+ update_index(no_commit=True)
+ refresh_embeddings_index()
+ return log
</file context>
| mark_archived(path) | ||
| except Exception as exc: # arquivo já se moveu; avisar > abortar | ||
| print(f"aviso: manifest não atualizado para {path.name} — {exc}", file=sys.stderr) | ||
| log.append({"file": path.name, "action": "archived_stub"}) |
There was a problem hiding this comment.
P2: When a stub is archived, the manifest update is best-effort and its failure is swallowed: the file is already moved to archive/, but if mark_archived raises, the manifest entry stays status: compiled pointing at the now-moved wiki path and the log still reports archived_stub as a success. That re-introduces the exact recompile-guard/misclassification problem this change (029 C2) is meant to fix — the guard can still resolve to a path that no longer exists — while hiding it from anyone consuming the returned log. Consider treating a manifest failure as a distinct outcome (e.g., log archived_stub_manifest_warning or archive_error) and not claiming full success for archived_stub when the manifest wasn't updated, or roll the move back when the manifest write fails so state stays consistent.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/heal.py, line 112:
<comment>When a stub is archived, the manifest update is best-effort and its failure is swallowed: the file is already moved to archive/, but if `mark_archived` raises, the manifest entry stays `status: compiled` pointing at the now-moved wiki path and the log still reports `archived_stub` as a success. That re-introduces the exact recompile-guard/misclassification problem this change (029 C2) is meant to fix — the guard can still resolve to a path that no longer exists — while hiding it from anyone consuming the returned `log`. Consider treating a manifest failure as a distinct outcome (e.g., log `archived_stub_manifest_warning` or `archive_error`) and not claiming full success for `archived_stub` when the manifest wasn't updated, or roll the move back when the manifest write fails so state stays consistent.</comment>
<file context>
@@ -98,9 +96,31 @@ def heal(
+ mark_archived(path)
+ except Exception as exc: # arquivo já se moveu; avisar > abortar
+ print(f"aviso: manifest não atualizado para {path.name} — {exc}", file=sys.stderr)
+ log.append({"file": path.name, "action": "archived_stub"})
+ # o --commit precisa versionar o move e o manifest, não só heals de texto
+ changed.append(path)
</file context>
| summary = wiki_dir / "_summaries" / artigo.relative_to(wiki_dir) | ||
| if summary.exists(): | ||
| plan.summary_moves.setdefault(slug, []).append( | ||
| (summary, wiki_dir / "_summaries" / "_chapters" / slug / artigo.name) |
There was a problem hiding this comment.
P2: When two chapters of the same book share a basename (e.g. algorithms/intro.md and intro.md), the article moves are correctly disambiguated by a directory prefix, but their summary mirrors are not: both map to _summaries/_chapters/<slug>/intro.md. During apply, move_to_archive then sees the destination already occupied on the second summary and silently renames the first to a .v1.<ts>.md versioned backup before overwriting — the exact 'backup silencioso do primeiro' data-reassociation problem the article path explicitly guards against. Apply the same prefix-based disambiguation to the summary destination (based on the article's relative directory) so summaries cannot collide within a book.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/regroup.py, line 72:
<comment>When two chapters of the same book share a basename (e.g. `algorithms/intro.md` and `intro.md`), the article moves are correctly disambiguated by a directory prefix, but their summary mirrors are not: both map to `_summaries/_chapters/<slug>/intro.md`. During apply, `move_to_archive` then sees the destination already occupied on the second summary and silently renames the first to a `.v1.<ts>.md` versioned backup before overwriting — the exact 'backup silencioso do primeiro' data-reassociation problem the article path explicitly guards against. Apply the same prefix-based disambiguation to the summary destination (based on the article's relative directory) so summaries cannot collide within a book.</comment>
<file context>
@@ -0,0 +1,119 @@
+ summary = wiki_dir / "_summaries" / artigo.relative_to(wiki_dir)
+ if summary.exists():
+ plan.summary_moves.setdefault(slug, []).append(
+ (summary, wiki_dir / "_summaries" / "_chapters" / slug / artigo.name)
+ )
+ return plan
</file context>
| status = subprocess.run( | ||
| ["git", "status", "--porcelain"], cwd=vault, check=True, capture_output=True, text=True | ||
| ).stdout | ||
| sujos = [linha for linha in status.splitlines() if "_index" not in linha] |
There was a problem hiding this comment.
P3: The if "_index" not in linha filter in test_should_commit_archived_stub_and_manifest_when_commit_enabled weakens the git-status assertion: heal's stub-archive path does not produce an _index.md, so the filter masks arbitrary uncommitted files containing '_index' instead of asserting the working tree is truly clean. Assert on the exact expected paths (or drop the filter) so the commit test verifies precisely the move + manifest are versioned.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_heal.py, line 358:
<comment>The `if "_index" not in linha` filter in test_should_commit_archived_stub_and_manifest_when_commit_enabled weakens the git-status assertion: heal's stub-archive path does not produce an _index.md, so the filter masks arbitrary uncommitted files containing '_index' instead of asserting the working tree is truly clean. Assert on the exact expected paths (or drop the filter) so the commit test verifies precisely the move + manifest are versioned.</comment>
<file context>
@@ -309,11 +309,76 @@ def test_should_backup_before_stub_delete(self, tmp_raw_wiki):
+ status = subprocess.run(
+ ["git", "status", "--porcelain"], cwd=vault, check=True, capture_output=True, text=True
+ ).stdout
+ sujos = [linha for linha in status.splitlines() if "_index" not in linha]
+ assert sujos == [], f"move do stub e manifest devem estar commitados; sobrou: {sujos}"
+
</file context>
| assert [r["action"] for r in result] == ["deleted_stub", "deleted_stub"] | ||
| backups = list((wiki / ".heal_backup").glob("*x.*.md")) | ||
| assert len(backups) == 2 | ||
| assert [r["action"] for r in result] == ["archived_stub", "archived_stub"] |
There was a problem hiding this comment.
P3: The docstring for test_should_create_distinct_backups_when_same_stem_in_different_topics still describes the old delete-and-create-backup behavior ('Quando heal deleta ambos no mesmo run... deve criar dois backups distintos'), but the test now archives to archive/a/x.md and archive/b/x.md. Update the docstring to describe the archive-preserves-hierarchy behavior so it doesn't mislead future readers.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/unit/test_heal.py, line 455:
<comment>The docstring for test_should_create_distinct_backups_when_same_stem_in_different_topics still describes the old delete-and-create-backup behavior ('Quando heal deleta ambos no mesmo run... deve criar dois backups distintos'), but the test now archives to archive/a/x.md and archive/b/x.md. Update the docstring to describe the archive-preserves-hierarchy behavior so it doesn't mislead future readers.</comment>
<file context>
@@ -378,14 +443,19 @@ def test_should_create_distinct_backups_when_same_stem_in_different_topics(
- assert [r["action"] for r in result] == ["deleted_stub", "deleted_stub"]
- backups = list((wiki / ".heal_backup").glob("*x.*.md"))
- assert len(backups) == 2
+ assert [r["action"] for r in result] == ["archived_stub", "archived_stub"]
+ # hierarquia preservada: mesmo stem em topics distintos não colide
+ assert (archive_dir / "a" / "x.md").is_file()
</file context>
|
|
||
| Decisão do dono em 2026-08-06: **mover 37 livros (630 artigos); `transcripts-youtube` (207) e `harness` (14) ficam** — não são livros, e com os 124 `unresolved` a plataforma mantém ~345 artigos visíveis até o compile multi-fonte. | ||
|
|
||
| Executado em 2026-08-06: **37 livros, 630 artigos + summaries movidos, 37 commits** (um por livro), zero erros, tag `pre-regroup-2026-08-06`. Vault final: **345 artigos vivos** (207 transcripts + 14 harness + 124 unresolved — soma exata), 630 em `_chapters/`. Smoke: índice em 345/345 artigos (2.666 chunks), `kb search` e `kb stats` respondendo, plataforma com home povoada e artigo vivo abrindo (tela conferida). |
There was a problem hiding this comment.
P3: The final count of '124 unresolved' (and implied 851 with provenance) contradicts the feature's own baseline of 126 unresolved stated in SPEC.md, CONTRACT.md premise 3, and PLAN.md. The −2 delta implies 2 articles left the unresolved set during C4 but the report never documents that event, breaking the documentary-traceability standard the project follows. Reconcile the numbers against the baseline or add a note explaining the two resolved articles.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At features/029-chapters-regroup/REPORT.md, line 20:
<comment>The final count of '124 unresolved' (and implied 851 with provenance) contradicts the feature's own baseline of 126 unresolved stated in SPEC.md, CONTRACT.md premise 3, and PLAN.md. The −2 delta implies 2 articles left the unresolved set during C4 but the report never documents that event, breaking the documentary-traceability standard the project follows. Reconcile the numbers against the baseline or add a note explaining the two resolved articles.</comment>
<file context>
@@ -0,0 +1,41 @@
+
+Decisão do dono em 2026-08-06: **mover 37 livros (630 artigos); `transcripts-youtube` (207) e `harness` (14) ficam** — não são livros, e com os 124 `unresolved` a plataforma mantém ~345 artigos visíveis até o compile multi-fonte.
+
+Executado em 2026-08-06: **37 livros, 630 artigos + summaries movidos, 37 commits** (um por livro), zero erros, tag `pre-regroup-2026-08-06`. Vault final: **345 artigos vivos** (207 transcripts + 14 harness + 124 unresolved — soma exata), 630 em `_chapters/`. Smoke: índice em 345/345 artigos (2.666 chunks), `kb search` e `kb stats` respondendo, plataforma com home povoada e artigo vivo abrindo (tela conferida).
+
+## Incidente do ciclo (registrado, não escondido)
</file context>
| if path.name == "_index.md": | ||
| return True | ||
| return "_summaries" in rel.parts or ".heal_backup" in rel.parts | ||
| return any(part.startswith(("_", ".")) for part in rel.parts) |
There was a problem hiding this comment.
P3: After this change _is_ignored_article is no longer called anywhere in production. Its only consumer, get_article_summary, now iterates over kb.fsutil.iter_articles, which encodes the exact same _*/.* predicate. A live rg shows the helper is referenced only by tests/unit/test_fsutil_articles.py. This leaves dead code in the module, and worse, the test drives a private implementation detail instead of the actual behavior (get_article_summary). I'd suggest deleting the helper and asserting get_article_summary()'s output for the populated fixture, which keeps a regression guard on the real production path.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/stats.py, line 6:
<comment>After this change `_is_ignored_article` is no longer called anywhere in production. Its only consumer, `get_article_summary`, now iterates over `kb.fsutil.iter_articles`, which encodes the exact same `_*`/`.*` predicate. A live `rg` shows the helper is referenced only by `tests/unit/test_fsutil_articles.py`. This leaves dead code in the module, and worse, the test drives a private implementation detail instead of the actual behavior (`get_article_summary`). I'd suggest deleting the helper and asserting `get_article_summary()`'s output for the populated fixture, which keeps a regression guard on the real production path.</comment>
<file context>
@@ -3,9 +3,7 @@
- if path.name == "_index.md":
- return True
- return "_summaries" in rel.parts or ".heal_backup" in rel.parts
+ return any(part.startswith(("_", ".")) for part in rel.parts)
</file context>
| for md in sorted(wiki_dir.rglob("*.md"), key=lambda p: p.as_posix()): | ||
| if md.is_symlink(): | ||
| continue | ||
| for md in sorted(iter_articles(wiki_dir), key=lambda p: p.as_posix()): |
There was a problem hiding this comment.
P3: Minor redundancy: iter_articles already returns a sorted sequence (sorted(wiki_dir.rglob("*.md"))), so wrapping it again in sorted(..., key=lambda p: p.as_posix()) does a second, unnecessary sort. The two orderings (Path parts vs. posix string) can even differ for nested files, so the extra sort silently overrides the generator's ordering. Since the order here is only for deterministic output messages, iterating the result directly (or keeping just the key= sort) is cleaner.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At kb/lint.py, line 34:
<comment>Minor redundancy: `iter_articles` already returns a sorted sequence (`sorted(wiki_dir.rglob("*.md"))`), so wrapping it again in `sorted(..., key=lambda p: p.as_posix())` does a second, unnecessary sort. The two orderings (Path parts vs. posix string) can even differ for nested files, so the extra sort silently overrides the generator's ordering. Since the order here is only for deterministic output messages, iterating the result directly (or keeping just the `key=` sort) is cleaner.</comment>
<file context>
@@ -26,13 +26,12 @@ def find_ambiguous_wikilinks(wiki_dir: Path) -> list[str]:
- for md in sorted(wiki_dir.rglob("*.md"), key=lambda p: p.as_posix()):
- if md.is_symlink():
- continue
+ for md in sorted(iter_articles(wiki_dir), key=lambda p: p.as_posix()):
text = md.read_text(encoding="utf-8", errors="replace")
vistos: set[str] = set()
</file context>
| for md in sorted(iter_articles(wiki_dir), key=lambda p: p.as_posix()): | |
| for md in iter_articles(wiki_dir): |
User description
Contexto
Terceira e última feature do esforço de higiene do corpus (ADR-0018, etapa 3). Fecha o ciclo aberto pelo plano de 2026-08-05: sete pontos do engine ignoravam a convenção
_*, o heal era a última remoção destrutiva, e 975 artigos de capítulo viviam misturados na wiki visível.Mudanças
kb/fsutil.iter_articles: semântica única de artigo vivo (_*,.*, symlink), adotada por lint, heal, archive (órfãos e idade),update_indexe stats. O furo mais grave era atual: o heal sorteava os 1.027 summaries de_summaries/e podia deletar um como stub; pós-reagrupamento,find_orphansmarcaria_chapters/inteiro como órfão.archive/com hierarquia + backup versionado e o manifest viraarchived— o guard de recompile não aponta para path movido.kb regroup scan|apply --book: plano por proveniência do manifest (nunca cosseno);unresolvedjamais movido por inferência; commit por livro com rollback granular.Lote final (C4 — gate explícito do dono)
Decisão registrada: mover 37 livros;
transcripts-youtube(207) eharness(14) ficam por não serem livros. Executado: 630 artigos + summaries em 37 commits, zero erros, tagpre-regroup-2026-08-06. Vault final: 345 vivos (207+14+124 unresolved, soma exata), 630 em_chapters/, índice reconstruído (345/345, 2.666 chunks), smoke de search/stats e tela da plataforma conferidos.Incidente registrado no ciclo
O RED do C2 expôs a terceira ocorrência do dia da mesma classe de vazamento: teste sem isolamento moveu stubs de fixture para o archive do vault REAL. Limpeza aprovada pelo dono; fix estrutural no conftest (piso autouse agora cobre
ARCHIVE_DIRekb.compile.WIKI_DIR, comoSTATE_DIRdesde 2026-07-29). O teste de corrida do schema também perdeu oBarrierque pendurava a suíte sob carga.Como testar
python -m pytest tests/unit/test_fsutil_articles.py tests/unit/test_regroup.py tests/integration/test_regroup_cli.py tests/unit/test_heal.py -q kb regroup scan # plano por livro + unresolved, nada se moveGates:
1042 passed, ruff limpo, appeasement exit 0, cobertura 93%.Com este PR, o esforço ADR-0018 etapas 1–3 está completo. Próximo esforço: compile multi-fonte (pré-requisitos agora existem).
🤖 Generated with Claude Code
https://claude.ai/code/session_01S3FL25TLKVHDdn99GjtkxW
CodeAnt-AI Description
Standardize live-article handling, archive healed stubs safely, and regroup chapters by book
What Changed
archive/with their directory structure preserved instead of being deleted; their manifest entries are marked archived.kb regroup scanto preview book-based moves and identify unresolved articles that need human review.kb regroup apply --bookto move a selected book’s articles and summaries into_chapters/, update the manifest and indexes, refresh embeddings, and optionally create one commit per book.Impact
✅ Fewer accidental changes to summaries and chapter files✅ Recoverable stub cleanup✅ Clearer book-by-book chapter organization💡 Usage Guide
Checking Your Pull Request
Every time you make a pull request, our system automatically looks through it. We check for security issues, mistakes in how you're setting up your infrastructure, and common code problems. We do this to make sure your changes are solid and won't cause any trouble later.
Talking to CodeAnt AI
Got a question or need a hand with something in your pull request? You can easily get in touch with CodeAnt AI right here. Just type the following in a comment on your pull request, and replace "Your question here" with whatever you want to ask:
This lets you have a chat with CodeAnt AI about your pull request, making it easier to understand and improve your code.
Example
Preserve Org Learnings with CodeAnt
You can record team preferences so CodeAnt AI applies them in future reviews. Reply directly to the specific CodeAnt AI suggestion (in the same thread) and replace "Your feedback here" with your input:
This helps CodeAnt AI learn and adapt to your team's coding style and standards.
Example
Retrigger review
Ask CodeAnt AI to review the PR again, by typing:
Check Your Repository Health
To analyze the health of your code repository, visit our dashboard at https://app.codeant.ai. This tool helps you identify potential issues and areas for improvement in your codebase, ensuring your repository maintains high standards of code health.
Summary by cubic
Standardizes live-article handling, makes
healnon-destructive, and adds book-based regrouping withkb regroup scan|applyto move chapter articles into_chapters/safely. Owner-approved rollout moved 37 books (630 files), kepttranscripts-youtubeandharness, rebuilt the index, and preserved search/stats consistency.New Features
kb/fsutil.iter_articles: single source of truth for live articles; used bylint,heal,archive(orphans/age),compile.update_index, andstatsto hide_chapters//_summaries/and skip symlinks.heal: stubs move toarchive/with versioned backups; manifest entries markedarchived(nounlink).regroup:kb regroup scanplans moves bymanifest.book;apply --book <slug>moves articles and mirrored summaries to_chapters/<slug>/, updates manifest paths, rebuilds_index.md, refreshes embeddings, and commits per book.unresolvednever moves.Bug Fixes
healno longer samples_summaries;archivewon’t flag_chaptersas orphans.heal --commitnow commits archived stubs (source, dest, backup) and the manifest;mark_archivedis guarded to avoid aborts if the file already moved.regroupdisambiguates destination names when basenames collide within a book, treats conflicting book provenance asunresolved, and preflights the whole book to abort if sources are missing or destinations exist.WIKI_DIRmodule-globals; the schema race test is deterministic;statsusesiter_articlesfor symlink parity.Written for commit 5c048bb. Summary will update on new commits.
Summary by CodeRabbit