Compare commits
3 Commits
f37122eea1
...
7c2da0f7ee
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7c2da0f7ee | ||
|
|
d499433f93 | ||
|
|
d95f0a78c7 |
@@ -37,7 +37,7 @@ A Feature's *current* delivery confidence (a snapshot, overwritten each review):
|
||||
_Avoid_: health, RAG, risk
|
||||
|
||||
**Week**:
|
||||
A column of the plan: one real calendar week, identified and labelled by the date of its first workday (Monday). Columns run contiguously from the earliest Feature start to the last marker or Milestone — empty weeks in between are still drawn.
|
||||
A column of the plan: one real calendar week, identified and labelled by the date of its first workday (Monday). Columns run contiguously from the earliest Feature start to the last marker or Milestone — empty weeks in between are still drawn. An optional authored **start**/**end** widens this span with lead-in or trailing empty Weeks; it only ever extends the range, never narrowing it or hiding a Feature.
|
||||
_Avoid_: column, period, sprint
|
||||
|
||||
**Now line**:
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
// an overdue Feature, and a Milestone with unmet required Features.
|
||||
export const SAMPLE_PLAN = `title = "Q3 — Checkout revamp"
|
||||
|
||||
# Optional plan span: pad the plan with lead-in / trailing weeks.
|
||||
# Rule: start ≤ every Feature's start, and end ≥ every Feature's last week.
|
||||
# Omit both to auto-fit the columns to the Features and Milestones.
|
||||
start = 2026-05-25
|
||||
end = 2026-08-03
|
||||
|
||||
# A Feature: start week, Original Estimate (the immovable baseline), then any
|
||||
# Re-estimates, an optional Delivery, and an optional Learning / Status.
|
||||
# Dates are TOML date literals; any day is snapped to that week's Monday.
|
||||
|
||||
@@ -26,6 +26,8 @@ export function parseMacroplan(source: string): RawPlan {
|
||||
|
||||
return {
|
||||
title: data.title != null ? String(data.title) : 'Untitled Macroplan',
|
||||
start: data.start != null ? toYmdOr('plan', 'start', data.start) : undefined,
|
||||
end: data.end != null ? toYmdOr('plan', 'end', data.end) : undefined,
|
||||
features,
|
||||
milestones,
|
||||
}
|
||||
|
||||
@@ -111,8 +111,8 @@ describe('bar extent relative to now', () => {
|
||||
describe('plan derivation', () => {
|
||||
it('derives a contiguous week range and places the now line', () => {
|
||||
const plan = buildPlan(parseMacroplan(SAMPLE_PLAN), TODAY)
|
||||
expect(plan.weeks[0]).toBe('2026-06-01') // earliest start
|
||||
expect(plan.weeks.at(-1)).toBe('2026-07-20') // latest marker (Payments delivery)
|
||||
expect(plan.weeks[0]).toBe('2026-05-25') // authored span start (lead-in before earliest Feature)
|
||||
expect(plan.weeks.at(-1)).toBe('2026-08-03') // authored span end (trailing past last marker)
|
||||
// contiguous, weekly
|
||||
expect(plan.weeks).toContain('2026-06-29')
|
||||
expect(plan.nowWeek).toBe('2026-06-15')
|
||||
@@ -128,6 +128,40 @@ describe('plan derivation', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('authored plan span (start / end)', () => {
|
||||
const body = '[[feature]]\nname="X"\nstart=2026-06-08\noriginal=2026-06-15\ndelivered=2026-06-15\n'
|
||||
|
||||
it('extends the range earlier to `start` and later to `end`', () => {
|
||||
const plan = buildPlan(parseMacroplan(`start = 2026-06-01\nend = 2026-06-29\n${body}`), TODAY)
|
||||
expect(plan.weeks).toEqual(weekRange('2026-06-01', '2026-06-29'))
|
||||
expect(plan.weeks[0]).toBe('2026-06-01') // before the earliest Feature week (06-08)
|
||||
expect(plan.weeks.at(-1)).toBe('2026-06-29') // after the last marker (06-15)
|
||||
})
|
||||
|
||||
it('snaps authored bounds to their Monday', () => {
|
||||
const plan = buildPlan(parseMacroplan(`start = 2026-06-03\nend = 2026-06-24\n${body}`), TODAY)
|
||||
expect(plan.weeks[0]).toBe('2026-06-01') // Wed 06-03 → Mon 06-01
|
||||
expect(plan.weeks.at(-1)).toBe('2026-06-22') // Wed 06-24 → Mon 06-22
|
||||
})
|
||||
|
||||
it('only extends — a marker outside the authored bounds is never clipped', () => {
|
||||
// end 06-08 is before the Feature's 06-15 delivery → the range still includes it
|
||||
const plan = buildPlan(parseMacroplan(`start = 2026-06-08\nend = 2026-06-08\n${body}`), TODAY)
|
||||
expect(plan.weeks[0]).toBe('2026-06-08')
|
||||
expect(plan.weeks.at(-1)).toBe('2026-06-15')
|
||||
})
|
||||
|
||||
it('renders an empty plan across the authored bounds when there are no Features', () => {
|
||||
const plan = buildPlan(parseMacroplan('start = 2026-06-01\nend = 2026-06-22\n'), TODAY)
|
||||
expect(plan.rows).toHaveLength(0)
|
||||
expect(plan.weeks).toEqual(weekRange('2026-06-01', '2026-06-22'))
|
||||
})
|
||||
|
||||
it('rejects a non-date span bound', () => {
|
||||
expect(() => parseMacroplan('start = 123\n')).toThrow(/start/)
|
||||
})
|
||||
})
|
||||
|
||||
describe('parse validation', () => {
|
||||
it('rejects a feature missing its Original Estimate', () => {
|
||||
expect(() => parseMacroplan('[[feature]]\nname = "A"\nstart = 2026-06-01\n')).toThrow(
|
||||
|
||||
@@ -25,7 +25,11 @@ export function buildPlan(raw: RawPlan, today: Date | string = new Date()): Plan
|
||||
})
|
||||
|
||||
// Range: earliest start to last marker/milestone (CONTEXT.md). Empty weeks drawn.
|
||||
// Optional authored `start`/`end` widen this span with lead-in / trailing weeks;
|
||||
// they only ever extend it — a Feature or marker outside them is never clipped.
|
||||
const allWeeks: WeekId[] = []
|
||||
if (raw.start != null) allWeeks.push(mondayOf(raw.start))
|
||||
if (raw.end != null) allWeeks.push(mondayOf(raw.end))
|
||||
for (const r of rows) {
|
||||
allWeeks.push(r.startWeek, r.barEndWeek)
|
||||
for (const mk of r.markers) allWeeks.push(mk.week)
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface RawMilestone {
|
||||
|
||||
export interface RawPlan {
|
||||
title: string
|
||||
start?: string // yyyy-mm-dd — optional authored left edge of the plan's span
|
||||
end?: string // yyyy-mm-dd — optional authored right edge of the plan's span
|
||||
features: RawFeature[]
|
||||
milestones: RawMilestone[]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user