asciidoc-pubkit 0.8.6 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e1fb57dff066c28a30d6205ad30ff8c5cd1a89fc27c35ec8379e9c80354843ae
4
- data.tar.gz: 9431768603df5a8191ba93b0bc5773ad188050e06304e6f0f35272bbe132ef51
3
+ metadata.gz: 75fa596bc050de3a0b084f4072fa830156ea1b82e923aaf8d2e429c0d7735465
4
+ data.tar.gz: 3b510d7c39d8c2d418fd05b31feaf724fed8d246a61a7bc6adb316e9d3cea32b
5
5
  SHA512:
6
- metadata.gz: 901984c71c8178ba62738e21ec67cb5585ebe72719ef06c03b38585c5ad7d0961a9fe4da78c47d1612e9c339d6170bee9a060ea4cf2cf68e9301ae4b80bb96ac
7
- data.tar.gz: 70570e253745de217ba1d4eb2c5447a7a1dba2f72e3d1f5f1abb40787d54c996fa9dca3072bb190071a459346146200e5744e9eb5fa72f4410c9dd314af71333
6
+ metadata.gz: 7c1906759470ebd93a70f1960c575893ec68e9ecb8ded59786ea4871e4c790ad70acf5f38db7dcd11f6291f01338ba000aec1f4357848409b75f0ca15fc08a1f
7
+ data.tar.gz: 4d26fe3ad162a62e326729cd76f269d2b0d42a0c1329a0526f65cd99ef5ab6bcfb23b19b9b8fb25fedd2415ebdaf0ef1d97074732a4a2abcc0dcae3b96a20c0f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.9.0 — 2026-10-03
4
+
5
+ - Add read-only `document toc`, `document info`, `document check-xrefs`, and
6
+ `document index` commands without MeCab, review settings, or AI invocation.
7
+ Parse local includes and conditions within an explicit source boundary;
8
+ reject remote includes, parse errors, and existing output files. Support book
9
+ parts, strict depth selection, metadata JSON, local cross-reference diagnostics,
10
+ and deterministic English document-title indexes.
11
+
12
+ - Introduce document inspection before prose replacements and review in the README.
13
+
14
+ - Explain when optional list review is needed, why its scope is separate from
15
+ running prose, and how to preserve phase baselines in a complete review.
16
+
17
+ ## 0.8.7 — 2026-10-02
18
+
19
+ - Make exact saved old-ID anchor permissions standard for eligible heading
20
+ scans. Remove `--preserve-heading-ids`; continue protecting existing anchors,
21
+ section IDs, structure, and body content. Existing session permissions and
22
+ baselines remain unchanged.
23
+ - Tell prose reviewers in scan output and generated prompts to review list text
24
+ in a separate `--scope lists` session without replacing the prose baseline.
25
+
3
26
  ## 0.8.6 — 2026-10-02
4
27
 
5
28
  - Add separate `review scan --scope lists` sessions for source-mapped single-line
data/README.md CHANGED
@@ -7,20 +7,24 @@
7
7
 
8
8
  A toolkit for authoring and reviewing AsciiDoc books.
9
9
 
10
- Version 0.8.6 provides shared Japanese technical writing criteria, an authoring
11
- prompt, separate prose, heading, and list review prompts, manuscript scoring, and
12
- baseline verification for edited manuscripts, plus explicit prose-only mechanical
13
- replacements using a limited prh-format rule set.
10
+ Version 0.9.0 provides AsciiDoc document inspection, explicit prose-only
11
+ mechanical replacements, and Japanese manuscript review. Start by inspecting the
12
+ outline, book metadata, and local cross-references; preview and apply your
13
+ replacement rules; then review and verify the edited manuscript against a saved
14
+ baseline. Document commands also generate English document-title indexes.
14
15
  The CLI, diagnostics, documentation, and generated instructions are in English.
15
16
  Japanese text is retained in manuscript excerpts, rule dictionaries, and fixtures.
16
17
 
17
18
  ## Available features
18
19
 
19
- The following commands are available in version 0.8.6.
20
+ The following commands are available in version 0.9.0.
20
21
 
21
22
  | Feature | Commands | Behavior |
22
23
  | --- | --- | --- |
23
- | [Writing criteria and prompts](#writing-commands) | `writing criteria`, `writing prompt` | Print Japanese prose criteria or generate a writing prompt without a manuscript or MeCab |
24
+ | [Document outline](#document-commands) | `document toc` | Print parsed section titles, including book parts and chapters, with optional depth and outline numbering |
25
+ | [Book metadata](#document-commands) | `document info` | Extract book attributes as text or JSON |
26
+ | [Cross-reference checks](#document-commands) | `document check-xrefs` | Report unresolved local cross-references with source locations |
27
+ | [Document-title index](#document-commands) | `document index` | Generate an alphabetical AsciiDoc index from English document titles and IDs |
24
28
  | [Replacement preview](#prose-replacements) | `replace check`, `replace diff` | Report mechanical replacement candidates or preview a diff for mapped running prose; preserve source files |
25
29
  | [Replacement application](#prose-replacements) | `replace apply` | Apply author-supplied rules after checking protected content and document structure; no MeCab or AI required |
26
30
  | [Prose review](#review-workflow) | `review scan` | Collect mapped Japanese running prose and contextual review candidates with MeCab/IPADIC by default |
@@ -29,42 +33,29 @@ The following commands are available in version 0.8.6.
29
33
  | [Review prompts](#prompt) | `review prompt` | Generate one Markdown prompt from a saved session for review and editing by an external reviewer |
30
34
  | [Baseline verification](#verify) | `review verify` | Check edited sources against a saved baseline for mechanical preservation; meaning is not verified |
31
35
  | [Manuscript scoring](#score-an-asciidoc-manuscript) | `review score` | Report a prose candidate-density score; explicit `--agent codex` or `--agent claude` optionally invokes a local AI CLI for readability ratings |
32
-
33
- Version 0.8.1 additionally supports nested replacement-rule imports,
34
- omitted/null `rules`, arrays in `pattern`, `/i`, and limited ECMAScript word
35
- boundaries. See [Prose replacements](#prose-replacements) for supported syntax
36
- and preservation limits. Only `replace apply` edits manuscripts directly;
37
- review prompts supply instructions for an external reviewer.
36
+ | [Writing criteria and prompts](#writing-commands) | `writing criteria`, `writing prompt` | Print Japanese prose criteria or generate a writing prompt without a manuscript or MeCab |
38
37
 
39
38
  ## Status
40
39
 
41
- Version 0.6.3 adds separate heading review with `review scan --scope headings`.
42
- See [Heading review](#heading-review).
43
-
44
- Version 0.8.0 adds explicit prose replacements with `replace apply`;
45
- see [Prose replacements](#prose-replacements). Review and scoring do not edit
46
- manuscripts. The CLI does not publish books. EPUB, image,
47
- and book scaffolding commands are planned extensions, not available features.
48
-
49
40
  Ruby 3.2 or later is required. Asciidoctor is installed as a gem dependency.
50
- Starting with version 0.1.1, the default review backend requires the external MeCab
51
- command and a UTF-8 IPADIC dictionary. Node.js and textlint are not required.
52
- Explicit `--tokenizer literal` mode provides limited phrase matching without MeCab.
53
-
54
- Version 0.6.2 also provides `review score FILE` for a manuscript score.
55
- It uses local candidate analysis by default; `--agent codex` or `--agent claude`
56
- explicitly asks an installed CLI to evaluate readability and may connect to its
57
- configured model provider. No scoring mode edits manuscripts.
58
-
59
- Version 0.1.1 added morphological analysis. Version 0.1.0 used literal matching.
60
-
61
- The CLI has `writing`, `review`, and `replace` command groups. All default to `--lang ja`.
62
- Language-specific criteria live under `data/writing/<language>/`; review rules
63
- use `data/review-rules.<language>.yml`. Writing and review have separate lists
64
- of supported languages, so a future writing language need not imply review
65
- support. Other languages are reserved for future implementations and now
66
- return an explicit error. A review session saves its language and the criteria
67
- used to generate its prompt.
41
+ Document inspection and prose replacement require neither MeCab nor an AI CLI.
42
+ Japanese review uses the external MeCab command and a UTF-8 IPADIC dictionary by
43
+ default; explicit `--tokenizer literal` provides limited phrase matching.
44
+ Node.js and textlint are not required.
45
+
46
+ Only `replace apply` edits manuscripts directly. Review prompts supply
47
+ instructions for an external reviewer; review and scoring do not edit
48
+ manuscripts. Only `review score --agent codex` or `--agent claude` invokes an
49
+ installed AI CLI, which may connect to its configured model provider.
50
+ The CLI does not publish books. EPUB, image, and book scaffolding commands are
51
+ planned extensions, not available features.
52
+
53
+ The CLI has `document`, `replace`, `review`, and `writing` command groups.
54
+ Document commands accept documents in any language and do not load review
55
+ configuration. Writing, replacement, and review default to `--lang ja` and reject
56
+ unsupported languages. Language-specific criteria live under
57
+ `data/writing/<language>/`; review rules use `data/review-rules.<language>.yml`.
58
+ A review session saves its language and the criteria used to generate its prompt.
68
59
 
69
60
  ## Install from RubyGems
70
61
 
@@ -80,16 +71,17 @@ Ruby 3.2 or later is required. RubyGems installs the required Ruby dependencies;
80
71
  no repository clone or Node.js installation is needed. MeCab and IPADIC must be
81
72
  installed separately when using version 0.1.1 or later in the default mode.
82
73
 
83
- Generate a writing prompt without an existing manuscript:
84
-
85
- ```sh
86
- asciidoc-pubkit writing criteria --lang ja
87
- asciidoc-pubkit writing prompt --lang ja --output writing-prompt.md
88
- ```
89
-
90
- Run the review workflow from your manuscript directory:
74
+ Inspect the document, apply explicit replacement rules, and start the review
75
+ workflow from your manuscript directory:
91
76
 
92
77
  ```sh
78
+ asciidoc-pubkit document toc book.adoc --depth 2 --numbered
79
+ asciidoc-pubkit document info book.adoc --json
80
+ asciidoc-pubkit document check-xrefs book.adoc
81
+ asciidoc-pubkit replace check book.adoc --rules replacements.yml
82
+ asciidoc-pubkit replace diff book.adoc --rules replacements.yml
83
+ asciidoc-pubkit replace apply book.adoc --rules replacements.yml
84
+ # Start a new review baseline after completing mechanical replacements.
93
85
  asciidoc-pubkit review scan book.adoc --output .pubkit/review
94
86
  asciidoc-pubkit review prompt .pubkit/review --output review-prompt.md
95
87
  # Ask your agent to review the prompt and edit the referenced manuscript.
@@ -108,13 +100,16 @@ Add the gem to your project's `Gemfile` to manage its version with Bundler:
108
100
 
109
101
  ```ruby
110
102
  source 'https://rubygems.org'
111
- gem 'asciidoc-pubkit', '~> 0.8.6'
103
+ gem 'asciidoc-pubkit', '~> 0.9.0'
112
104
  ```
113
105
 
114
106
  Then install dependencies and run the CLI through Bundler:
115
107
 
116
108
  ```sh
117
109
  bundle install
110
+ bundle exec asciidoc-pubkit document toc book.adoc
111
+ bundle exec asciidoc-pubkit replace diff book.adoc --rules replacements.yml
112
+ bundle exec asciidoc-pubkit replace apply book.adoc --rules replacements.yml
118
113
  bundle exec asciidoc-pubkit review scan book.adoc --output .pubkit/review
119
114
  bundle exec asciidoc-pubkit review prompt .pubkit/review --output review-prompt.md
120
115
  bundle exec asciidoc-pubkit review verify .pubkit/review --output verification.json
@@ -130,35 +125,79 @@ git clone https://github.com/cybergarage/asciidoc-pubkit.git
130
125
  cd asciidoc-pubkit
131
126
  bundle install
132
127
  gem build asciidoc-pubkit.gemspec
133
- gem install ./asciidoc-pubkit-0.8.6.gem
128
+ gem install ./asciidoc-pubkit-0.9.0.gem
134
129
  asciidoc-pubkit --version
135
130
  ```
136
131
 
137
132
  The gem name and CLI name are `asciidoc-pubkit`; the Ruby require path is
138
133
  `asciidoc_pubkit`, and the namespace is `AsciidocPubkit`.
139
134
 
140
- ## Writing commands
135
+ ## Document commands
141
136
 
142
- `writing criteria` prints the packaged common criteria. `writing prompt` adds
143
- task instructions around those same criteria. Neither command needs an AsciiDoc
144
- file, a review session, MeCab, or network access. Both accept `--lang ja` and
145
- `--output FILE`; output defaults to stdout, and an existing output file is never
146
- overwritten. The prompt asks the agent to read project instructions and evidence;
147
- it does not supply source facts or authorize edits. Book-specific voice and
148
- format still come from the book. The OSS-only prose style profile remains with
149
- the book workflow, not the shared default.
137
+ Version 0.9.0 provides four read-only document commands. They require neither
138
+ MeCab nor an AI CLI, accept documents in any language, and do not create review
139
+ sessions or change manuscripts.
150
140
 
151
141
  ```sh
152
- asciidoc-pubkit writing criteria --lang ja
153
- asciidoc-pubkit writing prompt --lang ja --output writing-prompt.md
142
+ asciidoc-pubkit document toc book.adoc --depth 2 --numbered
143
+ asciidoc-pubkit document info book.adoc --json
144
+ asciidoc-pubkit document check-xrefs book.adoc --json
145
+ asciidoc-pubkit document index . --output index.adoc --title "Index"
154
146
  ```
155
147
 
156
- For example, `--lang en` exits with an unsupported-language error. No English
157
- review or writing criteria are shipped in 0.8.6.
158
-
159
- For a small trial, use `examples/book.adoc` as the scan input. Its Japanese
160
- paragraphs deliberately contain review candidates; its code block must remain
161
- unchanged.
148
+ All four require exactly one input. Use `--help` for command-specific options.
149
+ Output defaults to stdout. `--output FILE` creates a new file and rejects existing
150
+ files and symlinks. Diagnostics go to stderr. These commands do not discover
151
+ `.asciidoc-pubkit.yml` or load review rules. Use repeatable `-a NAME=VALUE` options
152
+ for Asciidoctor attributes and `--base-dir DIR` for the local include boundary.
153
+ The default boundary is the input file's directory, or the scanned directory for
154
+ `index`. Local includes and conditional directives are parsed normally; source
155
+ files and symlink targets must remain within that boundary. Remote includes are
156
+ rejected. `/shared/` has no special mapping or automatic exclusion: migrate such
157
+ references to local includes within the configured boundary.
158
+
159
+ `toc` prints parsed section titles in document order, with two spaces per nesting
160
+ level. It includes book parts and their child chapters, but excludes the document
161
+ title and apparent headings inside code blocks. `--numbered` uses outline
162
+ positions (`1.`, `1-1.`, etc.), independently of Asciidoctor's publication
163
+ numbering. `--depth` must be a positive integer and limits the Asciidoctor section
164
+ level: chapters are level 1 and book parts are level 0. It does not generate or
165
+ rewrite heading text.
166
+
167
+ `info` prints `key: value` lines or a JSON object with `--json`. Its fields are
168
+ `doctitle`, `subtitle`, `description`, `keywords`, `lang`, `uuid`, `author`,
169
+ `producer`, and `creator`. Missing attributes become empty strings. Metadata is
170
+ extracted from the parsed document, including active local includes.
171
+
172
+ `check-xrefs` reports unresolved local `xref:ID[]` and `<<ID,label>>` references,
173
+ including fragments targeting the entry document or an included file. It
174
+ recognizes explicit and generated IDs and attribute-expanded targets. It scans
175
+ macro-enabled block text, list items, table cells, and titles; comments and
176
+ ordinary code blocks are excluded. References to separate documents and remote
177
+ URLs are outside its scope. This is a limited syntax scan, not complete rendered
178
+ link validation: complex inline passthroughs and custom macros are not analyzed.
179
+ Text output has `file:line`, a tab, and the missing ID; JSON output is an array of
180
+ `refid`, `file`, and `line` objects. Locations use Asciidoctor source cursors;
181
+ table cells, list continuations, and multiline titles may identify the enclosing node's start
182
+ rather than the exact token line. Duplicate occurrences of one ID on the same
183
+ source line are reported once.
184
+
185
+ `index` recursively scans lowercase `.adoc` files, skips hidden subdirectories
186
+ and its output path, and uses each document's ID and title. It produces an
187
+ AsciiDoc alphabetical cross-reference list for English names: parenthesized
188
+ text and subtitles after `:` or `:` are removed; remaining titles must be ASCII
189
+ and contain a letter. Documents without IDs are omitted. Titles are deduplicated
190
+ case-insensitively, preferring the shallowest relative path and then lexical path
191
+ order. `--title` adds a document title with `[#book-index]`; alphabetical groups
192
+ use discrete level-2 headings (`=== A`). This indexes document titles, not all
193
+ section headings. The generated references are useful when those documents are
194
+ included in the consuming book; the command does not verify that membership.
195
+
196
+ Success returns 0. `check-xrefs` returns 1 when local references are unresolved.
197
+ Invalid inputs/options, include or parse errors, and output failures return 2;
198
+ no output file is created on a parse error. Warnings remain visible on stderr.
199
+ Unlike the original directory index script, an input parse error fails the whole
200
+ index instead of silently omitting the file.
162
201
 
163
202
  ## Prose replacements
164
203
 
@@ -461,7 +500,21 @@ asciidoc-pubkit review verify .pubkit/review --output verification.json
461
500
  All three commands leave manuscript files unchanged. Only the agent edits them.
462
501
  `prompt` and `verify` output files must not already exist.
463
502
  Without `--output`, `prompt` writes Markdown and `verify` writes JSON to stdout.
464
- `scan` defaults to `.pubkit/review` and prints a short summary.
503
+ `scan` defaults to `.pubkit/review` and prints a short summary. Prose scan output
504
+ and prompts explicitly direct reviewers to a separate `--scope lists` session
505
+ for list text; the prose baseline must remain intact.
506
+
507
+ List review is optional when the assignment covers running prose only or there
508
+ are no list descriptions to review. It is needed when the assignment includes
509
+ list wording: the default prose scan does not review it. See [List review](#list-review)
510
+ for examples, supported items, and coverage limits.
511
+
512
+ For a review covering all three scopes, finish and verify headings first, then
513
+ scan and review the current prose, then scan and review the current list text.
514
+ Create each session after the preceding phase's edits and verification. Preserve
515
+ all earlier baselines and phase-end results; later edits in another scope may
516
+ legitimately make an earlier session's verification fail. On re-review, choose
517
+ unused session paths rather than replacing the previous evidence.
465
518
 
466
519
  ### Scan
467
520
 
@@ -566,9 +619,9 @@ and converted inline titles and physical title lines reused by multiple includes
566
619
  Unresolved section titles receive coverage notices. Verification permits only selected title text changes while
567
620
  protecting the body, title markers, hierarchy, order, section IDs, references,
568
621
  attributes, includes and other protected content. A title-derived section ID
569
- change fails verification. To permit limited ID preservation, scan with
570
- `--scope headings --preserve-heading-ids`. Eligible generated headings then save
571
- an exact `permitted_id_anchor` in their metadata. A reviewer may insert only that
622
+ change fails verification. Starting with version 0.8.7, heading scans preserve
623
+ generated IDs by default. Eligible generated headings save an exact `permitted_id_anchor` in
624
+ their metadata. A reviewer may insert only that
572
625
  anchor immediately before its selected heading, for example:
573
626
 
574
627
  ```adoc
@@ -578,13 +631,15 @@ anchor immediately before its selected heading, for example:
578
631
 
579
632
  Verification allows this exact addition while requiring the same section ID,
580
633
  hierarchy, order, and protected body. Existing explicit anchors remain protected;
581
- other anchors and reference migrations are outside this permission. Without the
582
- flag, only title text is editable. Scan and prompt never insert anchors or rewrite
634
+ other anchors and reference migrations are outside this permission. No additional
635
+ ID-preservation option is required. Previously saved permissions remain unchanged;
636
+ start a separate new session to obtain the default permissions, keeping the old
637
+ baseline intact. Scan and prompt never insert anchors or rewrite
583
638
  manuscripts. Numeric title changes
584
639
  are manual-review notices, and remaining candidates do not fail verification.
585
640
  `meaning_verified` remains `false`.
586
641
 
587
- Version 0.8.6 uses session schema 3. Earlier sessions must be recreated; preserve or
642
+ Version 0.8.7 uses session schema 3. Earlier sessions must be recreated; preserve or
588
643
  finish an ongoing review with its original tool before establishing a new baseline.
589
644
  `review score` continues to support prose only and rejects `--scope`.
590
645
 
@@ -1139,13 +1194,13 @@ reports analyzer changes instead of treating results from different dictionaries
1139
1194
  as directly comparable. Prompt generation uses saved evidence and does not need
1140
1195
  MeCab. Changed rules or dictionary settings require a new scan.
1141
1196
 
1142
- Version 0.8.6 uses session schema 3; version 0.8.5 uses schema 2.
1197
+ Version 0.8.7 uses session schema 3; version 0.8.5 uses schema 2.
1143
1198
  Sessions with a different schema or tool version cannot be loaded. Keep the original baseline for
1144
1199
  an ongoing review and finish it with the original version, or start a new review
1145
1200
  pass in a different directory:
1146
1201
 
1147
1202
  ```sh
1148
- asciidoc-pubkit review scan book.adoc --output .pubkit/review-0.8.6
1203
+ asciidoc-pubkit review scan book.adoc --output .pubkit/review-0.8.7
1149
1204
  ```
1150
1205
 
1151
1206
  Severity describes review priority, not proof of an error. There is no AI-authorship
@@ -1165,11 +1220,33 @@ An empty findings array does not establish full coverage or good prose.
1165
1220
 
1166
1221
  ## List review
1167
1222
 
1168
- Use a separate session to review list descriptions, including reference-list text:
1223
+ List review checks wording in unordered and ordered list items, including
1224
+ reference-list descriptions. For example, the description following this link
1225
+ is list text and is excluded from the default running-prose review:
1226
+
1227
+ ```adoc
1228
+ * link:https://example.com/cli[CLI documentation]: printとJSON/RPCの入口を説明しています。
1229
+ ```
1230
+
1231
+ AsciiDoc parses list items separately from ordinary paragraphs. The tool keeps
1232
+ these review scopes separate to map editable text and verify preservation of
1233
+ list markers, nesting, links, and inline syntax. This is a scope and preservation
1234
+ choice, not a claim that list descriptions are outside the book's body or an
1235
+ exclusion made to improve processing speed.
1236
+
1237
+ Use this optional phase when list descriptions are part of the requested review,
1238
+ including a whole-book editorial pass. Omit it for an explicitly prose-only or
1239
+ heading-only assignment, or when there is no eligible list text. Report omitted
1240
+ and unsupported list content as unreviewed; successful prose verification does
1241
+ not establish that lists were reviewed. A zero-item list scan still requires
1242
+ inspection of coverage notices.
1243
+
1244
+ Use a separate session:
1169
1245
 
1170
1246
  ```sh
1171
1247
  asciidoc-pubkit review scan book.adoc --scope lists --output .pubkit/lists
1172
1248
  asciidoc-pubkit review prompt .pubkit/lists --output lists-review.md
1249
+ # Ask the reviewer to read the prompt and edit only selected list text.
1173
1250
  asciidoc-pubkit review verify .pubkit/lists
1174
1251
  ```
1175
1252
 
@@ -1190,6 +1267,29 @@ Default prose scanning, scoring, and mechanical replacement keep their existing
1190
1267
  prose scope. Heading body evidence continues to omit lists. A successful verify
1191
1268
  checks preservation and does not establish editorial completion.
1192
1269
 
1270
+ ## Writing commands
1271
+
1272
+ `writing criteria` prints the packaged common criteria. `writing prompt` adds
1273
+ task instructions around those same criteria. Neither command needs an AsciiDoc
1274
+ file, a review session, MeCab, or network access. Both accept `--lang ja` and
1275
+ `--output FILE`; output defaults to stdout, and an existing output file is never
1276
+ overwritten. The prompt asks the agent to read project instructions and evidence;
1277
+ it does not supply source facts or authorize edits. Book-specific voice and
1278
+ format still come from the book. The OSS-only prose style profile remains with
1279
+ the book workflow, not the shared default.
1280
+
1281
+ ```sh
1282
+ asciidoc-pubkit writing criteria --lang ja
1283
+ asciidoc-pubkit writing prompt --lang ja --output writing-prompt.md
1284
+ ```
1285
+
1286
+ For example, `--lang en` exits with an unsupported-language error. No English
1287
+ review or writing criteria are shipped in 0.9.0.
1288
+
1289
+ For a small trial, use `examples/book.adoc` as the scan input. Its Japanese
1290
+ paragraphs deliberately contain review candidates; its code block must remain
1291
+ unchanged.
1292
+
1193
1293
  ## Development
1194
1294
 
1195
1295
  ```sh
@@ -6,6 +6,10 @@ module AsciidocPubkit
6
6
  Usage: asciidoc-pubkit <group> <command> [options] [input]
7
7
 
8
8
  Commands:
9
+ document toc FILE Print the parsed section outline
10
+ document info FILE Extract book metadata
11
+ document check-xrefs FILE Report unresolved local cross-references
12
+ document index DIRECTORY Generate an English-title AsciiDoc index
9
13
  writing criteria Print the shared Japanese prose criteria
10
14
  writing prompt Generate a Japanese technical writing prompt
11
15
  review scan FILE Collect Japanese prose, headings, or list text and review candidates
@@ -32,11 +36,14 @@ module AsciidocPubkit
32
36
  out.puts VERSION
33
37
  return 0
34
38
  end
35
- if args.empty? || args == ['--help'] || args == ['-h'] || %w[review writing replace].any? { |group| args == [group, '--help'] }
39
+ if args.empty? || args == ['--help'] || args == ['-h'] || %w[review writing replace document].any? { |group| args == [group, '--help'] }
36
40
  out.puts HELP
37
41
  return 0
38
42
  end
39
43
  group, command = args.shift(2)
44
+ if group == 'document' && %w[toc info check-xrefs index].include?(command)
45
+ return DocumentCommands.run(command, args, out: out, err: err)
46
+ end
40
47
  valid = (group == 'review' && %w[scan score prompt verify].include?(command)) ||
41
48
  (group == 'writing' && %w[criteria prompt].include?(command)) ||
42
49
  (group == 'replace' && %w[check diff apply].include?(command))
@@ -55,7 +62,6 @@ module AsciidocPubkit
55
62
  elsif group == 'review' && %w[scan score].include?(command)
56
63
  if command == 'scan'
57
64
  opts.on('--scope SCOPE', 'prose (default), headings, or lists; separate review sessions') { |v| options[:scope] = v }
58
- opts.on('--preserve-heading-ids', 'Allow exact old-ID anchors before eligible headings') { options[:preserve_heading_ids] = true }
59
65
  opts.on('--heading-rules FILE', 'Replace default heading rules with a YAML rule set') { |v| options[:heading_rules] = v }
60
66
  opts.on('-y', '--yes', 'Answer yes to replacement confirmation') { options[:yes] = true }
61
67
  opts.on('--no-input', 'Never prompt; fail on existing output unless --yes') { options[:no_input] = true }
@@ -133,6 +139,7 @@ module AsciidocPubkit
133
139
  out.puts "Scanned #{result['paragraphs']} list items; found #{result['findings']} review candidates."
134
140
  else
135
141
  out.puts "Scanned #{result['paragraphs']} paragraphs; found #{result['findings']} review candidates."
142
+ out.puts "List text is excluded. Review it in a separate session with --scope lists."
136
143
  end
137
144
  out.puts "Tokenizer: #{result['tokenizer']}#{result['tokenizer'] == 'literal' ? ' (limited phrase matching; no morphological analysis)' : ' (UTF-8 IPADIC)'}"
138
145
  out.puts "Coverage notices: #{result['coverage_notices']}. See document.json for limitations."
@@ -0,0 +1,215 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'find'
4
+
5
+ module AsciidocPubkit
6
+ # Read-only document utilities, independent of Japanese review settings.
7
+ class DocumentCommands
8
+ METADATA = %w[subtitle description keywords lang uuid author producer creator].freeze
9
+
10
+ def self.run(command, args, out:, err:)
11
+ options = { attributes: {} }
12
+ parser = OptionParser.new do |opts|
13
+ opts.banner = "Usage: asciidoc-pubkit document #{command} [options] #{command == 'index' ? 'DIRECTORY' : 'FILE'}"
14
+ opts.on('-o', '--output FILE', 'New output file (default: standard output)') { |v| options[:output] = v }
15
+ opts.on('--base-dir DIR', 'Local include boundary (default: input directory)') { |v| options[:base_dir] = v }
16
+ opts.on('-a', '--attribute NAME=VALUE', 'Set an Asciidoctor attribute; repeat as needed') do |v|
17
+ key, value = v.split('=', 2)
18
+ raise Error, 'Attribute name cannot be empty.' if key.empty?
19
+ raise Error, 'Remote includes are not supported.' if key == 'allow-uri-read'
20
+ options[:attributes][key] = value || ''
21
+ end
22
+ if command == 'toc'
23
+ opts.on('--depth N', 'Maximum section level (positive integer)') do |v|
24
+ raise Error, 'Depth must be a positive integer.' unless v.match?(/\A[1-9][0-9]*\z/)
25
+ options[:depth] = v.to_i
26
+ end
27
+ opts.on('-n', '--numbered', 'Output outline numbers such as 1-2') { options[:numbered] = true }
28
+ elsif command == 'index'
29
+ opts.on('-t', '--title TITLE', 'Prepend an AsciiDoc index title') { |v| options[:title] = v }
30
+ else
31
+ opts.on('--json', 'Output JSON') { options[:json] = true }
32
+ end
33
+ opts.on('-h', '--help', 'Show command help') { options[:help] = true }
34
+ end
35
+ parser.parse!(args)
36
+ if options[:help]
37
+ out.puts parser
38
+ return 0
39
+ end
40
+ raise Error, 'Exactly one input is required. Use --help.' unless args.length == 1
41
+ if options[:output] && (File.exist?(options[:output]) || File.symlink?(options[:output]))
42
+ raise Error, "Output already exists: #{options[:output]}"
43
+ end
44
+ tool = new(options, err)
45
+ code = 0
46
+ if command == 'index'
47
+ content = tool.index(args.first)
48
+ else
49
+ doc = tool.load(args.first)
50
+ case command
51
+ when 'toc'
52
+ content = tool.toc(doc)
53
+ when 'info'
54
+ metadata = { 'doctitle' => doc.doctitle(sanitize: true).to_s.strip }
55
+ METADATA.each { |key| metadata[key] = doc.attr(key).to_s.strip }
56
+ content = options[:json] ? JSON.pretty_generate(metadata) + "\n" : metadata.map { |k, v| "#{k}: #{v}\n" }.join
57
+ when 'check-xrefs'
58
+ missing = tool.check_xrefs(doc)
59
+ code = missing.empty? ? 0 : 1
60
+ content = options[:json] ? JSON.pretty_generate(missing) + "\n" : missing.map { |item| "#{item['file']}:#{item['line'] || '?'}\t#{item['refid']}\n" }.join
61
+ end
62
+ end
63
+ CLI.output(content, options[:output], out)
64
+ code
65
+ end
66
+
67
+ def initialize(options, err)
68
+ @options, @err = options, err
69
+ end
70
+
71
+ def load(file, default_root: nil)
72
+ entry = File.realpath(file)
73
+ root = File.realpath(@options[:base_dir] || default_root || File.dirname(entry))
74
+ raise Error, "Base directory is not a directory: #{root}" unless File.directory?(root)
75
+ check_source(entry, root)
76
+ logger = Asciidoctor::MemoryLogger.new
77
+ previous = Asciidoctor::LoggerManager.logger
78
+ begin
79
+ Asciidoctor::LoggerManager.logger = logger
80
+ doc = Asciidoctor.load_file(entry, safe: :safe, sourcemap: true, parse: false,
81
+ base_dir: root, attributes: @options[:attributes])
82
+ owner = self
83
+ # Reject targets before Asciidoctor can clamp an outside path into its
84
+ # safe-mode jail, and before reading any remote or symlinked source.
85
+ doc.reader.define_singleton_method(:resolve_include_path) do |target, attrlist, attributes|
86
+ raise Error, 'Remote includes are not supported.' if Asciidoctor::Helpers.uriish?(target)
87
+ candidate = File.expand_path(target, dir)
88
+ unless candidate.start_with?(root.end_with?('/') ? root : root + '/')
89
+ raise Error, "Include is outside the base directory: #{target}"
90
+ end
91
+ owner.check_source(candidate, root) if File.file?(candidate)
92
+ super(target, attrlist, attributes)
93
+ end
94
+ doc.reader.define_singleton_method(:push_include) do |*args|
95
+ owner.check_source(args[1], root) if args[1] && File.file?(args[1])
96
+ super(*args)
97
+ end
98
+ doc.parse
99
+ ensure
100
+ Asciidoctor::LoggerManager.logger = previous
101
+ end
102
+ messages = logger.messages
103
+ messages.each { |m| @err.puts "#{m[:severity]}: #{m[:message]}" }
104
+ raise Error, "Failed to parse document: #{entry}" if messages.any? { |m| %i[ERROR FATAL].include?(m[:severity]) }
105
+ doc
106
+ end
107
+
108
+ def check_source(file, root)
109
+ path = File.realpath(file)
110
+ raise Error, "Source is outside the base directory: #{path}" unless path.start_with?(root.end_with?('/') ? root : root + '/')
111
+ AsciidocPubkit.read_text(path)
112
+ end
113
+
114
+ def toc(doc)
115
+ lines = []
116
+ walk = lambda do |sections, prefix, nesting|
117
+ sections.each_with_index do |section, index|
118
+ number = prefix + [index + 1]
119
+ if !@options[:depth] || section.level <= @options[:depth]
120
+ title = section.title
121
+ lines << (@options[:numbered] ? "#{number.join('-')}. #{title}" : "#{' ' * nesting}#{title}")
122
+ walk.call(section.sections, number, nesting + 1)
123
+ end
124
+ end
125
+ end
126
+ walk.call(doc.sections, [], 0)
127
+ lines.empty? ? '' : lines.join("\n") + "\n"
128
+ end
129
+
130
+ def index(directory)
131
+ root = File.realpath(directory)
132
+ raise Error, "Input is not a directory: #{directory}" unless File.directory?(root)
133
+ entries = []
134
+ Find.find(root) do |path|
135
+ if File.directory?(path)
136
+ Find.prune if path != root && File.basename(path).start_with?('.')
137
+ next
138
+ end
139
+ next unless File.extname(path) == '.adoc'
140
+ next if @options[:output] && File.expand_path(path) == File.expand_path(@options[:output])
141
+ doc = load(path, default_root: root)
142
+ next if doc.id.to_s.empty?
143
+ title = doc.doctitle(sanitize: true).to_s.gsub(/\s*\([^)]*\)/, '').split(/[::]/, 2).first.to_s.gsub(/\s+/, ' ').strip
144
+ next unless title.ascii_only? && title.match?(/[A-Za-z]/)
145
+ entries << { id: doc.id, title: title, path: Pathname.new(path).relative_path_from(Pathname.new(root)).to_s }
146
+ end
147
+ selected = entries.sort_by { |e| [e[:path].count('/'), e[:path]] }.each_with_object({}) { |e, memo| memo[e[:title].downcase] ||= e }
148
+ lines = []
149
+ if @options[:title]
150
+ raise Error, 'Index title must be a nonempty single line.' if @options[:title].strip.empty? || @options[:title].match?(/[\r\n]/)
151
+ lines.concat(['[#book-index]', "= #{@options[:title]}"])
152
+ end
153
+ selected.values.sort_by { |e| [e[:title].downcase, e[:path]] }.group_by { |e| e[:title][/[A-Za-z]/].upcase }.each do |letter, group|
154
+ lines << '' unless lines.empty?
155
+ lines.concat(['[discrete]', "=== #{letter}"])
156
+ group.each { |e| lines << "* <<#{e[:id]},#{e[:title]}>>" }
157
+ end
158
+ lines.empty? ? '' : lines.join("\n") + "\n"
159
+ end
160
+
161
+ # Scan raw AST fields only where macro substitutions are active. Converted
162
+ # text would hide xref targets; source-wide matching would include comments/code.
163
+ def check_xrefs(doc)
164
+ missing = []
165
+ nodes = doc.find_by.dup
166
+ nodes.select { |node| node.context == :table }.each do |table|
167
+ [table.rows.head, table.rows.body, table.rows.foot].flatten.each do |cell|
168
+ nodes << cell
169
+ nodes.concat(cell.inner_document.find_by) if cell.inner_document
170
+ end
171
+ end
172
+ nodes.each do |node|
173
+ cursor = node.source_location
174
+ if node.respond_to?(:subs) && node.subs.include?(:macros)
175
+ if node.respond_to?(:lines)
176
+ node.lines.each_with_index { |line, i| scan_refs(line, node, cursor, i, doc, missing) }
177
+ elsif node.instance_variable_defined?(:@text)
178
+ scan_refs(node.instance_variable_get(:@text), node, cursor, 0, doc, missing)
179
+ end
180
+ end
181
+ title = node.instance_variable_get(:@title)
182
+ scan_refs(title, node, cursor, 0, doc, missing) if title
183
+ end
184
+ missing.uniq { |m| m.values_at('file', 'line', 'refid') }.sort_by { |m| [m['file'], m['line'] || 0, m['refid']] }
185
+ end
186
+
187
+ def scan_refs(text, node, cursor, offset, doc, missing)
188
+ text = node.apply_subs(text, [:attributes])
189
+ targets = text.scan(/(?<!\\)xref:([^\[]+)\[(?:\\.|[^\]\\])*\]/).map { |m| [m.first, :macro] }
190
+ targets.concat(text.scan(/(?<!\\)<<(.+?)>>/).map { |m| [m.first.split(',', 2).first, :short] })
191
+ targets.each do |target, kind|
192
+ target = target.strip
193
+ if target.include?('#')
194
+ path, id = target.split('#', 2)
195
+ next unless path.empty? || local_path?(path, doc, cursor)
196
+ else
197
+ next if kind == :macro && !File.extname(target).empty?
198
+ id = target
199
+ end
200
+ next if id.to_s.empty? || doc.catalog[:refs].key?(id) || doc.resolve_id(id)
201
+ missing << { 'refid' => id, 'file' => cursor&.file || doc.attr('docfile'), 'line' => cursor && cursor.lineno + offset }
202
+ end
203
+ end
204
+
205
+ def local_path?(path, doc, cursor)
206
+ return false if path.match?(/\A[a-z][a-z0-9+.-]*:/i)
207
+ normalized = path.sub(/\.(?:adoc|asciidoc|asc|ad)\z/, '')
208
+ files = [doc.attr('docfile')] + doc.catalog[:includes].keys
209
+ files.any? do |file|
210
+ candidate = File.expand_path(file, doc.base_dir).sub(/\.(?:adoc|asciidoc|asc|ad)\z/, '')
211
+ [doc.base_dir, cursor&.file && File.dirname(cursor.file)].compact.any? { |base| candidate == File.expand_path(normalized, base) }
212
+ end
213
+ end
214
+ end
215
+ end
@@ -215,6 +215,7 @@ module AsciidocPubkit
215
215
 
216
216
  Only source-mapped running-prose paragraphs are reviewed. Inline macros and literal spans are masked by a conservative heuristic.
217
217
  Headings, lists, tables, quotations, code, and passthrough blocks are not prose-reviewed in this release.
218
+ #{list_scope ? "" : "List text requires a separate review scan with --scope lists. Preserve this baseline and use a different session directory; do not count excluded lists as reviewed."}
218
219
  This is a local correction task, not a chapter-restructuring task.
219
220
  Coverage notices: #{@document['coverage'].length}
220
221
 
@@ -35,9 +35,6 @@ module AsciidocPubkit
35
35
  if scope == 'headings' && (options[:rules] || options[:style])
36
36
  raise Error, 'Heading scans use --heading-rules, not prose --rules or --style.'
37
37
  end
38
- if scope != 'headings' && options[:preserve_heading_ids]
39
- raise Error, '--preserve-heading-ids requires --scope headings.'
40
- end
41
38
  if scope != 'headings' && options[:heading_rules]
42
39
  raise Error, '--heading-rules requires --scope headings.'
43
40
  end
@@ -45,7 +42,7 @@ module AsciidocPubkit
45
42
  rules_path = options[:replacement] ? RuleSet.default_path(language) : options[:rules] ? File.expand_path(options[:rules]) : (review['rules'] ? File.expand_path(review['rules'], base) : RuleSet.default_path(language))
46
43
  @data = {
47
44
  'scope' => scope,
48
- 'preserve_heading_ids' => !!options[:preserve_heading_ids],
45
+ 'preserve_heading_ids' => scope == 'headings',
49
46
  'heading_rules' => scope == 'headings' ? HeadingRuleSet.load(heading_rules_path) : nil,
50
47
  'heading_rules_path' => heading_rules_path,
51
48
  'rules' => scope != 'headings' ? RuleSet.load(rules_path) : nil,
@@ -9,7 +9,7 @@ require 'pathname'
9
9
  require 'optparse'
10
10
 
11
11
  module AsciidocPubkit
12
- VERSION = '0.8.6'
12
+ VERSION = '0.9.0'
13
13
  class Error < StandardError; end
14
14
 
15
15
  def self.hash_text(text)
@@ -35,4 +35,5 @@ require_relative 'asciidoc_pubkit/session'
35
35
  require_relative 'asciidoc_pubkit/local_evaluator'
36
36
  require_relative 'asciidoc_pubkit/score'
37
37
  require_relative 'asciidoc_pubkit/replacement'
38
+ require_relative 'asciidoc_pubkit/document_commands'
38
39
  require_relative 'asciidoc_pubkit/cli'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoc-pubkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.6
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - CyberGarage
@@ -37,9 +37,10 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.0'
40
- description: Generate Japanese technical writing prompts, review AsciiDoc prose, section
41
- headings, and list text separately, verify protected manuscript content, and explicitly
42
- apply prose-only mechanical replacements.
40
+ description: Inspect AsciiDoc outlines, book metadata, and local cross-references,
41
+ generate English document-title indexes, and generate Japanese technical writing
42
+ prompts, review AsciiDoc prose, section headings, and list text separately, verify
43
+ protected manuscript content, and explicitly apply prose-only mechanical replacements.
43
44
  executables:
44
45
  - asciidoc-pubkit
45
46
  extensions: []
@@ -58,6 +59,7 @@ files:
58
59
  - lib/asciidoc_pubkit.rb
59
60
  - lib/asciidoc_pubkit/cli.rb
60
61
  - lib/asciidoc_pubkit/document.rb
62
+ - lib/asciidoc_pubkit/document_commands.rb
61
63
  - lib/asciidoc_pubkit/heading_rules.rb
62
64
  - lib/asciidoc_pubkit/language.rb
63
65
  - lib/asciidoc_pubkit/local_evaluator.rb
@@ -91,5 +93,6 @@ required_rubygems_version: !ruby/object:Gem::Requirement
91
93
  requirements: []
92
94
  rubygems_version: 4.0.20
93
95
  specification_version: 4
94
- summary: Japanese writing and review tools for AsciiDoc books
96
+ summary: Document inspection, replacement, and Japanese review tools for AsciiDoc
97
+ books
95
98
  test_files: []