feat(kb,okf): add kb init skill and okf graph-viewer renderer#80
Conversation
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>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Welcome! 👋Thank you for your first contribution to this project! We appreciate you taking the time to improve our codebase. Next Steps
Contribution Guidelines
Thanks again for contributing! 🎉 |
|
Warning Review limit reached
Next review available in: 27 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (12)
📝 WalkthroughWalkthroughThis PR adds a new ChangesKB init skill and scaffolding
Estimated code review effort: 3 (Moderate) | ~30 minutes OKF Graph Viewer Rendering
Manifest and Metadata Updates
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
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
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
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.
| for dp, dn, fn in os.walk(root): | ||
| dn.sort() |
There was a problem hiding this comment.
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()| subdirs = sorted(d for d in os.listdir(dirpath) | ||
| if os.path.isdir(os.path.join(dirpath, d))) |
There was a problem hiding this comment.
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))) | |
| subdirs = sorted(d for d in os.listdir(dirpath) | |
| if os.path.isdir(os.path.join(dirpath, d)) and not d.startswith(".")) |
| for dp, dn, fn in os.walk(root): | ||
| dn.sort() |
There was a problem hiding this comment.
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()| here = sorted(by_dir.get(dirpath, []), key=lambda c: c["title"].lower()) | ||
| if not subdirs and not here: |
There was a problem hiding this comment.
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)) |
There was a problem hiding this comment.
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.
| 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()) |
There was a problem hiding this comment.
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.
| 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()) |
There was a problem hiding this comment.
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.
| fm, body = split_fm(open(path, encoding="utf-8").read()) | |
| fm, body = split_fm(open(path, encoding="utf-8-sig").read()) |
| 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" |
| if command -v open >/dev/null 2>&1; then open "$KB_DIR/viz.html" | ||
| else echo "viz.html → $KB_DIR/viz.html"; fi ;; |
There was a problem hiding this comment.
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 ;;
| wire_one() { | ||
| local f="$1" body | ||
| mkdir -p "$(dirname "$f")"; touch "$f" |
There was a problem hiding this comment.
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.
| 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" |
There was a problem hiding this comment.
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
📒 Files selected for processing (25)
.claude-plugin/marketplace.jsonkb/.claude-plugin/plugin.jsonkb/.codex-plugin/plugin.jsonkb/README.mdkb/commands/init.mdkb/skills/init/SKILL.mdkb/skills/init/scripts/scaffold.shkb/skills/init/templates/AGENTS.mdkb/skills/init/templates/CLAUDE.mdkb/skills/init/templates/DREAM.mdkb/skills/init/templates/README.mdkb/skills/init/templates/bin/kbkb/skills/init/templates/gitignorekb/skills/init/templates/memory/_TEMPLATE.mdkb/skills/init/templates/raw/README.mdkb/skills/init/templates/scripts/lint.shkb/skills/init/templates/scripts/render_okf_viewer.pykb/skills/init/templates/scripts/sync.shkb/skills/init/templates/scripts/wire.shmarketplace.jsonokf/.claude-plugin/plugin.jsonokf/.codex-plugin/plugin.jsonokf/README.mdokf/skills/okf/SKILL.mdokf/skills/okf/scripts/render_okf_viewer.py
| 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()) |
There was a problem hiding this comment.
📐 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
| 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)); | ||
| } |
There was a problem hiding this comment.
🔒 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.
| 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()) |
There was a problem hiding this comment.
📐 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
| 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)); | ||
| } |
There was a problem hiding this comment.
🔒 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.pyRepository: 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 -nRepository: 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.
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>
Summary
kbgains 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, amemory/OKF bundle,raw/inbox/, and a vendoredbin/kbCLI (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).okfgainsrender_okf_viewer.py: a generic, self-contained HTML graph viewer for any OKF bundle. Reads edges fromrelated: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 theokf/kbClaude plugins are later uninstalled.SKILL.mds,README.mds, plugin manifests (Claude + Codex), and both marketplace files are updated; versions bumped to1.1.0(minor, new feature).Test plan
scaffold.shrun twice against a scratch dir — second run creates 0 files, correctly skips all pre-existing ones (idempotent).lint.shcrashed on a fresh, empty kb under bash 3.2 (macOS default) due toset -u+ empty-array expansion; now exits cleanly with "0 notes yet."kb capture,kb lint,kb gen,kb vizexercised end-to-end on a scaffolded kb; wikilink-based notes produce correct nodes/edges inviz.html.render_okf_viewer.pyalso tested directly against a hand-authored OKF-style bundle using plain[text](/path.md)and[text](relative.md)markdown links (the convention theokfskill itself teaches) — edges resolved correctly.validate_okf.pyreports the generated bundles as conformant.bash scripts/validate-plugins.sh— all manifests, cross-manifest parity, and marketplace files pass.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
Documentation