asciidoc-pubkit 1.0.3 → 1.2.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.
@@ -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
@@ -423,10 +461,14 @@ module AsciidocPubkit
423
461
  Check mechanical preservation and baseline integrity with this session before editing and at completion. If a baseline or artifact integrity check fails, stop editing, preserve the evidence, and report the failure. Do not repair or overwrite snapshots, update their hashes, or replace the session to turn a failed check into a successful report; a fresh scan cannot prove preservation against the original baseline.
424
462
  Record each reviewed #{unit} ID and each candidate ID with revise, keep, or needs-evidence and a reason grounded in that passage. For keep, identify the concrete referent, operation, condition, necessary distinction, or established terminology that justifies retaining it. A shared generic reason does not establish individual review. Appending a source quotation to a stock reason is still insufficient.
425
463
  For each candidate decision, identify its concrete role or claim, the evidence and source location, the direct wording considered, and why that alternative is better or why the original wording is necessary. For needs-evidence, name the missing support instead of inventing a role or revision. A quotation supports evidence but does not substitute for that explanation. For role metaphors such as 入口, apply the shared criteria even when a nearby identifier makes the referent clear.
464
+ For broad or everyday wording, record a concrete alternative and a passage-specific comparison before choosing keep or revise. Apply the saved shared terminology criteria even when the original is understandable. A passage summary is not a wording comparison.
465
+ Reporting helpers may validate and count explicit assessments but must not manufacture keep decisions or inherit a paragraph summary as a candidate assessment. Missing or insufficient comparisons remain pending; they are not needs-evidence unless factual support is missing. Automatic decisions and zero pending counts cannot establish completion.
426
466
  Never infer revise or keep from whether the matched string survives, disappears, or changes. Do not bulk-assign keep to unchanged #{unit}s or remaining candidates. An unreviewed item stays pending; use needs-evidence only after review identifies missing evidence.
427
467
  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
468
  #{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
469
  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.
470
+ 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.
471
+ 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
472
  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
473
  TEXT
432
474
  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|
@@ -0,0 +1,246 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'find'
4
+ require 'tmpdir'
5
+ require 'rubygems/version'
6
+
7
+ module AsciidocPubkit
8
+ # Manages bundled skills at an explicit path or agent-specific project path.
9
+ class SkillCommands
10
+ MANIFEST = '.asciidoc-pubkit-skill.json'
11
+ LOCK = '.asciidoc-pubkit-skills.lock'
12
+ AGENT_DIRECTORIES = { 'codex' => '.agents/skills', 'claude' => '.claude/skills' }.freeze
13
+
14
+ def self.bundled_root
15
+ File.expand_path('../../examples/skills', __dir__)
16
+ end
17
+
18
+ def self.run(command, args, out:, err:)
19
+ raise Error, 'Unknown skill command. Use skill --help.' unless %w[list install status upgrade].include?(command)
20
+ options = {}
21
+ parser = OptionParser.new do |opts|
22
+ opts.banner = "Usage: asciidoc-pubkit skill #{command}#{command == 'list' ? '' : ' NAME (--agent AGENT | --dir PATH)'} [options]"
23
+ unless command == 'list'
24
+ opts.on('--dir PATH', 'Explicit parent directory for installed skills') { |v| options[:dir] = v }
25
+ opts.on('--agent AGENT', AGENT_DIRECTORIES.keys, 'Project destination: codex or claude (does not invoke an agent)') { |v| options[:agent] = v }
26
+ end
27
+ opts.on('--json', 'Output structured skill information') { options[:json] = true } if %w[list status].include?(command)
28
+ opts.on('-h', '--help', 'Show command help') { options[:help] = true }
29
+ end
30
+ parser.parse!(args)
31
+ if options[:help]
32
+ out.puts parser
33
+ return 0
34
+ end
35
+ if command == 'list'
36
+ raise Error, 'skill list does not accept a name or destination.' unless args.empty?
37
+ skills = Dir.children(bundled_root).sort.select { |name| File.directory?(File.join(bundled_root, name)) }.map { |name| new(name).info }
38
+ out.puts(options[:json] ? JSON.pretty_generate({ 'tool_version' => VERSION, 'skills' => skills }) : skills.map { |s| "#{s['name']}: #{s['description']}" }.join("\n"))
39
+ return 0
40
+ end
41
+ if options[:dir] && options[:agent]
42
+ raise Error, 'Choose either --agent AGENT or --dir PATH, not both.'
43
+ end
44
+ directory = options[:dir] || AGENT_DIRECTORIES[options[:agent]]
45
+ unless args.length == 1 && directory && !directory.empty?
46
+ raise Error, 'Provide one bundled skill name and either --agent AGENT or --dir PATH.'
47
+ end
48
+ skill = new(args.first)
49
+ root = File.expand_path(directory)
50
+ check_path(root)
51
+ destination = File.join(root, skill.name)
52
+ if command == 'status'
53
+ report = skill.status(destination)
54
+ out.puts(options[:json] ? JSON.pretty_generate(report) : format_status(report))
55
+ return report['state'] == 'current' ? 0 : 1
56
+ end
57
+ if command == 'upgrade' && !%w[current update-available].include?(skill.status(destination)['state'])
58
+ raise Error, "Cannot upgrade this skill. Use skill status to inspect it; existing content was kept."
59
+ end
60
+ FileUtils.mkdir_p(root)
61
+ check_path(root)
62
+ lock = File.join(root, LOCK)
63
+ begin
64
+ Dir.mkdir(lock, 0o700)
65
+ rescue Errno::EEXIST
66
+ raise Error, "Skill destination is locked: #{lock}. Another install or upgrade may be running."
67
+ end
68
+ begin
69
+ result = skill.publish(destination, command)
70
+ ensure
71
+ Dir.rmdir(lock)
72
+ end
73
+ if result == :unchanged
74
+ out.puts "Already current: #{destination}"
75
+ return 0
76
+ end
77
+ out.puts "#{command == 'install' ? 'Installed' : 'Upgraded'} #{skill.name} from asciidoc-pubkit #{VERSION}: #{destination}"
78
+ 0
79
+ rescue Error, OptionParser::ParseError, SystemCallError, JSON::ParserError, Psych::Exception, ArgumentError => e
80
+ err.puts "Error: #{e.message}"
81
+ 2
82
+ end
83
+
84
+ def self.check_path(path)
85
+ Pathname.new(path).ascend do |part|
86
+ raise Error, "Symlink paths are not supported: #{part}" if File.symlink?(part)
87
+ raise Error, "Expected a directory: #{part}" if File.exist?(part) && !File.directory?(part)
88
+ end
89
+ end
90
+
91
+ def self.format_status(report)
92
+ lines = ["Skill: #{report['name']}", "Destination: #{report['destination']}", "State: #{report['state']}",
93
+ "Installed pubkit: #{report['installed_version'] || 'unknown'}", "Available pubkit: #{VERSION}"]
94
+ %w[modified missing extra updated].each do |kind|
95
+ report.fetch(kind, []).each { |file| lines << "#{kind.capitalize}: #{file}" }
96
+ end
97
+ lines.join("\n")
98
+ end
99
+
100
+ attr_reader :name
101
+
102
+ def initialize(name)
103
+ raise Error, 'Invalid skill name.' unless name.match?(/\A[a-z0-9]+(?:-[a-z0-9]+)*\z/) && name.length <= 64
104
+ @name = name
105
+ @source = File.join(self.class.bundled_root, name)
106
+ raise Error, "Unknown bundled skill: #{name}. Use skill list." unless File.directory?(@source)
107
+ self.class.check_path(@source)
108
+ @files = snapshot(@source)
109
+ raise Error, 'Bundled skill contains reserved registration metadata.' if @files.key?(MANIFEST)
110
+ text = AsciidocPubkit.read_text(File.join(@source, 'SKILL.md'))
111
+ frontmatter = text.match(/\A---\n(.*?)\n---(?:\n|\z)/m)
112
+ metadata = frontmatter && YAML.safe_load(frontmatter[1], permitted_classes: [], aliases: false)
113
+ unless metadata.is_a?(Hash) && metadata['name'] == name && metadata['description'].is_a?(String) && !metadata['description'].strip.empty?
114
+ raise Error, "Invalid bundled SKILL.md: #{name}"
115
+ end
116
+ @description = metadata['description']
117
+ end
118
+
119
+ def info
120
+ { 'name' => name, 'description' => @description, 'tool_version' => VERSION, 'files' => @files.keys.sort }
121
+ end
122
+
123
+ def snapshot(root)
124
+ files = {}
125
+ Find.find(root) do |path|
126
+ raise Error, "Symlink skill content is not supported: #{path}" if File.symlink?(path)
127
+ next if File.directory?(path)
128
+ raise Error, "Unsupported skill file: #{path}" unless File.file?(path)
129
+ relative = Pathname.new(path).relative_path_from(Pathname.new(root)).to_s
130
+ files[relative] = Digest::SHA256.file(path).hexdigest
131
+ end
132
+ files
133
+ end
134
+
135
+ def registration(destination)
136
+ path = File.join(destination, MANIFEST)
137
+ raise Error, "Symlink registration is not supported: #{path}" if File.symlink?(path)
138
+ return nil unless File.exist?(path)
139
+ raise Error, "Expected a registration file: #{path}" unless File.file?(path)
140
+ data = JSON.parse(AsciidocPubkit.read_text(path))
141
+ valid = data.is_a?(Hash) && data.keys.sort == %w[files name schema_version tool tool_version] &&
142
+ data['schema_version'] == 1 && data['tool'] == 'asciidoc-pubkit' && data['name'] == name &&
143
+ data['tool_version'].is_a?(String) && !data['tool_version'].empty? && Gem::Version.correct?(data['tool_version']) &&
144
+ data['files'].is_a?(Hash) && data['files'].key?('SKILL.md') && !data['files'].key?(MANIFEST)
145
+ valid &&= data['files'].all? do |file, sha|
146
+ file.is_a?(String) && !file.empty? && !file.start_with?('/') && !file.include?('\\') &&
147
+ file.split('/', -1).none? { |part| ['', '.', '..'].include?(part) || part.include?("\0") } &&
148
+ sha.is_a?(String) && sha.match?(/\A[0-9a-f]{64}\z/)
149
+ end
150
+ raise Error, "Invalid skill registration: #{path}" unless valid
151
+ data
152
+ end
153
+
154
+ def status(destination)
155
+ self.class.check_path(destination)
156
+ report = { 'name' => name, 'destination' => destination, 'available_version' => VERSION,
157
+ 'installed_version' => nil, 'state' => 'not-installed', 'modified' => [], 'missing' => [], 'extra' => [], 'updated' => [] }
158
+ return report unless File.directory?(destination)
159
+ data = registration(destination)
160
+ return report.merge('state' => 'unmanaged') unless data
161
+ actual = snapshot(destination).reject { |file, _| file == MANIFEST }
162
+ expected = data['files']
163
+ report['installed_version'] = data['tool_version']
164
+ report['modified'] = (expected.keys & actual.keys).select { |file| expected[file] != actual[file] }.sort
165
+ report['missing'] = (expected.keys - actual.keys).sort
166
+ report['extra'] = (actual.keys - expected.keys).sort
167
+ # Even empty additional directories are user-owned content.
168
+ permitted_dirs = expected.keys.flat_map do |file|
169
+ parts = file.split('/')[0...-1]
170
+ (1..parts.length).map { |length| parts.first(length).join('/') }
171
+ end
172
+ Find.find(destination) do |path|
173
+ next unless File.directory?(path) && path != destination
174
+ relative = Pathname.new(path).relative_path_from(Pathname.new(destination)).to_s
175
+ report['extra'] << relative + '/' unless permitted_dirs.include?(relative)
176
+ end
177
+ report['extra'].sort!
178
+ report['updated'] = (expected.keys | @files.keys).select { |file| expected[file] != @files[file] }.sort
179
+ report['state'] = if %w[modified missing extra].any? { |kind| !report[kind].empty? }
180
+ 'modified'
181
+ elsif Gem::Version.new(data['tool_version']) > Gem::Version.new(VERSION)
182
+ 'newer-installed'
183
+ elsif data['tool_version'] != VERSION || !report['updated'].empty?
184
+ 'update-available'
185
+ else
186
+ 'current'
187
+ end
188
+ report
189
+ end
190
+
191
+ def publish(destination, command)
192
+ self.class.check_path(destination)
193
+ if command == 'install'
194
+ raise Error, "Skill destination already exists: #{destination}. Use skill status or choose a different directory." if File.exist?(destination)
195
+ else
196
+ report = status(destination)
197
+ unless %w[current update-available].include?(report['state'])
198
+ raise Error, "Cannot upgrade #{report['state']} skill: #{destination}. Use skill status to inspect it; existing content was kept."
199
+ end
200
+ return :unchanged if report['state'] == 'current'
201
+ end
202
+ workspace = Dir.mktmpdir('.asciidoc-pubkit-skill-', File.dirname(destination))
203
+ staged = File.join(workspace, 'new')
204
+ backup = File.join(workspace, 'old')
205
+ preserve_backup = false
206
+ begin
207
+ Dir.mkdir(staged)
208
+ @files.each do |file, sha|
209
+ source = File.join(@source, file)
210
+ raise Error, 'Bundled skill changed while staging.' unless Digest::SHA256.file(source).hexdigest == sha
211
+ target = File.join(staged, file)
212
+ FileUtils.mkdir_p(File.dirname(target))
213
+ FileUtils.copy_file(source, target)
214
+ File.chmod(File.stat(source).mode & 0o777, target)
215
+ raise Error, 'Bundled skill changed while copying.' unless Digest::SHA256.file(target).hexdigest == sha
216
+ end
217
+ File.write(File.join(staged, MANIFEST), JSON.pretty_generate({ 'schema_version' => 1, 'tool' => 'asciidoc-pubkit',
218
+ 'name' => name, 'tool_version' => VERSION, 'files' => @files }) + "\n")
219
+ self.class.check_path(destination)
220
+ if command == 'upgrade'
221
+ raise Error, 'Installed skill changed while staging.' unless status(destination) == report
222
+ File.rename(destination, backup)
223
+ elsif File.exist?(destination)
224
+ raise Error, "Skill destination already exists: #{destination}"
225
+ end
226
+ begin
227
+ File.rename(staged, destination)
228
+ rescue SystemCallError
229
+ if File.directory?(backup)
230
+ preserve_backup = true
231
+ begin
232
+ File.rename(backup, destination)
233
+ preserve_backup = false
234
+ rescue SystemCallError => restore_error
235
+ raise Error, "Could not restore the skill. Original content is preserved at #{backup}: #{restore_error.message}"
236
+ end
237
+ end
238
+ raise
239
+ end
240
+ ensure
241
+ FileUtils.remove_entry_secure(workspace) unless preserve_backup
242
+ end
243
+ :updated
244
+ end
245
+ end
246
+ end
@@ -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.3'
12
+ VERSION = '1.2.0'
13
13
  class Error < StandardError; end
14
14
 
15
15
  def self.hash_text(text)
@@ -36,4 +36,5 @@ require_relative 'asciidoc_pubkit/local_evaluator'
36
36
  require_relative 'asciidoc_pubkit/score'
37
37
  require_relative 'asciidoc_pubkit/replacement'
38
38
  require_relative 'asciidoc_pubkit/document_commands'
39
+ require_relative 'asciidoc_pubkit/skill_commands'
39
40
  require_relative 'asciidoc_pubkit/cli'
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: asciidoc-pubkit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.3
4
+ version: 1.2.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,19 @@ 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-commands.md
74
+ - docs/skill-integration.md
70
75
  - docs/writing.md
71
76
  - examples/book.adoc
72
77
  - examples/replacements.yml
78
+ - examples/skills/review-asciidoc-manuscript/SKILL.md
73
79
  - exe/asciidoc-pubkit
74
80
  - lib/asciidoc_pubkit.rb
75
81
  - lib/asciidoc_pubkit/cli.rb
@@ -85,6 +91,7 @@ files:
85
91
  - lib/asciidoc_pubkit/score.rb
86
92
  - lib/asciidoc_pubkit/session.rb
87
93
  - lib/asciidoc_pubkit/settings.rb
94
+ - lib/asciidoc_pubkit/skill_commands.rb
88
95
  - lib/asciidoc_pubkit/writing.rb
89
96
  homepage: https://github.com/cybergarage/asciidoc-pubkit
90
97
  licenses: