Skip to content

(MOT-4292) feat(openwiki): add openwiki worker - #651

Merged
rohitg00 merged 2 commits into
mainfrom
feat/openwiki-worker
Jul 31, 2026
Merged

(MOT-4292) feat(openwiki): add openwiki worker#651
rohitg00 merged 2 commits into
mainfrom
feat/openwiki-worker

Conversation

@rohitg00

@rohitg00 rohitg00 commented Jul 31, 2026

Copy link
Copy Markdown
Contributor

Ports openwiki from iii-experimental/openwiki into the monorepo as a javascript bundle worker, so it installs with iii worker add openwiki.

openwiki builds and maintains a source-grounded, interlinked markdown wiki for any git repository and serves a browser UI + JSON API under /openwiki. A lead agent on the harness plans the index and spawns one writer sub-agent per page; each writer reads its files through openwiki's scoped readers and stores a cited page with openwiki::write-page. Without the harness it falls back to one router::complete per page, and below that to a model-free heuristic tier, so a bare engine still produces a browsable wiki. Refresh is incremental (git diff to affected pages through the file-to-page index) and each wiki can run it on its own cron cadence.

What changed for the monorepo

  • Bundle deploy matching the pi and claude-code layout: pnpm@10.18.2 with committed pnpm-lock.yaml, scripts/build-bundle.mjs (esbuild single file, iii-sdk package.json inlined), manifest scripts.start: node ./index.mjs, no scripts.setup.
  • iii-sdk ^0.21.6; manifest dependencies per sibling convention: state ^0.21.2, cron ^0.21.0, llm-router ^1.0.0.
  • Non-empty tags: for registry discovery; per-worker biome.json; source formatted with biome and two lint findings fixed (a restricted-name escape() shadow renamed, an unused parameter dropped).
  • README restructured per worker-readme.md (Install / Quickstart / Configuration, absolute image links); skills/SKILL.md per DOCUMENTATION_GUIDELINES.md.
  • Release wiring per docs/sops/new-worker.md section 6: create-tag.yml option, release.yml tag glob openwiki/v*, modules table row in the top-level README.
  • iii-permissions.yaml: denies for the internal trigger targets (openwiki::cron::refresh-due, openwiki::on-turn-started, openwiki::on-turn-completed, openwiki::on-config-change); the read surface stays at the needs_approval default.
  • Typed request and response schemas on every registered function, so the publish pipeline's assert-typed-schemas gate passes.

Verification

  • biome ci openwiki clean (CI-exact invocation), 44/44 tests on Node 22, node --check on entrypoint and bundle.
  • build:bundle produces a 764 KB self-contained dist/bundle/index.mjs.
  • Live boot against a 0.22.0 engine: worker registers, UI serves 200 at /openwiki, openwiki::generate on a sample repository ran the harness agent tier to ready with a cited page and openwiki::lint reporting zero issues, then openwiki::delete cleaned up.

Release notes

Fixes MOT-4292

Summary by CodeRabbit

  • New Features

    • Added OpenWiki for generating source-grounded, citation-backed repository wikis.
    • Browse pages, search content, explore source files, ask questions, create diagrams, and export wiki guidance for coding assistants.
    • Supports incremental refreshes from repository changes, scheduled updates, progress tracking, and fallback generation when AI services are unavailable.
    • Includes browser and JSON interfaces for wiki management, page creation, validation, and exports.
  • Documentation

    • Added setup, configuration, usage, API, scheduling, and workflow documentation.

Source-grounded markdown wiki for any git repository, ported to the
monorepo as a javascript bundle worker.

- openwiki::* functions: generate / status / pages / page / search /
  refresh / set-schedule / lint / delete, scoped source readers for
  writer sub-agents (src::read / src::list / src::grep), write-page,
  cited ask, Mermaid diagram, AGENTS.md export, and the MCP trio
  (read-wiki-structure / read-wiki-contents / ask-question)
- three generation tiers: harness lead agent spawning one writer
  sub-agent per page (line-cited), llm-router per-page completion,
  and a model-free heuristic fallback
- per-wiki auto-refresh on cron triggers with a content-hash gate;
  incremental refresh regenerates only pages whose source changed
- browser UI + JSON API served by the engine under /openwiki
- typed request and response schemas on every registered function
- release wiring: create-tag option, release.yml tag glob, modules
  table row, agent-permission denies for the internal trigger targets
@vercel

vercel Bot commented Jul 31, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview Jul 31, 2026 12:22pm
workers-tech-spec Ready Ready Preview Jul 31, 2026 12:22pm

Request Review

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 51 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

@coderabbitai

coderabbitai Bot commented Jul 31, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

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

Next review available in: 24 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: CHILL

Plan: Pro Plus

Run ID: 7bbbdfcc-3528-4c7c-877b-877e9c02f93e

📥 Commits

Reviewing files that changed from the base of the PR and between 9d7e166 and 6e58cc4.

📒 Files selected for processing (18)
  • openwiki/README.md
  • openwiki/src/index.mjs
  • openwiki/src/lib/ask.mjs
  • openwiki/src/lib/configuration.mjs
  • openwiki/src/lib/docs_oracle.mjs
  • openwiki/src/lib/generate.mjs
  • openwiki/src/lib/git.mjs
  • openwiki/src/lib/harness.mjs
  • openwiki/src/lib/heuristic.mjs
  • openwiki/src/lib/inventory.mjs
  • openwiki/src/lib/lint.mjs
  • openwiki/src/lib/model.mjs
  • openwiki/src/lib/quality.mjs
  • openwiki/src/lib/schemas.mjs
  • openwiki/src/lib/search.mjs
  • openwiki/src/lib/src.mjs
  • openwiki/src/lib/store.mjs
  • openwiki/src/lib/ui.mjs
📝 Walkthrough

Walkthrough

OpenWiki adds a Node worker for source-grounded wiki generation, incremental refresh, cited pages, Q&A, diagrams, APIs, MCP tools, scheduling, and a browser UI. It also adds persistence, deployment integration, permissions, documentation, and automated tests.

Changes

OpenWiki worker

Layer / File(s) Summary
Worker packaging and integration
.github/workflows/*, README.md, iii-permissions.yaml, openwiki/README.md, openwiki/package.json, openwiki/iii.worker.yaml, openwiki/skills/*
Adds release integration, permissions, package configuration, worker metadata, build configuration, documentation, and skill definitions.
Contracts, repository access, and persistence
openwiki/src/lib/schemas.mjs, inventory.mjs, git.mjs, src.mjs, store.mjs, nav.mjs
Adds request and response schemas, repository scanning, Git operations, scoped source access, navigation normalization, and state-backed wiki storage.
Planning, page generation, and event handling
openwiki/src/lib/generate.mjs, heuristic.mjs, harness.mjs, docs_oracle.mjs, quality.mjs, model.mjs, turnbus.mjs, progress.mjs
Adds LLM, harness, and heuristic planning and page generation with citations, quality checks, fallbacks, model selection, and progress routing.
Worker APIs, refresh, and auxiliary operations
openwiki/src/index.mjs, ask.mjs, diagram.mjs, search.mjs, lint.mjs, agents_md.mjs, configuration.mjs
Adds generation and refresh control flow, worker and HTTP APIs, scheduled refreshes, configuration reloads, Q&A, diagrams, search, linting, exports, recovery, and shutdown handling.
Browser interface and validation
openwiki/src/lib/ui.mjs, openwiki/tests/*
Adds the browser application and tests for configuration, Git parsing, harness utilities, models, navigation, quality, source access, storage, and utility surfaces.

Estimated code review effort: 5 (Critical) | ~120 minutes

Possibly related PRs

  • iii-hq/workers#438: Updates the same manual tag workflow with a selectable worker.
  • iii-hq/workers#529: Adds another worker with shared release, README, permissions, and tag integration.

Poem

A rabbit reviews the wiki’s bright new trail,
While cited pages hop through every file.
Models plan, and source lines sing,
Schedules wake the refresh spring.
“OpenWiki is ready!” I wiggle my tail.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 30.06% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the addition of the openwiki worker, which is the main change in the pull request.
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 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/openwiki-worker

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.

@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: 10

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

🟡 Minor comments (12)
openwiki/src/index.mjs-1142-1147 (1)

1142-1147: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

The live-progress channel has no failure contract. The server reports an unavailable stream with a success status, and the client discards stream errors. The result is a progress panel that stops updating with no fallback and no message.

  • openwiki/src/index.mjs#L1142-L1147: return 501 instead of jsonResponse(200, { error: 'streaming unsupported' }) when w?.stream is absent, so the client can detect the failure.
  • openwiki/src/lib/ui.mjs#L851-L892: in es.onerror, close the EventSource and start the existing poll fallback when no event has arrived yet, instead of relying on automatic reconnect.
🤖 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 `@openwiki/src/index.mjs` around lines 1142 - 1147, The live-progress failure
contract must be fixed in both sites: in openwiki/src/index.mjs:1142-1147,
update the openwiki::http::events handler to return status 501 when w?.stream is
unavailable; in openwiki/src/lib/ui.mjs:851-892, update es.onerror to close the
EventSource and start the existing polling fallback when no event has arrived
yet, rather than allowing automatic reconnect. Use the existing event-tracking
and poll-fallback symbols in that handler.
openwiki/src/lib/ask.mjs-124-151 (1)

124-151: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Fail fast when harness::send returns no session_id.

askDeep passes session_id straight to awaitTurn. If the trigger returns no session_id, awaitTurn receives undefined and the failure surfaces later as a timeout or an opaque error. runOrchestrator in openwiki/src/lib/harness.mjs (Lines 349-350) already throws for this case. Match that behavior here.

🐛 Proposed fix
   });
+  if (!session_id) throw new Error('harness::send returned no session_id');
   const result = await awaitTurn(worker, session_id, { timeoutMs: 240_000 });
🤖 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 `@openwiki/src/lib/ask.mjs` around lines 124 - 151, Update askDeep to validate
the session_id returned by worker.trigger before calling awaitTurn. If it is
missing, throw immediately using the same failure behavior and error semantics
as runOrchestrator, while preserving the existing awaitTurn flow for valid
session IDs.
openwiki/src/lib/lint.mjs-30-44 (1)

30-44: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Truncated files produce false broken-citation issues.

readSourceFile in openwiki/src/lib/inventory.mjs (Lines 173-185) caps the content at maxBytes and appends \n...[truncated]. The line count at Line 38 then measures the truncated content only. For a source file larger than 200 KB, any citation past the cut is reported as "beyond N lines" even though the line exists.

Read the full file for the line check, or skip the range check when the read is truncated.

🐛 Proposed fix
-        let content;
+        let content;
+        let truncated = false;
         try {
-          ({ content } = await readSourceFile(dir, c.path, 200_000));
+          ({ content, truncated } = await readSourceFile(dir, c.path, Number.MAX_SAFE_INTEGER));
         } catch {
           issues.push({ slug, kind: 'broken-citation', detail: `missing file ${c.path}` });
           continue;
         }
-        if (c.start_line || c.end_line) {
+        if (!truncated && (c.start_line || c.end_line)) {
🤖 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 `@openwiki/src/lib/lint.mjs` around lines 30 - 44, Update the citation range
validation around readSourceFile so truncated content does not produce false
broken-citation issues: either read the complete source for line-count
validation or detect the truncation marker and skip the start_line/end_line
bounds check. Preserve missing-file handling and existing validation for
complete content.
openwiki/src/lib/search.mjs-19-41 (1)

19-41: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Do not HTML-escape snippets in the JSON payload.

makeSnippet returns escaped markup. The consumers treat the value as text: openwiki::http::search serializes it to JSON, and renderPageList in openwiki/src/lib/ui.mjs (Line 1012) assigns it through el('div', { class:'page-snippet', text: r.snippet }), which sets textContent. A snippet that contains < or & therefore renders as the literal &lt; or &amp;. Escaping belongs in the presentation layer, which already sets textContent.

♻️ Proposed fix
-function escapeHtml(s) {
-  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
-}
-
 function makeSnippet(body, queryTokens) {
   const lower = body.toLowerCase();
   let idx = -1;
   for (const t of queryTokens) {
     const i = lower.indexOf(t);
     if (i !== -1 && (idx === -1 || i < idx)) idx = i;
   }
   if (idx === -1) {
     const head = body.slice(0, 200);
-    return escapeHtml(head) + (body.length > 200 ? '…' : '');
+    return head + (body.length > 200 ? '…' : '');
   }
   const start = Math.max(0, idx - 80);
   const end = Math.min(body.length, idx + 120);
   let snip = body.slice(start, end);
-  snip = escapeHtml(snip);
   if (start > 0) snip = `…${snip}`;
   if (end < body.length) snip = `${snip}…`;
   return snip;
 }
🤖 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 `@openwiki/src/lib/search.mjs` around lines 19 - 41, Update makeSnippet to
return raw snippet text instead of passing its output through escapeHtml,
including both the matched and fallback branches. Preserve the existing slicing
and ellipsis behavior; presentation already uses textContent, so remove only the
payload-level HTML escaping.
openwiki/src/lib/ui.mjs-736-741 (1)

736-741: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Confirm the wiki deletion before the request.

The × control calls deleteWiki on the first click. DELETE /openwiki/api/wikis/:id removes the wiki and every page permanently, and no undo exists. The control is small and appears on hover next to the wiki name, so a mis-click destroys a full generation run.

Add a confirmation step.

🛡️ Proposed fix
-        onclick: (e) => { e.stopPropagation(); deleteWiki(w.id); },
+        onclick: (e) => {
+          e.stopPropagation();
+          if (confirm('Remove the wiki for ' + (w.repo_name || w.id) + '? This deletes all its pages.')) deleteWiki(w.id);
+        },
🤖 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 `@openwiki/src/lib/ui.mjs` around lines 736 - 741, Update the `wiki-del` button
handler in the wiki rendering block to request explicit user confirmation before
calling `deleteWiki(w.id)`. Keep stopping event propagation, and invoke deletion
only when confirmation is accepted; otherwise leave the wiki unchanged.
openwiki/src/lib/configuration.mjs-7-11 (1)

7-11: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Sanitize OPENWIKI_MAX_PARALLEL and OPENWIKI_REFRESH_DEFAULT.

If OPENWIKI_MAX_PARALLEL is not numeric, parseInt returns NaN and Math.max(1, NaN) returns NaN. NaN then reaches schema().properties.max_parallel.default and initial_value, where JSON.stringify converts it to null. That value violates the declared type: 'integer' and can make configuration::register reject. OPENWIKI_REFRESH_DEFAULT has the same class of problem: an arbitrary string bypasses the declared enum.

🐛 Proposed fix
+const REFRESH_VALUES = ['off', '3h', '6h', '12h', 'daily', 'weekly'];
+const parsedParallel = Number.parseInt(process.env.OPENWIKI_MAX_PARALLEL || '3', 10);
+const envRefresh = process.env.OPENWIKI_REFRESH_DEFAULT || 'off';
+
 const DEFAULTS = {
   model: process.env.OPENWIKI_MODEL || 'claude-haiku-4-5-20251001',
-  max_parallel: Math.max(1, parseInt(process.env.OPENWIKI_MAX_PARALLEL || '3', 10)),
-  refresh_default: process.env.OPENWIKI_REFRESH_DEFAULT || 'off',
+  max_parallel: Number.isFinite(parsedParallel) ? Math.min(16, Math.max(1, parsedParallel)) : 3,
+  refresh_default: REFRESH_VALUES.includes(envRefresh) ? envRefresh : 'off',
 };

The Math.min(16, ...) clamp also keeps the default inside the declared maximum.

🤖 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 `@openwiki/src/lib/configuration.mjs` around lines 7 - 11, Sanitize the
DEFAULTS values for OPENWIKI_MAX_PARALLEL and OPENWIKI_REFRESH_DEFAULT in the
configuration module: use a validated numeric fallback, clamp max_parallel to
the declared 1–16 range, and accept refresh_default only when it matches the
declared enum, otherwise use its valid default. Ensure these sanitized values
are what schema().properties.max_parallel.default and initial_value receive.
openwiki/README.md-50-51 (1)

50-51: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a platform-neutral browser instruction.

The open command is not available by default on Linux or Windows. Replace it with the URL itself or provide platform-specific alternatives. The documented quickstart otherwise fails for common deployments.

🤖 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 `@openwiki/README.md` around lines 50 - 51, Update the README quickstart
browser instruction around the localhost URL to be platform-neutral: display the
URL directly or provide appropriate alternatives for macOS, Linux, and Windows
instead of relying on the `open` command.
openwiki/src/lib/model.mjs-10-17 (1)

10-17: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard against null catalog entries in the fallback branch.

Line 10 guards m &&, and listModels filters m?.id. The fallback predicates on Lines 14-16 read x.supports_structured_output without a guard, so a null entry in the router response throws a TypeError.

🛡️ Proposed fix
-  const list = Array.isArray(models) ? models : [];
+  const list = (Array.isArray(models) ? models : []).filter((m) => m?.id);
🤖 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 `@openwiki/src/lib/model.mjs` around lines 10 - 17, Guard each fallback
predicate in the model-selection logic around byId so null catalog entries are
skipped before reading supports_structured_output or supports_tools. Preserve
the existing preference order and list[0] fallback while ensuring null entries
cannot cause a TypeError.
openwiki/src/lib/schemas.mjs-16-45 (1)

16-45: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Add refresh_schedule, last_refresh_at, and steer to WIKI_META.

openwiki::wiki, openwiki::wikis, and the matching HTTP endpoints return the wiki record directly, but the schema requires additionalProperties: false and does not define these persisted fields. Add them as properties, or project the record down to the current schema before returning it.

🤖 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 `@openwiki/src/lib/schemas.mjs` around lines 16 - 45, Update WIKI_META to
define the persisted refresh_schedule, last_refresh_at, and steer fields in its
properties while preserving additionalProperties: false. Use the existing schema
type conventions and ensure direct wiki records and matching HTTP endpoint
responses validate without dropping these fields.
openwiki/src/lib/store.mjs-153-176 (1)

153-176: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Invalidate the source cache when deleting a wiki.

src.mjs keeps invCache and readStats per wiki id, but both delete handlers call only store.deleteWiki(id). Move the cache invalidation to the delete path so a re-created wiki cannot reuse stale inventory while the module cache still holds the old keys.

🤖 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 `@openwiki/src/lib/store.mjs` around lines 153 - 176, Update the wiki deletion
flow in the delete handlers that call store.deleteWiki(id) to also remove the
deleted wiki’s entries from the per-wiki invCache and readStats caches. Ensure
both delete paths invalidate these caches after deletion so a recreated wiki
cannot reuse stale inventory or statistics.
openwiki/src/lib/store.mjs-222-228 (1)

222-228: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Serialize appendLog like the page index.

appendLog performs an unguarded read-modify-write on openwiki:log. Concurrent writers (batch page writers and spawned sub-agents, the same concurrency the index lock was added for) overwrite each other's array, so log lines are lost. Reuse the per-wiki serialization.

🔒 Proposed fix
 export async function appendLog(wikiId, line) {
   const ts = new Date().toISOString().replace(/\.\d{3}Z$/, 'Z');
-  const prev = (await sget(S_LOG, wikiId)) || [];
-  prev.push(`${ts}  ${line}`);
-  if (prev.length > 500) prev.splice(0, prev.length - 500);
-  await sset(S_LOG, wikiId, prev);
+  await withIndexLock(wikiId, async () => {
+    const prev = (await sget(S_LOG, wikiId)) || [];
+    prev.push(`${ts}  ${line}`);
+    if (prev.length > 500) prev.splice(0, prev.length - 500);
+    await sset(S_LOG, wikiId, prev);
+  });
 }
🤖 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 `@openwiki/src/lib/store.mjs` around lines 222 - 228, Update appendLog to use
the same per-wiki serialization mechanism already used by the page index,
wrapping its read-modify-write of S_LOG so concurrent writers are serialized.
Preserve the existing timestamping, 500-entry trimming, and persistence
behavior.
openwiki/src/lib/heuristic.mjs-167-206 (1)

167-206: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Guard the inputs of the last-resort fallback.

generatePage in openwiki/src/lib/generate.mjs calls this function from a catch block. If categories, sourceReads, allSlugs, or outlineItem.source_paths is nullish, this function throws a TypeError, the original LLM error is masked, and the page is lost. generatePageLLM already defends the same inputs with (categories ?? []) and (sourceReads ?? []). Apply the same defaults here.

🛡️ Proposed guards
 export async function generatePageHeuristic({
   outlineItem,
-  sourceReads,
-  allSlugs,
-  allTitles,
-  categories,
+  sourceReads = [],
+  allSlugs = [],
+  allTitles = [],
+  categories = [],
   repoName,
   repoUrl,
 }) {
   const title = outlineItem.title;
-  const category = categories.find((c) => c.id === outlineItem.category);
+  const category = categories.find((c) => c.id === outlineItem.category);
   lines.push('## Sources');
   lines.push('');
-  for (const p of outlineItem.source_paths) {
+  for (const p of outlineItem.source_paths || []) {
     lines.push(`- \`${p}\``);
   }
🤖 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 `@openwiki/src/lib/heuristic.mjs` around lines 167 - 206, Update
generatePageHeuristic to safely handle nullish categories and sourceReads by
using empty-array defaults before calling find, indexing, or iterating. Also
guard any allSlugs and outlineItem.source_paths usage within this fallback with
the same defaults, preserving the original LLM error instead of throwing a
TypeError.
🧹 Nitpick comments (12)
openwiki/src/index.mjs (1)

716-720: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Apply needId to the remaining id-taking functions.

The comment at Lines 66-70 explains that agents guess wiki_id instead of id, and needId exists for that reason. openwiki::refresh, openwiki::status, openwiki::wiki, openwiki::lint, openwiki::gen-stats, openwiki::ask, openwiki::diagram, openwiki::read-wiki-structure, and openwiki::read-wiki-contents still destructure { id } directly. A wrong parameter name reaches the store as undefined and produces the cryptic error the helper was added to prevent.

♻️ Example for `openwiki::refresh`
-worker.registerFunction('openwiki::refresh', async ({ id }) => refreshWiki(id), {
+worker.registerFunction('openwiki::refresh', async (a) => refreshWiki(needId(a, 'openwiki::refresh')), {
🤖 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 `@openwiki/src/index.mjs` around lines 716 - 720, Update the remaining
id-taking worker registrations—refresh, status, wiki, lint, gen-stats, ask,
diagram, read-wiki-structure, and read-wiki-contents—to use the existing needId
helper instead of directly destructuring { id }. Preserve each handler’s current
behavior while ensuring wiki_id is normalized to the id argument before reaching
the store.
openwiki/src/lib/ask.mjs (2)

186-202: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Log the swallowed answer errors.

Both catch blocks discard the error. When ask silently degrades to the heuristic stitch, no diagnostic remains for the operator. Capture the message in a console.warn so provider outages are visible.

🤖 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 `@openwiki/src/lib/ask.mjs` around lines 186 - 202, Update both catch blocks in
the answer-generation flow around askDeep and askFastLLM to capture the thrown
error and emit its message with console.warn before setting answer to null,
preserving the existing fallback behavior.

153-168: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low value

fileAnswer overwrites earlier answers for similar questions.

slugify strips punctuation and truncates to 60 characters. "How does auth work?" and "How does auth work" produce the same slug, so store.savePage replaces the previous filed answer. Append a short hash or a counter to keep filed answers distinct.

🤖 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 `@openwiki/src/lib/ask.mjs` around lines 153 - 168, Update fileAnswer so the
slug derived from slugify(q) is made unique before passing it to store.savePage,
appending a short deterministic hash or counter to distinguish questions that
normalize to the same slug. Use the resulting unique slug consistently in the
saved metadata and returned value.
openwiki/src/lib/ui.mjs (1)

1046-1057: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The nav folder toggle is not keyboard-operable.

The .fhead element is a div with a click handler only. It has no tabindex, no role, and no key handler. The leaves at Lines 1039-1044 do set tabindex: '0'. A keyboard user can therefore reach the pages of an expanded folder but cannot expand a collapsed one.

♿ Proposed fix
     folder.appendChild(el('div', {
       class: 'fhead',
+      tabindex: '0',
+      role: 'button',
+      'aria-expanded': collapsed ? 'false' : 'true',
+      onkeydown: (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.currentTarget.click(); } },
       onclick: () => { if (state.collapsedNav.has(key)) state.collapsedNav.delete(key); else state.collapsedNav.add(key); renderPageList(); },
🤖 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 `@openwiki/src/lib/ui.mjs` around lines 1046 - 1057, Make the `.fhead`
navigation folder toggle keyboard-operable in `renderNavNode` by adding
focusability and an appropriate button-like role, then handle keyboard
activation for Enter and Space using the same collapse/expand logic as
`onclick`. Preserve the existing mouse behavior and ensure Space does not
trigger unwanted page scrolling.
openwiki/src/lib/search.mjs (1)

43-85: 🚀 Performance & Scalability | 🔵 Trivial | 🏗️ Heavy lift

Search reads and tokenizes every page body on each query.

searchPages calls getPage for each page and tokenizes the full body per query. The UI calls this on every debounced keystroke through GET /openwiki/api/wikis/:id/search, so cost grows with page count and with query rate. For a wiki with many large pages, each search performs one state read per page.

Consider caching a per-wiki inverted index or a token map, and invalidating it in store.savePage.

🤖 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 `@openwiki/src/lib/search.mjs` around lines 43 - 85, Optimize searchPages by
reusing a per-wiki cached token map or inverted index instead of calling getPage
and tokenizing every page body for each query. Build or refresh the cache from
page data as needed, preserve the existing scoring, matched terms, snippets,
sorting, and limit behavior, and invalidate the relevant wiki cache in
store.savePage whenever a page is saved.
openwiki/src/lib/schemas.mjs (1)

378-390: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider a model-facing citation schema without url.

PAGE_HARNESS_OUT reuses CITATION, which exposes url. The comment on Line 377 states that openwiki fills the host deep-link. A model can then supply an unverified url that mapResult overwrites or, when citationUrl returns null, drops. A separate schema with only path, start_line, end_line, and note (the shape already inlined in WRITE_PAGE_REQ) makes the contract explicit.

🤖 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 `@openwiki/src/lib/schemas.mjs` around lines 378 - 390, Update PAGE_HARNESS_OUT
to use a model-facing citation schema containing only path, start_line,
end_line, and note, instead of reusing CITATION with url. Keep the existing
citation array structure while ensuring model-supplied urls are not accepted.
openwiki/src/lib/progress.mjs (1)

7-17: 🩺 Stability & Availability | 🔵 Trivial | 💤 Low value

Namespace the event name.

pushProgress emits on the raw wiki id. EventEmitter reserves error: an emit with that name and no listener throws. A prefix removes the collision class and keeps ids greppable.

♻️ Proposed change
+const topic = (wikiId) => `wiki:${wikiId}`;
+
 export function pushProgress(wikiId, evt) {
-  bus.emit(wikiId, evt);
+  bus.emit(topic(wikiId), evt);
 }
 
 export function onProgress(wikiId, cb) {
-  bus.on(wikiId, cb);
-  return () => bus.off(wikiId, cb);
+  bus.on(topic(wikiId), cb);
+  return () => bus.off(topic(wikiId), cb);
 }
🤖 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 `@openwiki/src/lib/progress.mjs` around lines 7 - 17, Update pushProgress and
onProgress to use a shared prefixed event-name namespace derived from wikiId
instead of emitting or subscribing to the raw wikiId. Ensure both functions
apply the same prefix so progress delivery remains unchanged while preventing
collisions with reserved EventEmitter names such as error.
openwiki/src/lib/quality.mjs (1)

24-51: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Run the structural checks on fence-stripped markdown.

countWords strips fenced code, but the heading, Relevant Source Files, and Sources: checks read the raw markdown. A page that only contains those tokens inside a ``` block passes the gate. Strip fences once, then reuse the result for the structural checks.

♻️ Proposed change
+const stripFences = (s) => s.replace(/```[\s\S]*?```/g, '\n');
+
 export function getPageQualityIssues(markdown, opts = {}) {
   const minWords = opts.minWords ?? 180;
-  const md = String(markdown || '');
+  const md = stripFences(String(markdown || ''));
🤖 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 `@openwiki/src/lib/quality.mjs` around lines 24 - 51, Update
getPageQualityIssues to normalize markdown with stripFences(String(markdown ||
'')) before running heading, section, Sources, and word-count checks, reusing
that single stripped result for all validations. Ensure stripFences is available
through the existing quality module utility or define it alongside the function
without changing other validation behavior.
openwiki/src/lib/harness.mjs (2)

236-271: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

return best is unreachable for valid maxAttempts.

Every iteration either returns on Line 266 or continues, and the last iteration always satisfies attempt === maxAttempts. So Line 271 only runs when maxAttempts < 1, and it then returns null, which callers do not expect. Clamp maxAttempts to at least 1 and drop the trailing return, or keep the return and document the clamp.

🤖 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 `@openwiki/src/lib/harness.mjs` around lines 236 - 271, Update the retry loop
around runPageTurn to clamp maxAttempts to at least 1 before iteration, ensuring
every valid execution reaches the existing return inside the loop; then remove
the unreachable trailing return best, or retain it only if the clamp is
explicitly documented and callers’ expected result is preserved.

16-27: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Encode path segments in the deep link.

citationUrl interpolates path directly. A path with a space or # produces a broken GitHub URL. Encode each segment.

♻️ Proposed change
-  return `https://github.com/${owner}/${repo}/blob/${commit}/${path}${frag}`;
+  const safePath = String(path).split('/').map(encodeURIComponent).join('/');
+  return `https://github.com/${owner}/${repo}/blob/${commit}/${safePath}${frag}`;
🤖 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 `@openwiki/src/lib/harness.mjs` around lines 16 - 27, Update citationUrl to
encode each slash-delimited path segment before interpolating path into the
GitHub URL, preserving slash separators and line-fragment behavior. Use
encodeURIComponent for individual segments so spaces, #, and other special
characters produce valid deep links.
openwiki/src/lib/inventory.mjs (1)

146-161: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider hashing file metadata instead of file contents during the walk.

inventoryRepo opens and reads up to maxBytes of every file only to derive a 12-character sha1 prefix. On a large repository this reads hundreds of megabytes on the clone path, and the inventory is rebuilt after each clone or refresh. If the sha field is only used for change detection, size plus mtimeMs from the existing fs.stat result gives the same signal without reading bodies. If content identity is required, read only the first few kilobytes.

🤖 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 `@openwiki/src/lib/inventory.mjs` around lines 146 - 161, Update
inventoryRepo’s per-file hashing logic to avoid reading up to maxBytes during
repository walks. Derive the 12-character sha1 value from the existing fs.stat
metadata, such as size and mtimeMs, while preserving the sha field’s
change-detection role; if content-based identity is required, cap reads to only
a small fixed prefix instead of maxBytes.
openwiki/src/lib/docs_oracle.mjs (1)

61-72: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Cap the total time spent probing documentation origins.

The loop probes up to four origins with two paths each. Every webFetch call allows 30 s, and the calls are sequential. A slow or blackholed documentation host therefore adds up to 240 s to the planning phase before the docs/ fallback runs. The caller in openwiki/src/index.mjs wraps the oracle in try/catch, not in a deadline.

Add an overall budget, or lower the per-call timeoutMs, and stop probing when the budget is exhausted.

⏱️ Proposed budget
 export async function fetchDocsIndex(worker, { repoUrl, readme, repoDir }) {
   // 1) llms.txt at README-referenced documentation origins.
+  const deadline = Date.now() + 45_000;
   for (const origin of candidateOrigins(repoUrl, readme)) {
     for (const p of ['/llms.txt', '/llms-full.txt']) {
+      if (Date.now() > deadline) break;
       const txt = await webFetch(worker, origin + p);
🤖 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 `@openwiki/src/lib/docs_oracle.mjs` around lines 61 - 72, The fetchDocsIndex
function contains nested loops that sequentially call webFetch with 30-second
timeouts for each of up to eight calls (four origins and two paths each),
potentially causing a 240-second hang before fallback. Implement an overall time
budget for the entire fetchDocsIndex function that tracks elapsed time across
all webFetch calls. Either reduce the per-call timeoutMs value passed to
webFetch, or add logic to break out of both the inner path loop and outer
candidateOrigins loop when the remaining budget is exhausted. Ensure the
function returns or falls through to the docs/ fallback when the budget runs
out.
🤖 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 `@openwiki/src/index.mjs`:
- Around line 1072-1082: Update the HTTP DELETE handler registered as
openwiki::http::wiki-delete to call applyWikiSchedule(id, 'off') before
store.deleteWiki(id), matching the existing openwiki::delete behavior and
ensuring the per-wiki cron trigger is removed before deletion.
- Around line 551-620: Guard refreshWiki against concurrent runs by moving its
current implementation into a doRefresh helper and adding an inflight
check/entry at the outer refresh entry point before any gitPull or cloneRepo
work begins. Ensure the inflight marker remains active until the scheduled
runGeneration or runRefresh claims ownership, or re-add it inside the
setImmediate callback, while preserving cleanup and existing refresh responses.

In `@openwiki/src/lib/generate.mjs`:
- Around line 48-73: Update buildKeyDocs to accept the clone directory and read
each document using repoDir combined with e.relPath instead of the nonexistent
e.path. Thread repoDir through the caller chain from planWiki, preserving the
existing document filtering, truncation, and missing-file handling.

In `@openwiki/src/lib/git.mjs`:
- Around line 91-104: Update gitPull to fetch additional history instead of
using a depth-1 fetch, preserving previously stored commits for gitDiff
comparisons. In refreshWiki, handle gitDiff failures as unknown changes and
trigger the existing full rebuild path rather than leaving changed or affected
empty and returning up_to_date.
- Around line 75-82: Update cloneRepo to validate repoUrl and ref before
constructing git arguments: reject any value beginning with “-”, reject repoUrl
schemes outside https, http, git, and ssh-style URLs (including ext transport),
and terminate clone option parsing with “--” before repoUrl and destDir.
Preserve the existing ref handling and clone behavior for validated inputs.

In `@openwiki/src/lib/harness.mjs`:
- Around line 261-266: Update the schemas represented by PAGE_META and PAGE_RES
to declare the quality_issues frontmatter field with its numeric type,
preserving additionalProperties: false; keep the assignment in the quality-check
flow unchanged so returned persisted frontmatter validates against both schemas.

In `@openwiki/src/lib/inventory.mjs`:
- Around line 173-185: Update readSourceFile to read through a file handle with
a bounded buffer, requesting no more than maxBytes + 1 bytes instead of loading
the complete file via fs.readFile. Preserve the existing content and truncated
results: mark truncation when the extra byte is present, return only maxBytes
decoded bytes followed by the truncation marker, and close the handle reliably.

In `@openwiki/src/lib/model.mjs`:
- Around line 29-44: Update resolveModel so cache.set(key, out) runs only when
pickModel returns a resolved result; return unresolved results without caching
them, while preserving cached-result lookup and normal caching for successful
resolutions.

In `@openwiki/src/lib/src.mjs`:
- Around line 112-128: Update srcGrep to reject patterns exceeding a defined
maximum length before constructing the RegExp, returning the existing
empty-result response. Add a scan deadline and check it during the file/line
processing loop so the operation sets truncated and stops once the deadline is
reached, while preserving the existing max and MAX_GREP_FILES limits.

In `@openwiki/src/lib/ui.mjs`:
- Around line 1284-1332: Update openAbout and openAsk to use the shared
openModal helper with each modal box, declaring role="dialog" and
aria-modal="true". Use the returned close function for the Close buttons, and
ensure openModal handles Escape dismissal, traps Tab focus within the dialog,
and restores focus to the previously focused element when closed.

---

Minor comments:
In `@openwiki/README.md`:
- Around line 50-51: Update the README quickstart browser instruction around the
localhost URL to be platform-neutral: display the URL directly or provide
appropriate alternatives for macOS, Linux, and Windows instead of relying on the
`open` command.

In `@openwiki/src/index.mjs`:
- Around line 1142-1147: The live-progress failure contract must be fixed in
both sites: in openwiki/src/index.mjs:1142-1147, update the
openwiki::http::events handler to return status 501 when w?.stream is
unavailable; in openwiki/src/lib/ui.mjs:851-892, update es.onerror to close the
EventSource and start the existing polling fallback when no event has arrived
yet, rather than allowing automatic reconnect. Use the existing event-tracking
and poll-fallback symbols in that handler.

In `@openwiki/src/lib/ask.mjs`:
- Around line 124-151: Update askDeep to validate the session_id returned by
worker.trigger before calling awaitTurn. If it is missing, throw immediately
using the same failure behavior and error semantics as runOrchestrator, while
preserving the existing awaitTurn flow for valid session IDs.

In `@openwiki/src/lib/configuration.mjs`:
- Around line 7-11: Sanitize the DEFAULTS values for OPENWIKI_MAX_PARALLEL and
OPENWIKI_REFRESH_DEFAULT in the configuration module: use a validated numeric
fallback, clamp max_parallel to the declared 1–16 range, and accept
refresh_default only when it matches the declared enum, otherwise use its valid
default. Ensure these sanitized values are what
schema().properties.max_parallel.default and initial_value receive.

In `@openwiki/src/lib/heuristic.mjs`:
- Around line 167-206: Update generatePageHeuristic to safely handle nullish
categories and sourceReads by using empty-array defaults before calling find,
indexing, or iterating. Also guard any allSlugs and outlineItem.source_paths
usage within this fallback with the same defaults, preserving the original LLM
error instead of throwing a TypeError.

In `@openwiki/src/lib/lint.mjs`:
- Around line 30-44: Update the citation range validation around readSourceFile
so truncated content does not produce false broken-citation issues: either read
the complete source for line-count validation or detect the truncation marker
and skip the start_line/end_line bounds check. Preserve missing-file handling
and existing validation for complete content.

In `@openwiki/src/lib/model.mjs`:
- Around line 10-17: Guard each fallback predicate in the model-selection logic
around byId so null catalog entries are skipped before reading
supports_structured_output or supports_tools. Preserve the existing preference
order and list[0] fallback while ensuring null entries cannot cause a TypeError.

In `@openwiki/src/lib/schemas.mjs`:
- Around line 16-45: Update WIKI_META to define the persisted refresh_schedule,
last_refresh_at, and steer fields in its properties while preserving
additionalProperties: false. Use the existing schema type conventions and ensure
direct wiki records and matching HTTP endpoint responses validate without
dropping these fields.

In `@openwiki/src/lib/search.mjs`:
- Around line 19-41: Update makeSnippet to return raw snippet text instead of
passing its output through escapeHtml, including both the matched and fallback
branches. Preserve the existing slicing and ellipsis behavior; presentation
already uses textContent, so remove only the payload-level HTML escaping.

In `@openwiki/src/lib/store.mjs`:
- Around line 153-176: Update the wiki deletion flow in the delete handlers that
call store.deleteWiki(id) to also remove the deleted wiki’s entries from the
per-wiki invCache and readStats caches. Ensure both delete paths invalidate
these caches after deletion so a recreated wiki cannot reuse stale inventory or
statistics.
- Around line 222-228: Update appendLog to use the same per-wiki serialization
mechanism already used by the page index, wrapping its read-modify-write of
S_LOG so concurrent writers are serialized. Preserve the existing timestamping,
500-entry trimming, and persistence behavior.

In `@openwiki/src/lib/ui.mjs`:
- Around line 736-741: Update the `wiki-del` button handler in the wiki
rendering block to request explicit user confirmation before calling
`deleteWiki(w.id)`. Keep stopping event propagation, and invoke deletion only
when confirmation is accepted; otherwise leave the wiki unchanged.

---

Nitpick comments:
In `@openwiki/src/index.mjs`:
- Around line 716-720: Update the remaining id-taking worker
registrations—refresh, status, wiki, lint, gen-stats, ask, diagram,
read-wiki-structure, and read-wiki-contents—to use the existing needId helper
instead of directly destructuring { id }. Preserve each handler’s current
behavior while ensuring wiki_id is normalized to the id argument before reaching
the store.

In `@openwiki/src/lib/ask.mjs`:
- Around line 186-202: Update both catch blocks in the answer-generation flow
around askDeep and askFastLLM to capture the thrown error and emit its message
with console.warn before setting answer to null, preserving the existing
fallback behavior.
- Around line 153-168: Update fileAnswer so the slug derived from slugify(q) is
made unique before passing it to store.savePage, appending a short deterministic
hash or counter to distinguish questions that normalize to the same slug. Use
the resulting unique slug consistently in the saved metadata and returned value.

In `@openwiki/src/lib/docs_oracle.mjs`:
- Around line 61-72: The fetchDocsIndex function contains nested loops that
sequentially call webFetch with 30-second timeouts for each of up to eight calls
(four origins and two paths each), potentially causing a 240-second hang before
fallback. Implement an overall time budget for the entire fetchDocsIndex
function that tracks elapsed time across all webFetch calls. Either reduce the
per-call timeoutMs value passed to webFetch, or add logic to break out of both
the inner path loop and outer candidateOrigins loop when the remaining budget is
exhausted. Ensure the function returns or falls through to the docs/ fallback
when the budget runs out.

In `@openwiki/src/lib/harness.mjs`:
- Around line 236-271: Update the retry loop around runPageTurn to clamp
maxAttempts to at least 1 before iteration, ensuring every valid execution
reaches the existing return inside the loop; then remove the unreachable
trailing return best, or retain it only if the clamp is explicitly documented
and callers’ expected result is preserved.
- Around line 16-27: Update citationUrl to encode each slash-delimited path
segment before interpolating path into the GitHub URL, preserving slash
separators and line-fragment behavior. Use encodeURIComponent for individual
segments so spaces, #, and other special characters produce valid deep links.

In `@openwiki/src/lib/inventory.mjs`:
- Around line 146-161: Update inventoryRepo’s per-file hashing logic to avoid
reading up to maxBytes during repository walks. Derive the 12-character sha1
value from the existing fs.stat metadata, such as size and mtimeMs, while
preserving the sha field’s change-detection role; if content-based identity is
required, cap reads to only a small fixed prefix instead of maxBytes.

In `@openwiki/src/lib/progress.mjs`:
- Around line 7-17: Update pushProgress and onProgress to use a shared prefixed
event-name namespace derived from wikiId instead of emitting or subscribing to
the raw wikiId. Ensure both functions apply the same prefix so progress delivery
remains unchanged while preventing collisions with reserved EventEmitter names
such as error.

In `@openwiki/src/lib/quality.mjs`:
- Around line 24-51: Update getPageQualityIssues to normalize markdown with
stripFences(String(markdown || '')) before running heading, section, Sources,
and word-count checks, reusing that single stripped result for all validations.
Ensure stripFences is available through the existing quality module utility or
define it alongside the function without changing other validation behavior.

In `@openwiki/src/lib/schemas.mjs`:
- Around line 378-390: Update PAGE_HARNESS_OUT to use a model-facing citation
schema containing only path, start_line, end_line, and note, instead of reusing
CITATION with url. Keep the existing citation array structure while ensuring
model-supplied urls are not accepted.

In `@openwiki/src/lib/search.mjs`:
- Around line 43-85: Optimize searchPages by reusing a per-wiki cached token map
or inverted index instead of calling getPage and tokenizing every page body for
each query. Build or refresh the cache from page data as needed, preserve the
existing scoring, matched terms, snippets, sorting, and limit behavior, and
invalidate the relevant wiki cache in store.savePage whenever a page is saved.

In `@openwiki/src/lib/ui.mjs`:
- Around line 1046-1057: Make the `.fhead` navigation folder toggle
keyboard-operable in `renderNavNode` by adding focusability and an appropriate
button-like role, then handle keyboard activation for Enter and Space using the
same collapse/expand logic as `onclick`. Preserve the existing mouse behavior
and ensure Space does not trigger unwanted page scrolling.
🪄 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: CHILL

Plan: Pro Plus

Run ID: 1d37fead-1b74-4d61-ab00-87cefe4e095f

📥 Commits

Reviewing files that changed from the base of the PR and between c6957fc and 9d7e166.

⛔ Files ignored due to path filters (3)
  • openwiki/assets/openwiki-dark.png is excluded by !**/*.png
  • openwiki/assets/openwiki-light.png is excluded by !**/*.png
  • openwiki/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (43)
  • .github/workflows/create-tag.yml
  • .github/workflows/release.yml
  • README.md
  • iii-permissions.yaml
  • openwiki/.gitignore
  • openwiki/README.md
  • openwiki/biome.json
  • openwiki/iii.worker.yaml
  • openwiki/package.json
  • openwiki/scripts/build-bundle.mjs
  • openwiki/skills/SKILL.md
  • openwiki/src/index.mjs
  • openwiki/src/lib/agents_md.mjs
  • openwiki/src/lib/ask.mjs
  • openwiki/src/lib/configuration.mjs
  • openwiki/src/lib/diagram.mjs
  • openwiki/src/lib/docs_oracle.mjs
  • openwiki/src/lib/generate.mjs
  • openwiki/src/lib/git.mjs
  • openwiki/src/lib/harness.mjs
  • openwiki/src/lib/heuristic.mjs
  • openwiki/src/lib/inventory.mjs
  • openwiki/src/lib/lint.mjs
  • openwiki/src/lib/model.mjs
  • openwiki/src/lib/nav.mjs
  • openwiki/src/lib/progress.mjs
  • openwiki/src/lib/quality.mjs
  • openwiki/src/lib/schemas.mjs
  • openwiki/src/lib/search.mjs
  • openwiki/src/lib/src.mjs
  • openwiki/src/lib/store.mjs
  • openwiki/src/lib/turnbus.mjs
  • openwiki/src/lib/ui.mjs
  • openwiki/tests/config.test.mjs
  • openwiki/tests/docs.test.mjs
  • openwiki/tests/git.test.mjs
  • openwiki/tests/harness.test.mjs
  • openwiki/tests/model.test.mjs
  • openwiki/tests/nav.test.mjs
  • openwiki/tests/quality.test.mjs
  • openwiki/tests/src.test.mjs
  • openwiki/tests/store.test.mjs
  • openwiki/tests/surfaces.test.mjs

Comment thread openwiki/src/index.mjs
Comment thread openwiki/src/index.mjs
Comment thread openwiki/src/lib/generate.mjs Outdated
Comment thread openwiki/src/lib/git.mjs
Comment thread openwiki/src/lib/git.mjs
Comment thread openwiki/src/lib/harness.mjs
Comment thread openwiki/src/lib/inventory.mjs
Comment thread openwiki/src/lib/model.mjs
Comment thread openwiki/src/lib/src.mjs
Comment thread openwiki/src/lib/ui.mjs
…ded reads

- refresh: per-wiki inflight guard (concurrent refresh returns in_progress),
  failed diffs rebuild instead of reporting up_to_date, and gitPull fetches
  with history so the recorded commit stays diffable
- git: validate repo_url/ref before building argv (scheme allowlist, no
  leading dash) and terminate option parsing with --
- HTTP delete tears down the wiki's cron trigger and drops per-wiki
  inventory/read-stat caches, same as openwiki::delete
- schemas: declare refresh_schedule/last_refresh_at/steer on wiki meta and
  quality_issues on page meta; model-facing citation schema omits url
- planning: key-docs reader resolves inventory relPath against the clone
  dir (was silently reading nothing)
- bounded reads: readSourceFile reads at most maxBytes+1 through a handle;
  src::grep caps pattern length and stops at a scan deadline
- model resolution: never cache an unresolved router lookup; skip null
  catalog entries
- config: clamp OPENWIKI_MAX_PARALLEL and validate OPENWIKI_REFRESH_DEFAULT
  against the declared schema
- ask: fail fast on a missing harness session id, log fallback causes,
  hash-suffix filed-answer slugs; lint skips line checks on truncated reads;
  search snippets stay raw text (UI renders via textContent)
- UI: shared modal helper (dialog role, Escape, focus trap and restore),
  delete confirmation, keyboard-operable nav folders, SSE errors before the
  first event fall back to polling (server returns 501 when unstreamable)
- heuristic fallback tolerates partial options; docs probe runs under an
  overall time budget; quality checks ignore fenced code
@rohitg00
rohitg00 merged commit 3095ffe into main Jul 31, 2026
15 of 16 checks passed
@rohitg00
rohitg00 deleted the feat/openwiki-worker branch July 31, 2026 12:22
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