Skip to content

feat(cfl)!: standardize page body formats with --body-format - #459

Merged
rianjs merged 15 commits into
mainfrom
455-cfl-body-format
Jul 17, 2026
Merged

feat(cfl)!: standardize page body formats with --body-format#459
rianjs merged 15 commits into
mainfrom
455-cfl-body-format

Conversation

@rianjs

@rianjs rianjs commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Implements items 1, 3, 4, and 7 of #455. Stacked on #457; will retarget to main when it merges.

  • One selector for page view/create/edit: --body-format markdown|adf|xhtml (omission = Markdown). Deletes --raw, --no-markdown, --storage.
  • ADF/XHTML modes request and emit exact representations with no conversion; --legacy is valid only with Markdown input and is rejected otherwise.
  • page edit --editor is representation-safe: .md/.json/.xhtml buffers with correct prefill; XHTML never transits the Markdown→ADF converter; metadata-only edits preserve body bytes.
  • Markdown views now fail closed on conversion errors: empty stdout, actionable error suggesting --body-format adf|xhtml.
  • Help, READMEs, output contracts, Confluence skills, and roundtrip script updated in the same change.

@rianjs-bot rianjs-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: efa4cbbbe4b6
Profile: claude-rianjs-bot - Posting as: rianjs-bot[bot]

Summary

Reviewer Findings
go:implementation-tests 2
policies:conventions 1
structure:repo-health 2
documentation:docs 4
automation:ci-release 0
go:implementation-tests (2 findings)

Minor - tools/cfl/internal/cmd/page/edit.go:132

hasStdinData bypasses the stdinIsTTY seam, creating an untestable branch

runEdit computes whether stdin has data using os.Stdin.Stat() directly:

hasStdinData := opts.Stdin != nil && opts.Stdin != os.Stdin
if !hasStdinData {
    stat, _ := os.Stdin.Stat()
    hasStdinData = (stat.Mode() & os.ModeCharDevice) == 0
}

But the errMissingContentSource guard just above uses hasContentSource (which delegates to hasPipedOSStdin/stdinIsTTY), and getEditContent also uses hasPipedOSStdin. This hasNewContent computation is the only stdin-detection site that bypasses the overrideable stdinIsTTY seam.

Impact: the hasNewContent == false path (preserve old body, no new stdin data) cannot be driven by overriding stdinIsTTY in a unit test — only by injecting a non-nil opts.Stdin. If someone overrides stdinIsTTY to simulate piped stdin, the early hasContentSource check sees it but hasNewContent does not, producing inconsistent behaviour in test doubles and masking bugs where the body should be updated from stdin but silently isn't.

Fix: replace the inline stat call with hasPipedOSStdin(opts.Options) (same function used in getEditContent and hasContentSource) so all three stdin-detection sites share one testable seam:

hasStdinData := (opts.Stdin != nil && opts.Stdin != os.Stdin) || hasPipedOSStdin(opts.Options)
hasNewContent := opts.file != "" || opts.editor || hasStdinData

Nits - tools/cfl/internal/cmd/page/edit_test.go:123

TestRunEdit_TitleOnly has a stale comment that inverts the actual behavior

The comment at line 118 says:

Without file input and with a title, the current implementation will still try to open an editor.

This is incorrect. When opts.title != "", the errMissingContentSource guard is skipped and hasNewContent evaluates to false (no file, no editor, no stdin), so runEdit simply preserves the existing page body and updates only the title — no editor is opened. The test then unnecessarily provides a file, making it exercise the title-plus-content path instead of the title-only path.

The stale comment could mislead a maintainer into thinking title-only edits require a content workaround. The correct title-only path is already exercised by TestRunEdit_MoveWithTitleOnly_NoEditorOpened.

Fix: remove the workaround and test the actual title-only contract:

func TestRunEdit_TitleOnly(t *testing.T) {
    t.Parallel()
    var receivedBody map[string]any
    // ... server setup ...

    rootOpts := newEditTestRootOptions()
    rootOpts.Stdin = nil
    opts := &editOptions{
        Options: rootOpts,
        pageID:  "12345",
        title:   "New Title",
        // No file, no editor — body must be preserved from existing page
    }

    err := runEdit(context.Background(), opts)
    testutil.RequireNoError(t, err)
    testutil.Equal(t, "New Title", receivedBody["title"])
    // body in PUT should equal existing page body verbatim
}
policies:conventions (1 finding)

Nits - tools/cfl/internal/cmd/page/view.go:21

openBrowser immediately follows the closing brace of runView with no blank line (line 171 }, line 172 func openBrowser). Go gofmt requires a blank line between top-level function declarations; golangci-lint will flag this. Fix: insert a blank line between the two declarations.

structure:repo-health (2 findings)

Minor - tools/cfl/internal/pageview/projection.go:86

Project switches on raw string literals ("adf", "xhtml", "", "markdown") for body format routing, while the canonical values are defined as private constants (bodyFormatMarkdown, bodyFormatADF, bodyFormatXHTML) inside the page package. The pageview package cannot import page (that would be a cycle), so there is no compile-time contract tying these two routing sites together. Adding a fourth format to resolveBodyFormat in body_format.go requires manually updating the switch in projection.go; missing it produces a silent no-content result rather than an error. The fix is to export the format constants from pageview (which sits below page in the dependency graph) and have page/body_format.go reference them, giving both routing sites a shared, compiler-checked source of truth.

Minor - tools/cfl/internal/cmd/page/fetch.go:2

hasStorageContent (lines 112–116) and hasADFContent (lines 119–123) in fetch.go are byte-for-byte duplicates of the same helpers in tools/cfl/internal/pageview/projection.go (lines 156–166). Both test the same API struct fields (page.Body.Storage.Value and page.Body.AtlasDocFormat.Value). When the api.Page body shape changes — or when a third representation is introduced — there are now two unconnected sites to update with no compile-time signal. The page package already imports pageview, so the simplest fix is to export HasStorageContent / HasADFContent from pageview and delete the private copies in fetch.go.

documentation:docs (4 findings)

Major - skills/Confluence/CliReference.md:27

Three places in this file teach -o json as valid:

  1. Line 27 (Global Flags table): -o, --output FORMAT … table (default), json, plainjson should be removed; valid formats are table and plain only.
  2. Line 50 (Pages table): cfl page view PAGE_ID -o json | Full JSON output (body always included in full) — this command errors at the output-format guard; the row should be removed.
  3. Line 203 (Output section): "Use -o json for machine-readable output" — this is incorrect; -o json was removed in #392.

The OUTPUT_SPEC.md (in this PR) is authoritative: resource -o json is invalid and must fail. Fix all three locations to reflect only table and plain as valid -o values.

Major - skills/Confluence/SKILL.md:51

The "Output Representation and Format" section lists json (-o json) as a valid output format and gives --full -o json as a combination example (line 53). Both are wrong: resource -o json was removed in #392. OUTPUT_SPEC.md (also in this PR) states -o/--output accepts table and plain only, and that -o json must fail with invalid output format. tools/cfl/README.md likewise documents this as a breaking change.

This is the primary skill entry point for agents — teaching it to emit -o json will cause every workflow that follows the skill to fail at the output-format guard.

Fix: Change the output format list to table (default) and plain only. Remove the --full -o json example. If scripted extraction is the motivation, note that page copy and other mutation commands emit ID: <id> on a stable text line that can be parsed with grep/awk.

Major - skills/Confluence/Workflows/ManagePage.md:22

Line 121 contains a runnable example that will actively fail:

NEW_ID=$(cfl page copy SOURCE_ID --title "Copy of Page" -o json | ...)   # capture the new page ID from JSON

-o json is invalid; cfl will exit with invalid output format: "json" (valid formats: table, plain) before any copy occurs.

The page copy success output per OUTPUT_SPEC.md is:

Copied page: <title>
ID: <id>
...

Fix the example to parse the stable text output, e.g.:

NEW_ID=$(cfl page copy SOURCE_ID --title "Copy of Page" | awk '/^ID:/ {print $2}')
cfl page edit "$NEW_ID" --parent DESIRED_PARENT_ID

Major - skills/Confluence/Workflows/ViewPage.md:26

Three problems in ViewPage.md related to the removed -o json surface:

  1. Line 26 (View Mode table): cfl page view PAGE_ID -o json | Full JSON output (body always included in full — no truncation) — this command fails at the output-format guard and should be removed from the table.
  2. Line 35: "For scripted extraction, add -o json" — incorrect; -o json errors. Remove this suggestion. Scripted extraction from text output (e.g., grep '^ID:') should be substituted or the note removed entirely.
  3. Lines 68–83 ("JSON Output Structure" section): describes a {id, title, spaceId, spaceKey, parentId, content} JSON envelope that cannot be produced because -o json is invalid. This entire section should be removed — there is no JSON output structure to document.

Leaving this section intact will cause agents to attempt -o json and expect a JSON envelope that does not exist.

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/body_format_test.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/create_test.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/edit_test.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/fetch_test.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/cmd/page/view_test.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/pageview/projection_test.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/detail_test.go, tools/cfl/internal/present/emit.go unavailable unavailable
policies:conventions complete_broad docs/ARTIFACT_CONTRACT.md, tools/cfl/README.md, tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go unavailable Shared cli-common docs (https://github.com/open-cli-collective/cli-common/tree/main/docs) were not available locally; shared-standard checks are limited to what is visible in repo-local docs and STANDARDS.md.
structure:repo-health complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/emit.go unavailable unavailable
documentation:docs complete_broad README.md, docs/ARTIFACT_CONTRACT.md, skills/Confluence/CliReference.md, skills/Confluence/SKILL.md, skills/Confluence/Workflows/ManagePage.md, skills/Confluence/Workflows/ViewPage.md, tools/cfl/CHANGELOG.md, tools/cfl/README.md, tools/cfl/chaos-testing.md, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/present/README.md unavailable unavailable
automation:ci-release complete_broad tools/cfl/scripts/roundtrip-test.sh unavailable Review limited to the single assigned file: tools/cfl/scripts/roundtrip-test.sh. This is a developer-run manual script, not a CI workflow, so CI-specific invariants around required check names and workflow triggers are not applicable.

0 PR discussion threads considered. 0 summarized; 0 resolved.


Completed in 1m 42s | ~$3.05 (est.) | claude-sonnet-4-6 | cr 0.10.259
Field Value
Model claude-sonnet-4-6
Reviewers go:implementation-tests, policies:conventions, structure:repo-health, documentation:docs, automation:ci-release
Engine claude_cli · claude-sonnet-4-6
Reviewed by cr · rianjs-bot[bot]
Duration 1m 42s wall · 15m 24s compute
Cost ~$3.05 (est.)
Tokens 66 in / 43.1k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection claude-sonnet-4-6 7 2.5k 49.1k 21.1k ~$0.13 (est.) 52s
go:implementation-tests claude-sonnet-4-6 20 20.1k 1.2M 103.7k ~$1.06 (est.) 6m 36s
policies:conventions claude-sonnet-4-6 5 1.9k 102.3k 117.6k ~$0.50 (est.) 48s
structure:repo-health claude-sonnet-4-6 7 6.3k 144.6k 45.1k ~$0.31 (est.) 1m 56s
documentation:docs claude-sonnet-4-6 17 9.1k 746.6k 80.4k ~$0.66 (est.) 3m 36s
automation:ci-release claude-sonnet-4-6 6 2.1k 104.9k 41.0k ~$0.22 (est.) 56s
orchestrator-rollup claude-sonnet-4-6 4 1.1k 18.4k 39.5k ~$0.17 (est.) 36s

Comment thread skills/Confluence/Workflows/ViewPage.md Outdated
| "ADF", "Atlassian document format" | `cfl page view PAGE_ID --body-format adf` | Exact ADF JSON (subject to truncation) |
| "show macros" | `cfl page view PAGE_ID --show-macros` | Preserve macro placeholders like `[TOC]` (subject to truncation) |
| "open in browser", "open page" | `cfl page view PAGE_ID --web` | Opens in default browser |
| "page as JSON" | `cfl page view PAGE_ID -o json` | Full JSON output (body always included in full — no truncation) |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three problems in ViewPage.md related to the removed -o json surface:

  1. Line 26 (View Mode table): cfl page view PAGE_ID -o json | Full JSON output (body always included in full — no truncation) — this command fails at the output-format guard and should be removed from the table.
  2. Line 35: "For scripted extraction, add -o json" — incorrect; -o json errors. Remove this suggestion. Scripted extraction from text output (e.g., grep '^ID:') should be substituted or the note removed entirely.
  3. Lines 68–83 ("JSON Output Structure" section): describes a {id, title, spaceId, spaceKey, parentId, content} JSON envelope that cannot be produced because -o json is invalid. This entire section should be removed — there is no JSON output structure to document.

Leaving this section intact will cause agents to attempt -o json and expect a JSON envelope that does not exist.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Removed the -o json row, the scripted-extraction note, and the entire JSON Output Structure section from ViewPage.md.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses all three documented -o json references.

Summary:
Removed invalid -o json guidance and the unsupported JSON output documentation from ViewPage.md.

@@ -21,12 +21,13 @@ const maxViewChars = pageview.MaxChars

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-level note: tools/cfl/internal/cmd/page/view.go

openBrowser immediately follows the closing brace of runView with no blank line (line 171 }, line 172 func openBrowser). Go gofmt requires a blank line between top-level function declarations; golangci-lint will flag this. Fix: insert a blank line between the two declarations.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not reproducible: gofmt -d on view.go produces no output — the declarations are correctly separated in the PR head. No change made.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're right—gofmt -d confirms the declarations are correctly separated in the PR head. This finding was incorrect; no change is needed.

Summary:
Finding dismissed: formatting is correct in the PR head.

Comment thread skills/Confluence/CliReference.md Outdated
@@ -26,7 +26,6 @@ cfl config test
|------|-------------|
| `-o, --output FORMAT` | Output format (see SKILL.md "Output Representation and Format"): `table` (default), `json`, `plain` |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Three places in this file teach -o json as valid:

  1. Line 27 (Global Flags table): -o, --output FORMAT … table (default), json, plainjson should be removed; valid formats are table and plain only.
  2. Line 50 (Pages table): cfl page view PAGE_ID -o json | Full JSON output (body always included in full) — this command errors at the output-format guard; the row should be removed.
  3. Line 203 (Output section): "Use -o json for machine-readable output" — this is incorrect; -o json was removed in cfl: remove resource-read -o json #392.

The OUTPUT_SPEC.md (in this PR) is authoritative: resource -o json is invalid and must fail. Fix all three locations to reflect only table and plain as valid -o values.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed all three locations; wording matches the identical fix already on the stacked 455-cfl-search branch so the merge-up stays clean.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed—this addresses all three incorrect -o json references and keeps the stacked branches aligned.

Summary:
Removed outdated documentation that presented -o json as a valid output format.

Comment thread tools/cfl/internal/cmd/page/edit.go Outdated
!hasContentSource(opts.Options, opts.file, opts.editor) {
return errMissingContentSource()
}
hasStdinData := opts.Stdin != nil && opts.Stdin != os.Stdin

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hasStdinData bypasses the stdinIsTTY seam, creating an untestable branch

runEdit computes whether stdin has data using os.Stdin.Stat() directly:

hasStdinData := opts.Stdin != nil && opts.Stdin != os.Stdin
if !hasStdinData {
    stat, _ := os.Stdin.Stat()
    hasStdinData = (stat.Mode() & os.ModeCharDevice) == 0
}

But the errMissingContentSource guard just above uses hasContentSource (which delegates to hasPipedOSStdin/stdinIsTTY), and getEditContent also uses hasPipedOSStdin. This hasNewContent computation is the only stdin-detection site that bypasses the overrideable stdinIsTTY seam.

Impact: the hasNewContent == false path (preserve old body, no new stdin data) cannot be driven by overriding stdinIsTTY in a unit test — only by injecting a non-nil opts.Stdin. If someone overrides stdinIsTTY to simulate piped stdin, the early hasContentSource check sees it but hasNewContent does not, producing inconsistent behaviour in test doubles and masking bugs where the body should be updated from stdin but silently isn't.

Fix: replace the inline stat call with hasPipedOSStdin(opts.Options) (same function used in getEditContent and hasContentSource) so all three stdin-detection sites share one testable seam:

hasStdinData := (opts.Stdin != nil && opts.Stdin != os.Stdin) || hasPipedOSStdin(opts.Options)
hasNewContent := opts.file != "" || opts.editor || hasStdinData

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the inline os.Stdin.Stat() with hasPipedOSStdin so all three stdin-detection sites share the same testable seam, exactly as suggested.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the inconsistency by routing stdin detection through the shared, testable seam.

Summary:
stdin detection now consistently uses hasPipedOSStdin.

@@ -2,10 +2,60 @@ package page

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-level note: tools/cfl/internal/cmd/page/fetch.go

hasStorageContent (lines 112–116) and hasADFContent (lines 119–123) in fetch.go are byte-for-byte duplicates of the same helpers in tools/cfl/internal/pageview/projection.go (lines 156–166). Both test the same API struct fields (page.Body.Storage.Value and page.Body.AtlasDocFormat.Value). When the api.Page body shape changes — or when a third representation is introduced — there are now two unconnected sites to update with no compile-time signal. The page package already imports pageview, so the simplest fix is to export HasStorageContent / HasADFContent from pageview and delete the private copies in fetch.go.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exported HasStorageContent/HasADFContent from pageview and deleted the private duplicates in fetch.go.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for consolidating these checks in pageview; that removes the duplicate maintenance point.

Summary:
Content-presence helpers were exported from pageview and the duplicate private implementations in fetch.go were removed.

@@ -22,8 +22,9 @@ Create, edit, copy, move, and delete Confluence pages.
| "from file", "use this file" | `--file PATH` | Reads content from file |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-level note: skills/Confluence/Workflows/ManagePage.md

Line 121 contains a runnable example that will actively fail:

NEW_ID=$(cfl page copy SOURCE_ID --title "Copy of Page" -o json | ...)   # capture the new page ID from JSON

-o json is invalid; cfl will exit with invalid output format: "json" (valid formats: table, plain) before any copy occurs.

The page copy success output per OUTPUT_SPEC.md is:

Copied page: <title>
ID: <id>
...

Fix the example to parse the stable text output, e.g.:

NEW_ID=$(cfl page copy SOURCE_ID --title "Copy of Page" | awk '/^ID:/ {print $2}')
cfl page edit "$NEW_ID" --parent DESIRED_PARENT_ID

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Replaced the -o json example with awk parsing of the stable "ID:" line, per your suggested snippet.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—the example now parses the documented stable ID: output, so this is resolved.

Summary:
Replaced invalid -o json usage with awk parsing of the stable ID: output.

Comment thread skills/Confluence/SKILL.md Outdated
- `full` (`--full`) — inspection-oriented, additional fields (dates, authors, versions)
- `raw` (`--raw`) — source-faithful content (e.g., XHTML instead of markdown). Command-specific; only supported where source transformation occurs (currently `page view`).
- **Page body format** — `--body-format markdown|adf|xhtml` on page view/create/edit. Omission means Markdown; ADF and XHTML are exact.
- **Output format** — how it's rendered: `table` (default), `json` (`-o json`), `plain` (`-o plain`)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The "Output Representation and Format" section lists json (-o json) as a valid output format and gives --full -o json as a combination example (line 53). Both are wrong: resource -o json was removed in #392. OUTPUT_SPEC.md (also in this PR) states -o/--output accepts table and plain only, and that -o json must fail with invalid output format. tools/cfl/README.md likewise documents this as a breaking change.

This is the primary skill entry point for agents — teaching it to emit -o json will cause every workflow that follows the skill to fail at the output-format guard.

Fix: Change the output format list to table (default) and plain only. Remove the --full -o json example. If scripted extraction is the motivation, note that page copy and other mutation commands emit ID: <id> on a stable text line that can be parsed with grep/awk.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SKILL.md now lists table/plain only, the --full -o json example is gone, and scripted extraction points at the stable ID: text line.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the finding. The skill now documents only supported output formats and provides a stable parsing alternative.

Summary:
Removed unsupported JSON output guidance and documented stable ID-line parsing.

@@ -123,9 +123,8 @@ func TestRunEdit_TitleOnly(t *testing.T) {
err := os.WriteFile(mdFile, []byte("<p>Keep this</p>"), 0600)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-level note: tools/cfl/internal/cmd/page/edit_test.go

TestRunEdit_TitleOnly has a stale comment that inverts the actual behavior

The comment at line 118 says:

Without file input and with a title, the current implementation will still try to open an editor.

This is incorrect. When opts.title != "", the errMissingContentSource guard is skipped and hasNewContent evaluates to false (no file, no editor, no stdin), so runEdit simply preserves the existing page body and updates only the title — no editor is opened. The test then unnecessarily provides a file, making it exercise the title-plus-content path instead of the title-only path.

The stale comment could mislead a maintainer into thinking title-only edits require a content workaround. The correct title-only path is already exercised by TestRunEdit_MoveWithTitleOnly_NoEditorOpened.

Fix: remove the workaround and test the actual title-only contract:

func TestRunEdit_TitleOnly(t *testing.T) {
    t.Parallel()
    var receivedBody map[string]any
    // ... server setup ...

    rootOpts := newEditTestRootOptions()
    rootOpts.Stdin = nil
    opts := &editOptions{
        Options: rootOpts,
        pageID:  "12345",
        title:   "New Title",
        // No file, no editor — body must be preserved from existing page
    }

    err := runEdit(context.Background(), opts)
    testutil.RequireNoError(t, err)
    testutil.Equal(t, "New Title", receivedBody["title"])
    // body in PUT should equal existing page body verbatim
}

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rewrote TestRunEdit_TitleOnly to the true title-only contract: no content source, asserts the PUT preserves the existing storage body verbatim and updates the title. Stale comment removed.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — this now covers the actual title-only behavior and verifies the existing body is preserved.

Summary:
Title-only test corrected; stale comment removed and body preservation asserted.

case hasADFContent(page):
proj.Body, proj.BodyKind, proj.Fallback, proj.Truncated = projectADFBody(page.Body.AtlasDocFormat.Value, opts)
proj.HasContent = true
switch opts.BodyFormat {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Project switches on raw string literals ("adf", "xhtml", "", "markdown") for body format routing, while the canonical values are defined as private constants (bodyFormatMarkdown, bodyFormatADF, bodyFormatXHTML) inside the page package. The pageview package cannot import page (that would be a cycle), so there is no compile-time contract tying these two routing sites together. Adding a fourth format to resolveBodyFormat in body_format.go requires manually updating the switch in projection.go; missing it produces a silent no-content result rather than an error. The fix is to export the format constants from pageview (which sits below page in the dependency graph) and have page/body_format.go reference them, giving both routing sites a shared, compiler-checked source of truth.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exported the body-format constants from pageview and pointed page/body_format.go at them — both routing sites now share one compiler-checked source of truth.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — the shared exported constants provide the compiler-checked contract between both routing sites. Resolving.

Summary:
Body-format constants are now shared through pageview, preventing routing drift.

@rianjs-bot rianjs-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: 6d343f14992f
Profile: codex-rianjs-bot - Posting as: rianjs-bot[bot]

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0
structure:repo-health 2
documentation:docs 1
structure:repo-health (2 findings)

Minor - tools/cfl/internal/pageview/projection.go:99

Exact body modes mark an available-but-empty representation as having no content. The presenter consequently emits (No content) instead of the requested empty ADF/XHTML value, so page view --body-format xhtml --content-only is not an exact body artifact for blank pages and cannot be safely piped. Preserve the distinction between an absent representation and a present empty one in the projection/presenter, and add coverage for empty exact ADF and XHTML bodies.

Minor - tools/cfl/internal/cmd/page/body_format.go:50

adf currently validates only JSON syntax, so valid non-ADF values such as null, [], or {} are passed to the API despite the new selector promising ADF input. Validate the ADF document boundary (at least a top-level object with type: "doc", a supported version, and an array content, or an ADF schema) before mutation, and add tests for syntactically valid but non-ADF input.

documentation:docs (1 finding)

Minor - skills/Confluence/SKILL.md:53

-o plain is TSV only for list commands. Detail and mutation commands retain their text shape in plain mode, as the output contract states, so this unqualified example teaches callers to expect TSV from combinations such as cfl page view --full -o plain. Say that plain output is TSV for lists (or use a list-command example) and describe detail output as semantically equivalent plain text.

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/body_format_test.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/create_test.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/edit_test.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/fetch_test.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/cmd/page/view_test.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/pageview/projection_test.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/detail_test.go, tools/cfl/internal/present/emit.go unavailable Targeted Go tests could not run fully: the sandbox disallows httptest listeners, and the default cgo build fails on a space-containing clang cache path.
policies:conventions complete_broad README.md, docs/ARTIFACT_CONTRACT.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go unavailable Shared cli-common convention documents were not available in the local checkout.; Targeted Go tests could not build because the toolchain did not handle the workspace path containing spaces.
structure:repo-health complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/present/emit.go unavailable Focused Go tests were attempted; full command/entrypoint suites cannot open localhost listeners in this sandbox, and normal CGO builds also fail because the workspace path contains spaces.
documentation:docs complete_broad README.md, docs/ARTIFACT_CONTRACT.md, skills/Confluence/CliReference.md, skills/Confluence/SKILL.md, skills/Confluence/Workflows/ManagePage.md, skills/Confluence/Workflows/ViewPage.md, tools/cfl/CHANGELOG.md, tools/cfl/README.md, tools/cfl/chaos-testing.md, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/present/README.md unavailable Scoped to assigned documentation files; verified command behavior against the local page command implementation. External links were not checked.
unassigned incomplete_unassigned unavailable tools/cfl/scripts/roundtrip-test.sh changed files were not assigned to a selected reviewer

9 PR discussion threads considered. 9 summarized; 9 resolved.


Completed in 4m 34s | unavailable | gpt-5.6-terra | cr 0.10.259
Field Value
Model gpt-5.6-terra
Reviewers go:implementation-tests, policies:conventions, structure:repo-health, documentation:docs
Engine codex_cli · gpt-5.6-terra
Reviewed by cr · rianjs-bot[bot]
Duration 4m 34s wall · 7m 44s compute
Cost unavailable
Tokens 2.4M in / 19.4k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection gpt-5.6-terra 35.3k 906 27.6k unavailable unavailable 20s
go:implementation-tests gpt-5.6-terra 739.2k 4.1k 660.5k unavailable unavailable 1m 50s
policies:conventions gpt-5.6-terra 401.5k 3.3k 340.7k unavailable unavailable 1m 40s
structure:repo-health gpt-5.6-terra 627.8k 5.3k 562.9k unavailable unavailable 2m 02s
documentation:docs gpt-5.6-terra 539.2k 4.5k 468.2k unavailable unavailable 1m 42s
orchestrator-rollup gpt-5.6-terra 57.0k 1.2k 45.3k unavailable unavailable 8s

}
return adfBody(converted), nil
case bodyFormatADF:
if !json.Valid([]byte(content)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

adf currently validates only JSON syntax, so valid non-ADF values such as null, [], or {} are passed to the API despite the new selector promising ADF input. Validate the ADF document boundary (at least a top-level object with type: "doc", a supported version, and an array content, or an ADF schema) before mutation, and add tests for syntactically valid but non-ADF input.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ddea625: --body-format adf now validates the ADF envelope — top-level object with type "doc", numeric version, and array content — rejecting null/[]/{} and other valid-JSON-but-non-ADF inputs with an actionable error, plus tests for each shape.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks — the ADF envelope validation and coverage for valid-JSON-but-non-ADF shapes address this finding.

Summary:
ADF input now requires a document envelope and rejects non-ADF JSON shapes.

if page.Body != nil && page.Body.AtlasDocFormat != nil {
proj.Body, proj.Truncated = TruncateContent(page.Body.AtlasDocFormat.Value, opts)
proj.BodyKind = BodyKindADF
proj.HasContent = page.Body.AtlasDocFormat.Value != ""

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exact body modes mark an available-but-empty representation as having no content. The presenter consequently emits (No content) instead of the requested empty ADF/XHTML value, so page view --body-format xhtml --content-only is not an exact body artifact for blank pages and cannot be safely piped. Preserve the distinction between an absent representation and a present empty one in the projection/presenter, and add coverage for empty exact ADF and XHTML bodies.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ddea625: the projection now distinguishes an absent representation from a present-but-empty one; exact empty ADF/XHTML bodies emit the exact empty value (zero bytes under --content-only, pipe-safe), with coverage for both formats.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the distinction and adds the requested empty-body coverage.

Summary:
Absent and present-but-empty exact body representations are now distinguished; empty ADF/XHTML content-only output remains zero bytes and pipe-safe.

Comment thread skills/Confluence/SKILL.md Outdated
- **Output format** — how it's rendered: `table` (default), `plain` (`-o plain`). For scripted extraction, parse `ID: <id>` lines from command output.

They combine freely — e.g., `--full -o json` returns the inspection representation as JSON.
They combine freely — e.g., `--full -o plain` returns the inspection representation as TSV.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-o plain is TSV only for list commands. Detail and mutation commands retain their text shape in plain mode, as the output contract states, so this unqualified example teaches callers to expect TSV from combinations such as cfl page view --full -o plain. Say that plain output is TSV for lists (or use a list-command example) and describe detail output as semantically equivalent plain text.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in ddea625: SKILL.md now scopes TSV to list commands and describes detail output in plain mode as semantically equivalent text, per the output contract.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the finding by limiting TSV expectations to list commands and correctly describing plain detail output.

Summary:
Documentation now accurately distinguishes TSV list output from semantically equivalent plain-text detail output.

@rianjs

rianjs commented Jul 17, 2026

Copy link
Copy Markdown
Contributor Author

All three findings from the last review addressed in ddea625: (1) exact empty ADF/XHTML bodies now emit the exact empty value (zero bytes under --content-only) instead of "(No content)", with coverage for both formats; (2) --body-format adf now validates the ADF envelope (type:doc, numeric version, array content) and rejects null/[]/{}; (3) SKILL.md scopes TSV to list commands.

@rianjs-bot rianjs-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: ddea625bf5ea
Profile: codex-rianjs-bot - Posting as: rianjs-bot[bot]

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 1
structure:repo-health 0
documentation:docs 0
policies:conventions (1 finding)

Major - tools/cfl/internal/cmd/page/view.go:160

--body-format adf|xhtml is documented as emitting an exact body, but this forwards the normal truncation settings to projection. Without --no-truncate or --content-only, a body over 5000 characters is cut off; for ADF this can produce invalid partial JSON, so the advertised exact representation is not usable. Disable truncation automatically for the exact body formats (or revise the documented contract and examples to make full-body output explicit).

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/body_format_test.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/create_test.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/edit_test.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/fetch_test.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/cmd/page/view_test.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/pageview/projection_test.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/detail_test.go, tools/cfl/internal/present/emit.go unavailable Focused package tests were attempted. pageview and present passed with CGO disabled; cmd/page could not complete because the sandbox disallows httptest listener binding. The default CGO build also fails due an unquoted workspace path in the environment's clang cache configuration.
policies:conventions complete_broad README.md, docs/ARTIFACT_CONTRACT.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go unavailable Shared cli-common convention docs were not available as a local convenience copy.; Targeted Go tests could not build because the workspace path contains spaces and the compiler invocation failed.
structure:repo-health complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/present/emit.go unavailable Targeted Go tests were partially constrained by the sandbox: normal CGO builds cannot resolve a path containing spaces, and CGO-disabled page tests cannot open httptest listener ports. The pageview and present packages passed with CGO disabled.
documentation:docs complete_broad README.md, docs/ARTIFACT_CONTRACT.md, skills/Confluence/CliReference.md, skills/Confluence/SKILL.md, skills/Confluence/Workflows/ManagePage.md, skills/Confluence/Workflows/ViewPage.md, tools/cfl/CHANGELOG.md, tools/cfl/README.md, tools/cfl/chaos-testing.md, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/present/README.md unavailable Reviewed assigned documentation against the current page command implementation; no live Confluence instance was used.
unassigned incomplete_unassigned unavailable tools/cfl/scripts/roundtrip-test.sh changed files were not assigned to a selected reviewer

0 PR discussion threads considered. 0 summarized; 0 resolved.


Completed in 3m 18s | unavailable | gpt-5.6-terra | cr 0.10.260
Field Value
Model gpt-5.6-terra
Reviewers go:implementation-tests, policies:conventions, structure:repo-health, documentation:docs
Engine codex_cli · gpt-5.6-terra
Reviewed by cr · rianjs-bot[bot]
Duration 3m 18s wall · 7m 45s compute
Cost unavailable
Tokens 2.2M in / 21.5k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection gpt-5.6-terra 85.1k 1.9k 66.6k unavailable unavailable 14s
go:implementation-tests gpt-5.6-terra 458.9k 3.9k 398.3k unavailable unavailable 1m 39s
policies:conventions gpt-5.6-terra 305.3k 3.3k 246.0k unavailable unavailable 1m 24s
structure:repo-health gpt-5.6-terra 486.7k 4.2k 430.3k unavailable unavailable 1m 49s
documentation:docs gpt-5.6-terra 752.9k 6.2k 665.9k unavailable unavailable 2m 28s
orchestrator-rollup gpt-5.6-terra 116.6k 2.1k 94.5k unavailable unavailable 8s


proj := pageview.Project(page, spaceKey, pageview.Options{
Raw: opts.raw,
proj, err := pageview.Project(page, spaceKey, pageview.Options{

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--body-format adf|xhtml is documented as emitting an exact body, but this forwards the normal truncation settings to projection. Without --no-truncate or --content-only, a body over 5000 characters is cut off; for ADF this can produce invalid partial JSON, so the advertised exact representation is not usable. Disable truncation automatically for the exact body formats (or revise the documented contract and examples to make full-body output explicit).

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 60ab320: adf and xhtml body formats now bypass truncation unconditionally — exact means exact. Help text, OUTPUT_SPEC, README, and the Confluence skills document the behavior, with tests covering a >5000-char ADF body (complete and json-parseable), large XHTML, and default Markdown truncation unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed — disabling truncation for the exact ADF/XHTML formats addresses the issue, and the added coverage preserves the default Markdown behavior.

Summary:
Exact ADF and XHTML body formats now bypass truncation, with regression coverage for large outputs and unchanged default Markdown truncation.

Base automatically changed from 455-cfl-config to main July 17, 2026 15:06

@rianjs-bot rianjs-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: cc3ffe4cd399
Profile: codex-rianjs-bot - Posting as: rianjs-bot[bot]

Summary

Reviewer Findings
go:implementation-tests 1
policies:conventions 0
structure:repo-health 0
documentation:docs 1
automation:ci-release 1
go:implementation-tests (1 finding)

Major - tools/cfl/internal/present/detail.go:108

--content-only --body-format xhtml|adf is meant to emit the exact representation for lossless piping, but this only suppresses the renderer newline when the exact body is empty. Every non-empty exact body receives an additional \n from renderMessage, so cfl page view ... --body-format xhtml --content-only | cfl page edit ... --body-format xhtml changes stored body bytes. Set NoNewline for all content-only ADF/XHTML projections, and add output tests with non-empty bodies that assert byte-for-byte equality (including inputs with and without a trailing newline).

documentation:docs (1 finding)

Minor - tools/cfl/integration-tests.md:74

The execution checklists still require “View page (raw)” (lines 607 and 700) and say a raw ADF view shows ADF JSON (lines 625 and 718). --raw was removed, and raw is no longer an unambiguous mode: XHTML and ADF must be selected separately with --body-format xhtml or --body-format adf. Replace these entries with explicit XHTML/ADF checks so the documented live-test workflow does not omit or misstate the new interface.

automation:ci-release (1 finding)

Minor - tools/cfl/scripts/roundtrip-test.sh:142

Requesting XHTML explicitly disables the page fetcher's storage→ADF fallback. An ADF-native page with no storage representation now exits here (or reaches the empty-content failure), so the later SKIP branch is unreachable and a mixed input list incorrectly fails the script. Probe ADF after an unavailable/empty XHTML response and count it as SKIP, while retaining FAIL for genuine fetch errors.

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/body_format_test.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/create_test.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/edit_test.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/fetch_test.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/cmd/page/view_test.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/pageview/projection_test.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/detail_test.go, tools/cfl/internal/present/emit.go unavailable Focused on Go implementation quality and behavioral test coverage. The pageview package tests passed; page and present package test builds were blocked by the sandbox's space-containing cgo cache path being split by clang.
policies:conventions complete_broad README.md, docs/ARTIFACT_CONTRACT.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go unavailable Shared cli-common and .github convenience copies were not present in this checkout. Focused on the available repo-local standards and assigned diff. Targeted Go tests could not build because the RTK wrapper/clang mishandled the workspace path containing spaces.
structure:repo-health complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/present/emit.go unavailable Focused on assigned implementation files and their directly related tests/contracts. The targeted Go test command could not complete because the sandboxed path with spaces caused a clang build-path failure.
documentation:docs complete_broad README.md, docs/ARTIFACT_CONTRACT.md, skills/Confluence/CliReference.md, skills/Confluence/SKILL.md, skills/Confluence/Workflows/ManagePage.md, skills/Confluence/Workflows/ViewPage.md, tools/cfl/CHANGELOG.md, tools/cfl/README.md, tools/cfl/chaos-testing.md, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/present/README.md unavailable Documentation-only review; implementation was inspected only to validate the documented CLI contract.
automation:ci-release complete_broad tools/cfl/scripts/roundtrip-test.sh unavailable Review limited to the assigned roundtrip automation script and its direct body-format call behavior.

4 PR discussion threads considered. 4 summarized; 4 resolved.


Completed in 3m 26s | unavailable | gpt-5.6-terra | cr 0.10.260
Field Value
Model gpt-5.6-terra
Reviewers go:implementation-tests, policies:conventions, structure:repo-health, documentation:docs, automation:ci-release
Engine codex_cli · gpt-5.6-terra
Reviewed by cr · rianjs-bot[bot]
Duration 3m 26s wall · 8m 16s compute
Cost unavailable
Tokens 2.4M in / 27.0k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection gpt-5.6-terra 199.4k 3.5k 135.9k unavailable unavailable 14s
go:implementation-tests gpt-5.6-terra 466.9k 4.2k 408.1k unavailable unavailable 1m 52s
policies:conventions gpt-5.6-terra 248.1k 2.8k 200.7k unavailable unavailable 1m 09s
structure:repo-health gpt-5.6-terra 422.2k 4.0k 359.4k unavailable unavailable 1m 39s
documentation:docs gpt-5.6-terra 641.4k 5.7k 560.4k unavailable unavailable 2m 03s
automation:ci-release gpt-5.6-terra 184.6k 3.2k 149.5k unavailable unavailable 1m 10s
orchestrator-rollup gpt-5.6-terra 248.0k 3.6k 180.2k unavailable unavailable 6s

Comment thread tools/cfl/internal/present/detail.go Outdated
sections = append(sections, stdoutInfo(body))
sections = append(sections, &sharedpresent.MessageSection{
Message: body,
NoNewline: proj.ContentOnly && proj.HasContent && proj.Body == "" && (proj.BodyKind == pageview.BodyKindADF || proj.BodyKind == pageview.BodyKindXHTML),

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--content-only --body-format xhtml|adf is meant to emit the exact representation for lossless piping, but this only suppresses the renderer newline when the exact body is empty. Every non-empty exact body receives an additional \n from renderMessage, so cfl page view ... --body-format xhtml --content-only | cfl page edit ... --body-format xhtml changes stored body bytes. Set NoNewline for all content-only ADF/XHTML projections, and add output tests with non-empty bodies that assert byte-for-byte equality (including inputs with and without a trailing newline).

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 235ad4e: exact-format content-only output is byte-for-byte with no trailing newline (empty and non-empty), with byte-exact tests for both ADF and XHTML; Markdown unchanged.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the lossless content-only requirement and adds the requested byte-exact coverage.

Summary:
Resolved: content-only ADF/XHTML output preserves exact bytes for empty and non-empty bodies; Markdown behavior is unchanged.

# Capture raw content first to distinguish fetch failures from ADF pages
local raw_content
if ! raw_content=$(cfl page view "$id" --raw --content-only 2>&1); then
if ! raw_content=$(cfl page view "$id" --body-format xhtml --content-only 2>&1); then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting XHTML explicitly disables the page fetcher's storage→ADF fallback. An ADF-native page with no storage representation now exits here (or reaches the empty-content failure), so the later SKIP branch is unreachable and a mixed input list incorrectly fails the script. Probe ADF after an unavailable/empty XHTML response and count it as SKIP, while retaining FAIL for genuine fetch errors.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The script now probes ADF when XHTML is unavailable/empty and counts ADF-native pages as SKIP, restoring mixed-list behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the reported fallback regression by probing ADF after unavailable or empty XHTML and treating ADF-native pages as SKIP.

Summary:
ADF-native pages are skipped after unavailable or empty XHTML, preserving mixed-list behavior.

@@ -74,11 +74,12 @@ All cfl commands should work with both auth methods (no scope limitations for Co
| Test Case | Command | Expected Result |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

File-level note: tools/cfl/integration-tests.md

The execution checklists still require “View page (raw)” (lines 607 and 700) and say a raw ADF view shows ADF JSON (lines 625 and 718). --raw was removed, and raw is no longer an unambiguous mode: XHTML and ADF must be selected separately with --body-format xhtml or --body-format adf. Replace these entries with explicit XHTML/ADF checks so the documented live-test workflow does not omit or misstate the new interface.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All four checklist/expectation sites updated to explicit --body-format xhtml/adf wording.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks—this addresses the finding by making both XHTML and ADF checks explicit.

Summary:
Checklist and expectation wording now explicitly covers --body-format xhtml and --body-format adf.

@rianjs-bot rianjs-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: 235ad4ea0154
Profile: codex-rianjs-bot - Posting as: rianjs-bot[bot]

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0
structure:repo-health 0
documentation:docs 1
automation:ci-release 1
documentation:docs (1 finding)

Minor - skills/Confluence/CliReference.md:82

The new body-format guidance does not document that --show-macros is Markdown-only. cfl page view ID --body-format xhtml --show-macros (or adf) is rejected, but the adjacent flag table makes the options appear independently combinable. Qualify --show-macros as Markdown-only/incompatible with ADF and XHTML, matching the documented --legacy restriction.

automation:ci-release (1 finding)

Major - tools/cfl/scripts/roundtrip-test.sh:149

The ADF probe treats a zero-byte exact ADF body as proof that the page is ADF-backed, so an actually empty page is now reported as SKIP and the script exits successfully without running its fidelity check. cfl page view --body-format adf --content-only intentionally exits 0 for a present-but-empty ADF representation, just as the XHTML command does. Capture the ADF output and only skip when it contains a valid/non-empty ADF document; otherwise retain the empty-content failure (and ideally distinguish an unavailable representation from an empty one).

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/body_format_test.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/create_test.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/edit_test.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/fetch_test.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/cmd/page/view_test.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/pageview/projection_test.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/detail_test.go, tools/cfl/internal/present/emit.go unavailable Focused Go implementation and behavioral-test review only. The affected package test command was blocked by the sandbox: normal CGO compilation could not access its clang cache path, and CGO-disabled execution could not bind httptest listeners.
policies:conventions complete_broad README.md, docs/ARTIFACT_CONTRACT.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go unavailable Shared cli-common convention documents were not available locally; review used the repository-local development, architecture, artifact, and cfl output-contract guidance.
structure:repo-health complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/present/emit.go unavailable Review limited to assigned changed files. Package tests could not complete because the sandbox prohibits httptest from binding a local TCP port; pageview and present packages passed before the page package was blocked.
documentation:docs complete_broad README.md, docs/ARTIFACT_CONTRACT.md, skills/Confluence/CliReference.md, skills/Confluence/SKILL.md, skills/Confluence/Workflows/ManagePage.md, skills/Confluence/Workflows/ViewPage.md, tools/cfl/CHANGELOG.md, tools/cfl/README.md, tools/cfl/chaos-testing.md, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/present/README.md unavailable Documentation-focused review; verified the new flag behavior against the local page command implementation and output projection.
automation:ci-release complete_broad tools/cfl/scripts/roundtrip-test.sh unavailable Reviewed only the assigned roundtrip automation script and its relevant local CLI behavior; no live Confluence instance was available.

3 PR discussion threads considered. 3 summarized; 3 resolved.


Completed in 3m 02s | unavailable | gpt-5.6-terra | cr 0.10.260
Field Value
Model gpt-5.6-terra
Reviewers go:implementation-tests, policies:conventions, structure:repo-health, documentation:docs, automation:ci-release
Engine codex_cli · gpt-5.6-terra
Reviewed by cr · rianjs-bot[bot]
Duration 3m 02s wall · 7m 27s compute
Cost unavailable
Tokens 2.6M in / 26.7k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection gpt-5.6-terra 303.7k 4.2k 228.6k unavailable unavailable 15s
go:implementation-tests gpt-5.6-terra 444.9k 3.6k 379.9k unavailable unavailable 1m 31s
policies:conventions gpt-5.6-terra 225.4k 2.4k 176.1k unavailable unavailable 54s
structure:repo-health gpt-5.6-terra 436.1k 3.9k 379.9k unavailable unavailable 1m 40s
documentation:docs gpt-5.6-terra 627.8k 4.9k 534.3k unavailable unavailable 1m 52s
automation:ci-release gpt-5.6-terra 156.6k 3.3k 116.2k unavailable unavailable 1m 08s
orchestrator-rollup gpt-5.6-terra 362.9k 4.4k 283.6k unavailable unavailable 5s

| `--no-truncate` | Show full Markdown content without truncation; exact ADF/XHTML are always complete |
| `--content-only` | Output only page content (no metadata headers); implies `--no-truncate` |
| `--raw` | Raw Confluence storage format (XHTML) |
| `--body-format markdown\|adf\|xhtml` | Body representation; ADF and XHTML are emitted exactly and never truncated |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new body-format guidance does not document that --show-macros is Markdown-only. cfl page view ID --body-format xhtml --show-macros (or adf) is rejected, but the adjacent flag table makes the options appear independently combinable. Qualify --show-macros as Markdown-only/incompatible with ADF and XHTML, matching the documented --legacy restriction.

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Both flag-table rows now state --show-macros is Markdown-only and rejected with the exact body formats.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed—both flag-table entries now clearly mark --show-macros as Markdown-only and list ADF/XHTML as rejected combinations.

Summary:
Documented --show-macros as Markdown-only and incompatible with ADF and XHTML in both relevant flag-table rows.

Comment thread tools/cfl/scripts/roundtrip-test.sh Outdated
# Check for empty content
# Explicit XHTML does not fall back to ADF, so probe ADF before failing.
if [[ -z "$raw_content" ]]; then
if cfl page view "$id" --body-format adf --content-only >/dev/null 2>&1; then

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ADF probe treats a zero-byte exact ADF body as proof that the page is ADF-backed, so an actually empty page is now reported as SKIP and the script exits successfully without running its fidelity check. cfl page view --body-format adf --content-only intentionally exits 0 for a present-but-empty ADF representation, just as the XHTML command does. Capture the ADF output and only skip when it contains a valid/non-empty ADF document; otherwise retain the empty-content failure (and ideally distinguish an unavailable representation from an empty one).

Reply inline to this comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ADF probe now requires a non-empty body to classify a page as ADF-backed; a present-but-empty body falls through to the empty-page failure as before.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acknowledged — requiring a non-empty ADF body preserves the empty-page failure path.

Summary:
ADF-backed detection now requires a non-empty ADF body; empty pages continue to fail the fidelity check.

@rianjs-bot rianjs-bot Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: 107a50fe0835
Profile: codex-rianjs-bot - Posting as: rianjs-bot[bot]

Summary

Reviewer Findings
go:implementation-tests 0
policies:conventions 0
structure:repo-health 0
documentation:docs 1
automation:ci-release 0
documentation:docs (1 finding)

Minor - tools/cfl/chaos-testing.md:86

The refreshed page-view test table still says the adjacent -o json check passes with valid JSON. Resource JSON is now rejected at the root, as this PR's integration guide and output contract document, so this chaos guide would direct maintainers to expect an unsupported result. Replace that row with the invalid-output expectation (or mark the historical result obsolete).

Reviewer Coverage

Reviewer Status Inspected Skipped Constraints
go:implementation-tests complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/body_format_test.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/create_test.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/edit_test.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/fetch_test.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/cmd/page/view_test.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/pageview/projection_test.go, tools/cfl/internal/present/detail.go, tools/cfl/internal/present/detail_test.go, tools/cfl/internal/present/emit.go unavailable Full page command tests could not run in this sandbox because httptest cannot bind a local port; focused non-network page tests and pageview/present package tests passed with CGO disabled.
policies:conventions complete_broad README.md, docs/ARTIFACT_CONTRACT.md, tools/cfl/README.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go unavailable Shared cli-common standards documentation was not available in the workspace; review used the available repo-local guidance and assigned diff context.; Targeted Go tests could not build because the compiler invocation did not handle the workspace path containing spaces.
structure:repo-health complete_broad tools/cfl/cmd/cfl/main.go, tools/cfl/internal/cmd/page/body_format.go, tools/cfl/internal/cmd/page/create.go, tools/cfl/internal/cmd/page/edit.go, tools/cfl/internal/cmd/page/fetch.go, tools/cfl/internal/cmd/page/view.go, tools/cfl/internal/pageview/projection.go, tools/cfl/internal/present/emit.go unavailable Review limited to the assigned command, projection, and emission files; targeted tests could not run because the RTK wrapper passed the workspace path with spaces incorrectly to clang.
documentation:docs complete_broad README.md, docs/ARTIFACT_CONTRACT.md, skills/Confluence/CliReference.md, skills/Confluence/SKILL.md, skills/Confluence/Workflows/ManagePage.md, skills/Confluence/Workflows/ViewPage.md, tools/cfl/CHANGELOG.md, tools/cfl/README.md, tools/cfl/chaos-testing.md, tools/cfl/integration-tests.md, tools/cfl/internal/cmd/OUTPUT_SPEC.md, tools/cfl/internal/present/README.md unavailable Documentation-only review; verified flag and output behavior against the changed local implementation, without live Confluence integration.
automation:ci-release complete_broad tools/cfl/scripts/roundtrip-test.sh unavailable Review intentionally limited to the assigned roundtrip automation script and CI/release automation invariants.

2 PR discussion threads considered. 2 summarized; 2 resolved.


Completed in 3m 17s | unavailable | gpt-5.6-terra | cr 0.10.260
Field Value
Model gpt-5.6-terra
Reviewers go:implementation-tests, policies:conventions, structure:repo-health, documentation:docs, automation:ci-release
Engine codex_cli · gpt-5.6-terra
Reviewed by cr · rianjs-bot[bot]
Duration 3m 17s wall · 8m 36s compute
Cost unavailable
Tokens 3.6M in / 31.5k out

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost Duration
orchestrator-selection gpt-5.6-terra 429.4k 5.0k 342.3k unavailable unavailable 15s
go:implementation-tests gpt-5.6-terra 937.1k 6.3k 855.3k unavailable unavailable 2m 29s
policies:conventions gpt-5.6-terra 397.0k 3.1k 340.2k unavailable unavailable 1m 19s
structure:repo-health gpt-5.6-terra 662.9k 5.1k 593.2k unavailable unavailable 1m 58s
documentation:docs gpt-5.6-terra 493.7k 4.4k 416.8k unavailable unavailable 1m 34s
automation:ci-release gpt-5.6-terra 140.5k 2.5k 112.4k unavailable unavailable 54s
orchestrator-rollup gpt-5.6-terra 499.2k 5.1k 408.6k unavailable unavailable 4s

| Non-numeric ID (abc) | PASS | Good 400 error |
| Missing page ID | PASS | "accepts 1 arg(s)" error |
| `--raw` mode | PASS | Shows Confluence storage format |
| `--body-format xhtml` mode | PASS | Shows exact Confluence storage XHTML |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The refreshed page-view test table still says the adjacent -o json check passes with valid JSON. Resource JSON is now rejected at the root, as this PR's integration guide and output contract document, so this chaos guide would direct maintainers to expect an unsupported result. Replace that row with the invalid-output expectation (or mark the historical result obsolete).

Reply inline to this comment.

@rianjs
rianjs merged commit 6f877cd into main Jul 17, 2026
10 checks passed
@rianjs
rianjs deleted the 455-cfl-body-format branch July 17, 2026 17:21
rianjs added a commit that referenced this pull request Jul 17, 2026
rianjs added a commit that referenced this pull request Jul 17, 2026
…--full (#455) (#464)

* fix(cfl): resolve --config split-brain resolution (#455)

* feat(cfl)!: standardize page body formats with --body-format (#455)

* feat(cfl)!: explicit search scope, strict limits, implement page view --full (#455)

* fix(cfl): address review feedback on search/limits/--full batch (#455)

* fix(cfl): address review feedback on body-format batch (#455)

* fix(cfl): thread --config path through init and config show (#455)

* fix(cfl): exact empty bodies, ADF envelope validation, plain-mode docs (#455)

* fix(cfl): explicit --config skips shared-store layering (#455)

* fix(cfl): exact body formats bypass truncation (#455)

* docs(cfl): correct Go prerequisite and document bin/cfl invocation (#455)

* fix(cfl): omit absent --full fields, correct scripting and --full docs (#455)

* fix(cfl): restore #459 final-round changes clobbered by -X ours reconciliation (#455)
@rianjs rianjs mentioned this pull request Jul 17, 2026
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