Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 111 additions & 0 deletions TaskFlowDocs/2026-09-22-task-get-notes/plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
# Plan — Fix `task get` silently dropping indented Notes continuation lines

> Task version: v1
> Status: ready

No spec required — small, self-contained task.

## Reference Pointers

- `hooks/task` `entry` branch (`cmd == "entry"`) — the only production edit.
- `hooks/smoke-test` `== task get prints one entry and not the Todo file ==`
— contract the fix must keep; Notes assertions added in the same section.
- `hooks/task` `notes_end` / `next` — related write path, **not** edited here.
- `TaskFlowDocs/todo.md` `TF-20260918-9acf57` — this task's Todo item.

## Related Tasks

- `TF-20260918-9acf57` — this task's Todo item.
- `TF-20260918-454ac4` — Approval automation; depends on a trustworthy `get`
for Notes, but is not implemented here.
- `notes_end` / `task next` indent — deferred (PRD Q2).

## Skills / Tools Used

- `Unaided — no capability applied to this phase; considered: shell/awk text
processing, POSIX-portability references, and the repository's own hook
conventions, all read directly.`

## Preconditions

- [x] Applicable repository documents and personal rules inspected;
precedence/conflicts recorded. — `CONTRIBUTING.md`, `CODE_STYLE.md`,
`hooks/task`, `hooks/smoke-test`, `TaskFlowDocs/todo.md` read.
- [x] Worktree isolation: `.worktrees/fix-task-get-notes` on
`fix/task-get-notes` from `main` = `2dfffe3`.
- [x] Bug reproduced on this tree: indented Notes lines absent from `get`.

## Approval

- Status: approved
- Approved by: user
- Approved at: 2026-09-22
- Approved version: v1
- Approved scope: R1–R4; print non-blank body (PRD Q1=a); `notes_end` left
out (Q2).

## Steps

### Step 1 — `entry` prints the full non-blank body

- Goal: R1/R2 — no prefix filter beyond blankness.
- Dependencies: none.
- Files: `hooks/task`.
- Implementation checklist:
- [x] Replace `if (at(line[i], "- ")) print line[i]` with a non-blank
filter over `[a, b]`.
- [x] Leave internal `TASKFLOW_CMD=get` untouched (R2) — still 3 call sites.
- [x] `bash -n hooks/task`.
- Acceptance: A, B, C.
- Verification: reproduction fixture prints all three Notes lines; existing
get smoke section green (verbatim slice, no sibling/heading/blank,
positional ID, read-only, unknown ID, round-trip with `next`).
- Rollback: one-line revert of the filter.
- Status: done

### Step 2 — smoke assertions for Notes continuations

- Goal: R3/R4 — a dropped continuation fails the suite.
- Dependencies: Step 1.
- Files: `hooks/smoke-test`.
- Implementation checklist:
- [x] Fixture with indented `- ` line and indented no-dash line under Notes.
- [x] Assert both appear; no blank/heading; body verbatim via `diff`.
- Acceptance: D.
- Verification: one full `bash hooks/smoke-test` → `ALL SMOKE PASSED`.
- Rollback: drop the assertions; Step 1 stands alone.
- Status: done

## Checkpoints

- After Step 1: manual fixture + `bash -n` — done.
- After Step 2: one full suite; no chained mutation runs; process count 0.

## Verification / Review

| Check | Result |
|---|---|
| `bash -n hooks/task` | pass |
| `bash -n hooks/smoke-test` | pass |
| `bash hooks/smoke-test` | `ALL SMOKE PASSED` (rc=0) |
| `git diff --check` | clean |
| Reproduction fixture (A) | all three Notes lines present |
| Existing get contract (B) | suite section green |
| `TASKFLOW_CMD=get` call sites (R2) | still 3 |

## Change Log

- v1 — approved and implemented; scope from `TF-20260918-9acf57` (option (a):
print the body). `notes_end` indent left out per PRD Q2.

## Follow-ups

- `notes_end` / `task next` when Notes continuations are indented.
- Housekeeping still uncommitted on `main` (archive `337ab8`, three stale
removes) — separate commit when authorized.
- `985164` duplicate ID + merge-todo `key_of` — next candidate after this.

## Version History

- v1 — planning.
- v1 — approved (user, 2026-09-22); Steps 1–2 done; suite green.
109 changes: 109 additions & 0 deletions TaskFlowDocs/2026-09-22-task-get-notes/prd.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
# Fix `task get` silently dropping indented Notes continuation lines

> Task version: v1
> Status: ready

## Goal

`hooks/task get` prints every non-blank line of the entry body, so Notes
continuations and other non-column-0 content are no longer omitted without a
warning.

## Background / Confirmed Facts

All measured on `main` = `2dfffe3` (PR #48 merged), 2026-09-22, in worktree
`.worktrees/fix-task-get-notes`. Nothing here is inferred.

**Root cause (`hooks/task` `entry` branch, the CLI `get`):**

```awk
for (i = a; i <= b; i++) if (at(line[i], "- ")) print line[i]
```

`at(s, p)` is `index(s, p) == 1`, so only lines whose **first two characters**
are `- ` are printed. Everything else in the entry body is dropped.

**Live reproduction** (fixture with a three-line Notes block):

| In file | In `get` output |
|---|---|
| `- Notes: first line` | yes |
| `␣␣- second line indented` | **no** |
| `␣␣- third line indented` | **no** |

Real entries also carry Notes continuations that are indented prose with **no**
dash at all (e.g. `TF-20260918-454ac4` Notes bullets after the first line are
` **…**` / ` …`). Those are dropped by the same filter.

**Why it matters:** `get` exists so a caller reads one entry instead of the
whole file. A silent short read looks complete; the caller then decides on a
truncated Notes block. The todo item records the same finding: hazard is the
absence of any warning, not an error.

**What `get` must keep (existing smoke, `== task get prints one entry… ==`):**

- field lines verbatim, in file order;
- no sibling entry, no `## ` heading, no blank line, no file header;
- positional parse (`awk '{print $NF}'` on `- ID:`) still works;
- read-only.

Printing every non-blank line in the section body `[a, b]` satisfies all of
those: headings sit outside `[a, b]` (`bend` is the next `## ` minus one), and
blanks are filtered.

**Related but out of scope:** `notes_end` (used by `task next` when appending
a bullet) only continues on column-0 `- ` lines, so an indented Notes line ends
the block early and the next bullet can land mid-block. Same family, write
path; not required to fix `get`.

## Requirements

- **R1.** `entry` (CLI `get`) prints every line in the entry body that is not
blank, in file order, verbatim — fields, Notes header, Notes continuations
(indented or not), no other filtering by prefix.
- **R2.** No change to internal `TASKFLOW_CMD=get` (title + selected keys);
three internal callers keep that shape.
- **R3.** Smoke: fixture Notes block with an indented `- ` continuation **and**
an indented no-dash line; `get` output contains both; still no blank/heading;
existing get section stays green.
- **R4.** `bash hooks/smoke-test` → `ALL SMOKE PASSED`.

## Acceptance Criteria

- **A.** Reproduction fixture: `get` prints all three Notes lines.
- **B.** Existing get assertions (verbatim slice ID→Updated, no sibling, no
heading, no blank, positional ID, read-only, unknown ID fails) pass.
- **C.** `bash -n hooks/task` clean; only the `entry` branch changes.
- **D.** `bash hooks/smoke-test` → `ALL SMOKE PASSED`.

## In Scope

- `hooks/task` — `entry` branch only.
- `hooks/smoke-test` — R3 assertions beside the existing get section.
- Task documents for this item.

## Out of Scope

- `notes_end` / `task next` indent handling (related write-path note above).
- `TASKFLOW_CMD=get` internal shape.
- Title/heading alignment, archive family, Approval shape.

## Risks / Deferred Items

- Printing all non-blank body lines will also print a future non-field line a
hand edit puts inside an entry (e.g. a stray `foo`). That is still better
than dropping it: `get` is a raw body read, not a schema printer.

## Open Questions

1. Alternative (b) in the todo — stderr warn instead of print — **Recommend:
no.** A warning still leaves the caller without the data; printing the body
is the smaller correct contract.
2. Fold `notes_end` indent fix into this PR? — **Recommend: no** (separate
write path; keep the diff to `entry` + one smoke block).

## Version History

- v1 — planning.
- v1 — ready; approved by user 2026-09-22 (R1–R4; Q1=a print body, Q2 no
notes_end change).
8 changes: 4 additions & 4 deletions TaskFlowDocs/todo.md
Original file line number Diff line number Diff line change
Expand Up @@ -767,15 +767,15 @@ Every direct request or imported requireme
## Fix task get silently dropping indented Notes continuation lines: it prints only

- ID: TF-20260918-9acf57
- Status: inbox
- Status: promoted
- Priority: normal
- Owner: Codex
- Source: audit follow-up
- Added: 2026-09-18
- Updated: 2026-09-18
- Updated: 2026-09-22
- Goal: Fix task get silently dropping indented Notes continuation lines: it prints only lines starting with '- ', so a second Notes bullet written with the repo's two-space indent is absent from the output with no warning.
- Task: Not promoted.
- Next action: Promote with the task get/next family once the archive work lands; low urgency, high blast radius if trusted blindly.
- Task: `TaskFlowDocs/2026-09-22-task-get-notes/`
- Next action: Complete PRD / Spec / Plan and request approval.
- Notes: 本缺陷在归档 2026-09-18-todo-field-writes 时发现:`task get` 只打印 `^- ` 开头的行(`hooks/task` 的 `entry` 分支),而 Notes 的第二条及以后按仓库既有习惯写成**两空格缩进的 `- ` 行**,于是它们不出现在输出里,**且没有提示**。危害不是报错,是**静默**:调用方拿到一份看似完整、实则缺段的条目,据此决策。判据:本次取证用 `bash hooks/task get TF-20260918-454ac4` 只回出 Notes 的第一行,而文件里它下面还有三条缩进续行。修法二选一:(a) 把条目正文的缩进行也算正文一并打印;(b) 至少 stderr 提示该条目有 N 行未显示。v2 已合并,本缺陷未修。同族:`task next` 写 Notes 时用的是同一套字段边界(`isfield`),那边的续行判定虽已覆盖缩进,但输出侧没跟上。

## Publish TaskFlow v1.0.7: the conflict-review rule, the executable launcher, task
Expand Down
30 changes: 30 additions & 0 deletions hooks/smoke-test
Original file line number Diff line number Diff line change
Expand Up @@ -1305,6 +1305,36 @@ readback="$(bash "$HERE/task" get TF-20260101-aa --root "$nf" | sed -n 's/^- Nex
# "title line, then the fields named in TASKFLOW_KEYS".
[ "$(grep -c 'TASKFLOW_CMD=get' "$HERE/task")" -eq 3 ] \
|| { echo "FAIL the internal get callers changed"; exit 1; }

# Notes continuations are part of the entry body. They are often indented, and
# a column-0 `- ` filter dropped them with no warning — the caller saw a
# complete-looking entry that was missing its later bullets.
notes_root="$tmp/get-notes"
mkdir -p "$notes_root/TaskFlowDocs"
printf '%s\n' \
'# Todo inbox' '' '## Items' '' '## Notes probe' '' \
'- ID: TF-20260101-ee' '- Status: inbox' '- Priority: normal' '- Owner: smoke' \
'- Source: fixture' '- Added: 2026-01-01' '- Updated: 2026-01-01' \
'- Goal: notes probe' '- Task: Not promoted.' '- Next action: none' \
'- Notes: first line' \
' - second line indented' \
' no dash third line' \
> "$notes_root/TaskFlowDocs/todo.md"
bash "$HERE/task" get TF-20260101-ee --root "$notes_root" > "$tmp/get-notes-out"
grep -qxF -- ' - second line indented' "$tmp/get-notes-out" \
|| { echo "FAIL get dropped the indented Notes bullet"; cat "$tmp/get-notes-out"; exit 1; }
grep -qxF -- ' no dash third line' "$tmp/get-notes-out" \
|| { echo "FAIL get dropped the indented Notes continuation"; cat "$tmp/get-notes-out"; exit 1; }
grep -q '^[[:space:]]*$' "$tmp/get-notes-out" \
&& { echo "FAIL get printed a blank line in the Notes fixture"; exit 1; }
grep -q '^## ' "$tmp/get-notes-out" \
&& { echo "FAIL get printed a heading in the Notes fixture"; exit 1; }
# Still every non-blank body line, verbatim and in order — not "fields plus
# whatever the filter liked".
sed -n '/^- ID: TF-20260101-ee$/,/^ no dash third line$/p' \
"$notes_root/TaskFlowDocs/todo.md" > "$tmp/get-notes-expected"
diff -u "$tmp/get-notes-expected" "$tmp/get-notes-out" \
|| { echo "FAIL get Notes output is not the entry body verbatim"; exit 1; }
echo ok

echo "== task remove records the deletion that todo-check must not call a drop =="
Expand Down
8 changes: 5 additions & 3 deletions hooks/task
Original file line number Diff line number Diff line change
Expand Up @@ -412,13 +412,15 @@ END {

# Not named "get": that command already means "title line + the fields named in
# TASKFLOW_KEYS", and three internal callers depend on that shape. This branch
# prints the entry field lines verbatim instead -- the CLI wrapper below is
# what carries the name `get`.
# prints the entry body verbatim instead -- the CLI wrapper below is what
# carries the name `get`. Every non-blank line, not only column-0 `- ` fields:
# Notes continuations are often indented (with or without a dash) and a prefix
# filter dropped them with no warning, so a caller saw a truncated entry.
if (cmd == "entry") {
j = findsec(id, "")
if (j == 0) die(ENVIRON["TASKFLOW_NOTFOUND"])
a = bstart(j); b = bend(j)
for (i = a; i <= b; i++) if (at(line[i], "- ")) print line[i]
for (i = a; i <= b; i++) if (!is_blank(line[i])) print line[i]
exit 0
}

Expand Down
Loading