asciidoc-pubkit 0.8.5 → 0.8.6
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 +4 -4
- data/CHANGELOG.md +11 -0
- data/README.md +56 -13
- data/data/writing/ja/headings.md +3 -2
- data/lib/asciidoc_pubkit/cli.rb +6 -3
- data/lib/asciidoc_pubkit/document.rb +76 -1
- data/lib/asciidoc_pubkit/rules.rb +1 -1
- data/lib/asciidoc_pubkit/session.rb +46 -8
- data/lib/asciidoc_pubkit/settings.rb +7 -3
- data/lib/asciidoc_pubkit.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: e1fb57dff066c28a30d6205ad30ff8c5cd1a89fc27c35ec8379e9c80354843ae
|
|
4
|
+
data.tar.gz: 9431768603df5a8191ba93b0bc5773ad188050e06304e6f0f35272bbe132ef51
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 901984c71c8178ba62738e21ec67cb5585ebe72719ef06c03b38585c5ad7d0961a9fe4da78c47d1612e9c339d6170bee9a060ea4cf2cf68e9301ae4b80bb96ac
|
|
7
|
+
data.tar.gz: 70570e253745de217ba1d4eb2c5447a7a1dba2f72e3d1f5f1abb40787d54c996fa9dca3072bb190071a459346146200e5744e9eb5fa72f4410c9dd314af71333
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,16 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.8.6 — 2026-10-02
|
|
4
|
+
|
|
5
|
+
- Add separate `review scan --scope lists` sessions for source-mapped single-line
|
|
6
|
+
outline list text. Preserve markers, structure, inline tokens, and other scopes;
|
|
7
|
+
report unsupported or reused-source items as coverage notices.
|
|
8
|
+
- Add opt-in `--preserve-heading-ids` for heading scans. Save exact old-ID anchor
|
|
9
|
+
permissions and verify only those additions immediately before their selected
|
|
10
|
+
headings while retaining section IDs, structure, and protected body content.
|
|
11
|
+
- Advance sessions to schema 3; retain prior baselines and finish
|
|
12
|
+
ongoing reviews with their original tool and schema.
|
|
13
|
+
|
|
3
14
|
## 0.8.5 — 2026-10-02
|
|
4
15
|
|
|
5
16
|
- 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.
|
|
11
|
-
prompt, separate prose and
|
|
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
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.
|
|
19
|
+
The following commands are available in version 0.8.6.
|
|
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.
|
|
111
|
+
gem 'asciidoc-pubkit', '~> 0.8.6'
|
|
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.
|
|
133
|
+
gem install ./asciidoc-pubkit-0.8.6.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.
|
|
157
|
+
review or writing criteria are shipped in 0.8.6.
|
|
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
|
|
@@ -520,8 +521,9 @@ are frozen into the session; scan again after changing them.
|
|
|
520
521
|
|
|
521
522
|
### Heading review
|
|
522
523
|
|
|
523
|
-
Review headings and
|
|
524
|
-
`prose`; `--scope headings` selects section titles
|
|
524
|
+
Review headings, prose, and list text in separate sessions. The default scan scope
|
|
525
|
+
remains `prose`; `--scope headings` selects section titles and `--scope lists` selects
|
|
526
|
+
supported list text. There is no combined scope.
|
|
525
527
|
Default output directories are `.pubkit/review` for prose and `.pubkit/headings`
|
|
526
528
|
for headings, so separate scans do not replace each other by default.
|
|
527
529
|
The scope is saved at scan time; `prompt` and `verify` use that saved scope.
|
|
@@ -564,12 +566,25 @@ and converted inline titles and physical title lines reused by multiple includes
|
|
|
564
566
|
Unresolved section titles receive coverage notices. Verification permits only selected title text changes while
|
|
565
567
|
protecting the body, title markers, hierarchy, order, section IDs, references,
|
|
566
568
|
attributes, includes and other protected content. A title-derived section ID
|
|
567
|
-
change fails verification.
|
|
568
|
-
|
|
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
|
|
572
|
+
anchor immediately before its selected heading, for example:
|
|
573
|
+
|
|
574
|
+
```adoc
|
|
575
|
+
[#_手動圧縮の入口]
|
|
576
|
+
== 手動圧縮と拡張からの起動
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
Verification allows this exact addition while requiring the same section ID,
|
|
580
|
+
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
|
|
583
|
+
manuscripts. Numeric title changes
|
|
569
584
|
are manual-review notices, and remaining candidates do not fail verification.
|
|
570
585
|
`meaning_verified` remains `false`.
|
|
571
586
|
|
|
572
|
-
|
|
587
|
+
Version 0.8.6 uses session schema 3. Earlier sessions must be recreated; preserve or
|
|
573
588
|
finish an ongoing review with its original tool before establishing a new baseline.
|
|
574
589
|
`review score` continues to support prose only and rejects `--scope`.
|
|
575
590
|
|
|
@@ -1124,12 +1139,13 @@ reports analyzer changes instead of treating results from different dictionaries
|
|
|
1124
1139
|
as directly comparable. Prompt generation uses saved evidence and does not need
|
|
1125
1140
|
MeCab. Changed rules or dictionary settings require a new scan.
|
|
1126
1141
|
|
|
1127
|
-
|
|
1142
|
+
Version 0.8.6 uses session schema 3; version 0.8.5 uses schema 2.
|
|
1143
|
+
Sessions with a different schema or tool version cannot be loaded. Keep the original baseline for
|
|
1128
1144
|
an ongoing review and finish it with the original version, or start a new review
|
|
1129
1145
|
pass in a different directory:
|
|
1130
1146
|
|
|
1131
1147
|
```sh
|
|
1132
|
-
asciidoc-pubkit review scan book.adoc --output .pubkit/review-0.8.
|
|
1148
|
+
asciidoc-pubkit review scan book.adoc --output .pubkit/review-0.8.6
|
|
1133
1149
|
```
|
|
1134
1150
|
|
|
1135
1151
|
Severity describes review priority, not proof of an error. There is no AI-authorship
|
|
@@ -1147,6 +1163,33 @@ unambiguously, the paragraph is skipped with a coverage notice. Columns are
|
|
|
1147
1163
|
one-based Unicode character positions, not byte offsets or display widths.
|
|
1148
1164
|
An empty findings array does not establish full coverage or good prose.
|
|
1149
1165
|
|
|
1166
|
+
## List review
|
|
1167
|
+
|
|
1168
|
+
Use a separate session to review list descriptions, including reference-list text:
|
|
1169
|
+
|
|
1170
|
+
```sh
|
|
1171
|
+
asciidoc-pubkit review scan book.adoc --scope lists --output .pubkit/lists
|
|
1172
|
+
asciidoc-pubkit review prompt .pubkit/lists --output lists-review.md
|
|
1173
|
+
asciidoc-pubkit review verify .pubkit/lists
|
|
1174
|
+
```
|
|
1175
|
+
|
|
1176
|
+
The default output directory is `.pubkit/lists`. This scope reuses prose rules,
|
|
1177
|
+
glossary, style, analyzer, and shared writing criteria. It does not include running
|
|
1178
|
+
prose or edit headings. Each selected item appears once with heading context;
|
|
1179
|
+
read additional evidence separately when its technical role cannot be established.
|
|
1180
|
+
|
|
1181
|
+
Only exact source-mapped, single-line simple unordered or ordered item text is
|
|
1182
|
+
editable, including supported nested outline lists. Multiline, description,
|
|
1183
|
+
checklist, compound, ambiguous, and reused-source items remain protected with
|
|
1184
|
+
coverage notices. List markers, nesting, structure, links, and other inline tokens
|
|
1185
|
+
remain protected. Candidate columns include the source marker and indentation.
|
|
1186
|
+
Session JSON retains the `paragraphs` and `paragraph_id` keys, with
|
|
1187
|
+
`kind: list-item` and source `column`, `prefix`, and `suffix` metadata.
|
|
1188
|
+
|
|
1189
|
+
Default prose scanning, scoring, and mechanical replacement keep their existing
|
|
1190
|
+
prose scope. Heading body evidence continues to omit lists. A successful verify
|
|
1191
|
+
checks preservation and does not establish editorial completion.
|
|
1192
|
+
|
|
1150
1193
|
## Development
|
|
1151
1194
|
|
|
1152
1195
|
```sh
|
data/data/writing/ja/headings.md
CHANGED
|
@@ -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,
|
|
25
|
-
|
|
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
|
|
data/lib/asciidoc_pubkit/cli.rb
CHANGED
|
@@ -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
|
|
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
|
|
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,8 @@ 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
|
|
57
|
+
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 }
|
|
58
59
|
opts.on('--heading-rules FILE', 'Replace default heading rules with a YAML rule set') { |v| options[:heading_rules] = v }
|
|
59
60
|
opts.on('-y', '--yes', 'Answer yes to replacement confirmation') { options[:yes] = true }
|
|
60
61
|
opts.on('--no-input', 'Never prompt; fail on existing output unless --yes') { options[:no_input] = true }
|
|
@@ -128,6 +129,8 @@ module AsciidocPubkit
|
|
|
128
129
|
end
|
|
129
130
|
if result['scope'] == 'headings'
|
|
130
131
|
out.puts "Scanned #{result['headings']} headings; found #{result['findings']} review candidates."
|
|
132
|
+
elsif result['scope'] == 'lists'
|
|
133
|
+
out.puts "Scanned #{result['paragraphs']} list items; found #{result['findings']} review candidates."
|
|
131
134
|
else
|
|
132
135
|
out.puts "Scanned #{result['paragraphs']} paragraphs; found #{result['findings']} review candidates."
|
|
133
136
|
end
|
|
@@ -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' =>
|
|
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 =
|
|
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 =
|
|
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
|
|
|
@@ -213,6 +219,18 @@ module AsciidocPubkit
|
|
|
213
219
|
Coverage notices: #{@document['coverage'].length}
|
|
214
220
|
|
|
215
221
|
TEXT
|
|
222
|
+
if list_scope
|
|
223
|
+
instructions.sub!('running-prose paragraphs', 'single-line outline list item text')
|
|
224
|
+
instructions.sub!('proposed paragraph revisions', 'proposed list item revisions')
|
|
225
|
+
instructions.sub!('Do not change excluded content, lists, tables, quotations, or other files.',
|
|
226
|
+
'Preserve list markers, nesting, order, continuation syntax, and inline tokens. Do not edit running prose, excluded list items, tables, quotations, or other files.')
|
|
227
|
+
instructions.sub!('running-prose edit scope', 'selected list-text edit scope')
|
|
228
|
+
instructions.sub!('Only source-mapped running-prose paragraphs are reviewed.', 'Only unambiguously source-mapped, single-line simple unordered or ordered list text is reviewed.')
|
|
229
|
+
instructions.sub!('Headings, lists, tables, quotations, code, and passthrough blocks are not prose-reviewed in this release.',
|
|
230
|
+
'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.')
|
|
231
|
+
instructions.sub!('the supplied prose/body context omits lists and other excluded blocks',
|
|
232
|
+
'this session supplies only selected list text and heading labels; running prose and other excluded blocks are omitted')
|
|
233
|
+
end
|
|
216
234
|
instructions << <<~TEXT
|
|
217
235
|
## Reading order
|
|
218
236
|
|
|
@@ -232,7 +250,7 @@ module AsciidocPubkit
|
|
|
232
250
|
instructions << "## Context\n\n#{prompt_data(JSON.generate(context), 'json')}\n"
|
|
233
251
|
previous_context = context
|
|
234
252
|
end
|
|
235
|
-
instructions << "### Paragraph #{paragraph['id']} — lines #{paragraph['line']}–#{paragraph['end_line']}\n\n"
|
|
253
|
+
instructions << "### #{list_scope ? 'List item' : 'Paragraph'} #{paragraph['id']} — lines #{paragraph['line']}–#{paragraph['end_line']}\n\n"
|
|
236
254
|
instructions << prompt_data(paragraph['text'], 'text') << "\n"
|
|
237
255
|
related = findings_by_paragraph.fetch(paragraph['id'], [])
|
|
238
256
|
if related.empty?
|
|
@@ -252,7 +270,7 @@ module AsciidocPubkit
|
|
|
252
270
|
document.diagnostics.each { |d| issues << { 'kind' => 'parse-diagnostic', 'message' => d['message'] } }
|
|
253
271
|
old_paths = @manifest['sources'].map { |source| source['path'] }.sort
|
|
254
272
|
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)
|
|
273
|
+
current = self.class.protected_content(document, allowed_anchor_lines: permitted_anchor_lines(document))
|
|
256
274
|
@manifest['protected'].each do |path, saved|
|
|
257
275
|
next if current[path] == saved
|
|
258
276
|
issues << { 'kind' => 'protected-content-changed', 'file' => path,
|
|
@@ -288,6 +306,21 @@ module AsciidocPubkit
|
|
|
288
306
|
|
|
289
307
|
private
|
|
290
308
|
|
|
309
|
+
def permitted_anchor_lines(document)
|
|
310
|
+
return {} unless @manifest['settings']['scope'] == 'headings' && @manifest['settings']['preserve_heading_ids']
|
|
311
|
+
allowed = Hash.new { |hash, path| hash[path] = [] }
|
|
312
|
+
@document.fetch('headings', []).each do |saved|
|
|
313
|
+
anchor = saved['permitted_id_anchor']
|
|
314
|
+
next unless anchor
|
|
315
|
+
current = document.headings.find { |heading| heading['index'] == saved['index'] && heading['file'] == saved['file'] }
|
|
316
|
+
next unless current && current['section_id'] == saved['section_id']
|
|
317
|
+
line = current['line'] - 1
|
|
318
|
+
next unless line.positive? && document.sources.fetch(current['file']).lines[line - 1].chomp == anchor
|
|
319
|
+
allowed[current['file']] << line
|
|
320
|
+
end
|
|
321
|
+
allowed
|
|
322
|
+
end
|
|
323
|
+
|
|
291
324
|
def comparison_structure(structure, headings, heading_scope)
|
|
292
325
|
editable = headings.map { |h| h['index'] }
|
|
293
326
|
structure.each_with_index.filter_map do |node, index|
|
|
@@ -311,7 +344,11 @@ module AsciidocPubkit
|
|
|
311
344
|
Preserve section count, order, hierarchy, IDs, references, attributes, include directives,
|
|
312
345
|
inline tokens, title markers, and all content outside the selected title spans.
|
|
313
346
|
Title-derived section IDs can change after a title edit. Keep IDs unchanged;
|
|
314
|
-
|
|
347
|
+
when permitted_id_anchor is present in the saved heading metadata, you may insert
|
|
348
|
+
that exact anchor on the line immediately before its heading to preserve the old ID.
|
|
349
|
+
This is the only authorized addition outside title spans. Do not edit existing anchors,
|
|
350
|
+
invent IDs, or add anchors for other headings. Without that permission, report the
|
|
351
|
+
concern and leave the title for a separately authorized ID preservation task.
|
|
315
352
|
Preserve the configured fixed section names; do not rename them merely for variety.
|
|
316
353
|
Automated findings are review candidates, not proven defects.
|
|
317
354
|
Do not invent facts, guarantees, implementation mechanisms or benefits.
|
|
@@ -343,7 +380,8 @@ module AsciidocPubkit
|
|
|
343
380
|
|
|
344
381
|
## Coverage and reading order
|
|
345
382
|
|
|
346
|
-
Only selected, unambiguously source-mapped plain ATX section titles
|
|
383
|
+
Only selected, unambiguously source-mapped plain ATX section titles and explicitly
|
|
384
|
+
permitted old-ID anchor additions are editable.
|
|
347
385
|
Document titles, old-style headings, converted inline titles and attribute-expanded
|
|
348
386
|
titles remain protected. Body coverage excludes lists, tables, quotations and code;
|
|
349
387
|
use needs-evidence when the supplied body cannot establish a title's scope.
|
|
@@ -31,20 +31,24 @@ 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
|
|
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
|
|
38
|
+
if scope != 'headings' && options[:preserve_heading_ids]
|
|
39
|
+
raise Error, '--preserve-heading-ids requires --scope headings.'
|
|
40
|
+
end
|
|
41
|
+
if scope != 'headings' && options[:heading_rules]
|
|
39
42
|
raise Error, '--heading-rules requires --scope headings.'
|
|
40
43
|
end
|
|
41
44
|
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
45
|
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
46
|
@data = {
|
|
44
47
|
'scope' => scope,
|
|
48
|
+
'preserve_heading_ids' => !!options[:preserve_heading_ids],
|
|
45
49
|
'heading_rules' => scope == 'headings' ? HeadingRuleSet.load(heading_rules_path) : nil,
|
|
46
50
|
'heading_rules_path' => heading_rules_path,
|
|
47
|
-
'rules' => scope
|
|
51
|
+
'rules' => scope != 'headings' ? RuleSet.load(rules_path) : nil,
|
|
48
52
|
'rules_path' => rules_path,
|
|
49
53
|
'language' => language,
|
|
50
54
|
'style' => options[:style] || review.fetch('style', 'preserve'),
|
data/lib/asciidoc_pubkit.rb
CHANGED
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.
|
|
4
|
+
version: 0.8.6
|
|
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
|
|
41
|
-
|
|
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
|