asciidoc-pubkit 1.0.3 → 1.1.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.
@@ -0,0 +1,110 @@
1
+ # Outline review
2
+
3
+ Version 1.1.0 extends document inspection and heading sessions for reviewing
4
+ a book's table of contents. These commands do not invoke an AI CLI or rewrite
5
+ the manuscript. Provide the generated prompt to your external reviewer.
6
+
7
+ ## Inspect the outline
8
+
9
+ ```sh
10
+ asciidoc-pubkit document toc book.adoc --depth 3
11
+ asciidoc-pubkit document toc book.adoc --depth 3 --json --output toc.json
12
+ ```
13
+
14
+ Depth is the maximum parsed Asciidoctor section level, not a chapter count or
15
+ indentation count. Parts can be level 0; include offsets affect actual levels.
16
+ Inspect the outline before selecting a depth. Discrete headings and apparent
17
+ titles inside code blocks are excluded from the section outline.
18
+
19
+ JSON has `schema_version: 1`, the absolute `entry` path, requested `depth` (null
20
+ when unlimited), and a flat `headings` array in document order. Each entry contains:
21
+
22
+ | Field | Meaning |
23
+ | --- | --- |
24
+ | `index`, `parent_index` | Zero-based position and parent position within this output; roots have a null parent |
25
+ | `level`, `nesting` | Parsed section level and zero-based outline nesting |
26
+ | `number` | Outline position such as `1-2`, independently of publication numbering |
27
+ | `section_id`, `title` | Parsed ID and title after attribute/inline processing |
28
+ | `file`, `line` | Source cursor location; unavailable positions are null |
29
+ | `source_title` | Original plain ATX title text, including unexpanded attributes; null when unavailable |
30
+
31
+ `--numbered` affects text output only; JSON always contains `number`. Positions
32
+ and outline numbers are local to that report, not durable review identifiers.
33
+ Source locations and raw titles are informational: JSON does not grant editing
34
+ permission or prove that a title is unambiguously editable. Use heading sessions
35
+ for source mapping, reuse checks, saved anchor permissions and verification.
36
+ Document inspection accepts any language and requires neither MeCab nor an AI.
37
+ Output must be new. Existing files and symlinks are rejected.
38
+
39
+ ## Select a heading depth
40
+
41
+ ```sh
42
+ asciidoc-pubkit review scan book.adoc --scope headings --depth 3 \
43
+ --output .pubkit/toc-review --no-input
44
+ ```
45
+
46
+ `--depth` accepts a positive integer and is valid only for heading scans. Without
47
+ it or configuration, all eligible levels are selected as before. A book can set
48
+ `review.heading_depth` in `.asciidoc-pubkit.yml`; the CLI overrides that value.
49
+ This configuration applies only to heading sessions, not prose or lists.
50
+
51
+ The effective limit is saved as `settings.heading_depth`. The full outline and
52
+ existing mapped prose context remain reference evidence, including deeper
53
+ sections. Deeper titles stay protected and get coverage notices. `--only`,
54
+ exclusions and source-mapping restrictions still apply. Reuse is checked before
55
+ depth filtering, so a title reused deeper cannot become editable by hiding that
56
+ occurrence. A scan with no eligible titles still reports outline and coverage;
57
+ it does not establish successful editorial review.
58
+
59
+ ## Generate an outline-oriented prompt
60
+
61
+ ```sh
62
+ asciidoc-pubkit review prompt .pubkit/toc-review --view outline \
63
+ --mode diagnose --output toc-diagnosis.md
64
+ asciidoc-pubkit review prompt .pubkit/toc-review --view outline \
65
+ --mode revise --output toc-revision.md
66
+ # After the external review and authorized title edits:
67
+ asciidoc-pubkit review verify .pubkit/toc-review --output toc-verification.json
68
+ ```
69
+
70
+ `--view full` remains the default. Outline view requires a heading session. It
71
+ displays a numbered, indented outline with source positions and
72
+ `editable`/`reference-only` labels, selected mapped headings and findings, and
73
+ the same read-only body context once per paragraph in the same Markdown file.
74
+ Only selected-heading metadata grants title/anchor permissions. All excerpts
75
+ stay fenced as data, even when titles contain backticks.
76
+
77
+ Additional criteria consider reader prerequisites, subject coverage, reading
78
+ order, parent/child correspondence, sibling granularity, terminology and the
79
+ book's specified style. A nominal preference is advisory and must retain
80
+ conditions, order and technical distinctions. It does not impose a common chapter
81
+ pattern, keyword list or fixed title length. The existing heading-rule
82
+ `style: nominal` preference can be used without a new style rule.
83
+
84
+ The outline cannot establish agreement with the body by itself. Reviewers must
85
+ read supplied context and record missing evidence, including omitted tables,
86
+ figures and code. They record selected heading IDs, current/proposed titles,
87
+ `keep`/`revise`/`needs-evidence`, reasons, evidence and separate synchronization
88
+ proposals. These are reviewer instructions, not automatic decision validation
89
+ or a semantic score.
90
+
91
+ ## Preservation and existing sessions
92
+
93
+ Outline criteria are saved as `manifest.outline_criteria` at scan time and do not
94
+ change when installed criteria later change. Older sessions without that field
95
+ continue to support full prompts and their original verification, subject to
96
+ the exact tool-version requirement. For outline view, create a fresh session in
97
+ a different directory; do not replace an ongoing baseline or modify its artifacts.
98
+
99
+ Changing views does not change selection or verification. Only selected plain
100
+ ATX title spans and exact saved ID anchors may be edited. Body, unselected and
101
+ attribute-expanded titles, IDs, hierarchy, order and includes remain protected.
102
+ Moves, merges, splits and new sections are separate structure proposals, not
103
+ permitted edits. Body/table/figure synchronization requires its own authorized
104
+ scope and verification. Baseline integrity and stale-source checks still apply.
105
+ Verification remains mechanical and keeps `meaning_verified: false`; success
106
+ does not establish editorial completeness, semantic correctness or EPUB readiness.
107
+
108
+ For an end-to-end agent handoff, see the [agent review tutorial](agent-review-tutorial.md).
109
+
110
+ [Back to README](../README.md)
@@ -1,5 +1,8 @@
1
1
  # Review workflow
2
2
 
3
+ For a step-by-step walkthrough with Codex or Claude and workflow diagrams, see
4
+ the [agent review tutorial](agent-review-tutorial.md).
5
+
3
6
  Run the following commands from the manuscript project directory:
4
7
 
5
8
  ```sh
@@ -86,6 +89,12 @@ are frozen into the session; scan again after changing them.
86
89
 
87
90
  ### Heading review
88
91
 
92
+ Version 1.1.0 adds [outline review](outline-review.md) with depth-limited selection and
93
+ `review prompt --view outline` for assessing a table of contents. Full view remains
94
+ the default. Depth controls editable titles; changing views does not relax the
95
+ existing title/ID/body preservation checks. Additional outline criteria are saved
96
+ in new heading sessions rather than retrofitted into existing baselines.
97
+
89
98
  Review headings, prose, and list text in separate sessions. The default scan scope
90
99
  remains `prose`; `--scope headings` selects section titles and `--scope lists` selects
91
100
  supported list text. There is no combined scope.
@@ -151,7 +160,7 @@ manuscripts. Numeric title changes
151
160
  are manual-review notices, and remaining candidates do not fail verification.
152
161
  `meaning_verified` remains `false`.
153
162
 
154
- Version 1.0.3 uses session schema 3 and requires the exact tool version saved in
163
+ Version 1.1.0 uses session schema 3 and requires the exact tool version saved in
155
164
  the session. Preserve or finish an ongoing review with its original tool before
156
165
  establishing a new baseline in a separate directory.
157
166
  `review score` continues to support prose only and rejects `--scope`.
@@ -211,6 +220,18 @@ needs a passage-specific reason. Whether a matched string remains or disappears
211
220
  cannot determine that decision; unchanged paragraphs and remaining candidates
212
221
  must not receive automatic `keep` records. Unreviewed items remain pending.
213
222
 
223
+ The default Japanese rules now flag standalone `人` as a contextual hint in
224
+ prose, supported list text, and headings. Identify the evidenced role before
225
+ considering `開発者`, `利用者`, `評価担当者`, `作成者`, or `人間`. Preserve general
226
+ populations and established measures such as `人の介入` when narrowing the actor
227
+ would change meaning. Compound words, counts, protected inline text, and allowed
228
+ terms are excluded; literal mode uses conservative character boundaries and
229
+ cannot establish grammatical roles. This is not a mechanical replacement rule.
230
+ Existing sessions retain their saved rules and criteria; start a fresh session
231
+ for this new cue without replacing an ongoing baseline. Adding a cue can change
232
+ candidate-density scores; compare results under the same resolved rules and
233
+ tokenizer, rather than treating a score change as improved writing quality.
234
+
214
235
  Version 0.8.4 prompts require the reviewer to reconcile decisions with actual
215
236
  edits (or proposed revisions in diagnose mode), review paragraphs or headings
216
237
  without candidates, and inspect newly detected or remaining candidates after
@@ -225,6 +246,23 @@ the shared criteria ask whether creation, invocation, configuration, reference,
225
246
  or a learning role can be stated directly even when the referent is clear.
226
247
  Remaining role metaphors must be checked against those individual decisions.
227
248
 
249
+ For an explicitly task-limited assignment, such as clarifying human roles,
250
+ record the objective, relevant entry IDs, and the reasons for including those
251
+ passages. Separate total scanned entries, task-specific reviewed entries, and
252
+ entries assessed for general writing quality. A whole-book scan followed by a
253
+ terminology pass is not a completed whole-book editorial review. Leave unrelated
254
+ unreviewed entries and candidates pending; the CLI has no term-based selection
255
+ filter or automated decision-ledger validation.
256
+
257
+ When a term changes, inspect related titles, body text, table cells, figure
258
+ labels, and alternative text for the same meaning. Tables and figures remain
259
+ outside the heading/prose/list edit scopes. Record proposed synchronization and
260
+ apply it only under separate author authorization. Keep each phase's baseline
261
+ and phase-end verification; later synchronization may make an earlier phase's
262
+ verification historical. Check the combined authorized changes against the
263
+ original sources separately rather than relaxing protection or resetting the
264
+ baseline to conceal a failure.
265
+
228
266
  These are reviewer instructions: the gem does not validate a decision ledger
229
267
  or prove that an AI or human followed the instructions. Keep session metadata
230
268
  and baseline snapshots immutable; format only authorized manuscript spans.
@@ -131,13 +131,13 @@ reports analyzer changes instead of treating results from different dictionaries
131
131
  as directly comparable. Prompt generation uses saved evidence and does not need
132
132
  MeCab. Changed rules or dictionary settings require a new scan.
133
133
 
134
- Version 1.0.3 uses session schema 3.
134
+ Version 1.1.0 uses session schema 3.
135
135
  Sessions with a different schema or tool version cannot be loaded. Keep the original baseline for
136
136
  an ongoing review and finish it with the original version, or start a new review
137
137
  pass in a different directory:
138
138
 
139
139
  ```sh
140
- asciidoc-pubkit review scan book.adoc --output .pubkit/review-1.0.3
140
+ asciidoc-pubkit review scan book.adoc --output .pubkit/review-1.1.0
141
141
  ```
142
142
 
143
143
  Severity describes review priority, not proof of an error. There is no AI-authorship
@@ -0,0 +1,131 @@
1
+ # Integrate manuscript review into an agent skill
2
+
3
+ Use the [CLI tutorial](agent-review-tutorial.md) to try individual commands first.
4
+ A skill makes the same process reusable: the agent selects the manuscript,
5
+ executes the commands, reads the generated prompt, edits within scope, verifies
6
+ and records progress. Pubkit still performs parsing and preservation checks;
7
+ it does not discover skills or launch a reviewer.
8
+
9
+ ## Responsibilities
10
+
11
+ ```mermaid
12
+ flowchart TD
13
+ U["Author: assignment and edit authorization"] --> K["Agent reads the review skill"]
14
+ I["Book instructions and existing progress"] --> K
15
+ K --> S["Agent runs pubkit scan"]
16
+ S --> B["Saved session and baseline"]
17
+ B --> P["Agent runs pubkit prompt"]
18
+ P --> R["Agent reads saved criteria and manuscript evidence"]
19
+ R --> E["Agent reviews or edits the assigned scope"]
20
+ E --> V["Agent runs pubkit verify"]
21
+ B --> V
22
+ V --> C["Agent records decisions, results and pending work"]
23
+ C --> N["Next assigned phase after successful verification"]
24
+ ```
25
+
26
+ | Owner | Responsibility |
27
+ | --- | --- |
28
+ | Author and book instructions | Readers, style, technical evidence, assignment and publication decisions |
29
+ | Review skill | Scope selection, phase order, tool invocation, resumption and progress recording |
30
+ | Generated pubkit prompt | Criteria frozen at scanning, selected entries, candidates and edit permissions |
31
+ | Codex or Claude reviewer | Evidence-based decisions, authorized edits and unresolved proposals |
32
+ | Pubkit verification | Mechanical preservation against the saved baseline |
33
+
34
+ Keep shared prose and heading criteria in pubkit instead of copying them into
35
+ each skill. Skill instructions should explain when and how to use the tool,
36
+ while book instructions retain book-specific terminology and technology versions.
37
+
38
+ ## A minimal reusable example
39
+
40
+ The gem includes [review-asciidoc-manuscript/SKILL.md](../examples/skills/review-asciidoc-manuscript/SKILL.md).
41
+ It is a standalone example, not an automatically installed skill. Read it before
42
+ adapting it to your project. It supports full review, single-scope review and
43
+ diagnosis without adding publication or external-message permissions.
44
+
45
+ For a project using the CyberGarage `.agents/skills` layout, copy the example to:
46
+
47
+ ```text
48
+ manuscript-project/
49
+ AGENTS.md
50
+ book.adoc
51
+ .agents/skills/review-asciidoc-manuscript/SKILL.md
52
+ ```
53
+
54
+ Skill discovery and installation depend on your agent and project. Use the
55
+ location configured for that environment; pubkit does not install the example.
56
+ When automatic discovery is unavailable, explicitly ask Codex or Claude to read
57
+ the file and follow it. This file-based handoff needs no agent-specific CLI flags.
58
+
59
+ Example request for either reviewer:
60
+
61
+ ```text
62
+ Read .agents/skills/review-asciidoc-manuscript/SKILL.md and the project's
63
+ instructions. Review book.adoc in headings, prose and supported list phases.
64
+ Use a new pass named review-book-02, preserve previous baselines and unrelated
65
+ edits, verify each phase before the next scan, and update the existing review
66
+ record. Report applied changes, reviewed/pending IDs and coverage limits.
67
+ ```
68
+
69
+ For outline diagnosis only:
70
+
71
+ ```text
72
+ Read .agents/skills/review-asciidoc-manuscript/SKILL.md. Diagnose book.adoc's
73
+ headings through parsed level 2 using a fresh review-toc-02 session and an outline
74
+ prompt. Do not edit the manuscript. Record title/body agreement, reading order,
75
+ keep/revise/needs-evidence decisions and separate structural proposals.
76
+ ```
77
+
78
+ The agent checks the installed tool before using 1.1.0 options. A new session
79
+ does not upgrade an old baseline or certify preservation against it.
80
+
81
+ ## Commands the skill orchestrates
82
+
83
+ For a heading-only assignment, the agent runs the following from the manuscript
84
+ directory, reading the prompt and completing the assigned review between prompt
85
+ and verify. Substitute unused pass names and the project's CLI invocation.
86
+
87
+ ```sh
88
+ asciidoc-pubkit --version
89
+ asciidoc-pubkit review scan --help
90
+ asciidoc-pubkit review scan book.adoc --scope headings --lang ja \
91
+ --output .pubkit/review-book-02-headings --no-input
92
+ asciidoc-pubkit review prompt .pubkit/review-book-02-headings --view outline \
93
+ --mode revise --output .pubkit/review-book-02-headings/prompt.md
94
+ # Agent reads the prompt, records decisions and applies authorized title edits.
95
+ asciidoc-pubkit review verify .pubkit/review-book-02-headings \
96
+ --output .pubkit/review-book-02-headings/verification-01.json
97
+ ```
98
+
99
+ For a full review, proceed to fresh prose and list sessions only after the
100
+ preceding phase is finished and verifies. The [CLI tutorial](agent-review-tutorial.md#4-review-prose-then-list-text)
101
+ contains those command sequences. Do not scan all phases first: the later
102
+ baseline must capture earlier authorized edits. A pending integrity or
103
+ preservation failure blocks that phase boundary, not unrelated read-only work.
104
+
105
+ Use generated criteria rather than a second writing prompt. Keep the session's
106
+ four baseline artifacts immutable; adding prompt, decision and verification
107
+ reports beside them is permitted. Existing output files cannot be overwritten.
108
+
109
+ ## The CyberGarage integration
110
+
111
+ The CyberGarage publishing repository uses
112
+ `.agents/skills/review-japanese-technical-book/SKILL.md`, backed by its
113
+ `references/review-workflow.md`. Its workflow selects the installed CLI (or
114
+ `bundle exec`), reads project-local instructions, performs separate heading,
115
+ prose and list phases, and updates each book's existing review/progress records.
116
+ The simplified example above follows those orchestration principles; it is not
117
+ a copy of that project's full book workflow.
118
+
119
+ For a book such as `books/ai/ai-orbit`, its `AGENTS.md`, `PROGRESS.md`, `PLAN.md`
120
+ and `STRUCTURE.md` determine sources, publication order and technical versions.
121
+ A chapter-limited request uses the publication entrypoint with `--only`, while
122
+ a whole-book request covers active included manuscripts without that filter.
123
+
124
+ The 1.1.0 outline view is an optional enhancement for the heading phase; it is
125
+ not a claim that an existing external skill has already been updated to use it.
126
+ This example does not change CyberGarage's skills or manuscript files. Keep
127
+ mechanical preservation, editorial completion, book build checks and publication
128
+ approval as separate recorded results.
129
+
130
+ [CLI tutorial](agent-review-tutorial.md) · [Review reference](review-workflow.md) ·
131
+ [Back to README](../README.md)
data/docs/writing.md CHANGED
@@ -25,13 +25,21 @@ its known action. Keep source-provided undecided or investigation status in the
25
25
  body; put reviewer doubts and questions in the separate report, without adding
26
26
  editing markers to the manuscript.
27
27
 
28
+ The shared criteria also distinguish human roles from human-versus-AI
29
+ comparisons. Terms such as `開発者`, `利用者`, `評価担当者`, and `文書の作成者`
30
+ are evidence-based alternatives to `人`, not a replacement dictionary. Preserve
31
+ general populations and established research concepts, and do not introduce
32
+ new permissions or imply that different roles require different people.
33
+ Heading criteria connect terminology changes with the section's body and
34
+ separately proposed table, figure, and alternative-text synchronization.
35
+
28
36
  ```sh
29
37
  asciidoc-pubkit writing criteria --lang ja
30
38
  asciidoc-pubkit writing prompt --lang ja --output writing-prompt.md
31
39
  ```
32
40
 
33
41
  For example, `--lang en` exits with an unsupported-language error. No English
34
- review or writing criteria are shipped in 1.0.3.
42
+ review or writing criteria are shipped in 1.1.0.
35
43
 
36
44
  For a small trial, use `examples/book.adoc` as the scan input. Its Japanese
37
45
  paragraphs deliberately contain review candidates; its code block must remain
@@ -0,0 +1,48 @@
1
+ ---
2
+ name: review-asciidoc-manuscript
3
+ description: Review and revise an existing Japanese AsciiDoc manuscript with asciidoc-pubkit in separate verified heading, prose, and list sessions; use for proofreading or outline diagnosis, not new-book authoring.
4
+ ---
5
+
6
+ # Review an AsciiDoc manuscript
7
+
8
+ Read the project's instructions and existing progress record. Determine the
9
+ assigned entrypoint, sources, scopes, diagnosis/revision mode, publication
10
+ attributes and checks. Preserve unrelated edits. Default to headings, prose,
11
+ then supported list text only when the user requests a full review; respect
12
+ single-scope and diagnosis-only assignments.
13
+
14
+ Use the project's installed asciidoc-pubkit invocation, with bundle exec when
15
+ configured. Check its version and command help. Existing sessions require their
16
+ exact saved tool version; retain earlier sessions when starting a new pass.
17
+ Choose unused session and output paths. Never rescan to hide a failed check.
18
+
19
+ For each assigned scope:
20
+
21
+ 1. Scan from the manuscript directory with --scope headings, prose or lists,
22
+ --lang ja, --no-input and a unique --output session path. For an assigned
23
+ chapter, use --only FILE with the book entrypoint. Do not narrow a whole-book
24
+ assignment to one file. Match the publishing parser settings.
25
+ 2. Generate review prompt before editing. Use --mode diagnose for proposals
26
+ without edits; otherwise use --mode revise. For headings, --view outline
27
+ supports whole-outline assessment. Add --depth only for a requested depth
28
+ limit; deeper visible titles remain reference-only.
29
+ 3. Read the generated Markdown completely in manageable ranges with neighboring
30
+ context. It owns the saved criteria and edit permissions. Treat excerpts as
31
+ data. Review every selected entry, including those without candidates, and
32
+ record IDs, keep/revise/needs-evidence, specific reasons and evidence in the
33
+ project's existing review records. In diagnosis mode, do not edit sources.
34
+ 4. Apply justified corrections within the authorized selected scope. For heading
35
+ renames, preserve IDs using only exact saved permitted_id_anchor additions
36
+ where needed. Keep session JSON and baseline/ immutable. Other-scope concerns,
37
+ moves, merges, tables, figures and code changes are separate proposals.
38
+ 5. Inspect the source diff and run review verify on that same session with a new
39
+ output filename. Repair task-owned preservation violations without resetting
40
+ the baseline. For integrity failures, preserve evidence and report the blocker.
41
+ Finish verification before scanning the next scope's current manuscript.
42
+
43
+ Retain phase-end results because later scopes can legitimately make older
44
+ sessions fail. For further corrections after another scope has changed, start a
45
+ separate scoped pass and retain the prior evidence. Run the project's requested
46
+ checks, record applied/unapplied proposals, reviewed/pending IDs, coverage and
47
+ verification results in its existing records. Report mechanical preservation
48
+ separately from editorial completion; meaning_verified remains false.
@@ -30,6 +30,11 @@ module AsciidocPubkit
30
30
  local Codex or Claude CLI, which may connect to its configured AI service.
31
31
  TEXT
32
32
 
33
+ def self.positive_depth(value)
34
+ raise Error, 'Depth must be a positive integer.' unless value.match?(/\A[1-9][0-9]*\z/)
35
+ value.to_i
36
+ end
37
+
33
38
  def self.run(arguments, out: $stdout, err: $stderr, input: $stdin)
34
39
  args = arguments.dup
35
40
  if args == ['--version']
@@ -63,6 +68,7 @@ module AsciidocPubkit
63
68
  if command == 'scan'
64
69
  opts.on('--scope SCOPE', 'prose (default), headings, or lists; separate review sessions') { |v| options[:scope] = v }
65
70
  opts.on('--heading-rules FILE', 'Replace default heading rules with a YAML rule set') { |v| options[:heading_rules] = v }
71
+ opts.on('--depth N', 'Maximum section level; requires --scope headings') { |v| options[:depth] = positive_depth(v) }
66
72
  opts.on('-y', '--yes', 'Answer yes to replacement confirmation') { options[:yes] = true }
67
73
  opts.on('--no-input', 'Never prompt; fail on existing output unless --yes') { options[:no_input] = true }
68
74
  else
@@ -84,6 +90,7 @@ module AsciidocPubkit
84
90
  end
85
91
  elsif group == 'review' && command == 'prompt'
86
92
  opts.on('--mode MODE', 'revise (default) or diagnose') { |v| options[:mode] = v }
93
+ opts.on('--view VIEW', 'full (default) or outline; outline requires a heading session') { |v| options[:view] = v }
87
94
  end
88
95
  opts.on('-h', '--help', 'Show command help') { options[:help] = true }
89
96
  end
@@ -152,7 +159,7 @@ module AsciidocPubkit
152
159
  raise Error, "Requested language #{options[:language].inspect} does not match session language #{saved_language.inspect}."
153
160
  end
154
161
  if command == 'prompt'
155
- output(session.prompt(options.fetch(:mode, 'revise')), options[:output], out)
162
+ output(session.prompt(options.fetch(:mode, 'revise'), view: options.fetch(:view, 'full')), options[:output], out)
156
163
  return 0
157
164
  end
158
165
  result = session.verify
@@ -63,6 +63,14 @@ module AsciidocPubkit
63
63
  'reason' => 'The source title is reused by multiple sections and cannot be edited independently.' }
64
64
  true
65
65
  end
66
+ if (depth = @settings['heading_depth'])
67
+ @headings.reject! do |heading|
68
+ next false unless heading['level'] > depth
69
+ @coverage << { 'context' => 'heading', 'file' => heading['file'], 'line' => heading['line'],
70
+ 'reason' => 'Outside the saved heading depth.', 'text' => heading['text'] }
71
+ true
72
+ end
73
+ end
66
74
  nodes.each { |node| collect(node) }
67
75
  if scope == 'lists'
68
76
  reused = @paragraphs.group_by { |p| [p['file'], p['line']] }.select { |_, group| group.length > 1 }
@@ -102,10 +110,11 @@ module AsciidocPubkit
102
110
  # Converted inline titles, attribute substitutions and old-style titles stay protected.
103
111
  def collect_heading(node, index)
104
112
  parent_index = @section_indexes[node.parent] if node.parent.context == :section
113
+ cursor = node.source_location
105
114
  @outline << { 'index' => index, 'parent_index' => parent_index,
106
- 'level' => node.level, 'section_id' => node.id, 'text' => node.title }
115
+ 'level' => node.level, 'section_id' => node.id, 'text' => node.title,
116
+ 'file' => cursor&.file, 'line' => cursor&.lineno }
107
117
  return unless @settings.fetch('scope', 'prose') == 'headings'
108
- cursor = node.source_location
109
118
  path, line = cursor && [cursor.file, cursor.lineno]
110
119
  match = @heading_lines[[path, line]]
111
120
  unless match && match[2] == node.title
@@ -21,9 +21,9 @@ module AsciidocPubkit
21
21
  end
22
22
  if command == 'toc'
23
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
24
+ options[:depth] = CLI.positive_depth(v)
26
25
  end
26
+ opts.on('--json', 'Output a structured outline with source locations') { options[:json] = true }
27
27
  opts.on('-n', '--numbered', 'Output outline numbers such as 1-2') { options[:numbered] = true }
28
28
  elsif command == 'index'
29
29
  opts.on('-t', '--title TITLE', 'Prepend an AsciiDoc index title') { |v| options[:title] = v }
@@ -112,6 +112,7 @@ module AsciidocPubkit
112
112
  end
113
113
 
114
114
  def toc(doc)
115
+ return JSON.pretty_generate(toc_outline(doc)) + "\n" if @options[:json]
115
116
  lines = []
116
117
  walk = lambda do |sections, prefix, nesting|
117
118
  sections.each_with_index do |section, index|
@@ -127,6 +128,35 @@ module AsciidocPubkit
127
128
  lines.empty? ? '' : lines.join("\n") + "\n"
128
129
  end
129
130
 
131
+ def toc_outline(doc)
132
+ entries = []
133
+ source_lines = {}
134
+ walk = lambda do |sections, prefix, nesting, parent_index|
135
+ sections.each_with_index do |section, sibling|
136
+ number = prefix + [sibling + 1]
137
+ next if @options[:depth] && section.level > @options[:depth]
138
+ cursor = section.source_location
139
+ source_title = nil
140
+ if cursor && cursor.file && File.file?(cursor.file)
141
+ lines = source_lines[cursor.file] ||= begin
142
+ check_source(cursor.file, File.realpath(doc.base_dir))
143
+ AsciidocPubkit.read_text(cursor.file).lines
144
+ end
145
+ line = lines[cursor.lineno - 1]
146
+ source_title = line&.match(/\A={1,6}[ \t]+(.*?)[ \t]*\r?\n?\z/)&.[](1)
147
+ end
148
+ index = entries.length
149
+ entries << { 'index' => index, 'parent_index' => parent_index,
150
+ 'level' => section.level, 'nesting' => nesting, 'number' => number.join('-'),
151
+ 'section_id' => section.id, 'title' => section.title,
152
+ 'file' => cursor&.file, 'line' => cursor&.lineno, 'source_title' => source_title }
153
+ walk.call(section.sections, number, nesting + 1, index)
154
+ end
155
+ end
156
+ walk.call(doc.sections, [], 0, nil)
157
+ { 'schema_version' => 1, 'entry' => doc.attr('docfile'), 'depth' => @options[:depth], 'headings' => entries }
158
+ end
159
+
130
160
  def index(directory)
131
161
  root = File.realpath(directory)
132
162
  raise Error, "Input is not a directory: #{directory}" unless File.directory?(root)
@@ -52,6 +52,7 @@ module AsciidocPubkit
52
52
  next if settings['allows'].include?(term)
53
53
  text.to_enum(:scan, Regexp.new(Regexp.escape(term))).each do
54
54
  match = Regexp.last_match
55
+ next if term == '人' && !Rules.standalone_human?(text, match.begin(0))
55
56
  add(findings, heading, match.begin(0), term, rule, entry['question'])
56
57
  end
57
58
  end
@@ -113,6 +113,7 @@ module AsciidocPubkit
113
113
  tokens.each_with_index do |token, index|
114
114
  next if token['unknown'] || phrase_ranges.any? { |range| range.cover?(token['offset']) }
115
115
  lemma = token['lemma']
116
+ next if lemma == '人' && !standalone_human?(text, token['offset'])
116
117
  rule = nil
117
118
  finish_index = index
118
119
  compound = rules.fetch('compound_nouns').find do |term|
@@ -185,6 +186,7 @@ module AsciidocPubkit
185
186
  surfaces.each do |surface|
186
187
  text.to_enum(:scan, Regexp.new(Regexp.escape(surface))).each do
187
188
  match = Regexp.last_match
189
+ next if surface == '人' && !standalone_human?(text, match.begin(0))
188
190
  next if contextual.any? { |start, finish, _, candidate_rule, *_| start == match.begin(0) && finish == match.end(0) && candidate_rule == rule }
189
191
  candidates << [match.begin(0), match.end(0), surface, rule, question]
190
192
  end
@@ -200,6 +202,18 @@ module AsciidocPubkit
200
202
  end
201
203
  end
202
204
 
205
+ # Exclude compound words, identifiers and counts in both tokenizer modes.
206
+ # Hiragana context such as その人 or 人による remains a review cue.
207
+ def self.standalone_human?(text, offset)
208
+ compound = /[\p{Han}\p{N}\p{Latin}\p{Katakana}_]/
209
+ previous = offset.positive? ? text[offset - 1] : nil
210
+ following = text[offset + 1]
211
+ return false if previous == 'ー' || previous&.match?(compound)
212
+ return true if text[(offset + 1)..].start_with?('向け')
213
+
214
+ following != 'ー' && !following&.match?(compound)
215
+ end
216
+
203
217
  def self.find_term(findings, paragraph, text, term, rule, severity, question)
204
218
  text.to_enum(:scan, Regexp.new(Regexp.escape(term))).each do
205
219
  match = Regexp.last_match