asciidoc-pubkit 1.0.2 → 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.
@@ -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
@@ -51,6 +51,7 @@ module AsciidocPubkit
51
51
  'protected' => protected_content(document),
52
52
  'numeric_tokens' => numeric_tokens(document)
53
53
  }
54
+ manifest['outline_criteria'] = Writing.outline_criteria(settings.data['language']) if settings.data['scope'] == 'headings'
54
55
  write_json(File.join(staging, 'document.json'), document.to_h)
55
56
  write_json(File.join(staging, 'findings.json'), findings)
56
57
  manifest['artifacts'] = %w[document.json findings.json].to_h do |name|
@@ -157,14 +158,21 @@ module AsciidocPubkit
157
158
  @manifest.fetch('settings').fetch('language')
158
159
  end
159
160
 
160
- def prompt(mode)
161
+ def prompt(mode, view: 'full')
162
+ raise Error, 'view must be full or outline.' unless %w[full outline].include?(view)
163
+ if view == 'outline' && @manifest.fetch('settings').fetch('scope', 'prose') != 'headings'
164
+ raise Error, 'Outline view requires a heading session.'
165
+ end
166
+ if view == 'outline' && !@manifest['outline_criteria'].is_a?(String)
167
+ raise Error, 'Outline view requires saved outline criteria. Create a new heading session without replacing the existing baseline.'
168
+ end
161
169
  raise Error, 'mode must be revise or diagnose.' unless %w[revise diagnose].include?(mode)
162
170
  stale = @manifest['sources'].select do |source|
163
171
  !File.file?(source['path']) || Digest::SHA256.file(source['path']).hexdigest != source['sha256']
164
172
  end
165
173
  raise Error, 'Sources have changed since scanning. Create a new scan before generating a prompt.' unless stale.empty?
166
174
  language = Language.validate!(@manifest.fetch('settings').fetch('language'), operation: 'review')
167
- return heading_prompt(mode, language) if @manifest.fetch('settings').fetch('scope', 'prose') == 'headings'
175
+ return heading_prompt(mode, language, view: view) if @manifest.fetch('settings').fetch('scope', 'prose') == 'headings'
168
176
  list_scope = @manifest['settings']['scope'] == 'lists'
169
177
  criteria = @manifest.fetch('writing_criteria')
170
178
  instructions = <<~TEXT
@@ -332,7 +340,7 @@ module AsciidocPubkit
332
340
  end
333
341
  end
334
342
 
335
- def heading_prompt(mode, language)
343
+ def heading_prompt(mode, language, view: 'full')
336
344
  text = <<~TEXT
337
345
  # Japanese heading review
338
346
 
@@ -399,6 +407,14 @@ module AsciidocPubkit
399
407
  ## Selected headings
400
408
 
401
409
  TEXT
410
+ if view == 'outline'
411
+ text.sub!('# Japanese heading review', '# Japanese outline review')
412
+ text.sub!(prompt_data(JSON.generate(@document.fetch('outline')), 'json'), outline_view_data)
413
+ text.sub!('## Outline (reference data)', outline_review_guidance + "\n## Outline (reference data)")
414
+ end
415
+ if (depth = @manifest['settings']['heading_depth'])
416
+ text.sub!('## Selected headings', "Selected heading depth: #{depth} (maximum Asciidoctor section level).\nDeeper titles are reference data only and remain protected.\n\n## Selected headings")
417
+ end
402
418
  grouped = @findings.group_by { |finding| finding['heading_id'] }
403
419
  @document.fetch('headings').each do |heading|
404
420
  text << "### Heading #{heading['id']}\n\n"
@@ -415,6 +431,28 @@ module AsciidocPubkit
415
431
  text
416
432
  end
417
433
 
434
+ def outline_review_guidance
435
+ @manifest.fetch('outline_criteria')
436
+ end
437
+
438
+ def outline_view_data
439
+ selected = @document.fetch('headings').to_h { |h| [h['index'], h] }
440
+ numbers = {}
441
+ siblings = Hash.new(0)
442
+ rows = @document.fetch('outline').map do |heading|
443
+ parent = heading['parent_index']
444
+ siblings[parent] += 1
445
+ number = (numbers[parent] || []) + [siblings[parent]]
446
+ numbers[heading['index']] = number
447
+ source = selected[heading['index']] || heading
448
+ location = source['file'] ? "#{source['file']}:#{source['line'] || '?'}" : 'source position unavailable'
449
+ status = selected.key?(heading['index']) ? 'editable' : 'reference-only'
450
+ "#{' ' * (number.length - 1)}#{number.join('-')}. #{heading['text']} " \
451
+ "[#{status}; level #{heading['level']}; section_id #{heading['section_id']}; #{location}]"
452
+ end
453
+ prompt_data(rows.join("\n"), 'text')
454
+ end
455
+
418
456
  def review_process(unit, mode)
419
457
  <<~TEXT
420
458
  ## Review decisions and completion
@@ -427,6 +465,8 @@ module AsciidocPubkit
427
465
  Review #{unit}s without candidates against the shared criteria as well. For concerns outside the editable scope, report the location and a separate out-of-scope proposal; do not count protection as an editorial keep decision or edit protected content.
428
466
  #{mode == 'revise' ? 'Compare the original and edited passages, check both information loss and unsupported additions, and reconcile each recorded decision with the actual diff. After verification, individually inspect newly detected or remaining candidates; retain the original candidate IDs in the review record.' : 'Compare each proposed revision with the original passage for information loss and unsupported additions. A revise decision means a proposed revision, not an applied edit.'}
429
467
  Report mechanical preservation, candidate decisions, and #{unit}-level quality review separately. State selected and reviewed #{unit} counts, candidate decision counts, pending items, needs-evidence items, out-of-scope concerns, and evidence used. Recheck retained role metaphors by their actual roles and cite the individual decisions; listing remaining words is not enough. Out-of-scope concerns cover only evidence actually inspected; the supplied prose/body context omits lists and other excluded blocks, so do not claim those were reviewed. If any selected item or candidate remains pending, report the review as incomplete.
468
+ For an explicitly task-limited review (such as human-role terminology), state the objective and relevant entry IDs separately from the total scanned entries. Report task-specific decisions and full-quality review counts separately. Scanning the whole book or completing a terminology pass does not establish full editorial review; unrelated unreviewed entries and candidates remain pending, not automatic keep decisions. This reporting distinction does not change saved edit permissions or add a selection filter.
469
+ Check related tables, figures and alternative text when terminology changes affect them. Report updates outside this session's edit scope separately; this prompt does not authorize those edits. Preserve the original baseline and phase-end verification when separately authorized synchronization follows.
430
470
  Mechanical verification does not read or validate these decision records. Its passed result and candidate counts do not establish review completion or writing quality; meaning_verified remains false. Do not claim semantic correctness from those checks.
431
471
  TEXT
432
472
  end
@@ -23,7 +23,7 @@ module AsciidocPubkit
23
23
  reject_keys(config, ['review'], 'configuration')
24
24
  review = config.fetch('review', {})
25
25
  raise Error, 'review must be a mapping.' unless review.is_a?(Hash)
26
- reject_keys(review, %w[language style glossary exclude allows attributes base_dir tokenizer mecab_command mecab_dictionary rules heading_rules], 'review')
26
+ reject_keys(review, %w[language style glossary exclude allows attributes base_dir tokenizer mecab_command mecab_dictionary rules heading_rules heading_depth], 'review')
27
27
  raise Error, 'attributes must be a mapping.' unless review.fetch('attributes', {}).is_a?(Hash)
28
28
  %w[base_dir glossary mecab_command mecab_dictionary rules heading_rules].each do |key|
29
29
  raise Error, "#{key} must be a nonempty path string." if review.key?(key) && (!review[key].is_a?(String) || review[key].empty?)
@@ -38,6 +38,16 @@ module AsciidocPubkit
38
38
  if scope != 'headings' && options[:heading_rules]
39
39
  raise Error, '--heading-rules requires --scope headings.'
40
40
  end
41
+ if options.key?(:depth) && scope != 'headings'
42
+ raise Error, '--depth requires --scope headings.'
43
+ end
44
+ if review.key?('heading_depth') && (!review['heading_depth'].is_a?(Integer) || review['heading_depth'] < 1)
45
+ raise Error, 'heading_depth must be a positive integer.'
46
+ end
47
+ depth = options.fetch(:depth, review['heading_depth']) if scope == 'headings'
48
+ if depth && (!depth.is_a?(Integer) || depth < 1)
49
+ raise Error, 'Depth must be a positive integer.'
50
+ end
41
51
  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
52
  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
53
  @data = {
@@ -58,6 +68,7 @@ module AsciidocPubkit
58
68
  'base_dir' => File.expand_path(options[:base_dir] || review.fetch('base_dir', base), options[:base_dir] ? Dir.pwd : base),
59
69
  'glossary' => {}
60
70
  }
71
+ @data['heading_depth'] = depth if depth
61
72
  raise Error, 'tokenizer must be mecab or literal.' unless %w[mecab literal].include?(@data['tokenizer'])
62
73
  raise Error, 'style must be preserve, desu-masu, or dearu.' unless %w[preserve desu-masu dearu].include?(@data['style'])
63
74
  %w[exclude allows].each do |key|
@@ -18,6 +18,11 @@ module AsciidocPubkit
18
18
  AsciidocPubkit.read_text(File.join(DATA_ROOT, language, 'headings.md'))
19
19
  end
20
20
 
21
+ def self.outline_criteria(language = Language::DEFAULT)
22
+ Language.validate!(language, operation: 'review')
23
+ AsciidocPubkit.read_text(File.join(DATA_ROOT, language, 'outline.md'))
24
+ end
25
+
21
26
  def self.prompt(language = Language::DEFAULT)
22
27
  guide = prompt_criteria(language)
23
28
  <<~TEXT
@@ -9,7 +9,7 @@ require 'pathname'
9
9
  require 'optparse'
10
10
 
11
11
  module AsciidocPubkit
12
- VERSION = '1.0.2'
12
+ VERSION = '1.1.0'
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: 1.0.2
4
+ version: 1.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - CyberGarage
@@ -53,6 +53,8 @@ files:
53
53
  - data/review-rules.ja.yml
54
54
  - data/writing/ja/criteria.md
55
55
  - data/writing/ja/headings.md
56
+ - data/writing/ja/outline.md
57
+ - docs/agent-review-tutorial.md
56
58
  - docs/architecture.md
57
59
  - docs/configuration.md
58
60
  - docs/custom-rules.md
@@ -61,15 +63,18 @@ files:
61
63
  - docs/english-review-research.md
62
64
  - docs/installation.md
63
65
  - docs/list-review.md
66
+ - docs/outline-review.md
64
67
  - docs/prose-evaluation.md
65
68
  - docs/references.md
66
69
  - docs/replacements.md
67
70
  - docs/review-workflow.md
68
71
  - docs/rules-and-coverage.md
69
72
  - docs/scoring.md
73
+ - docs/skill-integration.md
70
74
  - docs/writing.md
71
75
  - examples/book.adoc
72
76
  - examples/replacements.yml
77
+ - examples/skills/review-asciidoc-manuscript/SKILL.md
73
78
  - exe/asciidoc-pubkit
74
79
  - lib/asciidoc_pubkit.rb
75
80
  - lib/asciidoc_pubkit/cli.rb