mirror of
https://github.com/magnus919/agent-skills.git
synced 2026-09-14 04:56:26 +03:00
6fe5aed86e
Rename the skill directory to writers-helper and update the name field, eval manifest skill_name, skill README title and example paths, root README catalog entry, and the skill-triggers index. Regenerate llms.txt, .claude-plugin/marketplace.json, .codex-plugin/plugin.json, and .agents/plugins/marketplace.json from their generators. Content is unchanged. Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
165 lines
5.9 KiB
Python
165 lines
5.9 KiB
Python
#!/usr/bin/env python3
|
|
"""Plan a timed writing session with blocks, breaks, and word targets.
|
|
|
|
Converts session minutes and a target word count into a concrete plan:
|
|
block/break schedule, per-block word targets, and a flow-sprint check
|
|
based on a words-per-hour estimate. Pure computation, non-interactive.
|
|
|
|
Examples:
|
|
session-planner.py --minutes 60 --target-words 1000
|
|
session-planner.py --minutes 90 --blocks 3 --break-minutes 10 --json
|
|
session-planner.py --minutes 30 --words-per-hour 1800
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import math
|
|
import sys
|
|
from datetime import datetime, timedelta
|
|
|
|
# Low-water mark from fast-drafting practice: below this speed the inner
|
|
# critic is likely interfering, so the plan flags it rather than worrying.
|
|
FLOW_FLOOR_WPH = 1200
|
|
STANDARD_WPH = 1500
|
|
DEFAULT_BLOCK_MINUTES = 25
|
|
DEFAULT_BREAK_MINUTES = 5
|
|
|
|
|
|
def plan(minutes, blocks, break_minutes, target_words, words_per_hour, start_time):
|
|
if blocks is None:
|
|
blocks = max(1, math.ceil(minutes / (DEFAULT_BLOCK_MINUTES + DEFAULT_BREAK_MINUTES)))
|
|
if blocks < 1:
|
|
raise ValueError("blocks must be >= 1")
|
|
if minutes < blocks:
|
|
raise ValueError("minutes must be >= the number of blocks")
|
|
|
|
writing_minutes = minutes - (blocks - 1) * break_minutes
|
|
if writing_minutes <= 0:
|
|
raise ValueError("break time exceeds session minutes; reduce blocks or breaks")
|
|
|
|
minutes_per_block = writing_minutes / blocks
|
|
wph = words_per_hour or STANDARD_WPH
|
|
words_per_minute = wph / 60.0
|
|
projected_words = int(writing_minutes * words_per_minute)
|
|
per_block_words = [int(minutes_per_block * words_per_minute)] * blocks
|
|
# Distribute the remainder of the projection across the first blocks.
|
|
remainder = projected_words - sum(per_block_words)
|
|
for index in range(remainder):
|
|
per_block_words[index] += 1
|
|
|
|
schedule = []
|
|
cursor = start_time
|
|
for index in range(blocks):
|
|
block_end = cursor + timedelta(minutes=minutes_per_block)
|
|
schedule.append(
|
|
{
|
|
"block": index + 1,
|
|
"start": cursor.strftime("%H:%M"),
|
|
"end": block_end.strftime("%H:%M"),
|
|
"minutes": round(minutes_per_block, 1),
|
|
"word_target": per_block_words[index],
|
|
}
|
|
)
|
|
cursor = block_end
|
|
if index < blocks - 1:
|
|
cursor += timedelta(minutes=break_minutes)
|
|
|
|
return {
|
|
"session_minutes": minutes,
|
|
"writing_minutes": round(writing_minutes, 1),
|
|
"break_minutes_total": (blocks - 1) * break_minutes,
|
|
"blocks": blocks,
|
|
"minutes_per_block": round(minutes_per_block, 1),
|
|
"assumed_words_per_hour": wph,
|
|
"projected_words": projected_words,
|
|
"per_block_word_targets": per_block_words,
|
|
"target_words": target_words,
|
|
"target_feasible": target_words is None or target_words <= projected_words,
|
|
"flow_floor_check": (
|
|
"ok" if wph >= FLOW_FLOOR_WPH else "below the ~1200 wph floor; re-relax before writing"
|
|
),
|
|
"schedule": schedule,
|
|
}
|
|
|
|
|
|
def main(argv=None):
|
|
parser = argparse.ArgumentParser(description="Plan a timed writing session.")
|
|
parser.add_argument("--minutes", type=int, required=True, help="Total session minutes.")
|
|
parser.add_argument("--target-words", type=int, default=None, help="Desired word count.")
|
|
parser.add_argument("--blocks", type=int, default=None, help="Number of writing blocks.")
|
|
parser.add_argument(
|
|
"--break-minutes", type=int, default=DEFAULT_BREAK_MINUTES, help="Minutes per break."
|
|
)
|
|
parser.add_argument("--words-per-hour", type=int, default=None, help="Assumed writing speed.")
|
|
parser.add_argument("--start-time", default=None, help="Start time as HH:MM (default: now).")
|
|
parser.add_argument("--json", action="store_true", help="Emit JSON.")
|
|
parser.add_argument(
|
|
"--dry-run", action="store_true", help="Compute the plan without side effects (no-op)."
|
|
)
|
|
args = parser.parse_args(argv)
|
|
|
|
if args.minutes < 1:
|
|
parser.error("--minutes must be >= 1")
|
|
if args.break_minutes < 0:
|
|
parser.error("--break-minutes must be >= 0")
|
|
if args.words_per_hour and args.words_per_hour < 1:
|
|
parser.error("--words-per-hour must be >= 1")
|
|
|
|
if args.start_time:
|
|
try:
|
|
start = datetime.strptime(args.start_time, "%H:%M").replace(
|
|
year=datetime.now().year,
|
|
month=datetime.now().month,
|
|
day=datetime.now().day,
|
|
)
|
|
except ValueError as error:
|
|
parser.error(f"invalid --start-time: {error}")
|
|
else:
|
|
start = datetime.now()
|
|
|
|
try:
|
|
result = plan(
|
|
args.minutes,
|
|
args.blocks,
|
|
args.break_minutes,
|
|
args.target_words,
|
|
args.words_per_hour,
|
|
start,
|
|
)
|
|
except ValueError as error:
|
|
print(f"error: {error}", file=sys.stderr)
|
|
return 1
|
|
|
|
if args.json:
|
|
print(json.dumps(result, indent=2))
|
|
return 0
|
|
|
|
print(
|
|
f"Session plan: {result['session_minutes']} minutes, "
|
|
f"{result['writing_minutes']} writing, {result['break_minutes_total']} on breaks"
|
|
)
|
|
print(
|
|
f"Blocks: {result['blocks']} x ~{result['minutes_per_block']} min "
|
|
f"at {result['assumed_words_per_hour']} words/hour"
|
|
)
|
|
print(f"Projected words: {result['projected_words']}")
|
|
if result["target_words"] is not None:
|
|
status = (
|
|
"feasible"
|
|
if result["target_feasible"]
|
|
else "over the projection; extend time or lower the target"
|
|
)
|
|
print(f"Target: {result['target_words']} words -> {status}")
|
|
print(f"Flow check: {result['flow_floor_check']}")
|
|
print("Schedule:")
|
|
for item in result["schedule"]:
|
|
print(
|
|
f" Block {item['block']}: {item['start']}-{item['end']} "
|
|
f"(~{item['minutes']} min, ~{item['word_target']} words)"
|
|
)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|