asciidoc-pubkit 0.8.5 → 0.8.7

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: bcbb2a3c913ace085eb70388740dfc506fefd0ef064380c58e0ebbab63b422a8
4
- data.tar.gz: 7b643916c2b5c19f2264541f9e8660d212e7a351a1de10142087e4437b527f56
3
+ metadata.gz: 60dd89f5f4d5ec6019749ec10d768d92e39eed7f32f72161a441c4fb49a8610f
4
+ data.tar.gz: fac66406826db7312d3da1a98ee8786970b1e8d1f10f9ffe7af8e546888fb690
5
5
  SHA512:
6
- metadata.gz: 3cbda445b71d34817dfb51f64c49665106ae471ad98c0af3995697475f578fb658bdf2abc9e1894538a01d1e6e18b5d6fe850a9c818d868d8c2ad2663dd29598
7
- data.tar.gz: dd89673d6281bc418a127dcd14ab7273e4919d677a84ef38b3b9a431ade81aa3d7ff8615b3bcfeaeeebe4de9e4016e157cb7d43065dbb4d511ee19bdd3f48341
6
+ metadata.gz: 4d022d2305fce3f649b276af067a6e56f5f321d72654586068a86868f20dd3347ae1d17db068cb098ba4856ed77f92c04240c7354f9ed8c61f16a57076a856d9
7
+ data.tar.gz: e75f1add0fd52381a60d86941f732a907b68cf962690c38894ca3350ea5e96271986fbf35f20f33564d25c3c09e0d006744f5d0c80b263a901502c12dc1c252b
data/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.7 — 2026-10-02
4
+
5
+ - Make exact saved old-ID anchor permissions standard for eligible heading
6
+ scans. Remove `--preserve-heading-ids`; continue protecting existing anchors,
7
+ section IDs, structure, and body content. Existing session permissions and
8
+ baselines remain unchanged.
9
+ - Tell prose reviewers in scan output and generated prompts to review list text
10
+ in a separate `--scope lists` session without replacing the prose baseline.
11
+
12
+ ## 0.8.6 — 2026-10-02
13
+
14
+ - Add separate `review scan --scope lists` sessions for source-mapped single-line
15
+ outline list text. Preserve markers, structure, inline tokens, and other scopes;
16
+ report unsupported or reused-source items as coverage notices.
17
+ - Add opt-in `--preserve-heading-ids` for heading scans. Save exact old-ID anchor
18
+ permissions and verify only those additions immediately before their selected
19
+ headings while retaining section IDs, structure, and protected body content.
20
+ - Advance sessions to schema 3; retain prior baselines and finish
21
+ ongoing reviews with their original tool and schema.
22
+
3
23
  ## 0.8.5 — 2026-10-02
4
24
 
5
25
  - Refine role-metaphor guidance for 入口: consider the actual creation, invocation,
data/README.md CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  A toolkit for authoring and reviewing AsciiDoc books.
9
9
 
10
- Version 0.8.5 provides shared Japanese technical writing criteria, an authoring
11
- prompt, separate prose and heading review prompts, manuscript scoring, and
10
+ Version 0.8.7 provides shared Japanese technical writing criteria, an authoring
11
+ prompt, separate prose, heading, and list review prompts, manuscript scoring, and
12
12
  baseline verification for edited manuscripts, plus explicit prose-only mechanical
13
13
  replacements using a limited prh-format rule set.
14
14
  The CLI, diagnostics, documentation, and generated instructions are in English.
@@ -16,7 +16,7 @@ Japanese text is retained in manuscript excerpts, rule dictionaries, and fixture
16
16
 
17
17
  ## Available features
18
18
 
19
- The following commands are available in version 0.8.5.
19
+ The following commands are available in version 0.8.7.
20
20
 
21
21
  | Feature | Commands | Behavior |
22
22
  | --- | --- | --- |
@@ -24,6 +24,7 @@ The following commands are available in version 0.8.5.
24
24
  | [Replacement preview](#prose-replacements) | `replace check`, `replace diff` | Report mechanical replacement candidates or preview a diff for mapped running prose; preserve source files |
25
25
  | [Replacement application](#prose-replacements) | `replace apply` | Apply author-supplied rules after checking protected content and document structure; no MeCab or AI required |
26
26
  | [Prose review](#review-workflow) | `review scan` | Collect mapped Japanese running prose and contextual review candidates with MeCab/IPADIC by default |
27
+ | [List review](#list-review) | `review scan --scope lists` | Review mapped single-line list text in a separate session |
27
28
  | [Heading review](#heading-review) | `review scan --scope headings` | Collect editable section titles separately, retaining the outline and read-only body evidence |
28
29
  | [Review prompts](#prompt) | `review prompt` | Generate one Markdown prompt from a saved session for review and editing by an external reviewer |
29
30
  | [Baseline verification](#verify) | `review verify` | Check edited sources against a saved baseline for mechanical preservation; meaning is not verified |
@@ -107,7 +108,7 @@ Add the gem to your project's `Gemfile` to manage its version with Bundler:
107
108
 
108
109
  ```ruby
109
110
  source 'https://rubygems.org'
110
- gem 'asciidoc-pubkit', '~> 0.8.5'
111
+ gem 'asciidoc-pubkit', '~> 0.8.7'
111
112
  ```
112
113
 
113
114
  Then install dependencies and run the CLI through Bundler:
@@ -129,7 +130,7 @@ git clone https://github.com/cybergarage/asciidoc-pubkit.git
129
130
  cd asciidoc-pubkit
130
131
  bundle install
131
132
  gem build asciidoc-pubkit.gemspec
132
- gem install ./asciidoc-pubkit-0.8.5.gem
133
+ gem install ./asciidoc-pubkit-0.8.7.gem
133
134
  asciidoc-pubkit --version
134
135
  ```
135
136
 
@@ -153,7 +154,7 @@ asciidoc-pubkit writing prompt --lang ja --output writing-prompt.md
153
154
  ```
154
155
 
155
156
  For example, `--lang en` exits with an unsupported-language error. No English
156
- review or writing criteria are shipped in 0.8.5.
157
+ review or writing criteria are shipped in 0.8.7.
157
158
 
158
159
  For a small trial, use `examples/book.adoc` as the scan input. Its Japanese
159
160
  paragraphs deliberately contain review candidates; its code block must remain
@@ -460,7 +461,9 @@ asciidoc-pubkit review verify .pubkit/review --output verification.json
460
461
  All three commands leave manuscript files unchanged. Only the agent edits them.
461
462
  `prompt` and `verify` output files must not already exist.
462
463
  Without `--output`, `prompt` writes Markdown and `verify` writes JSON to stdout.
463
- `scan` defaults to `.pubkit/review` and prints a short summary.
464
+ `scan` defaults to `.pubkit/review` and prints a short summary. Prose scan output
465
+ and prompts explicitly direct reviewers to a separate `--scope lists` session
466
+ for list text; the prose baseline must remain intact.
464
467
 
465
468
  ### Scan
466
469
 
@@ -520,8 +523,9 @@ are frozen into the session; scan again after changing them.
520
523
 
521
524
  ### Heading review
522
525
 
523
- Review headings and prose in separate sessions. The default scan scope remains
524
- `prose`; `--scope headings` selects section titles. There is no combined scope.
526
+ Review headings, prose, and list text in separate sessions. The default scan scope
527
+ remains `prose`; `--scope headings` selects section titles and `--scope lists` selects
528
+ supported list text. There is no combined scope.
525
529
  Default output directories are `.pubkit/review` for prose and `.pubkit/headings`
526
530
  for headings, so separate scans do not replace each other by default.
527
531
  The scope is saved at scan time; `prompt` and `verify` use that saved scope.
@@ -564,12 +568,27 @@ and converted inline titles and physical title lines reused by multiple includes
564
568
  Unresolved section titles receive coverage notices. Verification permits only selected title text changes while
565
569
  protecting the body, title markers, hierarchy, order, section IDs, references,
566
570
  attributes, includes and other protected content. A title-derived section ID
567
- change fails verification. Establish stable explicit IDs before scanning when
568
- needed; this tool does not add IDs or migrate references. Numeric title changes
571
+ change fails verification. Starting with version 0.8.7, heading scans preserve
572
+ generated IDs by default. Eligible generated headings save an exact `permitted_id_anchor` in
573
+ their metadata. A reviewer may insert only that
574
+ anchor immediately before its selected heading, for example:
575
+
576
+ ```adoc
577
+ [#_手動圧縮の入口]
578
+ == 手動圧縮と拡張からの起動
579
+ ```
580
+
581
+ Verification allows this exact addition while requiring the same section ID,
582
+ hierarchy, order, and protected body. Existing explicit anchors remain protected;
583
+ other anchors and reference migrations are outside this permission. No additional
584
+ ID-preservation option is required. Previously saved permissions remain unchanged;
585
+ start a separate new session to obtain the default permissions, keeping the old
586
+ baseline intact. Scan and prompt never insert anchors or rewrite
587
+ manuscripts. Numeric title changes
569
588
  are manual-review notices, and remaining candidates do not fail verification.
570
589
  `meaning_verified` remains `false`.
571
590
 
572
- The session schema is now 2. Earlier sessions must be recreated; preserve or
591
+ Version 0.8.7 uses session schema 3. Earlier sessions must be recreated; preserve or
573
592
  finish an ongoing review with its original tool before establishing a new baseline.
574
593
  `review score` continues to support prose only and rejects `--scope`.
575
594
 
@@ -1124,12 +1143,13 @@ reports analyzer changes instead of treating results from different dictionaries
1124
1143
  as directly comparable. Prompt generation uses saved evidence and does not need
1125
1144
  MeCab. Changed rules or dictionary settings require a new scan.
1126
1145
 
1127
- Sessions from earlier tool versions are not compatible with 0.8.5 (session schema 2). Keep the original baseline for
1146
+ Version 0.8.7 uses session schema 3; version 0.8.5 uses schema 2.
1147
+ Sessions with a different schema or tool version cannot be loaded. Keep the original baseline for
1128
1148
  an ongoing review and finish it with the original version, or start a new review
1129
1149
  pass in a different directory:
1130
1150
 
1131
1151
  ```sh
1132
- asciidoc-pubkit review scan book.adoc --output .pubkit/review-0.8.5
1152
+ asciidoc-pubkit review scan book.adoc --output .pubkit/review-0.8.7
1133
1153
  ```
1134
1154
 
1135
1155
  Severity describes review priority, not proof of an error. There is no AI-authorship
@@ -1147,6 +1167,33 @@ unambiguously, the paragraph is skipped with a coverage notice. Columns are
1147
1167
  one-based Unicode character positions, not byte offsets or display widths.
1148
1168
  An empty findings array does not establish full coverage or good prose.
1149
1169
 
1170
+ ## List review
1171
+
1172
+ Use a separate session to review list descriptions, including reference-list text:
1173
+
1174
+ ```sh
1175
+ asciidoc-pubkit review scan book.adoc --scope lists --output .pubkit/lists
1176
+ asciidoc-pubkit review prompt .pubkit/lists --output lists-review.md
1177
+ asciidoc-pubkit review verify .pubkit/lists
1178
+ ```
1179
+
1180
+ The default output directory is `.pubkit/lists`. This scope reuses prose rules,
1181
+ glossary, style, analyzer, and shared writing criteria. It does not include running
1182
+ prose or edit headings. Each selected item appears once with heading context;
1183
+ read additional evidence separately when its technical role cannot be established.
1184
+
1185
+ Only exact source-mapped, single-line simple unordered or ordered item text is
1186
+ editable, including supported nested outline lists. Multiline, description,
1187
+ checklist, compound, ambiguous, and reused-source items remain protected with
1188
+ coverage notices. List markers, nesting, structure, links, and other inline tokens
1189
+ remain protected. Candidate columns include the source marker and indentation.
1190
+ Session JSON retains the `paragraphs` and `paragraph_id` keys, with
1191
+ `kind: list-item` and source `column`, `prefix`, and `suffix` metadata.
1192
+
1193
+ Default prose scanning, scoring, and mechanical replacement keep their existing
1194
+ prose scope. Heading body evidence continues to omit lists. A successful verify
1195
+ checks preservation and does not establish editorial completion.
1196
+
1150
1197
  ## Development
1151
1198
 
1152
1199
  ```sh
@@ -21,8 +21,9 @@ For a title such as `手動圧縮の入口` or `画像・分類モデルへの
21
21
  shared role-metaphor criteria to the section evidence. Consider `起動方法` or
22
22
  `呼び出しAPI` only when that is what the body explains. Naming a subject does
23
23
  not by itself explain the operation. If a better title would change a generated
24
- section ID, record the proposed title and ID concern for a separate authorized
25
- migration rather than changing anchors or keeping it for stylistic reasons.
24
+ section ID, use the exact permitted_id_anchor only when the saved session explicitly
25
+ authorizes it. Otherwise record the proposed title and ID concern for separate
26
+ authorized preservation. Protection does not justify an editorial keep decision.
26
27
 
27
28
  ## Compare headings with the same role
28
29
 
@@ -8,7 +8,7 @@ module AsciidocPubkit
8
8
  Commands:
9
9
  writing criteria Print the shared Japanese prose criteria
10
10
  writing prompt Generate a Japanese technical writing prompt
11
- review scan FILE Collect Japanese prose or headings and review candidates
11
+ review scan FILE Collect Japanese prose, headings, or list text and review candidates
12
12
  review score FILE Score Japanese prose; optionally use a local AI CLI
13
13
  review prompt SESSION Generate an English review prompt with Japanese source excerpts
14
14
  review verify SESSION Compare edited sources with the saved baseline
@@ -45,7 +45,7 @@ module AsciidocPubkit
45
45
  parser = OptionParser.new do |opts|
46
46
  input_name = group == 'writing' ? '' : ((group == 'replace' || %w[scan score].include?(command)) ? ' FILE' : ' SESSION')
47
47
  opts.banner = "Usage: asciidoc-pubkit #{group} #{command} [options]#{input_name}"
48
- opts.on('-o', '--output PATH', group == 'review' && command == 'scan' ? 'New session directory (default: .pubkit/review for prose, .pubkit/headings for headings)' : 'New output file (default: standard output)') { |v| options[:output] = v }
48
+ opts.on('-o', '--output PATH', group == 'review' && command == 'scan' ? 'New session directory (default: .pubkit/review, .pubkit/headings, or .pubkit/lists)' : 'New output file (default: standard output)') { |v| options[:output] = v }
49
49
  opts.on('--lang LANG', 'Language (ja only in this release)') { |v| options[:language] = v }
50
50
  if group == 'replace'
51
51
  opts.on('--rules FILE', 'Required prh-format replacement rules (limited compatibility)') { |v| options[:rules] = v }
@@ -54,7 +54,7 @@ module AsciidocPubkit
54
54
  opts.on('--only FILE', 'Select one included file') { |v| options[:only] = v }
55
55
  elsif group == 'review' && %w[scan score].include?(command)
56
56
  if command == 'scan'
57
- opts.on('--scope SCOPE', 'prose (default) or headings; separate review sessions') { |v| options[:scope] = v }
57
+ opts.on('--scope SCOPE', 'prose (default), headings, or lists; separate review sessions') { |v| options[:scope] = v }
58
58
  opts.on('--heading-rules FILE', 'Replace default heading rules with a YAML rule set') { |v| options[:heading_rules] = v }
59
59
  opts.on('-y', '--yes', 'Answer yes to replacement confirmation') { options[:yes] = true }
60
60
  opts.on('--no-input', 'Never prompt; fail on existing output unless --yes') { options[:no_input] = true }
@@ -128,8 +128,11 @@ module AsciidocPubkit
128
128
  end
129
129
  if result['scope'] == 'headings'
130
130
  out.puts "Scanned #{result['headings']} headings; found #{result['findings']} review candidates."
131
+ elsif result['scope'] == 'lists'
132
+ out.puts "Scanned #{result['paragraphs']} list items; found #{result['findings']} review candidates."
131
133
  else
132
134
  out.puts "Scanned #{result['paragraphs']} paragraphs; found #{result['findings']} review candidates."
135
+ out.puts "List text is excluded. Review it in a separate session with --scope lists."
133
136
  end
134
137
  out.puts "Tokenizer: #{result['tokenizer']}#{result['tokenizer'] == 'literal' ? ' (limited phrase matching; no morphological analysis)' : ' (UTF-8 IPADIC)'}"
135
138
  out.puts "Coverage notices: #{result['coverage_notices']}. See document.json for limitations."
@@ -64,6 +64,15 @@ module AsciidocPubkit
64
64
  true
65
65
  end
66
66
  nodes.each { |node| collect(node) }
67
+ if scope == 'lists'
68
+ reused = @paragraphs.group_by { |p| [p['file'], p['line']] }.select { |_, group| group.length > 1 }
69
+ @paragraphs.reject! do |paragraph|
70
+ next false unless reused.key?([paragraph['file'], paragraph['line']])
71
+ @coverage << { 'context' => 'list_item', 'file' => paragraph['file'], 'line' => paragraph['line'],
72
+ 'reason' => 'The source list item is reused and cannot be edited independently.' }
73
+ true
74
+ end
75
+ end
67
76
  ensure
68
77
  Asciidoctor::LoggerManager.logger = previous_logger
69
78
  end
@@ -110,14 +119,80 @@ module AsciidocPubkit
110
119
  @coverage << { 'context' => 'heading', 'file' => path, 'line' => line, 'reason' => 'Excluded by configuration.' }
111
120
  return
112
121
  end
122
+ section_id = node.id
123
+ generated = section_id && !node.attributes.key?('id')
124
+ anchor = if @settings['preserve_heading_ids'] && generated && section_id.match?(/\A[\p{L}\p{N}_.:-]+\z/)
125
+ "[##{section_id}]"
126
+ end
113
127
  @headings << { 'id' => AsciidocPubkit.hash_text([path, line, match[2]].join("\0"))[0, 16],
114
128
  'index' => index, 'parent_index' => parent_index,
115
129
  'file' => path, 'line' => line, 'column' => match[1].length + 1,
116
130
  'prefix' => match[1], 'suffix' => match[3], 'level' => node.level,
117
- 'section_id' => node.id, 'text' => match[2] }
131
+ 'section_id' => section_id, 'id_generated' => !!generated, 'permitted_id_anchor' => anchor, 'text' => match[2] }
132
+ end
133
+
134
+ def collect_list_item(node)
135
+ cursor = node.source_location
136
+ path, line = cursor && [cursor.file, cursor.lineno]
137
+ parent = node.parent
138
+ supported = %i[ulist olist].include?(parent.context)
139
+ while parent
140
+ supported = false if %i[quote verse table listing literal pass dlist colist].include?(parent.context)
141
+ parent = parent.parent
142
+ end
143
+ # ListItem#text applies substitutions; read through the public subs API.
144
+ original_subs = node.subs.dup
145
+ begin
146
+ node.subs.clear
147
+ text = node.text
148
+ ensure
149
+ node.subs.replace(original_subs)
150
+ end
151
+ raw = path && @source_lines[path] && @source_lines[path][line - 1]
152
+ pattern = node.marker && /\A([ \t]*#{Regexp.escape(node.marker)}[ \t]+)(.*?)([ \t]*)\z/
153
+ match = raw && pattern && raw.match(pattern)
154
+ # Include boundaries can leave the cursor on the include directive.
155
+ # Recover a unique physical span; repeated use is rejected after collection.
156
+ if supported && node.simple? && text && !text.include?("\n") && pattern && (!match || match[2] != text)
157
+ candidates = @source_lines.flat_map do |source_path, lines|
158
+ lines.each_with_index.filter_map do |source_line, offset|
159
+ candidate = source_line.match(pattern)
160
+ [source_path, offset + 1, candidate] if candidate && candidate[2] == text
161
+ end
162
+ end
163
+ path, line, match = candidates.first if candidates.length == 1
164
+ end
165
+ unless supported && node.simple? && text && !text.include?("\n") && match && match[2] == text && !text.empty?
166
+ @coverage << { 'context' => 'list_item', 'file' => path, 'line' => line,
167
+ 'reason' => 'Only unambiguously mapped, single-line outline list text with simple content is editable.' }
168
+ return
169
+ end
170
+ return if @only && @only != path
171
+ relative = Pathname.new(path).relative_path_from(Pathname.new(@root)).to_s
172
+ return if @settings.fetch('exclude').any? { |glob| File.fnmatch?(glob, relative, File::FNM_PATHNAME | File::FNM_DOTMATCH) }
173
+ headings = []
174
+ section_index = nil
175
+ parent = node.parent
176
+ while parent
177
+ if parent.context == :section && parent.title
178
+ headings.unshift(parent.title)
179
+ section_index ||= @section_indexes[parent]
180
+ end
181
+ parent = parent.parent
182
+ end
183
+ @paragraphs << {
184
+ 'id' => AsciidocPubkit.hash_text([path, line, text].join("\0"))[0, 16],
185
+ 'file' => path, 'line' => line, 'end_line' => line, 'column' => match[1].length + 1,
186
+ 'prefix' => match[1], 'suffix' => match[3], 'kind' => 'list-item',
187
+ 'section_index' => section_index, 'headings' => headings, 'text' => text
188
+ }
118
189
  end
119
190
 
120
191
  def collect(node)
192
+ if scope == 'lists'
193
+ collect_list_item(node) if node.context == :list_item
194
+ return
195
+ end
121
196
  if %i[list_item table quote verse listing literal pass].include?(node.context)
122
197
  @coverage << { 'context' => node.context.to_s, 'reason' => 'Not reviewed as running prose.' }
123
198
  end
@@ -213,7 +213,7 @@ module AsciidocPubkit
213
213
  'id' => AsciidocPubkit.hash_text([paragraph['id'], rule, offset, match].join(':'))[0, 16],
214
214
  'paragraph_id' => paragraph['id'], 'rule' => rule, 'severity' => severity,
215
215
  'file' => paragraph['file'], 'line' => paragraph['line'] + prefix.count("\n"),
216
- 'column' => (prefix.rindex("\n") ? prefix.length - prefix.rindex("\n") : prefix.length + 1),
216
+ 'column' => (prefix.rindex("\n") ? prefix.length - prefix.rindex("\n") : prefix.length + paragraph.fetch('column', 1)),
217
217
  'match' => match, 'question' => question
218
218
  }
219
219
  end
@@ -4,7 +4,7 @@ require 'tmpdir'
4
4
 
5
5
  module AsciidocPubkit
6
6
  class Session
7
- SCHEMA = 2
7
+ SCHEMA = 3
8
8
 
9
9
  def self.scan(entry, options)
10
10
  settings = Settings.new(entry, options)
@@ -12,7 +12,7 @@ module AsciidocPubkit
12
12
  unless document.diagnostics.empty?
13
13
  raise Error, "Document diagnostics must be resolved before scanning:\n" + document.diagnostics.map { |d| "#{d['severity']}: #{d['message']}" }.join("\n")
14
14
  end
15
- default_output = settings.data['scope'] == 'headings' ? '.pubkit/headings' : '.pubkit/review'
15
+ default_output = { 'headings' => '.pubkit/headings', 'lists' => '.pubkit/lists' }.fetch(settings.data['scope'], '.pubkit/review')
16
16
  destination = File.expand_path(options.fetch(:output, default_output))
17
17
  replacing = File.exist?(destination) || File.symlink?(destination)
18
18
  if replacing
@@ -95,13 +95,18 @@ module AsciidocPubkit
95
95
  File.write(path, JSON.pretty_generate(value) + "\n")
96
96
  end
97
97
 
98
- def self.protected_content(document)
98
+ def self.protected_content(document, allowed_anchor_lines: {})
99
99
  document.sources.to_h do |path, raw|
100
100
  paragraphs = document.paragraphs.select { |p| p['file'] == path }
101
101
  heading_scope = document.scope == 'headings'
102
102
  editable_headings = document.headings.select { |h| h['file'] == path }.to_h { |h| [h['line'], h] }
103
+ editable_items = document.scope == 'lists' ? paragraphs.to_h { |p| [p['line'], p] } : {}
103
104
  editable_lines = heading_scope ? [] : paragraphs.flat_map { |p| (p['line']..p['end_line']).to_a }
104
105
  outside = raw.lines.each_with_index.filter_map do |line, index|
106
+ next if allowed_anchor_lines.fetch(path, []).include?(index + 1)
107
+ if (item = editable_items[index + 1])
108
+ next [item['prefix'], '<editable-list-text>', item['suffix']]
109
+ end
105
110
  next if editable_lines.include?(index + 1)
106
111
  if heading_scope && (heading = editable_headings[index + 1])
107
112
  # Keep a positional placeholder: removing entire lines would hide reordering.
@@ -160,6 +165,7 @@ module AsciidocPubkit
160
165
  raise Error, 'Sources have changed since scanning. Create a new scan before generating a prompt.' unless stale.empty?
161
166
  language = Language.validate!(@manifest.fetch('settings').fetch('language'), operation: 'review')
162
167
  return heading_prompt(mode, language) if @manifest.fetch('settings').fetch('scope', 'prose') == 'headings'
168
+ list_scope = @manifest['settings']['scope'] == 'lists'
163
169
  criteria = @manifest.fetch('writing_criteria')
164
170
  instructions = <<~TEXT
165
171
  # Japanese manuscript review
@@ -184,7 +190,7 @@ module AsciidocPubkit
184
190
  After editing, reread each paragraph in context. Report unresolved issues and do not claim publication readiness.
185
191
  Mechanical verification does not establish semantic correctness.
186
192
 
187
- #{review_process('paragraph', mode)}
193
+ #{review_process(list_scope ? 'list item' : 'paragraph', mode)}
188
194
 
189
195
  ## Shared prose criteria
190
196
 
@@ -209,10 +215,23 @@ module AsciidocPubkit
209
215
 
210
216
  Only source-mapped running-prose paragraphs are reviewed. Inline macros and literal spans are masked by a conservative heuristic.
211
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."}
212
219
  This is a local correction task, not a chapter-restructuring task.
213
220
  Coverage notices: #{@document['coverage'].length}
214
221
 
215
222
  TEXT
223
+ if list_scope
224
+ instructions.sub!('running-prose paragraphs', 'single-line outline list item text')
225
+ instructions.sub!('proposed paragraph revisions', 'proposed list item revisions')
226
+ instructions.sub!('Do not change excluded content, lists, tables, quotations, or other files.',
227
+ 'Preserve list markers, nesting, order, continuation syntax, and inline tokens. Do not edit running prose, excluded list items, tables, quotations, or other files.')
228
+ instructions.sub!('running-prose edit scope', 'selected list-text edit scope')
229
+ instructions.sub!('Only source-mapped running-prose paragraphs are reviewed.', 'Only unambiguously source-mapped, single-line simple unordered or ordered list text is reviewed.')
230
+ instructions.sub!('Headings, lists, tables, quotations, code, and passthrough blocks are not prose-reviewed in this release.',
231
+ 'Multiline, description, checklist, compound, and reused-source list items remain protected. Running prose, headings, tables, quotations, code, and passthrough blocks are not reviewed in this session.')
232
+ instructions.sub!('the supplied prose/body context omits lists and other excluded blocks',
233
+ 'this session supplies only selected list text and heading labels; running prose and other excluded blocks are omitted')
234
+ end
216
235
  instructions << <<~TEXT
217
236
  ## Reading order
218
237
 
@@ -232,7 +251,7 @@ module AsciidocPubkit
232
251
  instructions << "## Context\n\n#{prompt_data(JSON.generate(context), 'json')}\n"
233
252
  previous_context = context
234
253
  end
235
- instructions << "### Paragraph #{paragraph['id']} — lines #{paragraph['line']}–#{paragraph['end_line']}\n\n"
254
+ instructions << "### #{list_scope ? 'List item' : 'Paragraph'} #{paragraph['id']} — lines #{paragraph['line']}–#{paragraph['end_line']}\n\n"
236
255
  instructions << prompt_data(paragraph['text'], 'text') << "\n"
237
256
  related = findings_by_paragraph.fetch(paragraph['id'], [])
238
257
  if related.empty?
@@ -252,7 +271,7 @@ module AsciidocPubkit
252
271
  document.diagnostics.each { |d| issues << { 'kind' => 'parse-diagnostic', 'message' => d['message'] } }
253
272
  old_paths = @manifest['sources'].map { |source| source['path'] }.sort
254
273
  issues << { 'kind' => 'source-set-changed', 'message' => 'The included source file set changed.' } if old_paths != document.sources.keys.sort
255
- current = self.class.protected_content(document)
274
+ current = self.class.protected_content(document, allowed_anchor_lines: permitted_anchor_lines(document))
256
275
  @manifest['protected'].each do |path, saved|
257
276
  next if current[path] == saved
258
277
  issues << { 'kind' => 'protected-content-changed', 'file' => path,
@@ -288,6 +307,21 @@ module AsciidocPubkit
288
307
 
289
308
  private
290
309
 
310
+ def permitted_anchor_lines(document)
311
+ return {} unless @manifest['settings']['scope'] == 'headings' && @manifest['settings']['preserve_heading_ids']
312
+ allowed = Hash.new { |hash, path| hash[path] = [] }
313
+ @document.fetch('headings', []).each do |saved|
314
+ anchor = saved['permitted_id_anchor']
315
+ next unless anchor
316
+ current = document.headings.find { |heading| heading['index'] == saved['index'] && heading['file'] == saved['file'] }
317
+ next unless current && current['section_id'] == saved['section_id']
318
+ line = current['line'] - 1
319
+ next unless line.positive? && document.sources.fetch(current['file']).lines[line - 1].chomp == anchor
320
+ allowed[current['file']] << line
321
+ end
322
+ allowed
323
+ end
324
+
291
325
  def comparison_structure(structure, headings, heading_scope)
292
326
  editable = headings.map { |h| h['index'] }
293
327
  structure.each_with_index.filter_map do |node, index|
@@ -311,7 +345,11 @@ module AsciidocPubkit
311
345
  Preserve section count, order, hierarchy, IDs, references, attributes, include directives,
312
346
  inline tokens, title markers, and all content outside the selected title spans.
313
347
  Title-derived section IDs can change after a title edit. Keep IDs unchanged;
314
- otherwise report the concern and leave the title for a separate ID migration.
348
+ when permitted_id_anchor is present in the saved heading metadata, you may insert
349
+ that exact anchor on the line immediately before its heading to preserve the old ID.
350
+ This is the only authorized addition outside title spans. Do not edit existing anchors,
351
+ invent IDs, or add anchors for other headings. Without that permission, report the
352
+ concern and leave the title for a separately authorized ID preservation task.
315
353
  Preserve the configured fixed section names; do not rename them merely for variety.
316
354
  Automated findings are review candidates, not proven defects.
317
355
  Do not invent facts, guarantees, implementation mechanisms or benefits.
@@ -343,7 +381,8 @@ module AsciidocPubkit
343
381
 
344
382
  ## Coverage and reading order
345
383
 
346
- Only selected, unambiguously source-mapped plain ATX section titles are editable.
384
+ Only selected, unambiguously source-mapped plain ATX section titles and explicitly
385
+ permitted old-ID anchor additions are editable.
347
386
  Document titles, old-style headings, converted inline titles and attribute-expanded
348
387
  titles remain protected. Body coverage excludes lists, tables, quotations and code;
349
388
  use needs-evidence when the supplied body cannot establish a title's scope.
@@ -31,20 +31,21 @@ module AsciidocPubkit
31
31
  base = @path ? File.dirname(@path) : File.dirname(File.expand_path(entry))
32
32
  language = Language.validate!(options[:language] || review.fetch('language', Language::DEFAULT), operation: 'review')
33
33
  scope = options.fetch(:scope, 'prose')
34
- raise Error, 'scope must be prose or headings.' unless %w[prose headings].include?(scope)
34
+ raise Error, 'scope must be prose, headings, or lists.' unless %w[prose headings lists].include?(scope)
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 == 'prose' && options[:heading_rules]
38
+ if scope != 'headings' && options[:heading_rules]
39
39
  raise Error, '--heading-rules requires --scope headings.'
40
40
  end
41
41
  heading_rules_path = options[:heading_rules] ? File.expand_path(options[:heading_rules]) : (review['heading_rules'] ? File.expand_path(review['heading_rules'], base) : HeadingRuleSet.default_path(language))
42
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))
43
43
  @data = {
44
44
  'scope' => scope,
45
+ 'preserve_heading_ids' => scope == 'headings',
45
46
  'heading_rules' => scope == 'headings' ? HeadingRuleSet.load(heading_rules_path) : nil,
46
47
  'heading_rules_path' => heading_rules_path,
47
- 'rules' => scope == 'prose' ? RuleSet.load(rules_path) : nil,
48
+ 'rules' => scope != 'headings' ? RuleSet.load(rules_path) : nil,
48
49
  'rules_path' => rules_path,
49
50
  'language' => language,
50
51
  'style' => options[:style] || review.fetch('style', 'preserve'),
@@ -9,7 +9,7 @@ require 'pathname'
9
9
  require 'optparse'
10
10
 
11
11
  module AsciidocPubkit
12
- VERSION = '0.8.5'
12
+ VERSION = '0.8.7'
13
13
  class Error < StandardError; end
14
14
 
15
15
  def self.hash_text(text)
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.5
4
+ version: 0.8.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - CyberGarage
@@ -37,8 +37,8 @@ dependencies:
37
37
  - - "~>"
38
38
  - !ruby/object:Gem::Version
39
39
  version: '1.0'
40
- description: Generate Japanese technical writing prompts, review AsciiDoc prose and
41
- section headings separately, verify protected manuscript content, and explicitly
40
+ description: Generate Japanese technical writing prompts, review AsciiDoc prose, section
41
+ headings, and list text separately, verify protected manuscript content, and explicitly
42
42
  apply prose-only mechanical replacements.
43
43
  executables:
44
44
  - asciidoc-pubkit