Skip to content

fix(update): emit valid YAML flow arrays with quoted values#62

Open
ipreuss wants to merge 3 commits intowedow:masterfrom
ipreuss:fix/array-value-quoting
Open

fix(update): emit valid YAML flow arrays with quoted values#62
ipreuss wants to merge 3 commits intowedow:masterfrom
ipreuss:fix/array-value-quoting

Conversation

@ipreuss
Copy link
Copy Markdown

@ipreuss ipreuss commented Apr 26, 2026

Problem

update currently rejects JSON arrays whose items contain commas (error array item contains comma). This was symptom-treatment added during PR #59 review findings 1-6. Legitimate user content — release notes, acceptance criteria in natural prose — commonly contains commas, and forcing users to rewrite content is friction with no structural upside.

Fix

Replace map(tostring) with map(@json) in cmd_update's jq pipeline so values roundtrip losslessly as valid YAML 1.2 flow sequences (also valid JSON arrays):

-"[" + (map(tostring) | join(", ")) + "]"
+"[" + (map(@json)   | join(", ")) + "]"

Plus echoprintf '%s\n' for byte-exact jq input.

Tags-vs-freeform asymmetry (intentional)

tags is the only freeform field that's reader-iterated (via tk ls -T, cmd_ready, cmd_blocked, cmd_show — they gsub brackets/quotes/spaces and split on comma). A comma-in-tag would phantom-match in tk ls -T "foo" against a stored item "foo, bar". So tags keeps a comma-rejection guard via validate_field_update's new tags case; the error names the readers explicitly. release_notes and other non-iterated freeform fields accept commas and roundtrip via tk query.

Backward compatibility

Existing .tickets/*.md with unquoted inline arrays keep working:

  • Tags/deps/links readers widen the gsub char class from [\[\] ] to [\[\] "] — strips quotes when present, no-op when absent.
  • ticket-query detects the quoted form via /^\[[[:space:]]*"/ and emits verbatim (already valid JSON); legacy falls through to the existing split-on-comma branch.

No migration required.

Other fixes in scope

  • update_yaml_field ENVIRON switch: awk -v interprets C-style escapes (\"", \\\), which would corrupt the JSON-escaped output from jq @json. Switched to ENVIRON["TK_FIELD_VALUE"] for byte-exact value passing. All 6 callers of update_yaml_field benefit.
  • jq required for JSON-array values: the previous jq-less fallback used a regex that accepted commas-in-items inconsistently. New behavior: fail-fast with a clear error directing users to install jq. Scalar updates are unaffected.

Tests

8 new behave scenarios (5 happy-path + 3 asymmetry):

  • Comma-in-value roundtrip (release_notes)
  • cmd_update rejects tags with comma (reader limitation, error names readers)
  • Comma-in-tag rejection error specificity
  • Comma allowed for non-tag freeform fields (release_notes)
  • ticket-query emits valid JSON for new quoted form
  • ticket-query still handles legacy unquoted form (regression guard)
  • tk ls -T filter on quoted tags
  • tk ls -T filter on legacy unquoted tags (regression guard)

Two existing scenarios deleted (general comma-rejection guard) — replaced by the tags-specific case in validate_field_update. Existing tag-array scenario updated to expect quoted storage.

Test framework also extended: should have field with value and output should contain steps now support \" escapes in expected values, mirroring the existing When I run step.

Full suite: 163/163 (was 157 baseline on feature/update-command).

Relationship to #59

This PR is stacked on feature/update-command. When #59 merges, this one rebases onto master cleanly. Maintainer may alternatively choose to squash this into #59 directly.

Plan B

If the @json approach is rejected, a downstream consumer (M3RPG, where the original release-note bug was reported) has a content-rewrite workaround already in hand as a data fix.

ipreuss and others added 3 commits April 14, 2026 11:37
Adds `tk update <id> --field=value` for modifying YAML frontmatter
fields without opening an editor. Useful for AI agents and scripts.

Features:
- Update single or multiple fields in one command
- JSON array syntax with jq: --tags='["a", "b"]' → tags: [a, b]
- Graceful fallback when jq unavailable
- Proper error handling for invalid arguments/JSON

Also fixes update_yaml_field to work on macOS/BSD by using awk
instead of GNU-specific sed syntax for inserting new fields.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Exit 0 on success (updated=$((updated+1)) instead of ((updated++)))
- awk-based value replacement in both branches of update_yaml_field
  (eliminates sed metacharacter hazards: & / \)
- Hybrid field policy via validate_field_update helper:
  - immutable: id, created
  - routed: status, deps, links
  - body-backed (rejected): title, description, design, acceptance
  - validated: priority (0-4), parent (must exist), type (known values)
  - freeform: everything else with [A-Za-z][A-Za-z0-9_-]* name
- Reject array items with embedded commas (jq + no-jq paths)
- Add 24 regression scenarios

Ticket: M3R-7iue
Replace the comma-rejection guard (added during PR wedow#59 review) with
structural quoting via jq @JSON. Values with commas, quotes, or
backslashes now roundtrip losslessly as YAML 1.2 flow sequences
(also valid JSON arrays) for non-iterated freeform fields.

The 'tags' field stays comma-rejecting because tk ls -T, cmd_ready,
cmd_blocked, and cmd_show iterate tag values via comma-split. The
new error message names the readers explicitly. release_notes and
other freeform array fields accept commas via storage roundtrip
honored by tk query.

Backward compatibility for existing .tickets/*.md:
- Tags/deps/links readers strip quotes via widened gsub char class
  ([\[\] ] -> [\[\] "]); legacy unquoted and new quoted forms both
  parse to bare CSV for existing split() consumers.
- ticket-query detects quoted form via /^\\[[[:space:]]*\"/ and emits
  verbatim (already valid JSON); legacy falls through to existing
  split-on-comma branch.

cmd_update now requires jq for JSON-array values. Previously, jq
absence triggered a regex-based fallback that accepted commas
inconsistently. Fail-fast aligns with the rest of the codebase.

update_yaml_field switched from awk -v to ENVIRON for the value,
preserving JSON escape sequences (\", \\, \\n) byte-exact. Without
this, awk -v's C-style escape processing would corrupt stored YAML
when arrays contain quoted strings.

5 new behave scenarios:
- Comma-in-value roundtrip (release_notes)
- ticket-query on new quoted form
- ticket-query on legacy unquoted form (regression guard)
- ls -T on new quoted tags
- ls -T on legacy unquoted tags (regression guard)

Plus 3 scenarios codifying the tags-vs-freeform asymmetry:
- tags array items rejected with comma (reader limitation)
- comma-in-tag error names the reason
- comma-in-value allowed for non-tag freeform fields

Test framework: should-have-field-with-value and output-should-contain
steps now support \" escapes in expected values, mirroring the
existing 'I run' step.

Two existing scenarios deleted (Reject array items with embedded
commas, Comma-in-item error is specific) — their contract is
replaced by the tags-specific case in validate_field_update.

Full suite: 163/163 (was 157 baseline).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant