lux-hammer 0.3.24 → 0.3.25
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/.version +1 -1
- data/recipes/llm.rb +0 -287
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 273ffb060fd16cb52423002e5e8e041481a37b0487395d966a8f9ba90cfa53fa
|
|
4
|
+
data.tar.gz: 9dc14e358bfafc0050379d671711ca28d6d6eb3aa1935f7440c93ea897267431
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 8c9e26f85ded5283505130548bcd1b64718d1a405013149d086881e166c59239cb1a4db3fbefcb2a990ed2a06b82d90ae54769e6650be6d1d6032a9fe48328d5
|
|
7
|
+
data.tar.gz: 700f84954436b3e606d2b5234ff682f19c27d651083037e0faaa0f9ca796b1df604704d6908d58f7fa6a133dc9fa314df71084a805c6219b1d46d963557fe22b
|
data/.version
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.3.
|
|
1
|
+
0.3.25
|
data/recipes/llm.rb
CHANGED
|
@@ -9,7 +9,6 @@ desc <<~TXT
|
|
|
9
9
|
memory persistent memory store (backs the Claude Code memory plugin)
|
|
10
10
|
plan apply a /plan bundle - sha1 checked, drift aware
|
|
11
11
|
prompt token-prefix prompt expander (UserPromptSubmit hook + CLI)
|
|
12
|
-
todo per-project task queue for agents (./LLM_TODO.local.md)
|
|
13
12
|
|
|
14
13
|
Commands:
|
|
15
14
|
usage subscription limits and per-model token usage
|
|
@@ -333,292 +332,6 @@ namespace :memory do
|
|
|
333
332
|
end
|
|
334
333
|
end
|
|
335
334
|
|
|
336
|
-
TODO_GUIDE ||= <<~TXT
|
|
337
|
-
Per-project task queue in ./LLM_TODO.local.md - workable by humans and any agent.
|
|
338
|
-
|
|
339
|
-
Human usage:
|
|
340
|
-
llm todo:add "fix login redirect" queue a task; pipe stdin for bulk:
|
|
341
|
-
`* ` bullets delimit tasks (multi-line ok),
|
|
342
|
-
no bullets at all = one task per line
|
|
343
|
-
llm todo:list tasks by id, counts, format warnings
|
|
344
|
-
llm todo:pop + llm todo:done take / finish a single task by hand
|
|
345
|
-
|
|
346
|
-
Edit the file freely: only the `# todo`, `# doing` and `# done` sections are
|
|
347
|
-
managed. `# plan`, `# idea` and any prose stay untouched. A task starts with
|
|
348
|
-
`*` at the line start and runs until the next `*` or header; when finished it
|
|
349
|
-
moves to `# done` verbatim.
|
|
350
|
-
|
|
351
|
-
LLM usage - one of these lines is the whole prompt:
|
|
352
|
-
"Run `llm todo go` and follow its output." work through every task
|
|
353
|
-
"Run `llm todo pop`, do the task, run `llm todo done`." do a single task
|
|
354
|
-
|
|
355
|
-
Task text is printed raw on stdout. `todo go` repeats the protocol after each
|
|
356
|
-
task and says when the queue is empty; `todo pop` re-prints the task in
|
|
357
|
-
progress, so an interrupted agent resumes instead of skipping ahead.
|
|
358
|
-
TXT
|
|
359
|
-
|
|
360
|
-
task :todo do
|
|
361
|
-
desc <<~D
|
|
362
|
-
How the todo queue works, for humans and LLMs.
|
|
363
|
-
|
|
364
|
-
#{TODO_GUIDE}
|
|
365
|
-
D
|
|
366
|
-
example 'todo'
|
|
367
|
-
|
|
368
|
-
proc do
|
|
369
|
-
say TODO_GUIDE
|
|
370
|
-
say ''
|
|
371
|
-
self.class.print_help 'todo:'
|
|
372
|
-
end
|
|
373
|
-
end
|
|
374
|
-
|
|
375
|
-
namespace :todo do
|
|
376
|
-
# Same story as :memory - helpers must live inside the namespace block.
|
|
377
|
-
private
|
|
378
|
-
|
|
379
|
-
TODO_FILE ||= 'LLM_TODO.local.md'
|
|
380
|
-
TODO_SECTIONS ||= { todo: 'todo', doing: 'doing', done: 'done' }.freeze
|
|
381
|
-
|
|
382
|
-
def todo_path
|
|
383
|
-
File.join(Dir.pwd, TODO_FILE)
|
|
384
|
-
end
|
|
385
|
-
|
|
386
|
-
# Exact title match on purpose - "# done ideas" must stay an unmanaged section.
|
|
387
|
-
def todo_section_key(title)
|
|
388
|
-
case title.strip.downcase
|
|
389
|
-
when 'todo', 'pending' then :todo
|
|
390
|
-
when 'doing', 'in progress', 'in-progress' then :doing
|
|
391
|
-
when 'done', 'completed' then :done
|
|
392
|
-
end
|
|
393
|
-
end
|
|
394
|
-
|
|
395
|
-
# The tool manages only the `# todo`, `# doing` and `# done` sections (any
|
|
396
|
-
# heading level). Inside them a task starts at a `*` or `-` bullet in the
|
|
397
|
-
# first column and runs until the next bullet or header, so tasks can span
|
|
398
|
-
# multiple lines and move between sections verbatim. Every other section
|
|
399
|
-
# (`# plan`, `# idea`, ...) and any preamble is kept as-is and never touched.
|
|
400
|
-
# Returns [tasks, nodes]; nodes reproduce the document layout for todo_save.
|
|
401
|
-
def todo_parse
|
|
402
|
-
tasks = { todo: [], doing: [], done: [] }
|
|
403
|
-
nodes = []
|
|
404
|
-
return [tasks, nodes] unless File.file?(todo_path)
|
|
405
|
-
|
|
406
|
-
section = nil # managed section key, nil while inside a raw chunk
|
|
407
|
-
current = nil # lines of the task being collected
|
|
408
|
-
|
|
409
|
-
flush_task = lambda do
|
|
410
|
-
tasks[section] << current.join("\n").rstrip if current
|
|
411
|
-
current = nil
|
|
412
|
-
end
|
|
413
|
-
|
|
414
|
-
File.foreach(todo_path) do |raw|
|
|
415
|
-
line = raw.chomp
|
|
416
|
-
if (m = line.match(/^#+\s*(\S.*?)\s*$/))
|
|
417
|
-
flush_task.call
|
|
418
|
-
section = todo_section_key(m[1])
|
|
419
|
-
if section
|
|
420
|
-
nodes << [:section, section] unless nodes.include?([:section, section])
|
|
421
|
-
else
|
|
422
|
-
nodes << [:raw, [line]]
|
|
423
|
-
end
|
|
424
|
-
elsif section
|
|
425
|
-
if line =~ /^[-*]\s+(.*)$/
|
|
426
|
-
flush_task.call
|
|
427
|
-
current = [$1.strip]
|
|
428
|
-
elsif current
|
|
429
|
-
current << line.rstrip
|
|
430
|
-
end
|
|
431
|
-
else
|
|
432
|
-
nodes << [:raw, []] unless nodes.last && nodes.last[0] == :raw
|
|
433
|
-
nodes.last[1] << line.rstrip
|
|
434
|
-
end
|
|
435
|
-
end
|
|
436
|
-
flush_task.call
|
|
437
|
-
[tasks, nodes]
|
|
438
|
-
end
|
|
439
|
-
|
|
440
|
-
# Rewrite: managed sections in canonical form, raw chunks verbatim, original
|
|
441
|
-
# order kept. Managed sections missing from the file are appended at the end.
|
|
442
|
-
def todo_save(tasks, nodes)
|
|
443
|
-
order = nodes + (TODO_SECTIONS.keys - nodes.map { |t, v| v if t == :section }).map { |k| [:section, k] }
|
|
444
|
-
|
|
445
|
-
File.open(todo_path, 'w') do |io|
|
|
446
|
-
order.each_with_index do |(type, value), i|
|
|
447
|
-
io.puts unless i.zero?
|
|
448
|
-
if type == :section
|
|
449
|
-
io.puts "# #{TODO_SECTIONS[value]}"
|
|
450
|
-
tasks[value].each do |text|
|
|
451
|
-
io.puts
|
|
452
|
-
io.puts "* #{text}"
|
|
453
|
-
end
|
|
454
|
-
else
|
|
455
|
-
lines = value.dup
|
|
456
|
-
lines.pop while lines.any? && lines.last.empty?
|
|
457
|
-
lines.each { |l| io.puts l }
|
|
458
|
-
end
|
|
459
|
-
end
|
|
460
|
-
end
|
|
461
|
-
end
|
|
462
|
-
|
|
463
|
-
# One-line label for confirmations - multi-line tasks show their first line.
|
|
464
|
-
def todo_label(text)
|
|
465
|
-
first, rest = text.split("\n", 2)
|
|
466
|
-
rest ? "#{first} ..." : first
|
|
467
|
-
end
|
|
468
|
-
|
|
469
|
-
# Format sanity check for hand-edited files. Returns warning strings.
|
|
470
|
-
def todo_lint(tasks)
|
|
471
|
-
warns = []
|
|
472
|
-
seen = Hash.new(0)
|
|
473
|
-
section = :preamble
|
|
474
|
-
File.foreach(todo_path) do |raw|
|
|
475
|
-
line = raw.chomp
|
|
476
|
-
if (m = line.match(/^#+\s*(\S.*?)\s*$/))
|
|
477
|
-
key = todo_section_key(m[1])
|
|
478
|
-
section = key || :other
|
|
479
|
-
seen[key] += 1 if key
|
|
480
|
-
elsif section == :preamble && line =~ /^[-*]\s/
|
|
481
|
-
warns << "ignored bullet before any section header: #{line}"
|
|
482
|
-
end
|
|
483
|
-
end
|
|
484
|
-
TODO_SECTIONS.each_key do |key|
|
|
485
|
-
warns << "duplicate '# #{key}' sections - their tasks are merged" if seen[key] > 1
|
|
486
|
-
warns << "missing '# #{key}' section - will be added on next write" if seen[key].zero?
|
|
487
|
-
end
|
|
488
|
-
warns << "#{tasks[:doing].length} tasks in doing - expected at most 1" if tasks[:doing].length > 1
|
|
489
|
-
warns
|
|
490
|
-
end
|
|
491
|
-
|
|
492
|
-
task :add do
|
|
493
|
-
desc <<~DESC
|
|
494
|
-
Add task(s) to ./#{TODO_FILE} (created on first add).
|
|
495
|
-
|
|
496
|
-
Text comes from the argument (one task), or from stdin. Piped input
|
|
497
|
-
that has `*` / `-` bullets is a list: a bullet begins a task and the
|
|
498
|
-
lines under it belong to it, so multi-line tasks and whole lists can be
|
|
499
|
-
piped in at once. Input with no bullets at all is one task per line.
|
|
500
|
-
DESC
|
|
501
|
-
example 'todo add "migrate the user model to Sequel"'
|
|
502
|
-
example 'cat tasks.md | llm todo add'
|
|
503
|
-
opt :text, desc: 'task text (or pipe tasks on stdin)'
|
|
504
|
-
|
|
505
|
-
proc do |opts|
|
|
506
|
-
# unquoted multi-word input lands in :text + :args - join it back
|
|
507
|
-
arg = [opts[:text], *opts[:args]].compact.join(' ').strip
|
|
508
|
-
if arg.empty?
|
|
509
|
-
lines = opts[:stdin].to_s.split("\n")
|
|
510
|
-
bulleted = lines.any? { |l| l =~ /^[-*]\s+/ }
|
|
511
|
-
new_tasks = []
|
|
512
|
-
lines.each do |line|
|
|
513
|
-
if line =~ /^[-*]\s+(.*)$/
|
|
514
|
-
new_tasks << [$1.strip]
|
|
515
|
-
elsif bulleted
|
|
516
|
-
new_tasks.last << line.rstrip if new_tasks.any?
|
|
517
|
-
elsif line.strip != ''
|
|
518
|
-
new_tasks << [line.rstrip]
|
|
519
|
-
end
|
|
520
|
-
end
|
|
521
|
-
new_tasks = new_tasks.map { |ls| ls.join("\n").rstrip }.reject(&:empty?)
|
|
522
|
-
else
|
|
523
|
-
new_tasks = [arg]
|
|
524
|
-
end
|
|
525
|
-
error 'usage: llm todo add <text> (or pipe tasks on stdin)' if new_tasks.empty?
|
|
526
|
-
|
|
527
|
-
tasks, nodes = todo_parse
|
|
528
|
-
tasks[:todo].concat new_tasks
|
|
529
|
-
todo_save tasks, nodes
|
|
530
|
-
new_tasks.each { |t| say "added: #{todo_label(t)}", :green }
|
|
531
|
-
end
|
|
532
|
-
end
|
|
533
|
-
|
|
534
|
-
task :pop do
|
|
535
|
-
desc <<~DESC
|
|
536
|
-
Print the current task and stop there - one task, no loop.
|
|
537
|
-
|
|
538
|
-
Moves the first todo task to doing if nothing is doing yet. Prints only
|
|
539
|
-
the raw task text (agent-friendly stdout). Re-running without `todo done`
|
|
540
|
-
prints the same task again, so an interrupted agent resumes instead of
|
|
541
|
-
starting the next one. Exits 1 when the list is empty.
|
|
542
|
-
DESC
|
|
543
|
-
example 'todo pop'
|
|
544
|
-
|
|
545
|
-
proc do
|
|
546
|
-
tasks, nodes = todo_parse
|
|
547
|
-
if tasks[:doing].empty?
|
|
548
|
-
error 'todo list is empty' if tasks[:todo].empty?
|
|
549
|
-
tasks[:doing] << tasks[:todo].shift
|
|
550
|
-
todo_save tasks, nodes
|
|
551
|
-
end
|
|
552
|
-
puts tasks[:doing].first
|
|
553
|
-
end
|
|
554
|
-
end
|
|
555
|
-
|
|
556
|
-
task :go do
|
|
557
|
-
desc <<~DESC
|
|
558
|
-
Agent entry point: work through the whole todo list.
|
|
559
|
-
|
|
560
|
-
Prints the current task (pop) followed by the loop protocol, so telling
|
|
561
|
-
any agent "run llm todo go" is enough to drain the queue. When the list
|
|
562
|
-
is empty it says so and tells the agent to stop and summarize.
|
|
563
|
-
DESC
|
|
564
|
-
example 'todo go'
|
|
565
|
-
|
|
566
|
-
proc do
|
|
567
|
-
tasks, nodes = todo_parse
|
|
568
|
-
if tasks[:doing].empty? && tasks[:todo].empty?
|
|
569
|
-
say 'All tasks done - stop the loop and summarize what was done.'
|
|
570
|
-
next
|
|
571
|
-
end
|
|
572
|
-
if tasks[:doing].empty?
|
|
573
|
-
tasks[:doing] << tasks[:todo].shift
|
|
574
|
-
todo_save tasks, nodes
|
|
575
|
-
end
|
|
576
|
-
say "TASK: #{tasks[:doing].first}"
|
|
577
|
-
say ''
|
|
578
|
-
say 'Do this task fully and verify it. Then run `llm todo done`, and `llm todo go` for the next one.'
|
|
579
|
-
end
|
|
580
|
-
end
|
|
581
|
-
|
|
582
|
-
task :done do
|
|
583
|
-
desc 'Mark the in-progress task as done'
|
|
584
|
-
example 'todo done'
|
|
585
|
-
|
|
586
|
-
proc do
|
|
587
|
-
tasks, nodes = todo_parse
|
|
588
|
-
error 'no task in progress (run: llm todo pop)' if tasks[:doing].empty?
|
|
589
|
-
text = tasks[:doing].shift
|
|
590
|
-
tasks[:done] << text
|
|
591
|
-
todo_save tasks, nodes
|
|
592
|
-
say "done: #{todo_label(text)}", :green
|
|
593
|
-
end
|
|
594
|
-
end
|
|
595
|
-
|
|
596
|
-
task :list do
|
|
597
|
-
alt :inspect
|
|
598
|
-
desc 'Inspect the todo file: tasks by id, counts, format validity warnings'
|
|
599
|
-
example 'todo list'
|
|
600
|
-
|
|
601
|
-
proc do
|
|
602
|
-
error "no #{TODO_FILE} in #{Dir.pwd} (run: llm todo add <text>)" unless File.file?(todo_path)
|
|
603
|
-
tasks, = todo_parse
|
|
604
|
-
id = 0
|
|
605
|
-
TODO_SECTIONS.each do |key, title|
|
|
606
|
-
say "#{title}:", :cyan
|
|
607
|
-
color = { todo: nil, doing: :yellow, done: :gray }[key]
|
|
608
|
-
tasks[key].each do |text|
|
|
609
|
-
first, *rest = text.split("\n")
|
|
610
|
-
say "#{(id += 1).to_s.rjust(3)}. #{first}", color
|
|
611
|
-
rest.each { |l| say " #{l}", color }
|
|
612
|
-
end
|
|
613
|
-
say ' (none)', :gray if tasks[key].empty?
|
|
614
|
-
end
|
|
615
|
-
say ''
|
|
616
|
-
say "#{tasks[:todo].length} todo, #{tasks[:doing].length} doing, #{tasks[:done].length} done"
|
|
617
|
-
todo_lint(tasks).each { |w| say "warning: #{w}", :yellow }
|
|
618
|
-
end
|
|
619
|
-
end
|
|
620
|
-
end
|
|
621
|
-
|
|
622
335
|
namespace :prompt do
|
|
623
336
|
TOKEN_PATTERN ||= /[a-z0-9_-]+/.freeze
|
|
624
337
|
TOKEN_LINE_RE ||= /\A(?:\s*:[a-z0-9_-]+)+\s*\z/.freeze
|