Skip to content

kb init: /imkbinit skill to drive the stages - #250

Merged
tzhouam merged 12 commits into
mainfrom
kb/init-skill
Sep 30, 2026
Merged

tzhouam merged 12 commits into
mainfrom
kb/init-skill

Conversation

@tzhouam

@tzhouam tzhouam commented Sep 30, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Adds /imkbinit ($imkbinit in Codex), a thin host skill that drives kb init one reviewed stage at a time:

  1. Resolve the repository. Maps an alias, owner/name or URL to the adapter's repos/<name>. If there is no adapter, it stops, because the repository needs its own adapter PR first. If the adapter has no seeds, it points at --suggest-seeds.
  2. Find the next stage from <state-dir>/init/<repo>/<stage>.json. The order is skeleton → modules → deepen → harvest-calibration.
    • When publishing, a stage counts as done only when its PR is MERGED, so a previewed stage gets published rather than skipped.
    • In a dry run, a status: dry_run record also counts as done.
  3. Run that one stage. Dry run is the default. It publishes only when the user asks, and then names the missing ALLOW_PUSH / ALLOW_POST / KB_INIT_GIT_AUTHOR instead of setting them.
  4. Summarise the record and PR body: coverage, judge table, dropped rules, checklist, spend.
  5. Stop for the owner. It never edits pages, runs two stages in one go, or merges. It says what each stage opens: harvest-calibration refuses to open a PR with fewer than five owner or synthetic bad cases, and auto_merge still needs kb calibrate plus the shadow period (the skill never flips it).

Launcher: an MCP-only install puts no CLI on PATH. The skill uses infermatrix-copilot if it is installed, and otherwise uvx --from "<infermatrix-root>[kb]" infermatrix-copilot. I checked the fallback: it builds from a checkout and lists kb init.

Install: scripts/install_mcp.py::_install_skills copies every directory under plugins/infermatrix-copilot/skills/, so the installer itself needs no change. The install tests now assert that imkbinit is installed and that {{INFERMATRIX_COPILOT_ROOT}} is replaced. The README and the host guides (skill count, boundary table, open-skill list) now name it.

Tests

  • New test/test_imkbinit_skill.py:
    • Checks the skill's contract.
    • Pins its stage names and flags to the CLI's own (init_support.STAGES, kb_service/cli.py).
    • Checks the docs.
  • test/test_install_mcp_script.py is extended to cover the Cursor, zcode and .agents installs.
  • The commit gate (gpt-6.1-sol-high) approved on iteration 3. It caught three issues, all fixed:
    • the skill tried to run stages that weren't implemented yet (that fix is superseded now that all four stages exist)
    • a dry run counted as done in publish mode
    • there was no launcher for MCP-only installs
  • Local CI mirror (imc-check.sh, the full suite plus the doc, SPEC and knowledge linters): rc=0.

Part 9/9 of kb init (design v3, sol-approved), stacked on #254 (harvest), which sits on #252 (deepen), #251 (modules) and #248 (skeleton).

🤖 Generated with Claude Code

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 30, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-09-30T11:32:38.888892Z b419a27 PR opened
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b419a27718

ℹ️ 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".

Comment on lines +45 to +48
`kb init` takes the knowledge repository name: the `repos/<name>` in an
adapter's `knowledge.repo_subdir`. Match the user's alias, `owner/name` or URL
against `repo.full_name` in `adapters/*/manifest.yaml`. With no match, stop: a
repository needs its own adapter PR (with a `knowledge_lifecycle.init` block)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Resolve declared repository aliases before stopping

When the user supplies a declared alias that differs from repo.full_name—for example afd_plugin, which is listed in adapters/afd_plugin/manifest.yaml—this instruction compares it only with vllm-project/afd-plugin and can incorrectly conclude that no adapter exists. Match against repo.aliases as well as normalized owner/name and URL forms, then derive the CLI repository name from knowledge.repo_subdir.

Useful? React with 👍 / 👎.

Comment on lines +70 to +72
The next stage is the first one that isn't done. If an earlier stage's PR is
open, stop and say it's waiting for the owner's review. If a record says
`blocked`, report its `problems` and stop.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle closed unmerged stage PRs before rerunning

When a stage PR was closed without being merged, that stage is selected as the first incomplete stage but is not covered by the open-PR stop condition, so the skill reruns the CLI. For an unchanged input digest, run_stage returns the existing published record without opening another PR, leaving every subsequent invocation reporting the same closed PR and unable to progress. Detect the non-merged closed state and provide explicit reset/retry remediation instead of invoking the stage again.

Useful? React with 👍 / 👎.

@tzhouam
tzhouam changed the base branch from kb/init-skeleton to kb/init-harvest September 30, 2026 12:47
tzhouam and others added 5 commits September 30, 2026 23:36
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>
Stage 2 of kb init (design v3 §7.1-7.2): route every code module.

- init_stages: the skeleton's flow becomes a _Stage base shared by every
  stage (pin, record, rule screening and placement, the deterministic
  checks as _conclude, the publication); _Skeleton keeps only its own
  steps. New: the stage chain. Each earlier stage must be merged (record
  published and gh pr view MERGED, so main holds it), or, for a dry run
  of this stage, dry-run: its snapshot is overlaid on main as the base and
  handed to the knowledge validators with this stage's change. A stage
  with nothing to change is recorded "empty", not blocked. Later stages
  keep the earlier stages' pin unless --pin says otherwise.
- init_modules: scan_modules_at_depth at the pin (language from the
  adapter's repo.language; none or unknown -> no scan, checklist). A
  module an owner partly reaches is absorbed: its prefix is appended to
  that owner's scope_prefixes. Every other module gets one bounded
  generator call (file names, signatures, leading docstrings, never
  bodies) and a map card: a prose page (type architecture) in an existing
  components/<group>/ (index append-only) or a new group with its own
  _index.md and route owner, linked upward. Sections are bold labels: the
  knowledge format reads any "## <Word>" heading as a rule ID. The route
  table is only appended to (routes_append_only). Budget or card cap ->
  the cards written so far still make a PR; the rest is listed.
- InitRecord.coverage: the stage's coverage report, rendered as a
  before/after table in the PR body.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Stage 3 of kb init (design v3 §7.3, §9 PR 3).

- UpstreamPin.first_parent_changes: the PR window, read offline from the
  mirror: the files each of the last pr_window_count first-parent commits
  up to the pin changed, no older than pr_window_max_age_days before the
  PIN's commit time (a function of the pin, never of the clock).
- init_deepen: modules by churn in the window, highest first. A module no
  route reaches, or whose owner already bears rules (the page itself, or a
  page in the same directory, has an active rule), is skipped. Every other
  module gets one generator call that reads its code at the pin (numbered,
  byte-bounded) and proposes rules with line-ranged evidence, which must lie
  inside the module. They go through the shared screening (D5, pinned
  evidence and claims, placement, advisory judge) and are appended to the
  owner's rule page, or rules.md beside it; existing rules are never
  edited. Stops at coverage_target (rule-bearing PR-weighted), when the
  budget runs out, or at the end of the list. The record reports routed
  and rule-bearing coverage before/after and the hot paths still without
  rules.
- The shadow flip in the same PR: flip_to_shadow edits exactly the
  enabled / mode lines of the adapter's knowledge_lifecycle block
  (inserting enabled under the header if missing); comments and every
  other line are kept. _check_flip accepts only that manifest,
  check_flip_to_shadow, and parse_lifecycle on the head (on, shadow, same
  knowledge dir). A manifest without the block blocks before any model
  call.
- init_modules.scan_modules is now a module function both stages use.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The last kb init stage (design v3 §11). After the deepen PR merged (a dry
run chains on the dry-run snapshots), it compares what the advisory judge
said about every rule init wrote with what the owner merged, and proposes
adapters/<adapter>/kb-calibration/cases/*.json plus
knowledge_lifecycle.calibration_set: kb-calibration as its own PR.

- Stage records now keep the judged rule text and its pinned evidence in
  each verdict, so rules the judge stripped or the owner deleted can be
  turned into cases.
- Labels follow the design table: kept pass/unsure/unjudged -> good
  (source owner); stripped fail -> bad (source judge); deleted or
  rewritten pass -> bad with override; deleted or rewritten
  unsure/unjudged -> bad (source owner). A rewritten rule's case holds
  the text the judge saw.
- Cases are self-contained in the load_cases shape: the page without the
  rule as base, the judged text re-applied through apply_operations as
  head, evidence re-read from the pinned mirror. A case that does not pass
  L1 on its own is left out and noted. Case IDs carry the stage: an ID
  one stage's judge stripped can be reassigned by a later stage.
- Deterministic mutations of good rules (broken path claim, shifted
  evidence range, negated invariant, a sibling module's evidence) balance
  the set and top owner-sourced bad cases up to five (a broken path must
  really be absent at the pin, a moved range must show different text);
  with fewer than five bad cases from the owner or from mutations the
  stage is blocked, since the judge's own fails cannot calibrate it; so
  it is with no good case at all (kb calibrate never passes without one).
- The manifest edit is one line (set_calibration_set), checked with
  check_lifecycle_flip(allowed=CALIBRATION_KEYS) and parse_lifecycle;
  existing cases are never overwritten.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
tzhouam and others added 7 commits September 30, 2026 23:37
Found by the first afd-plugin pilot (skeleton, dry run):

- The adapter's repos/ seed was a type: guide page; the skeleton only
  adapted type: rule pages, so it was skipped with no trace, which the
  design forbids (a seed is never skipped silently). Any content page a
  seed names is now adapted (indexes are navigation, not content). A
  seed naming no content page goes on the checklist, a page that
  transfers no rule goes in the notes, and a budget stop lists that
  seed and every one after it as unfinished.
- The PR body said "Model spend (accounted): $0.00" while the record
  held $7.42: spent_usd was written in the stage's finally block, after
  the body was rendered. It is now written before rendering.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dule with an ancestor prefix

The afd-plugin pilot (dry run) made the coverage metric meaningless. The
skeleton wrote an aggregate owner "components" listing its components'
prefixes; the modules stage absorbed the package root by appending
afd_plugin/ to it, a catch-all that matches every file; deepen gave the
hottest module to that owner (it reached as many files as the component
owner and came first in routing order) and wrote 12 rules to
components/rules.md; and pr_weighted_coverage counted a file rule-bearing
when ANY matching owner had rules, so coverage jumped 10% -> 100% and
deepen stopped with 8 hot modules untouched.

- init_coverage.most_specific: the owners with the longest matching
  prefix, then the deepest page (a component's own page beats an
  aggregate entry page listing the same area); rule-bearing now uses
  only those. shadowing() lists directory prefixes that are strict
  ancestors of another owner's prefix.
- init_modules: absorption picks the owner that owns most of the
  module's files most specifically, and cover_prefixes adds only
  prefixes for the module's own unrouted files: the module directory
  when no other module or OTHER owner's prefix lies under it, else the
  highest such directory per file, else the file. A module needing more
  than 20 prefixes gets a map card instead; card owners use the same
  cover.
- init_deepen: a module's owner is its most specific owner; "already
  bears rules" and the uncovered-hot list use the same rule.
- skeleton: a checklist note names every owner prefix that shadows
  other owners' prefixes, so the owner can narrow it.

Replayed offline on the pilot's pin: the root absorbs 4 files instead of
afd_plugin/, the hottest module goes to execution-platforms, and rules
on the aggregate page now count for 4.5%, not 100%.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pped

The afd-plugin pilot re-run on #258 wrote 36 rules on 3 owner pages,
reached 74% rule-bearing coverage of an 85% target, and stopped with no
note and nothing unfinished. The hottest files still without rules
(v1/worker/attention_model_runner.py, ffn_model_runner.py, ...) sit in
modules already visited, but their most specific owners (attention-runtime,
ffn-runtime) differ from the module's majority owner, and the loop went
module by module, so it never reached them.

- Deepen now works on groups: the files of one module that share one most
  specific owner. Groups are ordered by churn; one bounded generator call
  per group sees only that group's files, and evidence must lie in them.
  A group whose owner already bears rules is skipped (counted). Caps,
  budget reservation, D5, pinned claims and the advisory judge are
  unchanged.
- The stop reason is always recorded in the notes (and so the PR body):
  target reached, budget exhausted (the remaining groups stay in
  unfinished, labelled with their owner), or "every hot group visited;
  target not reached (X% < Y%)" with the five hottest files still without
  rules. Hot files no route reaches are listed too. The pass never ends
  below target silently.

Replayed offline on the pilot (no model calls): from the pre-deepen state
the loop makes 5 calls (execution-platforms, model-integration,
connectors, attention-runtime, ffn-runtime) and reaches 81.8%; after the
module-loop re-run, exactly the attention-runtime and ffn-runtime groups
remain to visit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The jiuwenswarm pilot (empty knowledge base, skeleton, dry run) blocked
after all model calls: the generated index intro linked architecture.md
and the deterministic contents list linked it again, and the
knowledge-tree validator refuses a page registered twice in its
directory index. In a new index the contents list is now each page's
only registration: intro links to listed pages become their labels
(angle destinations and titles handled; anchors and ./ ignored; images
and other targets untouched). The result is then checked with the same
CommonMark parser L1 uses (new public l1.link_targets): if a link to a
listed page survives in a form the rewrite does not know (e.g. a
reference link), the optional intro is dropped and a checklist note is
added, so a page is never registered twice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…epository at the base

The jiuwenswarm pilot ran `kb init --stage modules` with its adapter PR
still unmerged. The stage reads the adapter manifest from the knowledge
repository at the base commit; it was absent there, `_manifest_text()`
returned None, `_language()` degraded to "", and the stage recorded
`empty` with "the adapter declares no repo.language" although the
adapter declares python. That is a silent degrade (invariant 7). Deepen
and harvest already blocked on the same condition, each with its own
copy of the message.

Every stage, the skeleton included, now checks at start that the
manifest exists at the base and blocks with one shared message
(`adapter_missing`) before any model call; deepen and harvest reuse it.
The check runs before the cached-record short cut: the inputs digest
never covered the manifest, so a record from a run before this fix
would otherwise be returned as a pass.
A present manifest that declares no language still scans nothing and
says so on the checklist (never guess a language).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
A thin host skill over `infermatrix-copilot kb init`. It resolves the
repository to its adapter, reads the stage records under
<state-dir>/init/<repo>/ to find the next stage (publishing counts a stage
done only when its PR is MERGED, so a previewed stage is published rather
than skipped; a dry run also counts dry-run records), runs that
one stage (dry run by default; publishing only when asked, and then it
names the ALLOW_PUSH / ALLOW_POST / KB_INIT_GIT_AUTHOR it needs instead
of setting them), summarises the record and PR body, and stops for the
owner. It points at --suggest-seeds when the adapter has no seeds and
never edits pages, runs two stages, or merges. A stage the installed
CLI does not implement yet is reported and the skill stops.

The MCP install puts no CLI on PATH, so the skill resolves a launcher
first: infermatrix-copilot if installed, else
`uvx --from "<infermatrix-root>[kb]" infermatrix-copilot` (checked:
builds from a checkout and lists `kb init`).

The installer copies every skill directory, so it installs like the other
four; install tests and the host guides now name it. A test pins the
skill's stage names and flags to the CLI's own.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
modules (#251), deepen (#252) and harvest-calibration (#254) landed, so
the skill no longer treats them as unimplemented: it runs the next
stage and says what each one opens. The rules are unchanged: only
MERGED PRs count as done when publishing, and dry-run records chain in
dry-run mode. It notes that harvest refuses fewer than five
owner-or-synthetic bad cases, and that auto_merge still needs
kb calibrate and the shadow period (the skill never flips it).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tzhouam
tzhouam changed the base branch from kb/init-harvest to main September 30, 2026 15:38
@tzhouam
tzhouam merged commit 3118067 into main Sep 30, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant