Skip to content

feat(kb,okf): add kb init skill and okf graph-viewer renderer#80

Merged
duyet merged 4 commits into
masterfrom
kb-okf-init-viewer
Jul 5, 2026
Merged

feat(kb,okf): add kb init skill and okf graph-viewer renderer#80
duyet merged 4 commits into
masterfrom
kb-okf-init-viewer

Conversation

@duyet

@duyet duyet commented Jul 5, 2026

Copy link
Copy Markdown
Owner

Summary

  • kb gains an init skill/command that scaffolds a self-contained, OKF v0.1-conformant shared-brain kb folder (default ~/kb): AGENTS.md/CLAUDE.md/DREAM.md/MEMORY.md, a memory/ OKF bundle, raw/inbox/, and a vendored bin/kb CLI (capture, lint, sync, gen, viz, wire). Idempotent — never overwrites existing files, never touches anything outside the target without explicit confirmation (wiring global agent config is a separate, confirm-first step).
  • okf gains render_okf_viewer.py: a generic, self-contained HTML graph viewer for any OKF bundle. Reads edges from related: frontmatter, [[wikilinks]], and OKF's own [text](path.md) markdown links, so it renders correctly regardless of which linking convention a bundle uses.
  • kb's scaffold vendors a copy of the viewer script into the generated folder so a scaffolded kb keeps working even if the okf/kb Claude plugins are later uninstalled.
  • Both SKILL.mds, README.mds, plugin manifests (Claude + Codex), and both marketplace files are updated; versions bumped to 1.1.0 (minor, new feature).

Test plan

  • scaffold.sh run twice against a scratch dir — second run creates 0 files, correctly skips all pre-existing ones (idempotent).
  • Fixed a real bug found during testing: lint.sh crashed on a fresh, empty kb under bash 3.2 (macOS default) due to set -u + empty-array expansion; now exits cleanly with "0 notes yet."
  • kb capture, kb lint, kb gen, kb viz exercised end-to-end on a scaffolded kb; wikilink-based notes produce correct nodes/edges in viz.html.
  • render_okf_viewer.py also tested directly against a hand-authored OKF-style bundle using plain [text](/path.md) and [text](relative.md) markdown links (the convention the okf skill itself teaches) — edges resolved correctly.
  • validate_okf.py reports the generated bundles as conformant.
  • bash scripts/validate-plugins.sh — all manifests, cross-manifest parity, and marketplace files pass.
  • Scanned for personal/environment-specific info (grep -rniE "duyet|/Users/|git@github") — only pre-existing, allowed author/homepage metadata and the published marketplace repo name remain; no leaks in the new templates/scripts.

https://claude.ai/code/session_019Rj1eySDQoLJXkKpPJonS8

Summary by CodeRabbit

  • New Features

    • Added a new KB initialization workflow that can scaffold a ready-to-use knowledge base, including starter docs, inbox folders, and CLI tooling.
    • Added OKF bundle rendering support, including an interactive graph viewer and generated index pages for easier browsing.
  • Documentation

    • Expanded the KB and OKF guides with setup, usage, and command examples for initialization and rendering.
    • Updated plugin descriptions and version info to reflect the new capabilities.

kb gains an `init` skill/command that scaffolds a self-contained,
OKF v0.1-conformant shared-brain folder (AGENTS.md/DREAM.md/MEMORY.md,
memory/, raw/inbox/, and a vendored bin/kb CLI) — idempotent, and never
touches anything outside the target folder without explicit confirmation.

okf gains render_okf_viewer.py: a generic self-contained HTML graph
viewer for any OKF bundle, reading edges from related: frontmatter,
[[wikilinks]], and OKF's own [text](path.md) markdown links. kb's
scaffold vendors a copy so a generated kb keeps working standalone.

Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@sourcery-ai sourcery-ai 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.

Sorry @duyet, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@github-actions

github-actions Bot commented Jul 5, 2026

Copy link
Copy Markdown

Welcome! 👋

Thank you for your first contribution to this project! We appreciate you taking the time to improve our codebase.

Next Steps

  • A maintainer will review your PR shortly
  • Please check for any review comments and address them
  • Feel free to ask questions if anything is unclear

Contribution Guidelines

  • Ensure all tests pass
  • Update documentation if needed
  • Keep changes focused and atomic

Thanks again for contributing! 🎉

@coderabbitai

coderabbitai Bot commented Jul 5, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@duyet, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 27 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: a395c6c1-9019-462c-ba6d-b42dcc7fb971

📥 Commits

Reviewing files that changed from the base of the PR and between 41400a4 and ecd9dd9.

📒 Files selected for processing (12)
  • kb/skills/init/SKILL.md
  • kb/skills/init/scripts/scaffold.sh
  • kb/skills/init/templates/AGENTS.md
  • kb/skills/init/templates/CLAUDE.md
  • kb/skills/init/templates/README.md
  • kb/skills/init/templates/bin/kb
  • kb/skills/init/templates/scripts/lint.sh
  • kb/skills/init/templates/scripts/render_okf_viewer.py
  • kb/skills/init/templates/scripts/sync.sh
  • kb/skills/init/templates/scripts/wire.sh
  • okf/skills/okf/scripts/render_okf_viewer.py
  • scripts/install-antigravity.sh
📝 Walkthrough

Walkthrough

This PR adds a new init skill to the kb plugin that scaffolds a shared-brain knowledge base directory (protocol docs, OKF bundle, CLI, inbox), adds a render_okf_viewer.py script (duplicated in kb templates and okf skill) that generates markdown indexes and a self-contained viz.html graph viewer, and bumps plugin manifest versions/descriptions accordingly.

Changes

KB init skill and scaffolding

Layer / File(s) Summary
Init skill and command docs
kb/commands/init.md, kb/skills/init/SKILL.md, kb/README.md
Adds /init command and skill spec describing scaffolding scope, idempotency, and follow-ups; updates README with init usage and skill description.
Scaffold script
kb/skills/init/scripts/scaffold.sh
Implements idempotent scaffolding: copies templates, creates directories/placeholders, initializes state/MEMORY.md, runs the viewer renderer, prints summary and next steps.
Protocol and doc templates
kb/skills/init/templates/AGENTS.md, CLAUDE.md, DREAM.md, README.md, memory/_TEMPLATE.md, raw/README.md, gitignore
Adds template documents defining memory protocol, note schema, dream consolidation workflow, and repo scaffolding structure.
CLI and maintenance scripts
kb/skills/init/templates/bin/kb, scripts/lint.sh, scripts/sync.sh, scripts/wire.sh
Adds kb CLI with capture/ingest/dream/sync/lint/wire/gen/viz/autosync commands, note-linting script, git sync script, and agent-config wiring script.

Estimated code review effort: 3 (Moderate) | ~30 minutes

OKF Graph Viewer Rendering

Layer / File(s) Summary
Renderer in kb templates
kb/skills/init/templates/scripts/render_okf_viewer.py
Adds a stdlib-only script that parses frontmatter, collects concepts, writes progressive index.md files, builds a graph model, and renders a Cytoscape+Marked viz.html.
Renderer in okf skill and docs
okf/skills/okf/scripts/render_okf_viewer.py, okf/README.md, okf/skills/okf/SKILL.md
Adds the equivalent renderer under the okf skill and documents rendering usage, edge sources, and a new checklist item.

Manifest and Metadata Updates

Layer / File(s) Summary
Version and description bumps
marketplace.json, .claude-plugin/marketplace.json, kb/.claude-plugin/plugin.json, kb/.codex-plugin/plugin.json, okf/.claude-plugin/plugin.json, okf/.codex-plugin/plugin.json
Bumps kb to 1.1.0 and okf to 1.1.0, updates descriptions/shortDescriptions to mention init/render, and refreshes metadata.lastUpdated.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Estimated code review effort: 3 (Moderate) | ~35 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant InitCommand as /init command
  participant ScaffoldSh as scaffold.sh
  participant Templates as templates/*
  participant Renderer as render_okf_viewer.py

  User->>InitCommand: run /init [path]
  InitCommand->>ScaffoldSh: invoke init skill
  ScaffoldSh->>Templates: copy AGENTS.md, CLAUDE.md, DREAM.md, bin/kb, scripts
  ScaffoldSh->>ScaffoldSh: create memory/, raw/inbox, .agent/state.json, MEMORY.md
  ScaffoldSh->>Renderer: run render_okf_viewer.py memory/
  Renderer-->>ScaffoldSh: write index.md files + viz.html
  ScaffoldSh-->>User: report created/skipped files, next steps
Loading
sequenceDiagram
  participant User
  participant KBCLI as bin/kb
  participant Renderer as render_okf_viewer.py
  participant FS as Filesystem

  User->>KBCLI: kb gen / kb viz
  KBCLI->>Renderer: invoke on memory/ bundle
  Renderer->>FS: walk bundle, parse frontmatter+wikilinks+links
  Renderer->>FS: write index.md per directory
  Renderer->>FS: write viz.html with embedded graph JSON
  Renderer-->>KBCLI: print summary counts
  KBCLI-->>User: optionally open viz.html
Loading

Poem

A rabbit scaffolds burrows deep and wide,
With memory notes and dreams inside,
A viewer graph now blooms in view,
Wikilinks connecting old and new,
Hop, commit, and sync — the brain shared true! 🐇🕸️✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: adding a kb init skill and an OKF graph-viewer renderer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch kb-okf-init-viewer

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new init skill for the kb plugin to scaffold a self-contained, OKF v0.1-conformant knowledge base, alongside updates to the okf plugin to render bundles as interactive graph viewers. Feedback focuses on enhancing the robustness and security of the helper scripts. Key recommendations include ignoring hidden directories and handling UTF-8 BOM files in render_okf_viewer.py, escaping JSON output to prevent XSS vulnerabilities, adding safety checks for merge conflicts in sync.sh, quoting paths with spaces in bin/kb, supporting Linux via xdg-open, and skipping non-existent files during unwiring in wire.sh.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +73 to +74
for dp, dn, fn in os.walk(root):
dn.sort()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The os.walk function does not ignore hidden directories (such as .git, .obsidian, or .agent). If the knowledge base is opened in Obsidian or contains other hidden folders, any .md files inside them (like plugin READMEs) will be incorrectly scanned and added to the graph and indexes. We should filter out hidden directories in-place.

    for dp, dn, fn in os.walk(root):
        dn[:] = [d for d in dn if not d.startswith(".")]
        dn.sort()

Comment on lines +124 to +125
subdirs = sorted(d for d in os.listdir(dirpath)
if os.path.isdir(os.path.join(dirpath, d)))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Similarly to collect, os.listdir will include hidden directories (like .git or .obsidian) when generating indexes, which results in unwanted entries in the generated index.md files. We should filter out hidden directories here as well.

Suggested change
subdirs = sorted(d for d in os.listdir(dirpath)
if os.path.isdir(os.path.join(dirpath, d)))
subdirs = sorted(d for d in os.listdir(dirpath)
if os.path.isdir(os.path.join(dirpath, d)) and not d.startswith("."))

Comment on lines +71 to +72
for dp, dn, fn in os.walk(root):
dn.sort()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

The os.walk function does not ignore hidden directories (such as .git, .obsidian, or .agent). If the knowledge base is opened in Obsidian or contains other hidden folders, any .md files inside them (like plugin READMEs) will be incorrectly scanned and added to the graph and indexes. We should filter out hidden directories in-place.

    for dp, dn, fn in os.walk(root):
        dn[:] = [d for d in dn if not d.startswith(".")]
        dn.sort()

Comment on lines +124 to +125
here = sorted(by_dir.get(dirpath, []), key=lambda c: c["title"].lower())
if not subdirs and not here:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

high

Similarly to collect, os.listdir will include hidden directories (like .git or .obsidian) when generating indexes, which results in unwanted entries in the generated index.md files. We should filter out hidden directories here as well.

        subdirs = sorted(d for d in os.listdir(dirpath)
                          if os.path.isdir(os.path.join(dirpath, d)) and not d.startswith("."))

concepts = collect(root)
idx = write_indexes(root, concepts, title)
bundle = build_bundle(root, concepts)
html = VIZ.replace("__NAME__", title).replace("__BUNDLE__", json.dumps(bundle))

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

Embedding raw JSON directly into a <script> tag using json.dumps(bundle) is vulnerable to HTML injection/XSS if any note contains </script> in its body or title. The browser will prematurely close the script block, breaking the page or executing arbitrary script. We should escape < as \u003c to prevent this.

Suggested change
html = VIZ.replace("__NAME__", title).replace("__BUNDLE__", json.dumps(bundle))
json_bundle = json.dumps(bundle).replace("<", "\\u003c")
html = VIZ.replace("__NAME__", title).replace("__BUNDLE__", json_bundle)

if not name.endswith(".md") or name in RESERVED or name.startswith("_"):
continue
path = os.path.join(dp, name)
fm, body = split_fm(open(path, encoding="utf-8").read())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If a markdown file is saved with a UTF-8 BOM (Byte Order Mark), which is common on Windows, the split_fm regex will fail to match the frontmatter because of the leading BOM character. Using utf-8-sig as the encoding automatically detects and strips the BOM.

Suggested change
fm, body = split_fm(open(path, encoding="utf-8").read())
fm, body = split_fm(open(path, encoding="utf-8-sig").read())

if not name.endswith(".md") or name in RESERVED or name.startswith("_"):
continue
path = os.path.join(dp, name)
fm, body = split_fm(open(path, encoding="utf-8").read())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If a markdown file is saved with a UTF-8 BOM (Byte Order Mark), which is common on Windows, the split_fm regex will fail to match the frontmatter because of the leading BOM character. Using utf-8-sig as the encoding automatically detects and strips the BOM.

Suggested change
fm, body = split_fm(open(path, encoding="utf-8").read())
fm, body = split_fm(open(path, encoding="utf-8-sig").read())

Comment thread kb/skills/init/templates/bin/kb Outdated
root|path) echo "$KB_DIR" ;;
autosync)
command -v crontab >/dev/null 2>&1 || { echo "crontab not available"; exit 1; }
line="*/15 * * * * $KB_DIR/scripts/sync.sh >> \$HOME/.kb-sync.log 2>&1"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If KB_DIR contains spaces, the crontab command will fail because the path to sync.sh is not quoted. We should quote the path to ensure it works correctly with spaces.

    line="*/15 * * * * \"$KB_DIR/scripts/sync.sh\" >> \$HOME/.kb-sync.log 2>&1"

Comment thread kb/skills/init/templates/bin/kb Outdated
Comment on lines +45 to +46
if command -v open >/dev/null 2>&1; then open "$KB_DIR/viz.html"
else echo "viz.html → $KB_DIR/viz.html"; fi ;;

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The open command is macOS-specific. To support Linux users running kb viz, we should fall back to xdg-open if open is not available.

            if command -v open >/dev/null 2>&1; then open "$KB_DIR/viz.html"
            elif command -v xdg-open >/dev/null 2>&1; then xdg-open "$KB_DIR/viz.html"
            else echo "viz.html → $KB_DIR/viz.html"; fi ;;

Comment on lines +34 to +36
wire_one() {
local f="$1" body
mkdir -p "$(dirname "$f")"; touch "$f"

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

If action is off and the target file does not exist, wire_one will still create the file and write a newline to it. When disabling/unwiring, we should skip non-existent files to avoid polluting the user's home directory.

Suggested change
wire_one() {
local f="$1" body
mkdir -p "$(dirname "$f")"; touch "$f"
wire_one() {
local f="$1" body
if [ "$action" = off ] && [ ! -f "$f" ]; then
return
fi
mkdir -p "$(dirname "$f")"; touch "$f"

@coderabbitai coderabbitai 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.

Actionable comments posted: 11

🤖 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/skills/init/SKILL.md`:
- Around line 46-51: The fenced bash example in SKILL.md needs blank lines
before and after the code block to satisfy markdownlint MD031. Update the
“Scaffold” section so the fenced block is separated from the surrounding prose,
keeping the content around the scaffold instructions intact.

In `@kb/skills/init/templates/AGENTS.md`:
- Around line 59-63: The frontmatter example in AGENTS.md is inconsistent with
the filename rule because the sample `name` omits the required `<type>-` prefix.
Update the example so the `name` field matches the full filename stem used by
the memory note naming convention, and keep the sample aligned with the
`memory/<group>/[<sub>/]<type>-<short-kebab-slug>.md` pattern. Check the related
template/example block in AGENTS.md so the canonical protocol and `[[...]]` link
targets stay consistent.

In `@kb/skills/init/templates/README.md`:
- Around line 17-33: The first fenced tree block in the README template is
missing a language identifier, causing markdownlint MD040. Update that fenced
block to use the text language tag on the existing directory tree example so
it’s properly labeled, and keep the rest of the template unchanged.

In `@kb/skills/init/templates/scripts/render_okf_viewer.py`:
- Line 79: The file reads/writes in render_okf_viewer.py use open(...).read()
and open(...).write() without a context manager, so update the affected code
paths to use with open(...) as f: and perform the read/write through that
handle. Apply the same fix around the split_fm input load and the other write
sites in the script, keeping the surrounding logic unchanged while ensuring each
file handle is closed deterministically.
- Around line 121-141: The “Groups” section in render_okf_viewer.py is rendering
links for every immediate subdirectory, even when that subdirectory is not
actually included in dirs_to_index and therefore has no generated index page.
Update the subdirectory collection in the main rendering loop to filter out
directories that won’t be indexed, using the same criteria that populate
dirs_to_index/by_dir so links only point to real generated pages. Keep the
change localized to the logic around subdirs and the per-directory output
assembly.
- Around line 148-183: The graph bundle currently uses c["slug"] directly as
Cytoscape node/edge ids in build_bundle(), but collect() derives slugs from the
basename, so identical filenames in different groups can collide. Update the
slugging/id strategy in build_bundle() (and the upstream collect()/concept
creation path if needed) to namespace slugs by path or group, and add a
duplicate check or warning before building nodes and edges so ids stay unique.
- Around line 244-259: The select() renderer is injecting untrusted
bundle/frontmatter fields directly into innerHTML, creating a stored-XSS risk.
Update render_okf_viewer.py so n.label, n.description, tags, backlinks, and the
n.resource link are HTML-escaped, sanitize the marked.parse(bodyMd) output
before insertion, and validate the href to allow only safe URL schemes. Keep the
fix localized to select() and the related detail/backlinks rendering helpers.

In `@kb/skills/init/templates/scripts/sync.sh`:
- Around line 20-30: The sync flow in sync.sh is swallowing rebase failures by
using `git pull --rebase --autostash origin "$BRANCH" || true`, which lets the
script continue into commit/push even when a rebase conflict occurs. Remove the
forced success behavior and make the script stop immediately on a failed
pull/rebase so `git add -A`, `git commit`, and `git push` only run after a clean
rebase. Use the existing `git pull --rebase --autostash` step and the later
commit/push block as the key places to adjust control flow.

In `@okf/skills/okf/scripts/render_okf_viewer.py`:
- Around line 119-139: The Groups section in render_okf_viewer.py is rendering
every filesystem subdirectory, so some links can point to directories that were
never indexed. Update the logic around render_okf_viewer (the subdirs list used
for the “## Groups” links) to filter against dirs_to_index before emitting
links, matching the same fix used in the other viewer template.
- Line 77: Multiple open() calls in the OKF viewer script are used without a
context manager, which Ruff flags (SIM115). Update the file-reading logic in
render_okf_viewer.py to use with open(...) as ... around each of the three
occurrences near the split_fm handling and the other two file reads, keeping the
existing fm/body parsing and downstream behavior unchanged.
- Around line 242-257: The select(id) renderer is building `#detail` with
unsanitized HTML from n.label, n.type, n.description, tags, backlinks,
n.resource, and marked.parse(bodyMd). Escape all plain-text fields before
concatenation, sanitize the markdown output before inserting it, and validate
n.resource in select(id) so only safe URL schemes are rendered as links.
🪄 Autofix (Beta)

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: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 8882c5eb-6b68-434c-aaff-9fe9df240ac9

📥 Commits

Reviewing files that changed from the base of the PR and between 02773fb and 41400a4.

📒 Files selected for processing (25)
  • .claude-plugin/marketplace.json
  • kb/.claude-plugin/plugin.json
  • kb/.codex-plugin/plugin.json
  • kb/README.md
  • kb/commands/init.md
  • kb/skills/init/SKILL.md
  • kb/skills/init/scripts/scaffold.sh
  • kb/skills/init/templates/AGENTS.md
  • kb/skills/init/templates/CLAUDE.md
  • kb/skills/init/templates/DREAM.md
  • kb/skills/init/templates/README.md
  • kb/skills/init/templates/bin/kb
  • kb/skills/init/templates/gitignore
  • kb/skills/init/templates/memory/_TEMPLATE.md
  • kb/skills/init/templates/raw/README.md
  • kb/skills/init/templates/scripts/lint.sh
  • kb/skills/init/templates/scripts/render_okf_viewer.py
  • kb/skills/init/templates/scripts/sync.sh
  • kb/skills/init/templates/scripts/wire.sh
  • marketplace.json
  • okf/.claude-plugin/plugin.json
  • okf/.codex-plugin/plugin.json
  • okf/README.md
  • okf/skills/okf/SKILL.md
  • okf/skills/okf/scripts/render_okf_viewer.py

Comment thread kb/skills/init/SKILL.md
Comment thread kb/skills/init/templates/AGENTS.md
Comment thread kb/skills/init/templates/README.md Outdated
if not name.endswith(".md") or name in RESERVED or name.startswith("_"):
continue
path = os.path.join(dp, name)
fm, body = split_fm(open(path, encoding="utf-8").read())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Files opened without a context manager.

open(...).read() / open(...).write(...) at Lines 79, 143, 289 leave file handles to be closed by GC rather than deterministically. Low real-world risk in CPython for a short-lived CLI, but flagged by Ruff (SIM115) and easy to fix with with open(...) as f:.

Also applies to: 143-143, 289-289

🧰 Tools
🪛 Ruff (0.15.20)

[warning] 79-79: Use a context manager for opening files

(SIM115)

🤖 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/skills/init/templates/scripts/render_okf_viewer.py` at line 79, The file
reads/writes in render_okf_viewer.py use open(...).read() and open(...).write()
without a context manager, so update the affected code paths to use with
open(...) as f: and perform the read/write through that handle. Apply the same
fix around the split_fm input load and the other write sites in the script,
keeping the surrounding logic unchanged while ensuring each file handle is
closed deterministically.

Source: Linters/SAST tools

Comment thread kb/skills/init/templates/scripts/render_okf_viewer.py Outdated
Comment on lines +244 to +259
function select(id){
cy.nodes().unselect(); const el=cy.getElementById(id); if(el&&el.length)el.select();
const n=byId[id]; if(!n) return;
const bodyMd=b.bodies[id]||"";
const tags=(n.tags||[]).map(t=>'<span>'+t+'</span>').join("");
const bl=(back[id]||[]).map(s=>'<li><a data-id="'+s+'">'+((byId[s]||{}).label||s)+'</a></li>').join("");
document.getElementById("detail").innerHTML=
'<h1>'+n.label+'</h1>'+
'<span class="badge" style="background:'+(b.palette[n.type]||'#64748b')+'">'+n.type+'</span>'+
(n.resource?' <a class="muted" href="'+n.resource+'" target="_blank" rel="noopener">source ↗</a>':'')+
'<p class="desc">'+(n.description||'')+'</p>'+
(tags?'<div class="tags">'+tags+'</div>':'')+
'<div class="body">'+marked.parse(bodyMd)+'</div>'+
(bl?'<div id="backlinks"><h2>Linked from</h2><ul>'+bl+'</ul></div>':'');
document.querySelectorAll('#backlinks a').forEach(a=>a.onclick=()=>select(a.dataset.id));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='kb/skills/init/templates/scripts/render_okf_viewer.py'
wc -l "$file"
printf '\n--- context ---\n'
sed -n '180,320p' "$file"

printf '\n--- search for sanitization helpers/usages ---\n'
rg -n "marked\.parse|DOMPurify|innerHTML|escape|sanitize|href=|javascript:" "$file"

Repository: duyet/codex-claude-plugins

Length of output: 6921


🏁 Script executed:

#!/bin/bash
set -euo pipefail

file='kb/skills/init/templates/scripts/render_okf_viewer.py'
printf '%s\n' '--- bundle-building functions ---'
rg -n "def (collect|build_bundle|write_indexes)|frontmatter|yaml|tags|description|resource|label|bodyMd|raw" "$file"
printf '\n%s\n' '--- relevant function bodies ---'
sed -n '1,220p' "$file"

Repository: duyet/codex-claude-plugins

Length of output: 11359


Escape all bundle content before injecting into innerHTML. bodyMd, n.label, n.description, tags, backlinks, and n.resource all come from bundle/frontmatter data, so a malicious concept can turn viz.html into a stored-XSS sink. Sanitize the Markdown output, HTML-escape text/attribute values, and allow only safe href schemes.

🤖 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/skills/init/templates/scripts/render_okf_viewer.py` around lines 244 -
259, The select() renderer is injecting untrusted bundle/frontmatter fields
directly into innerHTML, creating a stored-XSS risk. Update render_okf_viewer.py
so n.label, n.description, tags, backlinks, and the n.resource link are
HTML-escaped, sanitize the marked.parse(bodyMd) output before insertion, and
validate the href to allow only safe URL schemes. Keep the fix localized to
select() and the related detail/backlinks rendering helpers.

Comment thread kb/skills/init/templates/scripts/sync.sh Outdated
if not name.endswith(".md") or name in RESERVED or name.startswith("_"):
continue
path = os.path.join(dp, name)
fm, body = split_fm(open(path, encoding="utf-8").read())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Files opened without a context manager.

Same as the vendored kb copy — Lines 77, 141, 287 open files without with. Low-impact under CPython, flagged by Ruff (SIM115).

Also applies to: 141-141, 287-287

🧰 Tools
🪛 Ruff (0.15.20)

[warning] 77-77: Use a context manager for opening files

(SIM115)

🤖 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 `@okf/skills/okf/scripts/render_okf_viewer.py` at line 77, Multiple open()
calls in the OKF viewer script are used without a context manager, which Ruff
flags (SIM115). Update the file-reading logic in render_okf_viewer.py to use
with open(...) as ... around each of the three occurrences near the split_fm
handling and the other two file reads, keeping the existing fm/body parsing and
downstream behavior unchanged.

Source: Linters/SAST tools

Comment thread okf/skills/okf/scripts/render_okf_viewer.py Outdated
Comment on lines +242 to +257
function select(id){
cy.nodes().unselect(); const el=cy.getElementById(id); if(el&&el.length)el.select();
const n=byId[id]; if(!n) return;
const bodyMd=b.bodies[id]||"";
const tags=(n.tags||[]).map(t=>'<span>'+t+'</span>').join("");
const bl=(back[id]||[]).map(s=>'<li><a data-id="'+s+'">'+((byId[s]||{}).label||s)+'</a></li>').join("");
document.getElementById("detail").innerHTML=
'<h1>'+n.label+'</h1>'+
'<span class="badge" style="background:'+(b.palette[n.type]||'#64748b')+'">'+n.type+'</span>'+
(n.resource?' <a class="muted" href="'+n.resource+'" target="_blank" rel="noopener">source ↗</a>':'')+
'<p class="desc">'+(n.description||'')+'</p>'+
(tags?'<div class="tags">'+tags+'</div>':'')+
'<div class="body">'+marked.parse(bodyMd)+'</div>'+
(bl?'<div id="backlinks"><h2>Linked from</h2><ul>'+bl+'</ul></div>':'');
document.querySelectorAll('#backlinks a').forEach(a=>a.onclick=()=>select(a.dataset.id));
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the reviewed file and the sibling template copy
git ls-files 'okf/skills/okf/scripts/render_okf_viewer.py' 'kb/skills/init/templates/scripts/render_okf_viewer.py'

# Show file sizes
wc -l okf/skills/okf/scripts/render_okf_viewer.py kb/skills/init/templates/scripts/render_okf_viewer.py

# Inspect the relevant region in both files with line numbers
sed -n '220,280p' okf/skills/okf/scripts/render_okf_viewer.py | cat -n
printf '\n--- sibling ---\n'
sed -n '220,280p' kb/skills/init/templates/scripts/render_okf_viewer.py | cat -n

# Search for any sanitization / escaping helpers used in these files
rg -n 'DOMPurify|sanitize|escape|textContent|innerText|marked\.parse|innerHTML|href=' okf/skills/okf/scripts/render_okf_viewer.py kb/skills/init/templates/scripts/render_okf_viewer.py

Repository: duyet/codex-claude-plugins

Length of output: 8680


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Check whether this viewer ever sanitizes HTML or normalizes URLs
rg -n 'DOMPurify|sanitize|escapeHtml|escapeHTML|textContent|innerText|URL\(|new URL\(|marked\.setOptions|marked\.parse' okf/skills/okf/scripts/render_okf_viewer.py kb/skills/init/templates/scripts/render_okf_viewer.py

# Inspect the bundle-building logic to see where node fields come from
ast-grep outline okf/skills/okf/scripts/render_okf_viewer.py --view expanded

# Read the section that populates b.nodes / b.bodies / b.edges
sed -n '1,240p' okf/skills/okf/scripts/render_okf_viewer.py | cat -n

Repository: duyet/codex-claude-plugins

Length of output: 13059


Escape and sanitize the viewer output before assigning innerHTML. n.label, n.type, n.description, tags, backlinks, and n.resource are concatenated raw, and marked.parse(bodyMd) is injected without sanitization. Escape text fields, sanitize the markdown HTML, and restrict resource to safe URL schemes.

🤖 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 `@okf/skills/okf/scripts/render_okf_viewer.py` around lines 242 - 257, The
select(id) renderer is building `#detail` with unsanitized HTML from n.label,
n.type, n.description, tags, backlinks, n.resource, and marked.parse(bodyMd).
Escape all plain-text fields before concatenation, sanitize the markdown output
before inserting it, and validate n.resource in select(id) so only safe URL
schemes are rendered as links.

duyet and others added 3 commits July 5, 2026 19:15
scripts/install-antigravity.sh: replace unquoted command-substitution
array assignment (SC2207) with a portable read loop — avoids word
splitting/globbing and stays bash 3.2 compatible (macOS default).

kb/skills/init/templates/scripts/wire.sh: silence SC2088 with an
explicit disable comment — the "~/kb" there is an intentional literal
display string, not a path meant to expand.

Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com>
render_okf_viewer.py (okf canonical + kb vendored copy, kept in sync):
- Skip hidden directories (.git, .obsidian, .agent) when walking for
  concepts and when listing subdirs, so stray .md files and vault
  config never leak into the bundle or the graph.
- Fix a dead-link bug: "Groups" links in a generated index.md could
  point at a child directory that itself had no concepts and no
  index.md. Recompute index-worthiness bottom-up so a directory is
  only linked if it (or a descendant) actually has content.
- Warn (not silently corrupt) on duplicate slugs across directories,
  since build_bundle() uses the bare slug as the Cytoscape node id.
- Read notes with utf-8-sig so a UTF-8 BOM doesn't break frontmatter
  parsing.
- Escape text fields (label/type/description/tags/backlinks) before
  building the viewer's innerHTML, restrict the "source" link to
  http(s) URLs, and neutralize <, >, & in the embedded JSON bundle so
  a note's content can't break out of the <script> tag. Documented the
  remaining trust boundary: a concept's markdown body is still
  rendered via marked.parse unsanitized, same as opening a vault in
  Obsidian — this tool assumes the bundle itself is trusted content.

kb templates:
- sync.sh: don't swallow a failed rebase with `|| true` — a conflict
  must stop the script before it commits/pushes conflict markers.
- bin/kb: quote the cron path (spaces in $KB_DIR), add an xdg-open
  fallback for `kb viz` on Linux.
- wire.sh: `wire off` no longer creates a target file that didn't
  already exist.
- AGENTS.md: fix the frontmatter example to include the `<type>-`
  prefix, matching the naming rule described just above it.
- SKILL.md / README.md: markdownlint fixes (blank lines around a
  fenced block, language tag on the directory tree fence).

Addresses review feedback from CodeRabbit and Gemini Code Assist on
PR #80. Skipped: "open files without a context manager" (flagged
low-value by the bot itself, not worth the churn on a short-lived
CLI); full sanitization of rendered markdown body content (heavy
lift, documented as an explicit trust boundary instead — see
render_okf_viewer.py's module docstring).

Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com>
Reformat every shell script touched in this branch with shfmt (default
style, matching the repo's Lint job) and every markdown file with
prettier — whitespace/structure only, no behavior change. Also:
- bin/kb: quote ${f#"$KB_DIR"/} per shellcheck SC2295.
- AGENTS.md: fix the memory-types table's column alignment (MD060).

Co-Authored-By: duyetbot <duyetbot@users.noreply.github.com>
@duyet duyet merged commit 82de402 into master Jul 5, 2026
24 checks passed
@duyet duyet deleted the kb-okf-init-viewer branch July 5, 2026 15:00
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