Commit Graph
39 Commits
Author SHA1 Message Date
mathiasandClaude Sonnet 5.5 90752f2ac9 fix(pipeline,api): write the source page last; say how to clear an unreadable note (review of #211, pass 4)
CI / Lint / Test / Vet (pull_request) Successful in 1m3s
CI / Mirror to GitHub (pull_request) Skipped
The source page carries source_hash, which the drift check trusts. Written first,
a batch that failed midway left a hash behind, so re-ingesting the same content
was skipped and the rest of the batch was lost for good. In a wing it is now
written last. An over-cap note in brain_pending says promote/reject cannot act
on it and to delete or move it by hand. Mutation-checked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 21:00:18 +02:00
mathiasandClaude Sonnet 5.5 27b1579b80 fix(pipeline,api,wing): never archive a note as processed when nothing was ingested; titles do not merge sources; ties prefer the page on disk (review of #211, pass 3)
CI / Lint / Test / Vet (pull_request) Successful in 1m6s
CI / Mirror to GitHub (pull_request) Skipped
- in a wing, an ingest that writes no source page is an error: the note goes to
  raw/failed with the warnings in log.md instead of leaving the inbox as
  'processed' (model returned [], only invalid types, only clashing entities)
- the wing's source path is deterministic, so Resolve no longer redirects a
  source onto another note's page because the model gave both the same title
  ('Home'); that overwrote the hash and the text
- a batch tie between concept and entity prefers the page already on disk, so
  its update is not lost; the wing's own source page keeps its slug even when a
  foreign page shares it
- brain_pending lists an unreadable (over-cap) note with the reason instead of
  dropping it; the client filter's 4 KiB match-span limit is documented
Each has a test that fails without it (mutation-checked).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:56:03 +02:00
mathiasandClaude Sonnet 5.5 74d6a08c7b fix(pipeline): slug clashes inside one batch; the source page is never the one skipped (review of #211, pass 2)
dropSlugClashes only compared against what was on disk, so a clip named like an
entity the model extracted wrote both pages and neither could be updated again.
In-batch winners: source over entity over concept. Against the brain, only
entities/concepts are skipped; the source's slug was already chosen free or its
own by resolveSourceSlug, so an existing bad pair no longer locks the source out.
Mutation-checked.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:43:39 +02:00
mathiasandClaude Sonnet 5.5 ae02d4764c fix(pipeline): same-named sources in two wings, entity/source slug clashes, model-chosen types (review of #211)
- a source whose slug is taken by anything but this wing's own page gets a
  wing-qualified, stable slug (talk-homelab) instead of failing the ingest
- an extracted entity/concept whose slug belongs to a different page is skipped
  with a warning, so one page can no longer lock a source out of re-ingest
- in a wing only source/concept/entity are valid page types; the model (and so
  the clip) can no longer choose wiki/<type>/ directories
Mutation-checked; stable-slug choice pinned directly because title resolution
masks it at the Run level.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:27:28 +02:00
mathiasandClaude Sonnet 5.5 8e488b6e4b fix(pipeline): a slug used in two places is refused even when one is the wing's own (#208)
CI / Lint / Test / Vet (pull_request) Successful in 1m8s
CI / Mirror to GitHub (pull_request) Skipped
FindSlug returned the first hit, so a wing's own page of a name masked another
page of the same name (the retired dirs are searched before the wings, and wings
alphabetically). FindSlugs returns every match and callers refuse any that is
not their own. Found by the legacy-move planner's own test.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:22:20 +02:00
mathiasandClaude Sonnet 5.5 ce5a5268e7 feat(pipeline): per-wing sources, shared concepts/entities, one slug one page (#208)
With Config.Wing a source goes to wiki/<wing>/sources/ (wing/hall frontmatter);
concepts and entities stay in the shared wiki/concepts and wiki/entities, so an
entity cited from two wings is ONE page with both sources (the per-wing facts/
layout duplicated it). A source slug already used anywhere but its own wing's
sources/ is refused (slug is the graph identity). wiki.FindSlug added.
Mutation-checked: collision guard, shared inventory, per-wing source path.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:08:30 +02:00
mathiasandClaude Sonnet 5.5 2cc4f1b30d merge: land #195 (watcher/pipeline writes through the brain root, #158 part B) on top of inbox zero and the wing resolver
Reconciled with v0.25.0 semantics:
- markers are only the fallback when the original cannot be removed, and are
  now written through the brain root; the refused set (size+mtime keyed) replaces
  the path-only 'stuck' set
- #195's tests updated for inbox zero (archive written, original retired, no
  marker), and the planted-marker-link test keeps its point: nothing is ever
  written through the link, and the note is processed once
- the 'cannot retire' test now counts archive copies; it counted LLM calls,
  which pipeline.Run's source-hash check hides (mutation: removing the
  remember step left it green)

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 20:02:15 +02:00
mathiasandClaude Sonnet 5.5 ad793f320d feat(pipeline): write into wiki/<wing>/{sources,facts}/ when Config.Wing is set (#205 S2)
Sources -> wiki/<wing>/sources, concepts/entities -> wiki/<wing>/facts with
kind: frontmatter; inventory, title resolution and back-references are
confined to the target wing. Empty Wing keeps the retired layout unchanged.
Mutation-checked: widening the inventory beyond the wing fails the
cross-wing back-reference test; a non-plain wing name is rejected.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-20 06:07:14 +02:00
mathiasandClaude Opus 5.5 22d0653b61 test(watcher): a marker link planted during the run is refused and not retried (#158)
CI / Lint / Test / Vet (pull_request) Successful in 1m3s
CI / Mirror to GitHub (pull_request) Skipped
Review round 2 on PR #195: the marker writes had no test, so reverting
them to by-path writes, or the refusal to log-only, stayed green. The
link is planted from inside the fake model call, between the walk's
Lstat and the marker write, for both markers and both an outside and
an in-brain relative target; red on all four flips. The marker-entry
skip is now checked for .failed too.

Also pins cleanRel's acceptance of a backslash as a filename byte in
brainfs itself, and stops the buildAndWrite control claiming writes it
does not count.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:47:24 +02:00
mathiasandClaude Opus 5.5 1a2a93d2f4 fix(watcher): refusals end in a terminal state; pin every converted writer (#158)
CI / Lint / Test / Vet (pull_request) Successful in 1m6s
CI / Mirror to GitHub (pull_request) Skipped
Review round 1 on PR #195:
- A refused marker or archive write left no marker, so the note was
  reprocessed every interval (archive copies piling up; an LLM call per
  round on the failure path). The marker check now uses Lstat (any entry
  at the marker path counts, so a dangling marker link skips the note
  instead of looping), and a note whose archive copy or marker write is
  refused is remembered (path, size, mtime) and not retried until it
  changes.
- cleanRel no longer refuses a backslash: it is a legal filename byte on
  Linux, not a traversal, and a raw note named with one was never
  archived or marked.
- Tests now reach every converted writer: the log.md append (real
  raw/failed so the log is reached, symlinked log.md), a dangling marker
  link, the refused-retry loop with a positive control, a backslash name,
  a buildAndWrite swap race, and the claude-session archive with in-brain
  and outside symlinked archive dirs. The stale lint allowlist entry for
  appendWatcherLog is removed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:40:19 +02:00
mathiasandClaude Opus 5.5 77c44f298c fix: pipeline, backfill, watcher and archive writes go through the brain root (#158 part B)
CI / Lint / Test / Vet (pull_request) Successful in 1m13s
CI / Mirror to GitHub (pull_request) Skipped
The remaining writers created or rewrote brain files by path:
- pipeline buildAndWrite: os.MkdirAll + os.WriteFile of every page.
- BackfillRefs: os.WriteFile of each concept page; a page swapped for a
  symlink after the read was written through (the race test overwrote an
  in-brain target before this change).
- watcher: the raw/processed and raw/failed archive copies (os.Create),
  the .processed/.failed markers and the log.md entry (os.OpenFile).
- cmd/server: the claude-session archive.

All now go through brainfs PutRegular/AppendRegular (new package-level
PutFile/AppendFile for one-off writers: the watcher's call-site lint bans
.Open calls in that file so raw/ reads stay on OpenRegular).

Stacked on #189 (needs PutRegular/AppendRegular).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-05 23:26:02 +02:00
mathiasandClaude Opus 5.5 eedb110be5 fix(pipeline,classification): read the brain through brainfs (#157, slice 6) (#176)
CI / Lint / Test / Vet (push) Successful in 1m1s
CI / Mirror to GitHub (push) Successful in 4s
Ingest pipeline readers (readSourcePage and loadSchema feed the LLM prompt; reviseOrCreate; injectSourceRefs; BackfillRefs list and reads) and classification.Load read through brainfs: symlinked or special files no longer reach the prompt, written pages or the I1 sovereignty config, and a FIFO no longer hangs ingest or startup. A symlinked/special classification.yaml is a hard error; an absent one still yields defaults.

Refs #157. Adversarial review: BLOCKED, BLOCKED, PASS. Follow-ups: #158 (write paths), #179 (FIFO tripwire).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
2026-10-05 18:54:42 +00:00
mathiasandClaude Opus 5.5 a6da93332d feat(brain): audit-log every tools/call; server-owned write provenance (#147)
CI / Lint / Test / Vet (push) Successful in 39s
CI / Mirror to GitHub (push) Successful in 4s
Closes #144 (audit line per tools/call) and #145 (origin/written_by stamped on every model-reachable write, surfaced in brain_query/brain_get).

Passed pr-gatekeeper after four rounds of adversarial review, verified by execution: #147

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
2026-10-01 21:06:36 +00:00
mathias 9a37881441 feat(pipeline): feed the current source page into the extraction prompt (#106) (#132)
CI / Lint / Test / Vet (push) Successful in 39s
CI / Mirror to GitHub (push) Successful in 4s
Co-authored-by: Mathias <mthbqv@gmail.com>
2026-09-23 05:45:47 +00:00
mathiasandClaude Sonnet 5 cdafdf6e0f fix(wiki): surface merge conflicts as textual-diff warnings (closes #104)
CI / Lint / Test / Vet (push) Successful in 33s
CI / Mirror to GitHub (push) Successful in 4s
Textual-diff conflict detection only (semantic contradiction detection via brain-gardener Judge deferred, confirmed by Mathias, comment 12594). Includes canonicalBlock whitespace-run normalization fix for the disagreement comparison.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: Mathias <mthbqv@gmail.com>
2026-09-23 05:22:27 +00:00
mathias b340f86a45 feat(pipeline): skip extraction when the source has not changed (#105) (#126)
CI / Lint / Test / Vet (push) Successful in 40s
CI / Mirror to GitHub (push) Successful in 4s
Co-authored-by: Mathias <mthbqv@gmail.com>
2026-09-23 05:09:30 +00:00
mathias f450a6c84f feat(pipeline): ingest revises the existing page instead of clobbering it (#103)
CI / Lint / Test / Vet (push) Successful in 32s
CI / Mirror to GitHub (push) Successful in 5s
fix(wiki): give the preamble its own field instead of overloading heading=="" (#103)

Round 3 review: verified structurally that the preamble is no longer in the sections slice or the idx map, so it cannot collide with a degenerate `## ` heading. Flip-tested by reverting to the heading=="" model and confirming TestMerge_DegenerateEmptyHeadingDoesNotCollideWithPreamble goes red. Adversarial pass (multiple degenerate headings, frontmatter-only page, unicode around the ## boundary, 3x self-merge idempotency with preamble+degenerate) all held. CI green on 09aeaa9 (run 7820).

Co-authored-by: Mathias <mthbqv@gmail.com>
2026-09-22 21:43:04 +00:00
mathiasandClaude Sonnet 5 6d014c1d0f fix(ingest): CanonicalizeLinks strips wing:/wiki/ prefix and repairs path-style links (#87)
CI / Mirror to GitHub (push) Has been skipped
CI / Lint / Test / Vet (push) Failing after 2s
plainLinkRE matches any [[...]] without a pipe, including path-prefixed
forms like [[wing:homelab/failures/foo]] or [[wiki/agentsquad/decisions/bar]]
that the LLM extraction step occasionally emits. Title-lookup against
titleToSlug always fails for these (they're paths, not titles), so they
were silently left broken.

Before falling to the unknown-wikilink warning, strip a known wing:/wiki/
root prefix and emit the clean wing/hall/slug path directly — matching
the brain-graph path-style link convention.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Roq1ajWKR5f1hG5Df9wC6A
2026-07-27 14:32:31 +02:00
mathiasandClaude Sonnet 5 6ad275b505 fix(ingest): thread source/author/published frontmatter through extraction (#85)
CI / Lint / Test / Vet (push) Failing after 1s
CI / Mirror to GitHub (push) Has been skipped
pipeline.Run parses source/author/published from the raw content's own
frontmatter and applies them deterministically to source-type RawPages
after LLM extraction — never relies on the LLM to copy them through.
buildFrontmatter now emits all three (source pages only) when present.

Backfill of the 46 already-affected wiki/sources/*.md notes is a
separate concern per the issue, not done here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Roq1ajWKR5f1hG5Df9wC6A
2026-07-26 23:42:33 +02:00
mathiasandClaude Opus 4.6 0a70d9e972 feat(pipeline): add POST /ingest-raw for direct batch ingestion without LLM
CI / Lint / Test / Vet (push) Successful in 9s
CI / Mirror to GitHub (push) Has been skipped
Allows callers to provide pre-structured RawPage data directly, bypassing the
LLM extraction step. The pipeline still handles slug computation, frontmatter,
link canonicalization, source back-references, and dedup — only the extraction
is skipped. Useful when a more capable model or manual curation produces the
structured data.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-24 11:15:59 +02:00
mathiasandClaude Sonnet 4.6 3e9a648115 fix(pipeline): repair invalid JSON escape sequences from LLM output before parsing
CI / Lint / Test / Vet (push) Successful in 11s
CI / Mirror to GitHub (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 22:04:27 +02:00
mathiasandClaude Sonnet 4.6 923a665365 fix(pipeline): skip RawPages with empty title in BuildPages instead of producing broken paths
CI / Lint / Test / Vet (push) Successful in 9s
CI / Mirror to GitHub (push) Has been skipped
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 19:55:37 +02:00
mathiasandClaude Sonnet 4.6 537aebc302 feat(pipeline): update system prompt for new LLM JSON contract (no slugs)
- Change prompt to reflect new output format: title, type, subtype, domain, content
- Remove slug/path generation responsibility from LLM — pipeline now handles it
- Wikilinks change from [[slug|Display Name]] to [[Display Name]] only
- LLM no longer includes frontmatter or paths in output

docs(schema): update LLM output format and wikilink convention for Level 3

- Specify JSON schema: title, type, subtype, domain, content fields
- Remove frontmatter requirements from schema output (handled by pipeline)
- Simplify wikilink format to [[Display Name]] — no slug or pipe
- Pipeline now responsible for slug generation and frontmatter construction

These changes shift slug/frontmatter generation from LLM to pipeline,
reducing cognitive load on the model and improving control over output.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 19:45:21 +02:00
mathiasandClaude Sonnet 4.6 de35d4dbb0 feat(pipeline): wire ParseRawPages+BuildPages+CanonicalizeLinks into Run
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 19:07:33 +02:00
mathias 26855f69b0 feat(pipeline): add CanonicalizeLinks — convert [[Display Name]] to [[slug|Display Name]] 2026-04-23 18:59:10 +02:00
mathias a7b363d589 fix(pipeline): quote YAML scalar fields in buildFrontmatter to prevent injection 2026-04-23 18:56:39 +02:00
mathias 7b57051af8 feat(pipeline): add BuildPages — compute slugs/paths/frontmatter from RawPage 2026-04-23 18:50:37 +02:00
mathiasandClaude Sonnet 4.6 a620f6cb01 fix(pipeline): guard empty-title bridge + skip stale integration tests until task4
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:46:07 +02:00
mathiasandClaude Sonnet 4.6 26b5636b43 feat(pipeline): replace ParsePages with ParseRawPages + RawPage type
Strips slug authority from the LLM. The new RawPage type carries only
{title, type, subtype, domain, content} — no paths or frontmatter.
Pipeline will derive slugs deterministically (Task 4).

pipeline.go gets a temporary bridge stub (TODO task4) to keep the
package compiling between tasks.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-23 18:41:33 +02:00
mathias 1605624668 feat(pipeline): add POST /backfill-refs endpoint to retroactively inject source back-references 2026-04-23 16:50:00 +02:00
mathias 3c2bd9268c feat(pipeline): wire source back-reference injection into Run 2026-04-23 16:36:22 +02:00
mathias 29727ec2a5 feat(pipeline): inject source back-references into concept and entity pages 2026-04-23 16:35:47 +02:00
mathias 3607920601 fix(lint): resolve all errcheck violations in ingestion module
cd / Build and deploy (push) Successful in 10s
CI / Lint / Test / Vet (push) Successful in 10s
CI / Mirror to GitHub (push) Successful in 3s
2026-04-23 16:20:59 +02:00
mathias 53e46781b1 feat(pipeline): resolve proposed pages against inventory before writing 2026-04-23 16:00:31 +02:00
mathias e9b5cc401c feat(pipeline): add fuzzy entity resolution to prevent slug proliferation 2026-04-23 15:59:36 +02:00
mathiasandClaude Sonnet 4.6 1b0706f270 chore(brain): rename CLAUDE.md to schema.md for clarity
CLAUDE.md has a specific meaning in the Claude Code ecosystem (agent
instructions). The wiki schema for the ingestion pipeline should live
in schema.md to avoid confusion.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 23:06:32 +02:00
mathiasandClaude Sonnet 4.6 04fefe8e9c fix(ingestion): wrap naked error returns and harden mustJSON helper
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:51:19 +02:00
mathiasandClaude Sonnet 4.6 103f4d90bf feat(ingestion): add pipeline orchestrator with prompt builder
Adds prompt.go (BuildPrompt + systemPrompt) and pipeline.go (Run, Config,
Result, mergeAll) that wire chunking, LLM calls, parse, merge, index rebuild,
and log append into a single ingestion pipeline. Includes integration tests
covering write, dry-run, and duplicate-path merge scenarios.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 22:45:19 +02:00
mathias 9b11719481 feat(ingestion): add content chunking and LLM JSON output parser 2026-04-22 22:37:14 +02:00