This roadmap is the working plan for the project. It is organised by priority rather than by date, and every item is written so it can be turned into a task or a test without further design discussion.
How to use it
P0items are defects or gaps that block reliable use of the current feature set. Do these first.P1items complete or harden something that is already designed.P2items add a designed-but-unbuilt capability.P3items are larger or deliberately later; they should not start before the items they depend on.- When an item is done, move its acceptance criteria into
planscript/tests/and updateSYNTAX.md/DESIGN.mdin the same change. - Anything in Explicitly deferred decisions is not settled and must not be assumed by new code.
Work items are tagged with the modules they touch.
Parser and model
- Line-oriented
.planparser with line-numbered errors: project declaration, project attributes (calendar,start,finish), metadata, tasks, task metadata, dependencies, budgets, invoices, and tracking events. - Hierarchical alphanumeric task IDs, summary tasks (no duration), milestones
(
0d), decimal durations,h/d/wunits. FS/SS/FF/SFdependencies with signed lag and the defaultFS/+0behaviour.Project.validate()enforcing dates, summary rules, dependency endpoints and cycles, budget legality, duration sign, tracking references, and invoice allocation totals.
Scheduling
- Full CPM: topological order, forward pass, backward pass, total float, critical tasks, multiple critical paths, and calendar-day projections when a project start date is set (calculated-only otherwise).
- Summary date rollup from descendants.
Tracking, cost, and status
- Event model (
start,progressabsolute and incremental,complete,note), lifecycle validation, and derivedTaskState(status + percent complete). - Derived actual start/finish/duration (inclusive) and actual cost rolled through summaries.
- Invoices with per-task allocations that must reconcile to the invoice total.
Analyzervariance (start/finish/duration), per-task and total cost variance (actual vs. resolved budget), project actual start, and duration-weighted project progress.ReportBuilderstatus report: project status, overdues, blocked tasks with blockers and root causes, late tasks, and look-ahead windows.
Budgets
- Explicit (
$) and weighted (%) allocations, nested weighting, bottom-up rollup, whole-cent allocation with largest-remainder rounding, and unallocated reporting.
CLI
check,summary,schedule(-c,-d,-g),status(-ao,-la), andbudget(-ao) with documented exit codes.- Calculated schedule table, date table, budget table, critical paths, and a textual Gantt chart.
Quality baseline
- 248
unittesttests covering parser errors, CPM examples, dependency types, soft and mandatory constraints, tracking, budgets, variance, reports, data-date behavior, and CLI behavior.
These are confirmed defects in the current build. Each one is small.
A trailing token that is not a valid duration is currently folded into the task
name (task 1.2 Design 5m becomes the name Design 5m), and inline comments
(task 1.2 Design 5d ; rush) are absorbed the same way. The user then gets a
"task must have a duration" validation error that does not point at the real
problem.
- Detect a trailing token that looks like a duration attempt but is invalid
(no unit, unknown unit, embedded space, signed value) and raise a
ParseErrornaming the line and token. - Give inline comments their own explicit error, or decide to support them.
- Reconcile the
m(month) unit: either accept5mon task lines with a documented 30-day definition, or reject it explicitly. - Acceptance: parser tests for each rejected shape, plus
SYNTAX.mdupdated to remove the corresponding "sharp edge".
The tracking design is written; these rules are not yet enforced:
- Same-day lifecycle precedence — within one date, events should be
applied as
start → progress → completeregardless of file order. Today same-date order is file order, soprogresswritten abovestartis rejected. - Future-dated events as a validation error — decided differently for now:
rather than rejecting them at parse time, every derived figure is measured to
an explicit data date (
as_of, default today), events after that date are excluded, andReportBuilderprints aData Noticessection listing what was excluded (Tracker.future_dated_events). SoFull_Plan.plan's2026-10-11event no longer distorts a report run on an earlier date. Remaining: decide whether a hard parse-time rejection is still wanted alongside that, and document the decision inSYNTAX.md. - Duplicate same-day events — two
progressevents for one task on one date must be rejected rather than resolved by file order. - Tracking on summary tasks — currently accepted silently. Decide: reject, derive from leaves, or allow with rollup, then enforce and document.
- Date-only line with no entries — should be invalid.
- Done:
as_of/data-date parameter exists onTrackerandAnalyzer(Analyzer(project, as_of)), so historical state is derived deterministically and tested without depending on the wall clock;test_reporter.pypins the behaviour across several data dates. - Acceptance: tests per rule;
DESIGN.md,SYNTAX.md, and the deferred list below updated to match the decision.
- Done:
ProjectBudgetReport/TaskBudgetReportare populated (planned and actual amounts, withremaining/cost_variancederived from them) and the budget section renders inProjectReport.render_text; the progress section renders planned/actual progress andbudget_consumed. - Done: the variance values computed by
Analyzerare exposed — planned and actual duration, start/finish and duration variance per task, and planned duration for the project. The project's forecast finish and schedule variance are derived by theForecasterat the report's data date (P3-8). - Done:
test_reporter.pyasserts project status, schedule conditions, budget reconciliation, thebudget = actual + remaininginvariant, and data notices. - Remaining: render the summary sections the report model already computes —
overdue/blocked/late lists (with
blocked_by/root_causes) and the look-ahead windows — which is what the commented-out assertion intest_agreed_cli_regressionswaits for. - Done: forecast finish and schedule variance (P3-8) are derived from
actuals, remaining duration, and dependencies at the report's data date;
a project with no target finish still reports
n/avariance. - Acceptance:
python -m planscript status <file>shows budget vs. actual and variance; reporter tests assert values; suite fully green.
Calendars are modelled (planscript/model/calendar.py) but nothing uses them,
so schedules and actual durations are plain calendar days, and 5d spans a
weekend.
Design questions to settle first:
- Is the CPM offset unit a calendar day or a working day?
- How is
calendar: <name>resolved to aCalendarobject, and how are custom work weeks and holidays expressed in a.planfile? - Is there a project default calendar plus optional per-task calendars?
Suggested sequence:
- P2-1 Resolve the
calendar:name to aCalendarat parse time and validate the name. Registry of built-ins (standard,7day) plus declared custom calendars. - P2-2 Define and parse custom calendar syntax (work week, holidays).
- P2-3 Make the scheduler working-time aware: convert durations and offsets to working-time units and project calendar dates through the calendar, keeping summary rollup and lag semantics intact. Pin weekend/holiday behaviour with a regression suite.
- P2-4 Make
Tracker.actual_durationobey the task's planning calendar. The inclusivefinish − start + 1convention is already documented. - P2-5 Expose the inclusive/exclusive duration convention as a documented project setting.
Acceptance: a Friday-to-Monday one-day task and a holiday-spanning task produce
documented, tested dates; Calendar.working_days_between semantics (currently
excluding the finish day) are either fixed or documented and tested.
The CLI is read-only today and PlanSerializer is a skeleton that does not even
emit syntax the parser accepts.
- P2-6 Complete
PlanSerializer: project declaration and attributes, metadata, calendars, tasks with budgets and metadata,dependslines with correct type and lag (no trailingFS 0dnoise), invoices, and tracking events. - P2-7 Guarantee round-trip: parse → serialize → parse must be an identity
on the model, with byte-stable output for an unchanged project. Add a
round-trip test over
Simple.plan,Detailed.plan, andFull_Plan.plan. - P2-8 Add CLI write commands (
format,save) that write back to the authoritative file and preserve comments where practical. - P2-9 Settle how a library of
.planfiles is organised (where projects live, naming, and how "recent projects" is tracked) so project storage is no longer an open question.
- P3-5 Add a
pyproject.toml(packaging, console script entry point, Python version, dev extras) so the tool installs asplanscriptrather than requiringpython -m planscriptfrom the repository root. - P3-6 Performance: scheduling and reporting currently iterate the full task and dependency lists repeatedly. Check scaling on a few thousand tasks and introduce indexes only where measurement justifies them.
These require the prior decisions in Explicitly deferred decisions.
- P3-7 Baselines and plan revisions: decide representation (snapshot section in the file, separate baseline file, or derived from tracking history) before writing any code. This is the largest open architectural question.
- P3-8 Forecast: forecast finish from actuals, remaining duration, and
dependencies, and report forecast-vs-target. Implemented:
planscript/engine/forecaster.pyschedules a forecast copy of the project: finished work pinned to its actual finish, and each unfinished task's remaining duration floored at the data date; the reporter exposes the figures andtest_forecaster.pypins the behaviour. Forecasts are derived and must never be written back as authoritative data. - P3-9 Staleness reporting: "task has been at 40% for 14 days", driven by tracking cadence configuration.
- P3-10 Derived/calculated progress, kept conceptually distinct from
explicitly reported progress (
DESIGN.md, Tracking design). - P3-11 Additional lifecycle events (
reopen, pause/resume), once the tracking model is stable. - P3-12 Constraints follow-through: the four soft (
SNET,SNLT,FNET,FNLT) and two mandatory (MSON,MFON) task-level constraints are implemented in the model and scheduler (programmatic only; no authoring syntax yet). Remaining: authoring syntax, reporting them, and evaluating them against portfolio targets such asProject.finish_datewhile preserving the target-vs-constraint distinction. - P3-17 Over-constrained reporting: surface tasks with negative total float
(a soft constraint the network cannot honour) as a task state in the
statusreport, so infeasible constraints are visible beyond the critical-path and Gantt views.
- P3-13 Decide the GUI direction (the original notes mention Godot) and whether it consumes the model in-process or through a stable command interface.
- P3-14 A Logseq-style integration, consuming and writing
.planfiles. - P3-15 Import/export paths that would make the tool adoptable: MS Project / CSV / Excel interchange, and an explicit note on what round-trip fidelity is acceptable.
- P3-16 Reporting outputs beyond the console: Markdown or HTML status reports, and a rendered Gantt worthy of sending to a client.
These were deliberately left unresolved. They must not be assumed by new code; each one is a decision waiting to be made, not an oversight.
| Decision | State | Where it lands |
|---|---|---|
| Event representation in the model | Settled. TaskEvent(date, task_id, directive, info); events carry no independent IDs. |
planscript/engine/tracker.py |
| Tracking validation architecture (parser vs. project validation) | Partly settled. Syntax and references fail in the parser; lifecycle rules fail during state derivation. Revisit when tracking is data-date aware. | P1-2 |
Incremental progress syntax (+10%, -20%) |
Implemented in TaskState._derive. |
planscript/engine/tracker.py |
| Late / Blocked / Overdue task status | Implemented as ScheduleCondition in the reporter. |
planscript/engine/reporter.py |
| Variance calculations | Partly settled. Planned-vs-actual variance is implemented against the calculated schedule. Whether variance should be measured against a revised plan or a baseline is still open; forecast-vs-target is implemented (P3-8). | P3-7 |
| How tracking interacts with task hierarchy | Open. Tracking a summary task is currently accepted with no defined semantics. | P1-2 |
| Calendar semantics | Open. Working days, work week, holidays, hours, per-task calendars, and the inclusive/exclusive duration convention are undesigned. | P2 |
| Plan revisions / baselines | Open and the largest architectural question. If a planned start changes from 9/10 to 9/15, historical reports become ambiguous without baselines or plan versions. Do not introduce versioning until a concrete use case forces it. | P3-7 |
| Project-level tracking events and actual project start/finish directives | Open. Tracking is task-level only. | P3-7 |
| Reopening or restarting completed tasks, pause/resume | Open. complete is currently irreversible. |
P3-11 |
| Derived/calculated progress | Open. Must stay conceptually distinct from explicitly reported progress. | P3-10 |
| Staleness ("at 40% for 14 days") and tracking cadence | Open. Reporting logic, not tracking-model logic. | P3-9 |
| Forecasting | Settled. Forecasts are derived at the data date from actuals, remaining duration, and dependencies (planscript/engine/forecaster.py): finished work is pinned to its actual finish, unfinished work is projected forward from the data date. They consume state and are never written back. |
P3-8 |
| Resource modelling and resource-constrained scheduling / leveling | Open. resource.py exists only as a sketch in the model TODO list. Would be a major scope decision. |
P3-2 |
| Whether tracking events may be intermixed with the project definition | Settled for now. Events are recognised wherever they appear; the convention is to place them last (SYNTAX.md). Reopen only if placement needs enforcement. |
planscript/parser/parser.py |
Project.start_date / finish_date semantics |
Partly settled. Both are soft targets and do not constrain CPM. How target analysis is surfaced is still open. | P3-12 |
| Exact duration configuration and calendar settings | Open. | P2-5 |
| Cost scope and currency | Settled. Cost is in scope; the model is dollars-only with no currency abstraction. | P2-12 |
- Becoming a general-purpose configuration or data language. No drift toward JSON/YAML, no required quoting, no punctuation added purely for parser convenience.
- A database, server, multi-user backend, hidden identifiers, or an immutable
audit log. The
.planfile remains the single source of truth. - Silently repairing, reinterpreting, or "best-guessing" invalid or contradictory input. It fails explicitly.
- Being a full MS Project replacement. The value here is readability, CPM correctness, and honest reporting, not feature parity.
- Multi-currency support. Cost is in scope but the model is dollars-only:
amounts are plain dollar figures rendered with a leading
$.
- Tests first for defects. Every P0/P1 item lands with a test that fails before the change.
- Keep the suite green.
python -m unittest discover -s planscript/tests -t .must pass before a commit; the current baseline is 274 tests. - No new runtime dependencies without an explicit decision;
unittestis the test framework. - Validation ownership. Syntax and structure in the parser, model legality
in
Project.validate(), derived-state legality where the state is derived. - Docs move with code. Update
SYNTAX.mdfor syntax,DESIGN.mdfor structure, and this roadmap when an item is completed (remove it and mention the docs in the commit message). - Preserve the central rule: derived information never becomes a second source of truth.
| Milestone | Contents | Exit criteria |
|---|---|---|
| M1 — Reliable current build | P0-1 … P0-3, P1-3, P1-4, P1-5 | Every CLI command works for tracked and untracked plans on Windows; no internal errors escape as tracebacks for malformed input. Redirected-output hardening is parked (see Parked). |
| M2 — Trustworthy tracking | P1-2 | Tracking conforms to the tracking design in DESIGN.md for ordering, dates, duplicates, and summary tasks, with a data-date parameter for deterministic tests. |
| M3 — Working time | P2-1 … P2-5 | Calendars drive scheduling and actual durations; weekend/holiday behaviour is pinned by tests and documented. |
| M4 — Writable plans | P2-6 … P2-9 | format/save round-trip the sample plans without loss; edits can be made safely from the CLI. |
| M5 — Reporting depth | P2-10 … P2-12, P3-8, P3-9 | Status reports show cost and schedule variance, forecast finish, and staleness with tests. |
| M6 — Baselines and interfaces | P3-7, P3-11 … P3-16 | Baseline/version strategy decided and implemented; an interface beyond the CLI consumes the model. |
P0 items are defects to fix now, P1 completes what is already designed,
P2 adds the next designed capabilities, and P3 holds the larger questions.
The status section above describes what the code does today, and the milestone
table gives the order to work in. When in doubt, trust the code and the tests,
then correct this file.
Items taken off the active lists on purpose. They are not abandoned, but they are not being worked on right now.
display.view_critical_paths prints →, which raises UnicodeEncodeError
when stdout is redirected or piped on a cp1252 console (this is why the test
suite forces PYTHONIOENCODING=utf-8).
- Encoding work is intentionally deferred for now; the
PYTHONIOENCODING=utf-8override inplanscript/tests/test_cli.pyremains the interim mitigation. - When this is picked up: use an ASCII separator (for example
->) or make the renderer encoding-safe, reconfigure stdout encoding inmain(), and remove the override from the CLI tests. - Acceptance (future):
python -m planscript schedule Simple.plan | Out-File ...succeeds on Windows without an explicitPYTHONIOENCODING; CLI tests pass with the override removed.
planscript/cli/display.py reads project.tracker.events, which does not
exist (Tracker stores task_events).
- Fix the attribute, or expose
Tracker.get_events()as the single accessor and use it everywhere. - Acceptance:
python -m planscript summary Simple.planexits0and reports a tracking event count; add a CLI regression test.
Tracker.get_all_task_events and Tracker.get_latest_task_event reference
self.events / self.get_events(), and display.render_log calls
project.tracker.get_events().
- Decide on one event accessor API (
get_events,get_tasks_events,get_latest_task_event) and implement it againsttask_events. - Acceptance: unit tests exercise each accessor and event ordering by date.
In planscript/parser/parser.py, an indented <word> $<amount> line that is
not a valid budget used to reach the invoice-entry branch before
invoice_date was assigned, raising UnboundLocalError instead of a
ParseError. Invoice state is now tracked through the current invoice object,
so internal errors no longer escape.
-
Budget and invoice amounts accept zero, one, or two decimal places:
$10is$10.00and$10.5is$10.50. This is documented inSYNTAX.md. -
Acceptance: parser tests assert that
budget $10.5parses asDecimal("10.5"), that a malformed amount such asbudget $10.555raises aParseError(notUnboundLocalError), and thatmain()reports aParseErrorasParse error:with exit code 1. -
planscript/model/schedule.pyannotates CPM values asintwhile the scheduler storestimedelta;durationis annotatedintand holds atimedelta. -
planscript/model/project.pycarries a standingTODOto replacefloatwithDecimal. -
The parser assigns
project.calendar(astr) althoughProjectdeclarescalendars: dict[str, Calendar]. Decide which is the real field and align the model, parser, serializer, and tests. -
Acceptance: annotations match runtime types, tests assert the calendar representation, and no stale
TODOremains for these items.
planscript/parser/DESIGN.md and planscript/engine/TRACKING_DESIGN.md contain
decisions that the implementation has since moved past — for example comments
are ; rather than #, dependencies are indented depends lines rather than
dependency 1.1 > 1.2 FS, month durations are unreachable, and budgets,
invoices, and the start:/finish:/calendar: attributes are not documented
there at all.
- Split each note document into current behaviour and deferred design, or
fold the current parts into
SYNTAX.md/DESIGN.mdand keep the notes purely as open questions. - Acceptance: no top-level document states a syntax rule that the parser rejects.
- P2-10 Cost variance: compare actual cost (
Tracker.actual_cost) against the resolvedBudgetper task and in total, and report it. [COMPLETED] - P2-11 Invoice validation beyond totals: decide whether allocations to summary tasks are legal, and detect invoices dated after the data date or before project start. [COMPLETED]
- P2-12 Decide whether cost belongs in this tool's scope at all (see Non-goals) and, if so, whether currency or a dollars-only model is intended. [CONFIRMED/PARKED($)]
- P3-3 Implied-parent semantics decided — keep the tolerance and document
it: when a task's implied parent is absent, it attaches to its nearest
existing ancestor, so with
1present and1.2absent,1.2.3is a child of1; a task with no existing ancestor is a root (planscript/model/hierarchy.py, documented inDESIGN.md, tested intest_model.py). [COMPLETED] - P3-4 Replace the hard-coded
2026-01-01fallback inScheduler._get_dateswith a calculated-only schedule: when no project start date exists,start_dates/finish_datesareNone, date views print a note, and variance/status fail withSchedulingError. [COMPLETED]