Evidence-Based Scheduling (EBS) for Todo.txt specification.
Note: This specification is very, very far from finished. If you have any suggestions, please edit this page or the discussion page linked above.
User Estimation and Time Tracking
Users estimate the time it takes to complete a task, start a timer when they begin working on the task, and stop the timer when they finish.
Entering Estimates
The user enters their time estimate anywhere on the task line between matched curley braces. For example:
$ todo.sh add Fill out TPS report {15}
The time format is either the number of mintutes or the number of hours followed by the number of minutes. For example:
- {15}: 15 minutes
- {1:15}: One-hour, 15 minutes
- {75}: Also one-hour, 15 minutes
The time estimate may be followed by a pipe to specify additional options. For example:
$ todo.sh add Meeting with the Bobs '{30|rate 1h*60}'
Starting and Stopping The Timer
The user finds the task number of their next task using todo.sh and then starts a timer using that task number.
$ todo.sh ls TPS
1 Fill out TPS report {15}
$ ts 1
Start: 1 Fill out TPS report {15}
If a task is already running, ts stops the timer on the previous task, updates the tag, and starts the timer on the next task. For example:
$ ts 2
Stop After 56 minutes: 1 Fill out TPS report {15,56,a}
Start: 2 Meeting with the Bobs {30|rate 60*60}
The elapsed time portion of the tag shall round to the nearest minutes. It shall display time in minutes for 59 minutes or less and hours followed by minutes for one hour or greater.
Before starting the timer, ts checks the task for a time estimate tag. If it doesn’t find the tag, it fails with a descriptive error.
$ ts 3
Stop after 35 minutes: 2 Meeting with the Bobs {30,35,b|rate 60*60}
Error -- will not time tasks without a time estimate: 3 Fishing with Lawrence
Tasks in done.txt cannot be started.
$ ts 99
Error -- will not start tasks in done.txt: 99 Go to Chotchkis
If it finds a tag without any elapsed time or unique identifier, ts will automatically generate a unique identifier and add zero elapsed time plus the unique identifier to the tag. Unique identifiers should contain only case sensitive English letters (a-z, A-Z) and must never contain any arabic numerals (0–9) or commas (,). The unique identifier should be kept as short as possible.
The user may stop the timer without switching tasks by running ts on the number zero. For example:
$ ts 0
Stop after 5 minutes: 5 Upload bank transaction shaving program {10,5,c}
Revising Estimates
Users should not revise their initial time estimates after they start timing so that the program can predict completion time based on the original estimate. However, the revised_estimate extension forces the program to use the revised estimate as the original estimate when predicting how much time remains until this task is finished.
$ todo.sh ls revised_estimate
6 Steal fax machine {5,10,d|revised_estimate 15}
The revised_estimate extension may also be written as re. For example:
$ todo.sh ls re
6 Steal fax machine {5,10,d|re 15}
Calculating Velocities
The program shall automatically calculate all uncalculated velocities whenever it must update the done.txt file. If other tasks have uncalculated velocities in done.txt when “ts 0” is run, the velocities shall be calculated and the following message displayed:
$ ts 0
Stop after 5 minutes: 5 Upload bank transaction shaving program {10,5,c}
Notice: Adding velocities to tasks completed while timer not running.
Please complete tasks with todo.sh before stopping the timer so the
whole task duration is recorded.
Reports
Insufficent Data Error
No projections shall be made unless there are six or more velocities. If there are fewer than six velocities, the following error shall be printed:
You must complete at least six tasks with estimiates before program name can predict completion time probabilities. Stopping calculation.
Show Adjusted Estimate Gradient
Use historical evidence to calculate 100 estimates of how long a set of tasks will take and print each estimate on a line graph. For example, generate the report for task five:
$ ebs gradient 7
7 Take fax machine to empty field {1:00}
![]() |
Multiple task numbers may be specified. For example:
$ ebs gradient 7 8
8 Check bank balance at ATM {20}
7 Take fax machine to empty field {1:00}
Search strings may be specified as well. Each argument is a single todo.sh query (which are restrictive), so the total arguments are culmative. Duplicates are removed. For example:
$ todo.sh ls fax
7 Take fax machine to empty field {1:00}
$ todo.sh ls bank
9 (A) Talk to Michael and Samir about bank balance {1:00}
7 Check bank balance at ATM {20}
$ todo.sh ls bank ATM
8 Check bank balance at ATM {20}
$ ebs gradient fax bank
9 (A) Talk to Michael and Samir about bank balance {1:00}
8 Check bank balance at ATM {20}
7 Take fax machine to empty field {1:00}
$ ebs gradient fax 'bank ATM'
8 Check bank balance at ATM {20}
7 Take fax machine to empty field {1:00}
Print Adjusted Estimates and Billing Information
Use historical evidence to calculate 100 estimates of how long a set of tasks will take and print the selected adjusted estimate for each task. If more than one task is selected, print the total time to complete all of the tasks. For example:
$ ebs time 90% 'fax field' bank
1.5h 9 (A) Talk to Michael and Samir about bank balance {1:00}
30m 8 Check bank balance at ATM {20}
1.5h 7 Take fax machine to empty field {1:00}
====
3.5h
The time report subtracts completed work from its adjusted estimates:
$ ebs time 90% 'fax field'
1h 7 Take fax machine to empty field {1:00,30,e}
If all of the selectd tasks use the rate extension, the bill report prints an itemized invoice you can use to pre-bill your customers. For example:
$ ebs bill 90% meeting
$60 1h Meeting with the Bobs
$200 1h Follow-up Meeting with the Bobs
==========
$260 2h
Note: version 1.0 of the program shall only support U.S. Dollars.
To prevent mistakes, the bill report prints an error if not all the selected tasks use the rate extension:
$ ebs bill 90% meet
Error: the following task doesn't use the rate extension: 4 Meet Joannah
The bill report shall adhear to the following format so that it can be read and modified by machines:
Each output line except the optional total delimeter line shall start with a currence symbol
Immediately following the currence symbol shall be a whole or decimal number.
Immediatley following the number shall be one or more consecutive tabs.
Immediately following the tabs shall be a whole or decimal number with either no suffix, the suffix m, or the suffix h
Immediately following the number and it’s optional suffix shall be one or more consecutive tabs.
Immdiatley following the tabs shall be the task description without its line number or time estimate tag.
ts Internals
Input is command line arguments, todo.txt, and done.txt.
Output is stdout, stderr, todo.txt, and done.txt.
Sub-Routines
parse_tag
The parse_tag sub-routine extracts a tag from a line of todo.txt or done.txt and converts it into an internal data structure.
The function shall accept two parameters: a text string and a hash reference.
It shall return true on success and shall terminate the program on failure. If it does not return true, the program shall terminate and request that a bug be filed.
The following elements of the referenced shall be created, replaced, or set to empty:
estimated_time =>
ellapsed_time =>
unique_tag_identifier =>
If any pipe-delimited extensions are found, each extension shall be placed into its own hash element. The key shall be the extension name; the value shall be all of the extension’s arguments. For example:
rate => '60*60'
Extension names may not clobber the estimated_time, ellapsed_time, or unique_tag_identifier elements.
generate_unique_tag_identifier
The generate_unique_tag_identifier sub-routine finds all the used unique identifiers in todo.txt and done.txt. Then it finds the shortest unused unique identifier and returns that string.
- ? How does it get all the used identifiers?
It shall return a case-sensitive alphabetic string on success. It shall terminate the program on failure with a descriptive error. If it does not return a string on success, the program shall terminate and ask the user to file a bug.
If a unique identifier cannot be found that consists of a-z, A-Z with less than three letters (meaning 140,608 unique identifiers are already in use), the function shall terminate the program with the following error:
Too many unique identifiers in use. Please file a bug. We’d like to know how you got more than 140,608 open tasks.
check_for_race_condition
The check_for_race_condition sub-routine makes sure the contents of todo.txt and done.txt are the same now as they were before processing started. The function should be called immediately before making any changes to those files.
Uses:
slurp_files
slurp_files (array_ref)
The slurp_files sub-routine reads the contents of todo.txt and done.txt into an array. Note: the first element of the array shall contain the index number of the last line number of todo.txt. This shall also make the index numbers are the same as the line numbers printed by todo lsa (which start from one).
Its one argument is an array reference to store results.
write_files (array_ref, bool, bool)
The write_files sub-routine writes an array to todo.txt and done.txt. The first array element points to the split point: array elements 1 to this split point are written to todo.txt; the remaining array elements are written to done.txt.
It takes three arguments: an array reference to be written to todo.txt, a boolean that forces writing todo.txt if true, and a boolen that force writing to done.txt if true.
Uses:
check_for_race_conditioncalculate_velocities
timer_start (string)
The timer_start sub-routine starts the timer. It takes one argument, the number of the task to start.
It calls timer_stop.
If the line number is 0, it exits.
It calls slurp_files.
If the line number is greater than the value of array[0], it exits. (See no done.txt timing error message above.)
It calls parse_tag on its line number/array number.
It writes the line number and unique tag identifier to the temporary file.
It calls write_dirty_files.
It opens the todo.txt file, goes to the line number, and reads in the requested line number. It calls parse_tag on the line.
