Skip to content

Improve sdpub archive exploration for agents#58

Merged
Moskize91 merged 10 commits into
mainfrom
fix/blind-review-cli-flow
Jun 16, 2026
Merged

Improve sdpub archive exploration for agents#58
Moskize91 merged 10 commits into
mainfrom
fix/blind-review-cli-flow

Conversation

@Moskize91

@Moskize91 Moskize91 commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Rework archive exploration around structure, search, and reading flows for .sdpub archives.
  • Remove the legacy sdpub command family and evidence/sentence-facing surfaces from routine CLI usage.
  • Improve find semantics for agent workflows, including any-match defaults, search lenses, paging, and typed discovery hints.
  • Add chapter maintenance support for title updates.

Verification

  • pnpm run lint:fix
  • pnpm verify
  • pnpm test:run
  • pnpm exec tsc -p tsconfig.json --noEmit

@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Summary by CodeRabbit

  • New Features

    • Added list, find, grep, page, and read commands for archive exploration.
    • Introduced spinedigest transform for one-shot digest operations.
    • Added spinedigest meta and spinedigest cover for archive metadata management.
    • New chapter set-title command for editing chapter titles.
  • Removed Features

    • Removed deprecated evidence, sdpub, and map commands.
  • Documentation

    • Completely revised CLI help text and user guides with archive-first workflow emphasis.
    • Updated command reference documentation across English and Chinese locales.

Walkthrough

This PR replaces the older sdpub-centered CLI surface with archive-first commands and help text. It adds list, read, meta, cover, chapter, transform, and config status command coverage, removes older sdpub help/routing, and updates argument parsing and main dispatch. Archive view APIs now return typed collection and search results with pagination, node groups, fragment navigation, and source fragment excerpts. Chapter title editing and archive metadata/cover maintenance were added. Documentation and tests were updated to match the new command shapes and error messages.

Possibly related PRs

  • oomol-lab/spinedigest#54: Both PRs touch the chapter/stage maintenance surface and CLI routing around archive editing workflows.
  • oomol-lab/spinedigest#37: Both PRs modify the layered CLI help rendering and help-route plumbing in src/cli/help.ts and related parsing code.
  • oomol-lab/spinedigest#40: Both PRs change CLI help-route construction in src/cli/errors.ts, including the maintenance help path behavior.
✨ Finishing Touches
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/blind-review-cli-flow

@Moskize91 Moskize91 merged commit 4d403e5 into main Jun 16, 2026
2 of 3 checks passed
@Moskize91 Moskize91 deleted the fix/blind-review-cli-flow branch June 16, 2026 11:14

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/cli/args.ts (2)

435-439: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Reject unsupported globally-registered flags in each command parser.

These parser branches don’t reject all globally-known options, so flags like --id, --cursor, --type, --order, --match, --chapter, --confirm, --budget, and --to can be parsed and then silently ignored depending on command. That hides user mistakes instead of failing fast.

Also applies to: 1146-1152, 1632-1641

🤖 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 `@src/cli/args.ts` around lines 435 - 439, The parser branches are not
rejecting all globally-known options, allowing flags like --id, --cursor,
--type, --order, --match, --chapter, --to to be silently ignored instead of
failing fast. In src/cli/args.ts at lines 435-439, add rejectConvertFlag calls
for all missing globally-known flags (--id, --cursor, --type, --order, --match,
--chapter, --to) in addition to the existing rejectConvertFlag calls for budget,
confirm, and json. Apply the same set of rejectConvertFlag calls to the other
affected sites at lines 1146-1152 and 1632-1641 to ensure consistent validation
across all command parser branches.

2093-2114: ⚠️ Potential issue | 🟠 Major

Remove stale fragments acceptance from isArchiveAction.

isArchiveAction currently returns true for "fragments", but that action is not part of CLIArchiveAction and has no branch in parseArchiveArguments. This can route invalid input into an internal parser failure path instead of a clean unknown-command error.

Suggested fix
 function isArchiveAction(value: string | undefined): value is CLIArchiveAction {
   return (
     value === "backlinks" ||
     value === "build" ||
     value === "estimate" ||
-    value === "fragments" ||
     value === "export" ||
🤖 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 `@src/cli/args.ts` around lines 2093 - 2114, Remove the stale `"fragments"`
string comparison from the isArchiveAction type guard function. The
`"fragments"` action is not a valid member of the CLIArchiveAction type and has
no corresponding handler in parseArchiveArguments, so accepting it as valid
causes invalid input to route into an internal parser failure instead of
producing a clean unknown-command error. Simply delete the line checking `value
=== "fragments" ||` from the function's conditional chain.
🧹 Nitpick comments (1)
test/cli/main.test.ts (1)

165-165: ⚡ Quick win

Strengthen negative-dispatch assertions for all maintenance handlers.

At Line 165, Line 182, Line 208, and Line 251, the tests only verify archiveMetaRunCalls is untouched. Add zero-call checks for archiveCoverRunCalls and archiveChapterRunCalls too, so accidental misrouting in non-maintenance paths is caught.

Suggested assertion additions
-    expect(mainMockState.archiveMetaRunCalls).toHaveLength(0);
+    expect(mainMockState.archiveMetaRunCalls).toHaveLength(0);
+    expect(mainMockState.archiveCoverRunCalls).toHaveLength(0);
+    expect(mainMockState.archiveChapterRunCalls).toHaveLength(0);

Also applies to: 182-182, 208-208, 251-251

🤖 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 `@test/cli/main.test.ts` at line 165, Strengthen the negative-dispatch
assertions at all four locations (lines 165, 182, 208, and 251) by adding checks
for archiveCoverRunCalls and archiveChapterRunCalls in addition to the existing
archiveMetaRunCalls checks. For each location, after the existing
expect(mainMockState.archiveMetaRunCalls).toHaveLength(0) assertion, add two
more assertions to verify that mainMockState.archiveCoverRunCalls and
mainMockState.archiveChapterRunCalls also have length 0, ensuring that
accidental misrouting in non-maintenance paths is properly caught.
🤖 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 `@data/help/commands/maintenance/chapter/reset.jinja`:
- Line 18: The help text on line 18 of the reset.jinja file incorrectly
references the `--stage` flag, but the actual command usage shown on line 10
uses `--to <stage>` instead. Update the wording in the line that begins with "-
`--stage summary --confirm` is not supported" to replace `--stage` with `--to`
so the flag reference matches the correct command syntax documented in the usage
section.

In `@data/help/commands/maintenance/chapter/set-summary.jinja`:
- Line 15: The workflow example at line 15 uses an incomplete command `read >
/tmp/summary.md` which is ambiguous and doesn't show the full CLI command
structure used elsewhere in the documentation. Replace this ambiguous example
with a complete, explicitly runnable workflow that demonstrates the full command
shape, showing how to use the CLI command to read into a file, edit it, and then
pass it back with the `--input` option in the proper command format.

In `@data/help/commands/transform.jinja`:
- Line 8: The spinedigest transform command synopsis is missing the standard
help flag documentation. Add --help|-h to the command synopsis in the transform
usage line to maintain consistency with the actual CLI capabilities and keep the
documentation complete. This flag should be included along with the other
optional flags to reflect that the help option is supported by the command.

In `@docs/zh-CN/cli.md`:
- Line 106: The chapter command synopsis in the Chinese CLI reference
documentation at docs/zh-CN/cli.md is missing the set-title subcommand from its
list of supported operations. Update the spinedigest chapter command synopsis on
line 106 to include set-title in the list of available subcommands alongside
list, status, add, remove, reset, set-source, and set-summary to make this
shipped action discoverable in the documentation.

In `@src/facade/archive-view.ts`:
- Around line 924-941: The fallback to fragment-based grouping only occurs when
snakes.length is zero, but does not account for the case where snake records
exist but all resulting groups are filtered out at the filter call. Modify the
return statement to capture the filtered result from the Promise.all chain and
check if it is empty. If the filtered array has zero length, return the fallback
result from listChapterNodeGroupsByFragment instead. Otherwise, return the
non-empty filtered array. This ensures that when all snake node groups resolve
to empty nodeCount values, the function properly falls back to fragment-based
grouping rather than returning an empty array.
- Around line 363-375: The code currently only includes the meta:book item when
meta is defined, creating an inconsistency with listArchiveObjects("meta") which
exposes meta entries regardless. Remove the `if (meta !== undefined)` condition
that gates the items.push() call, so that meta:book is always exposed when types
includes "meta". Instead of conditionally pushing, modify the snippet field to
conditionally use either formatMetaSummary(meta) when meta is defined or a
placeholder string (such as "No metadata available") when meta is undefined,
ensuring the item is always added to the collection.

In `@test/cli/archive.test.ts`:
- Around line 243-255: The grepArchiveObjects mock in the test file has
semantics that do not match the actual grep facade contract. Change the match
property from "any" to "all" and update the terms array to reflect how grep
actually normalizes phrase terms (rather than splitting them into individual
terms). This ensures the mock accurately represents the real grep facade
behavior and prevents hiding regressions in grep-specific CLI messaging paths.

---

Outside diff comments:
In `@src/cli/args.ts`:
- Around line 435-439: The parser branches are not rejecting all globally-known
options, allowing flags like --id, --cursor, --type, --order, --match,
--chapter, --to to be silently ignored instead of failing fast. In
src/cli/args.ts at lines 435-439, add rejectConvertFlag calls for all missing
globally-known flags (--id, --cursor, --type, --order, --match, --chapter, --to)
in addition to the existing rejectConvertFlag calls for budget, confirm, and
json. Apply the same set of rejectConvertFlag calls to the other affected sites
at lines 1146-1152 and 1632-1641 to ensure consistent validation across all
command parser branches.
- Around line 2093-2114: Remove the stale `"fragments"` string comparison from
the isArchiveAction type guard function. The `"fragments"` action is not a valid
member of the CLIArchiveAction type and has no corresponding handler in
parseArchiveArguments, so accepting it as valid causes invalid input to route
into an internal parser failure instead of producing a clean unknown-command
error. Simply delete the line checking `value === "fragments" ||` from the
function's conditional chain.

---

Nitpick comments:
In `@test/cli/main.test.ts`:
- Line 165: Strengthen the negative-dispatch assertions at all four locations
(lines 165, 182, 208, and 251) by adding checks for archiveCoverRunCalls and
archiveChapterRunCalls in addition to the existing archiveMetaRunCalls checks.
For each location, after the existing
expect(mainMockState.archiveMetaRunCalls).toHaveLength(0) assertion, add two
more assertions to verify that mainMockState.archiveCoverRunCalls and
mainMockState.archiveChapterRunCalls also have length 0, ensuring that
accidental misrouting in non-maintenance paths is properly caught.
🪄 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

Run ID: 379c2d35-2230-402d-a456-70114a39dc15

📥 Commits

Reviewing files that changed from the base of the PR and between 4015d0c and a828d55.

📒 Files selected for processing (93)
  • README.md
  • README_zh-CN.md
  • data/help/commands/archive/evidence.jinja
  • data/help/commands/archive/find.jinja
  • data/help/commands/archive/grep.jinja
  • data/help/commands/archive/list.jinja
  • data/help/commands/archive/ls.jinja
  • data/help/commands/archive/pack.jinja
  • data/help/commands/archive/page.jinja
  • data/help/commands/archive/read.jinja
  • data/help/commands/archive/status.jinja
  • data/help/commands/config-status.jinja
  • data/help/commands/maintenance/chapter.jinja
  • data/help/commands/maintenance/chapter/add.jinja
  • data/help/commands/maintenance/chapter/list.jinja
  • data/help/commands/maintenance/chapter/remove.jinja
  • data/help/commands/maintenance/chapter/reset.jinja
  • data/help/commands/maintenance/chapter/set-source.jinja
  • data/help/commands/maintenance/chapter/set-summary.jinja
  • data/help/commands/maintenance/chapter/set-title.jinja
  • data/help/commands/maintenance/chapter/status.jinja
  • data/help/commands/maintenance/cover.jinja
  • data/help/commands/maintenance/meta.jinja
  • data/help/commands/root.jinja
  • data/help/commands/sdpub/cat.jinja
  • data/help/commands/sdpub/chapter.jinja
  • data/help/commands/sdpub/chapter/reset.jinja
  • data/help/commands/sdpub/chapter/set-summary.jinja
  • data/help/commands/sdpub/graph.jinja
  • data/help/commands/sdpub/graph/blame.jinja
  • data/help/commands/sdpub/graph/grep.jinja
  • data/help/commands/sdpub/graph/log.jinja
  • data/help/commands/sdpub/graph/neighbors.jinja
  • data/help/commands/sdpub/graph/path.jinja
  • data/help/commands/sdpub/graph/show.jinja
  • data/help/commands/sdpub/graph/status.jinja
  • data/help/commands/sdpub/index.jinja
  • data/help/commands/sdpub/info.jinja
  • data/help/commands/sdpub/list.jinja
  • data/help/commands/sdpub/stage.jinja
  • data/help/commands/sdpub/stage/advance.jinja
  • data/help/commands/sdpub/stage/pending.jinja
  • data/help/commands/sdpub/toc.jinja
  • data/help/commands/transform.jinja
  • data/help/topics/ai.jinja
  • data/help/topics/command.jinja
  • data/help/topics/config-file.jinja
  • data/help/topics/config.jinja
  • data/help/topics/env.jinja
  • data/help/topics/format.jinja
  • data/help/topics/index.jinja
  • data/help/topics/overview.jinja
  • data/help/topics/recipe.jinja
  • data/help/topics/runtime.jinja
  • data/help/topics/sdpub.jinja
  • data/help/topics/task.jinja
  • data/help/topics/troubleshoot.jinja
  • docs/en/ai-agents.md
  • docs/en/cli.md
  • docs/en/quickstart.md
  • docs/sdpub.md
  • docs/zh-CN/ai-agents.md
  • docs/zh-CN/cli.md
  • docs/zh-CN/quickstart.md
  • src/cli/archive-chapter.ts
  • src/cli/archive-maintenance.ts
  • src/cli/archive.ts
  • src/cli/args.ts
  • src/cli/errors.ts
  • src/cli/help.ts
  • src/cli/main.ts
  • src/cli/sdpub-graph.ts
  • src/cli/sdpub-stage.ts
  • src/cli/sdpub.ts
  • src/facade/archive-view.ts
  • src/facade/chapter.ts
  • src/facade/graph.ts
  • src/facade/index.ts
  • src/facade/spine-digest.ts
  • src/output/epub/book.ts
  • src/output/plain-text.ts
  • src/serial.ts
  • test/cli/README.md
  • test/cli/archive-chapter.test.ts
  • test/cli/archive-maintenance.test.ts
  • test/cli/archive.test.ts
  • test/cli/args.test.ts
  • test/cli/main.test.ts
  • test/cli/sdpub-graph.test.ts
  • test/cli/sdpub-stage.test.ts
  • test/cli/sdpub.test.ts
  • test/facade/archive-view.test.ts
  • test/facade/chapter.test.ts
💤 Files with no reviewable changes (29)
  • data/help/commands/sdpub/stage/pending.jinja
  • data/help/commands/archive/evidence.jinja
  • data/help/topics/sdpub.jinja
  • data/help/commands/sdpub/chapter.jinja
  • data/help/commands/sdpub/toc.jinja
  • data/help/commands/sdpub/info.jinja
  • docs/en/quickstart.md
  • data/help/commands/sdpub/stage/advance.jinja
  • data/help/commands/sdpub/cat.jinja
  • data/help/commands/sdpub/graph/grep.jinja
  • src/cli/sdpub-stage.ts
  • data/help/commands/sdpub/index.jinja
  • test/cli/sdpub-graph.test.ts
  • data/help/commands/sdpub/graph/log.jinja
  • data/help/commands/sdpub/chapter/set-summary.jinja
  • data/help/commands/sdpub/graph.jinja
  • data/help/commands/sdpub/chapter/reset.jinja
  • data/help/commands/sdpub/graph/show.jinja
  • data/help/commands/sdpub/stage.jinja
  • data/help/commands/sdpub/list.jinja
  • data/help/commands/sdpub/graph/path.jinja
  • data/help/commands/sdpub/graph/neighbors.jinja
  • src/cli/sdpub.ts
  • docs/zh-CN/quickstart.md
  • data/help/commands/sdpub/graph/status.jinja
  • test/cli/sdpub-stage.test.ts
  • test/cli/sdpub.test.ts
  • src/cli/sdpub-graph.ts
  • data/help/commands/sdpub/graph/blame.jinja

graphed Keep source text and graph data; delete summary.

Behavior:
- `--stage summary --confirm` is not supported because reset is a backward operation.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fix reset flag wording to match command usage.

Line 18 references --stage, but this command’s usage on Line 10 is --to <stage>. The current wording mixes reset and build flag semantics.

Suggested patch
-  - `--stage summary --confirm` is not supported because reset is a backward operation.
+  - `--to summary` is not supported because reset is a backward operation.
+  - To move forward to summary, use `spinedigest build <path> --chapter <id> --stage summary --confirm`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- `--stage summary --confirm` is not supported because reset is a backward operation.
- `--to summary` is not supported because reset is a backward operation.
- To move forward to summary, use `spinedigest build <path> --chapter <id> --stage summary --confirm`.
🤖 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 `@data/help/commands/maintenance/chapter/reset.jinja` at line 18, The help text
on line 18 of the reset.jinja file incorrectly references the `--stage` flag,
but the actual command usage shown on line 10 uses `--to <stage>` instead.
Update the wording in the line that begins with "- `--stage summary --confirm`
is not supported" to replace `--stage` with `--to` so the flag reference matches
the correct command syntax documented in the usage section.

Input:
- If `--input` is omitted, summary text is read from stdin.
- Summary text is stored as the chapter's final summary.
- A common workflow is `read > /tmp/summary.md`, edit the file, then pass it back with `--input`.

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Make the workflow example explicitly runnable.

Line 15 uses read > /tmp/summary.md, which is ambiguous in shell context and omits the CLI command shape used elsewhere in this page.

Suggested patch
-  - A common workflow is `read > /tmp/summary.md`, edit the file, then pass it back with `--input`.
+  - A common workflow is `spinedigest read <path> ... > /tmp/summary.md`, edit the file, then pass it back with `--input`.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- A common workflow is `read > /tmp/summary.md`, edit the file, then pass it back with `--input`.
- A common workflow is `spinedigest read <path> ... > /tmp/summary.md`, edit the file, then pass it back with `--input`.
🤖 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 `@data/help/commands/maintenance/chapter/set-summary.jinja` at line 15, The
workflow example at line 15 uses an incomplete command `read > /tmp/summary.md`
which is ambiguous and doesn't show the full CLI command structure used
elsewhere in the documentation. Replace this ambiguous example with a complete,
explicitly runnable workflow that demonstrates the full command shape, showing
how to use the CLI command to read into a file, edit it, and then pass it back
with the `--input` option in the proper command format.

Run a direct one-shot digest/export without creating a reusable `.sdpub` archive.

Usage:
spinedigest transform [--input <path>] [--output <path>] [--input-format <format>] [--output-format <format>] [--digest-dir <path>] [--llm <json>] [--prompt <text>] [--stage <planned|sourced|graphed|summarized>] [--verbose|-v]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Include --help|-h in the transform usage synopsis.

Line 8 omits the standard help flag even though spinedigest transform --help is supported. Keeping the synopsis complete avoids CLI-doc drift.

Proposed patch
-  spinedigest transform [--input <path>] [--output <path>] [--input-format <format>] [--output-format <format>] [--digest-dir <path>] [--llm <json>] [--prompt <text>] [--stage <planned|sourced|graphed|summarized>] [--verbose|-v]
+  spinedigest transform [--input <path>] [--output <path>] [--input-format <format>] [--output-format <format>] [--digest-dir <path>] [--llm <json>] [--prompt <text>] [--stage <planned|sourced|graphed|summarized>] [--verbose|-v] [--help|-h]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
spinedigest transform [--input <path>] [--output <path>] [--input-format <format>] [--output-format <format>] [--digest-dir <path>] [--llm <json>] [--prompt <text>] [--stage <planned|sourced|graphed|summarized>] [--verbose|-v]
spinedigest transform [--input <path>] [--output <path>] [--input-format <format>] [--output-format <format>] [--digest-dir <path>] [--llm <json>] [--prompt <text>] [--stage <planned|sourced|graphed|summarized>] [--verbose|-v] [--help|-h]
🤖 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 `@data/help/commands/transform.jinja` at line 8, The spinedigest transform
command synopsis is missing the standard help flag documentation. Add --help|-h
to the command synopsis in the transform usage line to maintain consistency with
the actual CLI capabilities and keep the documentation complete. This flag
should be included along with the other optional flags to reflect that the help
option is supported by the command.

Comment thread docs/zh-CN/cli.md
spinedigest sdpub graph <status|log|show|grep|neighbors|blame|path> <path> --chapter <id> [options]
spinedigest meta <archive.sdpub> [metadata options] [--json]
spinedigest cover <archive.sdpub>
spinedigest chapter <list|status|add|remove|reset|set-source|set-summary> <path> [options]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add set-title to the chapter command synopsis.

The Chinese CLI reference omits set-title from the supported chapter subcommands, but the parser/help surface supports it. This makes a shipped action undiscoverable in this page.

Suggested doc fix
-spinedigest chapter <list|status|add|remove|reset|set-source|set-summary> <path> [options]
+spinedigest chapter <list|status|add|remove|reset|set-source|set-summary|set-title> <path> [options]
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
spinedigest chapter <list|status|add|remove|reset|set-source|set-summary> <path> [options]
spinedigest chapter <list|status|add|remove|reset|set-source|set-summary|set-title> <path> [options]
🤖 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 `@docs/zh-CN/cli.md` at line 106, The chapter command synopsis in the Chinese
CLI reference documentation at docs/zh-CN/cli.md is missing the set-title
subcommand from its list of supported operations. Update the spinedigest chapter
command synopsis on line 106 to include set-title in the list of available
subcommands alongside list, status, add, remove, reset, set-source, and
set-summary to make this shipped action discoverable in the documentation.

Comment on lines +363 to +375
if (types.includes("meta")) {
const meta = await document.readBookMeta();

if (meta !== undefined) {
items.push({
field: "metadata",
id: "meta:book",
snippet: formatMetaSummary(meta),
title: meta.title ?? "Book metadata",
type: "meta",
});
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Always expose meta:book in collection listings

Line 366 currently suppresses the meta object when metadata is missing. That makes collection-based discovery inconsistent with listArchiveObjects("meta"), which still exposes a meta entry. Keep meta:book visible and use a missing placeholder snippet instead.

Proposed fix
   if (types.includes("meta")) {
     const meta = await document.readBookMeta();

-    if (meta !== undefined) {
-      items.push({
-        field: "metadata",
-        id: "meta:book",
-        snippet: formatMetaSummary(meta),
-        title: meta.title ?? "Book metadata",
-        type: "meta",
-      });
-    }
+    items.push({
+      field: "metadata",
+      id: "meta:book",
+      snippet: formatMetaSummary(meta),
+      title: meta?.title ?? "Book metadata",
+      type: "meta",
+    });
   }
🤖 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 `@src/facade/archive-view.ts` around lines 363 - 375, The code currently only
includes the meta:book item when meta is defined, creating an inconsistency with
listArchiveObjects("meta") which exposes meta entries regardless. Remove the `if
(meta !== undefined)` condition that gates the items.push() call, so that
meta:book is always exposed when types includes "meta". Instead of conditionally
pushing, modify the snippet field to conditionally use either
formatMetaSummary(meta) when meta is defined or a placeholder string (such as
"No metadata available") when meta is undefined, ensuring the item is always
added to the collection.

Comment on lines +924 to +941
if (snakes.length === 0) {
return listChapterNodeGroupsByFragment(document, chapterId);
}

return (
await Promise.all(
snakes.map(async (snake) =>
createArchiveNodeGroup({
groupId: snake.localSnakeId,
id: `node-group:${chapterId}:${snake.localSnakeId}`,
nodes: await listSnakeNodeLabels(document, snake),
weight: snake.weight,
wordsCount: snake.wordsCount,
}),
),
)
).filter((group) => group.nodeCount > 0);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Fallback grouping is missing when snake groups resolve to empty

Line 924 only falls back when there are zero snake records. If snake records exist but all are filtered out at Line 940 (e.g., stale snake-chunk links), chapter pages return empty nodeGroups despite non-zero nodeCount. Add a post-filter fallback to fragment-based grouping.

Proposed fix
 async function listChapterNodeGroups(
   document: Document,
   chapterId: number,
 ): Promise<readonly ArchiveNodeGroup[]> {
   const snakes = await document.snakes.listBySerial(chapterId);

   if (snakes.length === 0) {
     return listChapterNodeGroupsByFragment(document, chapterId);
   }

-  return (
+  const groups = (
     await Promise.all(
       snakes.map(async (snake) =>
         createArchiveNodeGroup({
           groupId: snake.localSnakeId,
           id: `node-group:${chapterId}:${snake.localSnakeId}`,
           nodes: await listSnakeNodeLabels(document, snake),
           weight: snake.weight,
           wordsCount: snake.wordsCount,
         }),
       ),
     )
   ).filter((group) => group.nodeCount > 0);
+
+  return groups.length > 0
+    ? groups
+    : await listChapterNodeGroupsByFragment(document, chapterId);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (snakes.length === 0) {
return listChapterNodeGroupsByFragment(document, chapterId);
}
return (
await Promise.all(
snakes.map(async (snake) =>
createArchiveNodeGroup({
groupId: snake.localSnakeId,
id: `node-group:${chapterId}:${snake.localSnakeId}`,
nodes: await listSnakeNodeLabels(document, snake),
weight: snake.weight,
wordsCount: snake.wordsCount,
}),
),
)
).filter((group) => group.nodeCount > 0);
}
if (snakes.length === 0) {
return listChapterNodeGroupsByFragment(document, chapterId);
}
const groups = (
await Promise.all(
snakes.map(async (snake) =>
createArchiveNodeGroup({
groupId: snake.localSnakeId,
id: `node-group:${chapterId}:${snake.localSnakeId}`,
nodes: await listSnakeNodeLabels(document, snake),
weight: snake.weight,
wordsCount: snake.wordsCount,
}),
),
)
).filter((group) => group.nodeCount > 0);
return groups.length > 0
? groups
: await listChapterNodeGroupsByFragment(document, chapterId);
}
🤖 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 `@src/facade/archive-view.ts` around lines 924 - 941, The fallback to
fragment-based grouping only occurs when snakes.length is zero, but does not
account for the case where snake records exist but all resulting groups are
filtered out at the filter call. Modify the return statement to capture the
filtered result from the Promise.all chain and check if it is empty. If the
filtered array has zero length, return the fallback result from
listChapterNodeGroupsByFragment instead. Otherwise, return the non-empty
filtered array. This ensures that when all snake node groups resolve to empty
nodeCount values, the function properly falls back to fragment-based grouping
rather than returning an empty array.

Comment thread test/cli/archive.test.ts
Comment on lines +243 to +255
grepArchiveObjects: vi.fn(() =>
Promise.resolve({
chapters: null,
items: archiveMockState.grepHits,
lens: "exact",
lensHint: null,
limit: 20,
match: "any",
nextCursor: null,
order: "doc-asc",
query: "exact phrase",
terms: ["exact", "phrase"],
types: null,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Align grep mock semantics with actual facade contract.

Line 250 sets match: "any" and Lines 254-255 split terms, but grep currently returns exact phrase semantics (match: "all" with normalized phrase terms). This mismatch can hide regressions in grep-specific CLI messaging paths.

Suggested fix
-  grepArchiveObjects: vi.fn(() =>
+  grepArchiveObjects: vi.fn((_document: unknown, query: string) =>
     Promise.resolve({
       chapters: null,
       items: archiveMockState.grepHits,
       lens: "exact",
       lensHint: null,
       limit: 20,
-      match: "any",
+      match: "all",
       nextCursor: null,
       order: "doc-asc",
-      query: "exact phrase",
-      terms: ["exact", "phrase"],
+      query,
+      terms: [query.trim().toLowerCase()],
       types: null,
     }),
   ),
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
grepArchiveObjects: vi.fn(() =>
Promise.resolve({
chapters: null,
items: archiveMockState.grepHits,
lens: "exact",
lensHint: null,
limit: 20,
match: "any",
nextCursor: null,
order: "doc-asc",
query: "exact phrase",
terms: ["exact", "phrase"],
types: null,
grepArchiveObjects: vi.fn((_document: unknown, query: string) =>
Promise.resolve({
chapters: null,
items: archiveMockState.grepHits,
lens: "exact",
lensHint: null,
limit: 20,
match: "all",
nextCursor: null,
order: "doc-asc",
query,
terms: [query.trim().toLowerCase()],
types: null,
🤖 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 `@test/cli/archive.test.ts` around lines 243 - 255, The grepArchiveObjects mock
in the test file has semantics that do not match the actual grep facade
contract. Change the match property from "any" to "all" and update the terms
array to reflect how grep actually normalizes phrase terms (rather than
splitting them into individual terms). This ensures the mock accurately
represents the real grep facade behavior and prevents hiding regressions in
grep-specific CLI messaging paths.

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