Repository navigation
kb init: skeleton stage (docs map, doc invariants, seeds) with its own runtime, budget and publisher - #248
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 41157519bc
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| self.record = InitRecord(stage="skeleton", repo=lc.repo, pin=pin, kb_base_sha=base_sha, | ||
| inputs_digest=digest, started_at=float(int(rt.clock())), | ||
| dry_run=self.dry_run, notes=list(self.notes)) | ||
| self.budget = Budget(init.budget_usd) |
There was a problem hiding this comment.
Carry prior spend into same-stage retries
When a model call fails, the stage is recorded as blocked with its spend, but a retry with the same inputs ignores that record and creates a fresh zero-spend Budget. Repeated retries can therefore spend budget_usd each time while overwriting the record with only the latest attempt's cost, defeating the advertised hard per-stage ceiling. Initialize the retry budget from the matching prior record's spent_usd or prevent rerunning an exhausted stage.
Useful? React with 👍 / 👎.
| seen.add(slug) | ||
| owners.append({"owner": slug, "path": page, "signals": signals, "scope_prefixes": prefixes}) | ||
| if not owners: | ||
| owners.append({"owner": _slug(self.lifecycle.repo), "path": f"{self.repo_dir}/{INDEX_NAME}", | ||
| "signals": [self.lifecycle.repo], "scope_prefixes": self._root_prefixes()}) |
There was a problem hiding this comment.
Fall back when a generated owner has no valid prefixes
If the model supplies a valid owner/page but every scope_prefixes entry is absent or malformed, this still appends an owner with an empty prefix list. Because owners is then nonempty, the repository-wide fallback is skipped, leaving changed-file routing unable to select any owner for that repository. Only append generated owners that retain at least one valid prefix, or add the fallback when none of the resulting owners can route files.
Useful? React with 👍 / 👎.
| title = f"kb init({lc.repo}): skeleton" | ||
| body = render_pr_body(record, lc) |
There was a problem hiding this comment.
Populate spend before rendering the PR body
The PR body is rendered before record.spent_usd is copied from the budget in run()'s finally block, so every prepared or published PR reports $0.00 even when generator and judge calls incurred cost. The saved record is corrected afterward, but the already persisted PR body is not; update the record from the budget before calling render_pr_body.
Useful? React with 👍 / 👎.
| self.record.seeds.append({"origin": origin, "kb_sha": self.record.kb_base_sha, | ||
| "new_page": page, "new_rule_ids": [c.rule_id for c in found]}) |
There was a problem hiding this comment.
Record seed provenance after screening
Seed provenance is recorded for every preliminary candidate before D5, evidence, pinned-claim, and judge screening. If any candidate is later dropped—or all candidates for the seed are dropped—the record and PR body still claim those rule IDs and possibly a nonexistent new_page were produced. Build record.seeds from the kept candidates after screening so the review metadata matches the actual change.
Useful? React with 👍 / 👎.
| if not isinstance(data.get("owners", []), list) or not isinstance(data.get("general_links", []), list): | ||
| raise ValueError("owners and general_links must be lists") |
There was a problem hiding this comment.
Validate generated general-link entries before use
The map response validator only checks that general_links is a list, but _write_map assumes every element is a mapping and calls .get() on it. A schema-valid model response such as "general_links": ["general/rules.md"] therefore raises an uncaught AttributeError, leaving the stage record in started rather than producing a controlled blocked result. Validate each link object's shape alongside the outer list.
Useful? React with 👍 / 👎.
Stage 1 of kb init (design kb-init v3): bootstrap a repository's knowledge base from its docs and seed pages, as one human-merged PR. - init_budget: hard per-stage ceiling by reservation. A generator call reserves threshold + one worst-case request (claude's --max-budget-usd only stops the next request); models without a price are refused before any call. Judge calls are accounted at a fixed cost. - init_support: InitRuntime (never KbRuntime, so never kb.db), the per-stage InitRecord, the upstream pin (bare mirror; the pin defaults to the mirror's HEAD, i.e. the upstream default branch, never an assumed main; safe export; PinnedObserver), model calls through the budget, claim/evidence and outside-knowledge path checks, the CI knowledge validators on the result, and InitPublisher (one deterministic commit on an exact base, pushed to a branch that must not exist, PR via the owner's gh; or a local dry run). Writes need ALLOW_PUSH=1, ALLOW_POST=1 and KB_INIT_GIT_AUTHOR (checked before any model call); private upstreams are always dry runs. INIT_PATHS allows knowledge/repos/_index.md, the shared list a new repository joins. - init_stages: run_stage + validate_change (the only check_changeset(bootstrap=True) caller). Skeleton: map call (entry page, architecture page, routes, general links), doc-invariant rules, adapted seed pages. Every rule is D5-filtered, evidenced and checked at the pin, then PLACED on a running tree before judging (a page that fills up sends the rule to a sibling page), so the advisory judge sees each rule where it is written; fail verdicts are stripped. Existing pages are only appended to; routes start from owners read deterministically from existing sub-directory entry pages (code paths they name that exist at the pin); findings on existing pages go to the PR checklist; a failed push is recorded as blocked. suggest_seeds ranks candidates without a model call. - InitConfig gains generator_call_usd and harness_overhead_bytes; build_doc_corpus takes optional globs (doc_files). - knowledge.init step, kb-init playbook, `kb init REPO --stage S [--dry-run] [--pin SHA]` and `kb init REPO --suggest-seeds`, dispatched before any ledger is opened. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
L1 reads any line starting with "## <Word>" as a rule ID (lifecycle.RULE_HEADING / ANY_RULE_HEADING), so a model-written "## Overview" or "## SERV-1 — x" in architecture.md or the entry page intro could collide with real rule IDs or masquerade as a rule. Every non-rule page the skeleton writes now goes through neutral_headings: an ATX heading outside code becomes a bold paragraph, any other line that starts with "#" is escaped, and such a line inside a fence is indented by one space. No line starts with "#", so the rule regexes (all anchored at column 0) can never match. The entry page's list heading is bold too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
b10547b to
7e98f94
Compare
Summary
Stage 1 of
kb init(design kb-init v3): bootstrap a repository's knowledge base from its docs and seed pages, as one human-merged PR. This PR adds the stage and everything later stages reuse.kb_service/init_budget.py: the per-stagebudget_usdis a hard ceiling, enforced by reservation. Before dispatch, a generator call reserves its threshold plus one worst-case request,input_bytes × 1.25 × in_price + max_output_tokens × out_price. This is needed because claude's--max-budget-usdonly stops the next request (Add a per-call spend threshold (claude --max-budget-usd) and report call cost #241). A generator model with no price is refused before any call. Judge calls are accounted atjudge_call_usd.kb_service/init_support.py:InitRuntime: neverKbRuntime, so init never toucheskb.db.InitRecord: one per stage under<state>/init/<repo>/<stage>.json.UpstreamPin: a bare mirror. The pin is the upstream's current default branch, refreshed vials-remote --symref, never an assumedmain. The export refuses links and escapes. Also provides aPinnedObserver.generate/judge: every model call goes through the budget.InitPublisher: one deterministic commit on an exact base, pushed to a branch that must be absent, with the PR opened through the owner'sgh.kb_service/init_stages.py:run_stageandvalidate_change. This is the onlycheck_changeset(bootstrap=True)caller, as kb init: let L1 accept new-directory indexes in bootstrap mode, and check index links #243's guard test requires.general/links), a doc-invariant call, and one adapt call perrepos/<other>/seed page.failverdicts.apply_operations. Existing pages are only appended to.source_roots. Model owners come after and must be this repository's own pages.suggest_seedsranks candidates without a model call.knowledge.initstep,kb-initplaybook, and CLIkb init REPO --stage S [--dry-run] [--pin SHA]/kb init REPO --suggest-seeds. The CLI dispatchesinitbefore any ledger is opened.InitConfiggainsgenerator_call_usdandharness_overhead_bytes.build_doc_corpusaccepts optionalglobs, viadoc_files.Deviations from the spec (kb-init-stage-spec.md, PR C)
validate_changelives ininit_stages.py, notinit_support.py: kb init: let L1 accept new-directory indexes in bootstrap mode, and check index links #243's guard test allowsbootstrap=Trueonly ininit_stages.py.validate_seeds(Path). It is the same rule, and no checkout is needed._routes.yamlonly when it is absent. An existing one is left to the modules stage and noted on the checklist.doc/knowledge/SCHEMA.md's taxonomy, because the wiki lint requires it. Otherwise the stage is blocked with an instruction to add the tag in the adapter PR (relevant for jiuwenswarm).facts.attestreport at the upstream head (design §9 item 5) is not a separate step. The pin is the default-branch head at stage start, unless--pinnames an older commit.KB_INIT_GIT_AUTHORis checked before any model call.blocked.INIT_PATHSallowsknowledge/repos/_index.md.Tests
test/test_kb_init_skeleton.py, 30 tests, fully offline, against a toy upstream, a toy knowledge repository, scripted models and a fakedgh:check_tree,check_index_links, the pinned checks, and the realcheck_knowledge_tree.py/check_wiki_lint.py.kb.db; step and playbook are registered.test/test_kb_init_config.pyis extended for the two new fields.--strict, knowledge linters.Part 5/9 of kb init (design v3, sol-approved). Stacked on #239 #240 #241 #242 #243 via kb/init-integration; retarget to main after those merge.
🤖 Generated with Claude Code