RESEARCH — Task-list field discipline + content audit (ID-34.1)
RESEARCH — Task-list field discipline + content audit (ID-34.1)
Section titled “RESEARCH — Task-list field discipline + content audit (ID-34.1)”Task: ID-34 — Task-list description discipline + content audit. This Subtask: {34.1} RESEARCH — per-field length audit + canonical-ref-vs-traceability boundary + Zod-encodable-vs-doc-only split. Downstream consumer: ID-35 ledger-cli (the CLI must honour whatever discipline this chain ratifies; per the S62E crossover audit §5 the contract is “ID-34 produces inputs the CLI consumes”, not merely “ID-34 must precede the CLI”).
§1 Problem statement
Section titled “§1 Problem statement”S62 W3 Liam concern: task-list Task.description fields are trending too long
(ID-30 + ID-31 descriptions ~3000 chars each at the time). The original ID-34 framing
audited 22 Tasks. The ledger has since grown to 40 Tasks / 292 Subtasks and the
drift is now materially worse — Task.description max is 9590 chars (ID-28).
The root cause is not a single field but an absence of per-field write discipline: the
ledger has nine human-writable text fields (description, status_note, priority_note,
details, testStrategy, cross_doc_links[].raw, plus the document-level
document_purpose) and no codified rule for what shape of content belongs in each. Authors
default to the path of least resistance — dumping session narrative, design rationale, and
canonical-reference prose into whichever field is in front of them, most often
description.
The guiding principle (ID-34 description): documentation is the canonical reference
(docs/specs/, docs/research/, docs/reference/); the task-list carries
implementation/test steps + traceability context only. This RESEARCH establishes the
measured baseline, the per-field boundary, and — critically per the crossover audit — the
binary split between discipline points that can be Zod-encoded (becoming free runtime
guards for the ID-35 CLI) versus those that remain human conventions enforced by
Planner/Checker review.
§2 Per-field length audit (measured baseline)
Section titled “§2 Per-field length audit (measured baseline)”Measured over docs/reference/task-list.json at HEAD 73ffb2b4 (40 Tasks, 292 Subtasks).
Char counts; percentiles are nearest-rank.
2.1 Task-level fields
Section titled “2.1 Task-level fields”| Field | n | min | p50 | p90 | max | mean | Over-budget count |
|---|---|---|---|---|---|---|---|
description | 40 | 258 | 1434 | 3348 | 9590 | 1848 | 19/40 >1500; 10/40 >2500 |
status_note | 40 | 0 | 210 | 474 | 836 | 220 | 10/40 >300 |
priority_note | 40 | 0 | 74 | 217 | 302 | 80 | 1/40 >300 |
Worst offenders (description):
| Task | chars | title (truncated) |
|---|---|---|
| ID-28 | 9590 | Cocoindex flow scaffolding + Cloud Run sidecar dep |
| ID-29 | 8203 | TanStack workspace-types migration (Option C hybrid) |
| ID-33 | 3777 | Orchestrator-of-orchestrators polish |
| ID-31 | 3348 | Canonical-pipeline → task-list migration approach |
| ID-42 | 3211 | Deploy pullmd Cloud Run Service |
| ID-27 | 2919 | session-driver-cmux maintenance |
| ID-23 | 2796 | Code-intelligence integration into SDLC workflow |
| ID-30 | 2769 | Roadmap-backlog consolidation |
2.2 Subtask-level fields
Section titled “2.2 Subtask-level fields”| Field | n | min | p50 | p90 | max | mean | Over-budget count |
|---|---|---|---|---|---|---|---|
details | 292 | 0 | 2063 | 7996 | 26971 | 3473 | journal home — long is legitimate |
description | 292 | 36 | 220 | 464 | 1957 | 282 | 119/292 >250 |
testStrategy | 292 | 0 | 211 | 421 | 951 | 236 | 78/292 >300 |
2.3 Reading the data
Section titled “2.3 Reading the data”Task.descriptionis the primary drift surface. Half of all descriptions exceed ~1.4k chars; the top decile exceeds 3.3k. The two extreme cases (ID-28, ID-29) carry full design rationale + migration narrative that belongs indocs/specs/.status_notedrift is real but milder. A quarter exceed the 300-char convention documented in the workflow-orchestration §Ledger field-discipline table. The over-budget notes carry session-by-session carryover that belongs in the Subtaskdetailsjournal.Subtask.descriptiondrift. 41% exceed 250 chars; the schema comment says “one-sentence summary (~80–200 chars)”. Authors are duplicatingdetailscontent up intodescription.Subtask.testStrategydrift. 27% exceed 300 chars; many encode a multi-criterion test plan where a one-line acceptance criterion is the contract (the Checker verifies againsttestStrategy; a paragraph is harder to gate against than a sentence).Subtask.detailsis correctly the largest field (p50 2k, max 27k). This is the load-bearing dispatch brief + append-only journal (<info added on …>blocks). Length here is expected and should NOT be capped — capping the journal would defeat its purpose. The discipline fordetailsis structural (journal-block format), not length.
§3 Canonical-reference vs traceability boundary
Section titled “§3 Canonical-reference vs traceability boundary”The boundary question: for any piece of text an author wants to record, does it belong in documentation (canonical reference) or in the task-list (traceability)?
Decision rule (the “would-it-survive-the-task” test):
Content that remains true and useful after the Task closes — design rationale, architecture decisions, the “why” behind an approach, API contracts, schema definitions — is canonical reference. It belongs in
docs/specs/<feature>/{PRODUCT,TECH}.md,docs/research/, ordocs/reference/. The task-list field carries a pointer (cross_doc_links[]) to it, not a copy.Content that is specific to executing or tracking this Task — what to build next, which files a Subtask owns, the one-line acceptance the Checker gates against, the session-by-session record of what happened — is traceability. It belongs in the task-list, in the field whose shape matches.
Worked examples from the current ledger:
| Current content | Where it lives now | Where it belongs |
|---|---|---|
| ID-28 9590-char flow-scaffolding design narrative | Task.description | docs/specs/cocoindex-*/ — description keeps a 1-paragraph what+why + cross_doc_links pointer |
| ID-29 8203-char migration-option rationale (Option A/B/C trade-offs) | Task.description | docs/specs/.../TECH.md REJECTED-alternatives §; description keeps the chosen-approach summary |
| ID-35 “REJECTED alternatives: (A)… (B)…” | Task.description (1100+ chars of it) | docs/specs/id-35-ledger-cli/TECH.md; description keeps the one-line verdict |
| ”S266: PRIORITISED for next-session parallel-cmux phase…” | ID-35 status_note | correct — acute status context, ≤300 |
| Session-by-session “I did X, learned Y, Checker said Z” | sometimes status_note, sometimes description | Subtask details <info added on …> journal block |
The pattern: Task.description should be a compact what+why (one paragraph), with the
heavy material extracted to docs/ and pointed at via cross_doc_links. This is exactly
the relationship ID-35’s own record already models well (docs/research/id-35-crossover-audit.md
is referenced, not inlined) and ID-28/ID-29 model poorly (rationale inlined).
§4 Zod-encodable vs doc-only split (the load-bearing analysis)
Section titled “§4 Zod-encodable vs doc-only split (the load-bearing analysis)”Per crossover audit §5 (RECOMMEND-CHANGE on ID-34 TECH scope): the discipline points must be split into those that can become a Zod schema delta (free runtime guard for the ID-35 CLI, since the CLI re-parses via the vendored schema on every write) versus those that remain human conventions (Planner/Checker review + skill-body amendments).
4.1 The hard-cap trap (why naive .max() is wrong)
Section titled “4.1 The hard-cap trap (why naive .max() is wrong)”The KH task-list Zod schema (lib/validation/task-list-schema.ts) currently has no
length caps on any text field — description: z.string().min(1), status_note: z.string().nullable(), etc. Adding a hard z.string().max(N) is dangerous:
-
It would reject the current ledger. 19 Tasks have descriptions >1500 chars; if we set
description: z.string().max(1500),TaskListSchema.parse()throws on the livetask-list.jsonimmediately — breakingparseTaskListWithWarnings, the task-view viewer, every consumer, and the ID-35 CLI itself — until the retroactive sweep completes. The sweep is explicitly the LOWEST-priority, integration-sensitive piece (4 terminals share the file). A hard cap inverts the dependency: it would force the sweep to land first and atomically, which the parallel-cmux phase cannot accommodate. -
It creates vendoring drift.
lib/validation/task-list-schema.tsis a vendored copy of task-view’spackages/schemas/src/task-list-schema.ts, guarded by the non-blockingtask-view-vendor-drift.ymlworkflow. A KH-side.max()that is not also in task-view’s source diverges the two; a task-view re-vendor would silently revert it. Any Zod delta must be applied to both repos to stay coherent — a real maintenance cost.
4.2 The established KH precedent: soft warnings, not hard rejection
Section titled “4.2 The established KH precedent: soft warnings, not hard rejection”KH already solved “advisory limit that must not break the ledger” — the 25-Subtask soft
ceiling (PRODUCT inv 20). It is implemented in parseTaskListWithWarnings() as a
warning, not a schema .max():
// lib/validation/task-list-schema.ts:211-230 (installed, verified)export function parseTaskListWithWarnings(input: unknown): { value: TaskList; warnings: TaskListWarning[];} { const value = TaskListSchema.parse(input); // hard schema still passes const warnings: TaskListWarning[] = []; for (const task of value.tasks) { if (task.subtasks.length > 25) { warnings.push({ taskId: task.id, message: `… consider splitting …` }); } } return { value, warnings };}This is the correct home for field-length discipline: soft warnings that surface drift
without rejecting the document. The ID-35 CLI already plans to parse via this helper, so
length warnings flow to the CLI operator “for free” — exactly the audit §5 framing
(“ID-34 produces inputs the CLI consumes via Zod”). It avoids the hard-cap trap and is
naturally implemented once in parseTaskListWithWarnings (KH-side; task-view’s viewer can
adopt the same helper independently without a schema-shape divergence, since adding a
function — not a field constraint — does not trip the vendor-drift field diff).
4.3 The split
Section titled “4.3 The split”| Discipline point | Mechanism | Rationale |
|---|---|---|
Task.description ≤ ~1500 chars (warn) | Soft warning in parseTaskListWithWarnings | Hard cap rejects current ledger; warning surfaces drift to author + CLI |
Task.status_note ≤ 300 chars | Soft warning (could later harden once swept) | Already a documented convention; warn to enforce |
Subtask.description ≤ 250 chars (warn) | Soft warning | Schema comment already says ~80–200; warn on drift |
Subtask.testStrategy one-line (≤ ~300, no newline-heavy) | Soft warning | Checker contract is a sentence; warn on multi-criterion blobs |
Subtask.details length | No limit | Journal + dispatch brief; length is legitimate |
”description carries what+why, not session narrative” | Doc-only (skill amendment) | Not mechanically detectable |
”canonical rationale lives in docs/, pointed at via cross_doc_links” | Doc-only (skill amendment) | Semantic judgement, not Zod-expressible |
”journal goes in details <info added on …> blocks” | Doc-only (already in workflow-orchestration table) | Structural convention, Planner/Checker enforced |
cross_doc_links[] shape (path + anchor + raw) | Already Zod-enforced (DocLinkSchema) | No change needed |
Net: four soft-warning additions to parseTaskListWithWarnings (Zod-encodable side) +
a documented per-field discipline table + four skill-body amendments (doc-only side). No
hard .max() caps. No vendor-schema field divergence.
§5 Current state — what already exists (do not reinvent)
Section titled “§5 Current state — what already exists (do not reinvent)”workflow-orchestration/SKILL.mdalready carries a §Ledger field-discipline table (verified in the installed skill body) mappinglast_updated/ Subtaskdetails/Task.description/Task.status_note/testStrategy/cross_doc_links/ commit messages / continuation prompts / Mempalace diary to shape + load-bearing-for. ID-34 does not invent this from scratch — it promotes the table to a canonical reference doc, adds the measured budgets, and back-references the doc from the four spec/planning skills.docs/reference/taskmaster-schema-reference.mdis the empirical TM schema reference (field-by-field shapes, conventions). It documents what the fields are, not what discipline to write into them — the discipline doc is a distinct concern (the doc-location decision is {34.3} TECH’s call: newtask-list-discipline.mdvs an appended §to the schema-reference).parseTaskListWithWarningsis the soft-warning seam (verified installed).DocLinkSchema(lib/validation/roadmap-schema.ts) already constrainscross_doc_links[]entries.
§6 Open Questions (provisional defaults applied; parent may override)
Section titled “§6 Open Questions (provisional defaults applied; parent may override)”-
OQ-34-1 — Discipline doc location. New
docs/reference/task-list-discipline.mdvs an appended §todocs/reference/taskmaster-schema-reference.md? Provisional default (→ {34.3} TECH to ratify): new dedicated docdocs/reference/task-list-discipline.md. The schema-reference is a stable empirical what-the-fields-are artefact (snapshot-dated, “re-run if upstream changes”); discipline is an evolving how-to-write policy with a different update cadence and audience (authors/Planners, not parser implementers). Mixing them couples two doc lifecycles. -
OQ-34-2 — Soft-warning caps: warn-only now, or warn-now-harden-later? Defaulting to warn-only, indefinitely — hardening to
.max()requires the sweep to have driven the ledger fully under budget AND a coordinated task-view re-vendor, neither of which ID-34 should block on. Revisit as a future backlog item if drift recurs post-sweep. -
OQ-34-3 — Retroactive sweep scope. The sweep is a broad
task-list.jsonedit; 4 parallel terminals share the file (integration-sensitive). Defaulting to: spec the sweep policy + thresholds in PRODUCT, decompose it as the LOWEST-priority {34.5+} Subtask, and write anOQ-pending.mdto the parent BEFORE attempting any broad description rewrite so the parent sequences it against the other terminals’ status flips. Per the cmux brief this is explicitly deferred to last. -
OQ-34-4 — Where does the soft-warning code land vs task-view? Defaulting to: extend
parseTaskListWithWarningsin KHlib/validation/task-list-schema.tsonly (a new function-internal warning loop, not a schema field change → does not trip vendor-drift). task-view can adopt the same warnings independently. Avoids forcing a coupled two-repo schema edit inside the parallel-cmux phase.
§7 Acceptance for this Subtask ({34.1})
Section titled “§7 Acceptance for this Subtask ({34.1})”- Per-field length distribution measured over all 40 Tasks / 292 Subtasks (§2).
- Canonical-ref vs traceability boundary articulated with worked examples from the live ledger (§3).
- Zod-encodable-vs-doc-only split produced (§4) — the load-bearing crossover-audit §5
deliverable; grounded in the installed
parseTaskListWithWarningssoft-warning precedent and the vendor-drift coupling. - Existing artefacts catalogued so {34.2}+ do not reinvent (§5).
- ≥4 OQs surfaced with provisional defaults (§6).