ligarb 0.4.0 → 0.5.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.
- checksums.yaml +4 -4
- data/assets/review.css +665 -0
- data/assets/review.js +681 -0
- data/assets/serve.js +76 -0
- data/assets/style.css +67 -0
- data/lib/ligarb/builder.rb +176 -1
- data/lib/ligarb/chapter.rb +26 -2
- data/lib/ligarb/claude_runner.rb +185 -0
- data/lib/ligarb/cli.rb +106 -8
- data/lib/ligarb/config.rb +25 -1
- data/lib/ligarb/inotify.rb +75 -0
- data/lib/ligarb/review_store.rb +112 -0
- data/lib/ligarb/server.rb +1091 -0
- data/lib/ligarb/template.rb +2 -1
- data/lib/ligarb/version.rb +1 -1
- data/lib/ligarb/writer.rb +71 -18
- data/templates/book.html.erb +124 -12
- metadata +36 -1
data/lib/ligarb/template.rb
CHANGED
|
@@ -12,7 +12,7 @@ module Ligarb
|
|
|
12
12
|
@css_path = File.join(ASSETS_DIR, "style.css")
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def render(config:, chapters:, structure:, assets:, index_entries: [])
|
|
15
|
+
def render(config:, chapters:, structure:, assets:, index_entries: [], bibliography: [])
|
|
16
16
|
css = File.read(@css_path)
|
|
17
17
|
template = File.read(@template_path)
|
|
18
18
|
|
|
@@ -34,6 +34,7 @@ module Ligarb
|
|
|
34
34
|
b.local_variable_set(:ai_generated, config.ai_generated)
|
|
35
35
|
b.local_variable_set(:footer, config.effective_footer)
|
|
36
36
|
b.local_variable_set(:index_tree, build_index_tree(index_entries, chapters))
|
|
37
|
+
b.local_variable_set(:bibliography, bibliography)
|
|
37
38
|
|
|
38
39
|
ERB.new(template, trim_mode: "-").result(b)
|
|
39
40
|
end
|
data/lib/ligarb/version.rb
CHANGED
data/lib/ligarb/writer.rb
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "yaml"
|
|
4
|
+
require "json"
|
|
4
5
|
require "fileutils"
|
|
5
6
|
|
|
6
7
|
module Ligarb
|
|
7
8
|
class Writer
|
|
9
|
+
class WriterError < RuntimeError; end
|
|
10
|
+
|
|
8
11
|
BRIEF_FIELDS_FOR_BOOK_YML = %w[author output_dir chapter_numbers style repository].freeze
|
|
9
12
|
|
|
10
13
|
def initialize(brief_path, no_build: false)
|
|
@@ -19,8 +22,7 @@ module Ligarb
|
|
|
19
22
|
book_yml_path = File.join(output_dir, "book.yml")
|
|
20
23
|
|
|
21
24
|
if File.exist?(book_yml_path)
|
|
22
|
-
|
|
23
|
-
exit 1
|
|
25
|
+
raise WriterError, "#{book_yml_path} already exists. Remove it first to regenerate."
|
|
24
26
|
end
|
|
25
27
|
|
|
26
28
|
FileUtils.mkdir_p(output_dir)
|
|
@@ -28,8 +30,7 @@ module Ligarb
|
|
|
28
30
|
run_claude(prompt)
|
|
29
31
|
|
|
30
32
|
unless File.exist?(book_yml_path)
|
|
31
|
-
|
|
32
|
-
exit 1
|
|
33
|
+
raise WriterError, "Claude did not generate book.yml in #{output_dir}"
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
puts "Book files generated in #{output_dir}"
|
|
@@ -39,6 +40,8 @@ module Ligarb
|
|
|
39
40
|
require_relative "builder"
|
|
40
41
|
Builder.new(book_yml_path).build
|
|
41
42
|
end
|
|
43
|
+
|
|
44
|
+
book_yml_path
|
|
42
45
|
end
|
|
43
46
|
|
|
44
47
|
def self.init_brief(directory = nil)
|
|
@@ -47,8 +50,7 @@ module Ligarb
|
|
|
47
50
|
path = File.join(target, "brief.yml")
|
|
48
51
|
|
|
49
52
|
if File.exist?(path)
|
|
50
|
-
|
|
51
|
-
exit 1
|
|
53
|
+
raise WriterError, "#{path} already exists."
|
|
52
54
|
end
|
|
53
55
|
|
|
54
56
|
FileUtils.mkdir_p(target)
|
|
@@ -95,21 +97,18 @@ module Ligarb
|
|
|
95
97
|
|
|
96
98
|
def check_claude_installed!
|
|
97
99
|
unless system("claude", "--version", out: File::NULL, err: File::NULL)
|
|
98
|
-
|
|
99
|
-
exit 1
|
|
100
|
+
raise WriterError, "'claude' command not found. Install Claude Code first."
|
|
100
101
|
end
|
|
101
102
|
end
|
|
102
103
|
|
|
103
104
|
def load_brief
|
|
104
105
|
unless File.exist?(@brief_path)
|
|
105
|
-
|
|
106
|
-
exit 1
|
|
106
|
+
raise WriterError, "#{@brief_path} not found."
|
|
107
107
|
end
|
|
108
108
|
|
|
109
109
|
brief = YAML.safe_load_file(@brief_path)
|
|
110
110
|
unless brief.is_a?(Hash) && brief["title"] && !brief["title"].empty?
|
|
111
|
-
|
|
112
|
-
exit 1
|
|
111
|
+
raise WriterError, "'title' is required in #{@brief_path}."
|
|
113
112
|
end
|
|
114
113
|
|
|
115
114
|
brief
|
|
@@ -148,6 +147,13 @@ module Ligarb
|
|
|
148
147
|
lines << "In book.yml, set: #{settings}"
|
|
149
148
|
end
|
|
150
149
|
|
|
150
|
+
sources = parse_brief_sources(brief)
|
|
151
|
+
if sources.any?
|
|
152
|
+
lines << ""
|
|
153
|
+
lines << "Reference sources (read these files for context):"
|
|
154
|
+
sources.each { |src| lines << "- #{src[:label]}: #{src[:path]}" }
|
|
155
|
+
end
|
|
156
|
+
|
|
151
157
|
lines << ""
|
|
152
158
|
lines << "Create all files in: #{abs_output_dir}"
|
|
153
159
|
lines << "In book.yml, always set: ai_generated: true"
|
|
@@ -156,18 +162,65 @@ module Ligarb
|
|
|
156
162
|
lines << "Each chapter: substantive content with multiple ## sections."
|
|
157
163
|
lines << "Use code blocks, admonitions, mermaid diagrams where appropriate."
|
|
158
164
|
lines << "Chapter filenames: 01-topic.md, 02-topic.md, etc."
|
|
165
|
+
lines << ""
|
|
166
|
+
lines << "Create references.bib with real bibliography entries in BibTeX format."
|
|
167
|
+
lines << "In book.yml, set: bibliography: references.bib"
|
|
168
|
+
lines << "Cite references in chapter text using [text](#cite:key) syntax."
|
|
169
|
+
lines << "Search the web to find accurate bibliographic information (DOI, pages, volume, etc.)."
|
|
170
|
+
lines << "Do NOT fabricate references. Only include real, verifiable publications."
|
|
171
|
+
lines << "Use entry types: @article, @inproceedings, @book, @misc as appropriate."
|
|
172
|
+
lines << "Use UTF-8 directly for special characters (no LaTeX commands like \\c{c})."
|
|
173
|
+
lines << "BibTeX keys: authorsurname+year (e.g. knuth1984, cousot1977)."
|
|
159
174
|
|
|
160
175
|
lines.join("\n")
|
|
161
176
|
end
|
|
162
177
|
|
|
178
|
+
def parse_brief_sources(brief)
|
|
179
|
+
base_dir = File.dirname(@brief_path)
|
|
180
|
+
(brief["sources"] || []).map do |entry|
|
|
181
|
+
case entry
|
|
182
|
+
when String
|
|
183
|
+
{ path: File.expand_path(entry, base_dir), label: File.basename(entry) }
|
|
184
|
+
when Hash
|
|
185
|
+
path = entry["path"] or raise WriterError, "source entry missing 'path'"
|
|
186
|
+
{ path: File.expand_path(path, base_dir), label: entry.fetch("label", File.basename(path)) }
|
|
187
|
+
else
|
|
188
|
+
raise WriterError, "invalid source entry: #{entry.inspect}"
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
163
193
|
def run_claude(prompt)
|
|
164
|
-
tools = "Write,Bash,WebFetch,WebSearch"
|
|
165
|
-
allowed = "Write,Bash(mkdir:*),Bash(ls:*),Bash(ligarb:*),WebFetch,WebSearch"
|
|
166
|
-
cmd = ["claude", "-p",
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
194
|
+
tools = "Read,Write,Bash,WebFetch,WebSearch"
|
|
195
|
+
allowed = "Read,Write,Bash(mkdir:*),Bash(ls:*),Bash(ligarb:*),WebFetch,WebSearch"
|
|
196
|
+
cmd = ["claude", "-p", prompt, "--tools", tools, "--allowedTools", allowed,
|
|
197
|
+
"--output-format", "stream-json", "--verbose"]
|
|
198
|
+
puts "Writing with Claude... (this may take a few minutes)"
|
|
199
|
+
unparsed_lines = []
|
|
200
|
+
IO.popen(cmd, err: [:child, :out]) do |io|
|
|
201
|
+
io.each_line do |line|
|
|
202
|
+
unless parse_stream_event(line)
|
|
203
|
+
unparsed_lines << line.rstrip
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
207
|
+
unless $?.success?
|
|
208
|
+
msg = unparsed_lines.reject(&:empty?).last(10).join("\n")
|
|
209
|
+
raise WriterError, "Claude process failed.#{"\n#{msg}" unless msg.empty?}"
|
|
210
|
+
end
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def parse_stream_event(line)
|
|
214
|
+
json = JSON.parse(line) rescue (return false)
|
|
215
|
+
case json["type"]
|
|
216
|
+
when "content_block_start"
|
|
217
|
+
tool = json.dig("content_block", "tool_use")
|
|
218
|
+
if tool
|
|
219
|
+
name = tool["name"]
|
|
220
|
+
puts " [#{name}]..." if name
|
|
221
|
+
end
|
|
170
222
|
end
|
|
223
|
+
true
|
|
171
224
|
end
|
|
172
225
|
end
|
|
173
226
|
end
|
data/templates/book.html.erb
CHANGED
|
@@ -110,6 +110,11 @@
|
|
|
110
110
|
</li>
|
|
111
111
|
<%- end -%>
|
|
112
112
|
<%- end -%>
|
|
113
|
+
<%- unless bibliography.empty? -%>
|
|
114
|
+
<li class="toc-chapter" data-chapter="__bibliography__">
|
|
115
|
+
<a href="#__bibliography__" class="toc-h1" onclick="showChapter('__bibliography__')"><%= language == 'ja' ? '参考文献' : 'Bibliography' %></a>
|
|
116
|
+
</li>
|
|
117
|
+
<%- end -%>
|
|
113
118
|
<%- unless index_tree.empty? -%>
|
|
114
119
|
<li class="toc-chapter" data-chapter="__index__">
|
|
115
120
|
<a href="#__index__" class="toc-h1" onclick="showChapter('__index__')"><%= language == 'ja' ? '索引' : 'Index' %></a>
|
|
@@ -147,6 +152,16 @@
|
|
|
147
152
|
<%- end -%>
|
|
148
153
|
</section>
|
|
149
154
|
<%- end -%>
|
|
155
|
+
<%- unless bibliography.empty? -%>
|
|
156
|
+
<section class="chapter bibliography-chapter" id="chapter-__bibliography__" style="display: none;">
|
|
157
|
+
<h1><%= language == 'ja' ? '参考文献' : 'Bibliography' %></h1>
|
|
158
|
+
<ul class="bibliography-list">
|
|
159
|
+
<%- bibliography.each do |entry| -%>
|
|
160
|
+
<li id="bib-<%= entry[:key] %>"><span class="bib-label">[<%= entry[:label] %>]</span> <%= entry[:formatted_html] %></li>
|
|
161
|
+
<%- end -%>
|
|
162
|
+
</ul>
|
|
163
|
+
</section>
|
|
164
|
+
<%- end -%>
|
|
150
165
|
<%- unless index_tree.empty? -%>
|
|
151
166
|
<section class="chapter index-chapter" id="chapter-__index__" style="display: none;">
|
|
152
167
|
<h1><%= language == 'ja' ? '索引' : 'Index' %></h1>
|
|
@@ -175,10 +190,12 @@
|
|
|
175
190
|
|
|
176
191
|
<script>
|
|
177
192
|
(function() {
|
|
178
|
-
var chapters = [<%= chapters.map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ', "__index__"' unless index_tree.empty? %>];
|
|
193
|
+
var chapters = [<%= chapters.map { |c| "\"#{c.slug}\"" }.join(", ") %><%= ', "__bibliography__"' unless bibliography.empty? %><%= ', "__index__"' unless index_tree.empty? %>];
|
|
179
194
|
var currentChapter = null;
|
|
180
195
|
|
|
181
|
-
|
|
196
|
+
var navigating = false; // flag to suppress pushState during popstate/initial load
|
|
197
|
+
|
|
198
|
+
function showChapter(slug, hash) {
|
|
182
199
|
chapters.forEach(function(ch) {
|
|
183
200
|
var el = document.getElementById('chapter-' + ch);
|
|
184
201
|
if (el) el.style.display = (ch === slug) ? 'block' : 'none';
|
|
@@ -195,7 +212,10 @@
|
|
|
195
212
|
});
|
|
196
213
|
|
|
197
214
|
currentChapter = slug;
|
|
198
|
-
|
|
215
|
+
var newHash = '#' + (hash || slug);
|
|
216
|
+
if (!navigating) {
|
|
217
|
+
history.pushState(null, '', newHash);
|
|
218
|
+
}
|
|
199
219
|
window.scrollTo(0, 0);
|
|
200
220
|
|
|
201
221
|
// Re-apply highlight if search is active
|
|
@@ -231,36 +251,53 @@
|
|
|
231
251
|
}
|
|
232
252
|
|
|
233
253
|
function showChapterAndScroll(slug, headingId) {
|
|
234
|
-
showChapter(slug);
|
|
235
|
-
history.replaceState(null, '', '#' + headingId);
|
|
254
|
+
showChapter(slug, headingId);
|
|
236
255
|
setTimeout(function() {
|
|
237
256
|
var target = document.getElementById(headingId);
|
|
238
257
|
if (target) target.scrollIntoView({ behavior: 'smooth' });
|
|
239
258
|
}, 50);
|
|
240
259
|
}
|
|
241
260
|
|
|
242
|
-
//
|
|
261
|
+
// Navigate to chapter/heading based on current URL hash
|
|
243
262
|
function handleHash() {
|
|
263
|
+
navigating = true;
|
|
244
264
|
var hash = location.hash.replace('#', '');
|
|
245
265
|
if (!hash) {
|
|
246
266
|
if (chapters.length > 0) showChapter(chapters[0]);
|
|
267
|
+
navigating = false;
|
|
247
268
|
return;
|
|
248
269
|
}
|
|
249
270
|
|
|
250
271
|
// Check if hash matches a chapter slug directly
|
|
251
272
|
if (chapters.indexOf(hash) !== -1) {
|
|
252
273
|
showChapter(hash);
|
|
274
|
+
navigating = false;
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
<%- unless bibliography.empty? -%>
|
|
279
|
+
// Check for bibliography entry (bib-KEY)
|
|
280
|
+
var bibMatch = hash.match(/^bib-(.+)/);
|
|
281
|
+
if (bibMatch) {
|
|
282
|
+
showChapter('__bibliography__', hash);
|
|
283
|
+
setTimeout(function() {
|
|
284
|
+
var target = document.getElementById(hash);
|
|
285
|
+
if (target) target.scrollIntoView({ behavior: 'smooth' });
|
|
286
|
+
}, 50);
|
|
287
|
+
navigating = false;
|
|
253
288
|
return;
|
|
254
289
|
}
|
|
290
|
+
<%- end -%>
|
|
255
291
|
|
|
256
292
|
// Check for footnote links (fn: or fnref:)
|
|
257
293
|
var fnMatch = hash.match(/^(?:fn|fnref):(.+?)--/);
|
|
258
294
|
if (fnMatch) {
|
|
259
295
|
var fnChSlug = fnMatch[1];
|
|
260
296
|
if (chapters.indexOf(fnChSlug) !== -1) {
|
|
261
|
-
if (currentChapter !== fnChSlug) showChapter(fnChSlug);
|
|
297
|
+
if (currentChapter !== fnChSlug) showChapter(fnChSlug, hash);
|
|
262
298
|
var fnTarget = document.getElementById(hash);
|
|
263
299
|
if (fnTarget) fnTarget.scrollIntoView({ behavior: 'smooth' });
|
|
300
|
+
navigating = false;
|
|
264
301
|
return;
|
|
265
302
|
}
|
|
266
303
|
}
|
|
@@ -271,26 +308,98 @@
|
|
|
271
308
|
var chSlug = parts[0];
|
|
272
309
|
if (chapters.indexOf(chSlug) !== -1) {
|
|
273
310
|
showChapterAndScroll(chSlug, hash);
|
|
311
|
+
navigating = false;
|
|
274
312
|
return;
|
|
275
313
|
}
|
|
276
314
|
}
|
|
277
315
|
|
|
278
316
|
// Fallback
|
|
279
317
|
if (chapters.length > 0) showChapter(chapters[0]);
|
|
318
|
+
navigating = false;
|
|
280
319
|
}
|
|
281
320
|
|
|
282
321
|
// TOC search + content highlight
|
|
283
322
|
var searchInput = document.getElementById('toc-search');
|
|
284
323
|
var clearBtn = document.getElementById('search-clear');
|
|
285
324
|
|
|
325
|
+
// Build text cache for full-text search (lazy, built on first search)
|
|
326
|
+
var chapterTextCache = null;
|
|
327
|
+
function buildTextCache() {
|
|
328
|
+
if (chapterTextCache) return;
|
|
329
|
+
chapterTextCache = {};
|
|
330
|
+
chapters.forEach(function(slug) {
|
|
331
|
+
var section = document.getElementById('chapter-' + slug);
|
|
332
|
+
if (section) {
|
|
333
|
+
chapterTextCache[slug] = section.textContent.toLowerCase();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function countMatches(text, query) {
|
|
339
|
+
var count = 0;
|
|
340
|
+
var idx = text.indexOf(query);
|
|
341
|
+
while (idx !== -1) {
|
|
342
|
+
count++;
|
|
343
|
+
idx = text.indexOf(query, idx + query.length);
|
|
344
|
+
}
|
|
345
|
+
return count;
|
|
346
|
+
}
|
|
347
|
+
|
|
286
348
|
function updateSearch() {
|
|
287
349
|
var query = searchInput.value.toLowerCase();
|
|
350
|
+
clearBtn.style.display = query ? 'block' : 'none';
|
|
351
|
+
|
|
352
|
+
// Remove existing match count badges
|
|
353
|
+
document.querySelectorAll('.search-match-count').forEach(function(el) { el.remove(); });
|
|
354
|
+
|
|
288
355
|
var items = document.querySelectorAll('.toc-chapter');
|
|
356
|
+
var partItems = document.querySelectorAll('.toc-part, .toc-appendix');
|
|
357
|
+
|
|
358
|
+
if (!query || query.length < 2) {
|
|
359
|
+
// Show all items when query is too short
|
|
360
|
+
items.forEach(function(item) { item.style.display = ''; });
|
|
361
|
+
partItems.forEach(function(item) { item.style.display = ''; });
|
|
362
|
+
highlightContent('');
|
|
363
|
+
return;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
buildTextCache();
|
|
367
|
+
|
|
368
|
+
// Track which chapters match (content or TOC text)
|
|
369
|
+
var matchingSlugs = {};
|
|
289
370
|
items.forEach(function(item) {
|
|
290
|
-
var
|
|
291
|
-
|
|
371
|
+
var slug = item.dataset.chapter;
|
|
372
|
+
var tocText = item.textContent.toLowerCase();
|
|
373
|
+
var contentText = chapterTextCache[slug] || '';
|
|
374
|
+
var tocMatch = tocText.indexOf(query) !== -1;
|
|
375
|
+
var contentMatch = contentText.indexOf(query) !== -1;
|
|
376
|
+
|
|
377
|
+
if (tocMatch || contentMatch) {
|
|
378
|
+
item.style.display = '';
|
|
379
|
+
matchingSlugs[slug] = true;
|
|
380
|
+
// Show match count badge for content matches
|
|
381
|
+
if (contentMatch) {
|
|
382
|
+
var count = countMatches(contentText, query);
|
|
383
|
+
var badge = document.createElement('span');
|
|
384
|
+
badge.className = 'search-match-count';
|
|
385
|
+
badge.textContent = count;
|
|
386
|
+
var link = item.querySelector('a');
|
|
387
|
+
if (link) link.appendChild(badge);
|
|
388
|
+
}
|
|
389
|
+
} else {
|
|
390
|
+
item.style.display = 'none';
|
|
391
|
+
}
|
|
292
392
|
});
|
|
293
|
-
|
|
393
|
+
|
|
394
|
+
// Show/hide part and appendix headers based on whether they have visible children
|
|
395
|
+
partItems.forEach(function(item) {
|
|
396
|
+
var children = item.querySelectorAll('.toc-chapter');
|
|
397
|
+
var hasVisible = Array.prototype.some.call(children, function(ch) {
|
|
398
|
+
return ch.style.display !== 'none';
|
|
399
|
+
});
|
|
400
|
+
item.style.display = hasVisible ? '' : 'none';
|
|
401
|
+
});
|
|
402
|
+
|
|
294
403
|
highlightContent(searchInput.value);
|
|
295
404
|
}
|
|
296
405
|
|
|
@@ -410,9 +519,12 @@
|
|
|
410
519
|
window.showChapterAndScroll = showChapterAndScroll;
|
|
411
520
|
window.renderSpecialBlocks = function() { if (currentChapter) renderSpecialBlocks(currentChapter); };
|
|
412
521
|
|
|
413
|
-
// Initialize
|
|
522
|
+
// Initialize (replace initial entry so first back goes to previous page, not same page)
|
|
523
|
+
navigating = true;
|
|
414
524
|
handleHash();
|
|
415
|
-
|
|
525
|
+
history.replaceState(null, '', location.hash || '#' + (chapters[0] || ''));
|
|
526
|
+
navigating = false;
|
|
527
|
+
window.addEventListener('popstate', handleHash);
|
|
416
528
|
})();
|
|
417
529
|
</script>
|
|
418
530
|
<%- if assets.need?(:highlight) -%>
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: ligarb
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.5.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- ligarb contributors
|
|
@@ -37,6 +37,34 @@ dependencies:
|
|
|
37
37
|
- - "~>"
|
|
38
38
|
- !ruby/object:Gem::Version
|
|
39
39
|
version: '1.1'
|
|
40
|
+
- !ruby/object:Gem::Dependency
|
|
41
|
+
name: webrick
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: '1.7'
|
|
47
|
+
type: :runtime
|
|
48
|
+
prerelease: false
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '1.7'
|
|
54
|
+
- !ruby/object:Gem::Dependency
|
|
55
|
+
name: fiddle
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
57
|
+
requirements:
|
|
58
|
+
- - ">="
|
|
59
|
+
- !ruby/object:Gem::Version
|
|
60
|
+
version: '1.1'
|
|
61
|
+
type: :runtime
|
|
62
|
+
prerelease: false
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '1.1'
|
|
40
68
|
- !ruby/object:Gem::Dependency
|
|
41
69
|
name: rake
|
|
42
70
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -72,14 +100,21 @@ executables:
|
|
|
72
100
|
extensions: []
|
|
73
101
|
extra_rdoc_files: []
|
|
74
102
|
files:
|
|
103
|
+
- assets/review.css
|
|
104
|
+
- assets/review.js
|
|
105
|
+
- assets/serve.js
|
|
75
106
|
- assets/style.css
|
|
76
107
|
- exe/ligarb
|
|
77
108
|
- lib/ligarb/asset_manager.rb
|
|
78
109
|
- lib/ligarb/builder.rb
|
|
79
110
|
- lib/ligarb/chapter.rb
|
|
111
|
+
- lib/ligarb/claude_runner.rb
|
|
80
112
|
- lib/ligarb/cli.rb
|
|
81
113
|
- lib/ligarb/config.rb
|
|
82
114
|
- lib/ligarb/initializer.rb
|
|
115
|
+
- lib/ligarb/inotify.rb
|
|
116
|
+
- lib/ligarb/review_store.rb
|
|
117
|
+
- lib/ligarb/server.rb
|
|
83
118
|
- lib/ligarb/template.rb
|
|
84
119
|
- lib/ligarb/version.rb
|
|
85
120
|
- lib/ligarb/writer.rb
|