feat: enforce status:in_progress when task has calendar date#19
Conversation
There was a problem hiding this comment.
Based on my review of the changed files, here is my assessment:
Summary: The PR implements the calendar-as-commitment rule (spec 017) correctly across three enforcement points: lint detection/fix, defer-time auto-promote, and task-creator agent. The code is well-structured, follows existing patterns, and the test coverage is comprehensive.
Must Fix (Critical): None
Should Fix (Important): None — implementation is sound
Nice to Have (Optional): One minor edge case in lint.go
Detailed Findings
1. pkg/ops/lint.go:569-581 — detectStatusDateMismatch regex
Finding: The date value regex [^\s'"]+ doesn't match quoted values containing spaces, e.g., defer_date: "2026-12-01 12:00" (quoted string with a space).
dateRegex := regexp.MustCompile(
`(?m)^(planned_date|defer_date|due_date):\s*['"]?([^\s'"]+)?['"]?\s*$`,
)Impact: Low — ISO dates don't contain spaces; quoted datetime strings with spaces are uncommon in this vault's date format conventions.
Verdict: Accept as-is. The pattern matches the project's established date format (ISO 8601 without spaces in quoted values).
2. pkg/ops/lint.go:775 — ReplaceAllString usage in fixStatusDateMismatch
Finding: Uses ReplaceAllString rather than replacing only the first occurrence. If the frontmatter contained duplicate status lines (which would also be a DUPLICATE_KEY lint issue), all would be replaced.
newContent := statusRegex.ReplaceAllString(content, "status: in_progress")Impact: Minimal — YAML frontmatter with duplicate keys is already flagged as DUPLICATE_KEY; the replace-all behavior is correct for the single-status case.
Verdict: Accept as-is.
3. pkg/ops/defer.go:147-152 — Auto-promote atomicity
Finding: The concern about atomicity is addressed correctly. The status promotion and defer-date write happen in-memory, then a single WriteTask call persists both atomically:
if status := task.Status(); status == domain.TaskStatusNext ||
status == domain.TaskStatusBacklog {
if err := task.SetStatus(domain.TaskStatusInProgress); err != nil {
return nil, errors.Wrap(ctx, err, "set status to in_progress")
}
}
// ... later ...
if err := d.taskStorage.WriteTask(ctx, task); err != nil {
return nil, errors.Wrap(ctx, err, "write task")
}If SetStatus fails, WriteTask is never called — no partial write occurs.
Verdict: Correctly implemented.
4. pkg/ops/lint_test.go — Test isolation
Finding: The new 330-line STATUS_DATE_MISMATCH suite properly isolates each test case. AfterEach cleans up vaultPath, and each subtest writes to a unique filename via writeSDM. No cross-test contamination.
5. pkg/ops/defer_test.go — Renamed test behavior
Finding: The test at line 70 ("promotes status from next (todo alias) to in_progress") correctly reflects the new behavior: status: todo (alias for next) is auto-promoted to in_progress on defer. The behavior change is intentional per spec 017.
6. agents/task-creator.md:114 — Conditional status emit
Finding: Step 8 correctly implements the spec rule: status: in_progress is emitted when any date field is being written, status: next otherwise. The conditional is clear and well-documented.
{
"verdict": "approve",
"summary": "The PR implements spec 017 (calendar-as-commitment rule) correctly across lint detection/fix, defer-time auto-promote, and task-creator agent. Code follows existing patterns in the codebase. The atomic write concern in defer.go is resolved — status change and file write happen in one WriteTask call. Test coverage is comprehensive for all three enforcement points.",
"comments": [],
"concerns_addressed": [
"correctness: auto-promote block in findAndDeferTask — SetStatus error handling wraps correctly, WriteTask is called once atomically after all in-memory changes",
"correctness: detectStatusDateMismatch regex — handles the project's date format conventions (unquoted ISO dates); edge case with quoted values containing spaces is low risk",
"correctness: fixStatusDateMismatch ReplaceAllString — only matches status:next or status:backlog lines; duplicate status would already be DUPLICATE_KEY",
"tests: STATUS_DATE_MISMATCH test suite — proper isolation via unique filenames per subtest, AfterEach cleanup",
"tests: defer_test.go renamed test behavior — status:todo alias correctly auto-promotes to in_progress"
]
}
Summary
Enforce the calendar-as-commitment invariant: any task with a calendar date (
planned_date,defer_date, ordue_date) must havestatus: in_progress(or terminal). Closes silent-miss failure mode wherestatus: next+ future date hides tasks from Kanban (filter isstatus=in_progress&status=completed).Spec:
017-enforce-status-in-progress-on-calendar-dateThree enforcement points
pkg/ops/lint.goaddsIssueTypeStatusDateMismatch;vault-cli task lint --fixpromotesnext/backlog→in_progress; same detector servestask validatepkg/ops/defer.go::findAndDeferTaskwritesstatus: in_progressin the same atomic file write asdefer_datefornext/backlogtasks; idempotent onin_progress; terminal statuses (completed/aborted/hold) untouchedagents/task-creator.mdStep 8 emitsstatus: in_progresswhen any date field is being written,status: nextotherwise;docs/task-writing.mdLifecycle gains a "Calendar-as-commitment rule" subsection;CHANGELOG.md## UnreleasedbulletOut of scope (intentionally deferred)
task-auditoragent — semantic/judgment checks only; deterministic field-combo belongs in Go linttask setCLI warn-and-offer-promote flagtask-creator— separate follow-upTest plan
make precommitgreenvault-cli task linton astatus: next+defer_datefixture reportsSTATUS_DATE_MISMATCHvault-cli task lint --fixpromotes status, leaves date byte-identicalvault-cli task validate <fixture>surfaces the same issue (single source of truth)vault-cli task deferonstatus: nexttask auto-promotes toin_progressvault-cli task deferonstatus: in_progresstask is idempotent (no re-write)task-creatoragent emitsstatus: in_progresswhen given date field