feat(cfl)!: standardize page body formats with --body-format - #459
Conversation
There was a problem hiding this comment.
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
hasStdinDatabypasses thestdinIsTTYseam, creating an untestable branch
runEditcomputes whether stdin has data usingos.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
errMissingContentSourceguard just above useshasContentSource(which delegates tohasPipedOSStdin/stdinIsTTY), andgetEditContentalso useshasPipedOSStdin. ThishasNewContentcomputation is the only stdin-detection site that bypasses the overrideablestdinIsTTYseam.Impact: the
hasNewContent == falsepath (preserve old body, no new stdin data) cannot be driven by overridingstdinIsTTYin a unit test — only by injecting a non-nilopts.Stdin. If someone overridesstdinIsTTYto simulate piped stdin, the earlyhasContentSourcecheck sees it buthasNewContentdoes 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 ingetEditContentandhasContentSource) 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_TitleOnlyhas a stale comment that inverts the actual behaviorThe 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 != "", theerrMissingContentSourceguard is skipped andhasNewContentevaluates tofalse(no file, no editor, no stdin), sorunEditsimply 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
openBrowserimmediately follows the closing brace ofrunViewwith no blank line (line 171}, line 172func openBrowser). Gogofmtrequires 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
Projectswitches on raw string literals ("adf","xhtml","","markdown") for body format routing, while the canonical values are defined as private constants (bodyFormatMarkdown,bodyFormatADF,bodyFormatXHTML) inside thepagepackage. Thepageviewpackage cannot importpage(that would be a cycle), so there is no compile-time contract tying these two routing sites together. Adding a fourth format toresolveBodyFormatinbody_format.gorequires manually updating the switch inprojection.go; missing it produces a silent no-content result rather than an error. The fix is to export the format constants frompageview(which sits belowpagein the dependency graph) and havepage/body_format.goreference 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) andhasADFContent(lines 119–123) infetch.goare byte-for-byte duplicates of the same helpers intools/cfl/internal/pageview/projection.go(lines 156–166). Both test the same API struct fields (page.Body.Storage.Valueandpage.Body.AtlasDocFormat.Value). When theapi.Pagebody shape changes — or when a third representation is introduced — there are now two unconnected sites to update with no compile-time signal. Thepagepackage already importspageview, so the simplest fix is to exportHasStorageContent/HasADFContentfrompageviewand delete the private copies infetch.go.
documentation:docs (4 findings)
Major - skills/Confluence/CliReference.md:27
Three places in this file teach
-o jsonas valid:
- Line 27 (Global Flags table):
-o, --output FORMAT … table (default), json, plain—jsonshould be removed; valid formats aretableandplainonly.- 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.- Line 203 (Output section): "Use
-o jsonfor machine-readable output" — this is incorrect;-o jsonwas removed in #392.The OUTPUT_SPEC.md (in this PR) is authoritative: resource
-o jsonis invalid and must fail. Fix all three locations to reflect onlytableandplainas valid-ovalues.
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 jsonas a combination example (line 53). Both are wrong: resource-o jsonwas removed in #392.OUTPUT_SPEC.md(also in this PR) states-o/--outputacceptstableandplainonly, and that-o jsonmust fail withinvalid output format.tools/cfl/README.mdlikewise documents this as a breaking change.This is the primary skill entry point for agents — teaching it to emit
-o jsonwill cause every workflow that follows the skill to fail at the output-format guard.Fix: Change the output format list to
table(default) andplainonly. Remove the--full -o jsonexample. If scripted extraction is the motivation, note thatpage copyand other mutation commands emitID: <id>on a stable text line that can be parsed withgrep/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 jsonis invalid;cflwill exit withinvalid output format: "json" (valid formats: table, plain)before any copy occurs.The
page copysuccess output perOUTPUT_SPEC.mdis: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 jsonsurface:
- 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.- Line 35: "For scripted extraction, add
-o json" — incorrect;-o jsonerrors. Remove this suggestion. Scripted extraction from text output (e.g.,grep '^ID:') should be substituted or the note removed entirely.- Lines 68–83 ("JSON Output Structure" section): describes a
{id, title, spaceId, spaceKey, parentId, content}JSON envelope that cannot be produced because-o jsonis 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 jsonand 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 |
| | "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) | |
There was a problem hiding this comment.
Three problems in ViewPage.md related to the removed -o json surface:
- 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. - Line 35: "For scripted extraction, add
-o json" — incorrect;-o jsonerrors. Remove this suggestion. Scripted extraction from text output (e.g.,grep '^ID:') should be substituted or the note removed entirely. - Lines 68–83 ("JSON Output Structure" section): describes a
{id, title, spaceId, spaceKey, parentId, content}JSON envelope that cannot be produced because-o jsonis 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.
There was a problem hiding this comment.
Removed the -o json row, the scripted-extraction note, and the entire JSON Output Structure section from ViewPage.md.
There was a problem hiding this comment.
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 | |||
|
|
|||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Not reproducible: gofmt -d on view.go produces no output — the declarations are correctly separated in the PR head. No change made.
There was a problem hiding this comment.
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.
| @@ -26,7 +26,6 @@ cfl config test | |||
| |------|-------------| | |||
| | `-o, --output FORMAT` | Output format (see SKILL.md "Output Representation and Format"): `table` (default), `json`, `plain` | | |||
There was a problem hiding this comment.
Three places in this file teach -o json as valid:
- Line 27 (Global Flags table):
-o, --output FORMAT … table (default), json, plain—jsonshould be removed; valid formats aretableandplainonly. - 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. - Line 203 (Output section): "Use
-o jsonfor machine-readable output" — this is incorrect;-o jsonwas 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.
There was a problem hiding this comment.
Fixed all three locations; wording matches the identical fix already on the stacked 455-cfl-search branch so the merge-up stays clean.
There was a problem hiding this comment.
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.
| !hasContentSource(opts.Options, opts.file, opts.editor) { | ||
| return errMissingContentSource() | ||
| } | ||
| hasStdinData := opts.Stdin != nil && opts.Stdin != os.Stdin |
There was a problem hiding this comment.
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 || hasStdinDataReply inline to this comment.
There was a problem hiding this comment.
Replaced the inline os.Stdin.Stat() with hasPipedOSStdin so all three stdin-detection sites share the same testable seam, exactly as suggested.
There was a problem hiding this comment.
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 | |||
|
|
|||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Exported HasStorageContent/HasADFContent from pageview and deleted the private duplicates in fetch.go.
There was a problem hiding this comment.
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 | | |||
There was a problem hiding this comment.
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_IDReply inline to this comment.
There was a problem hiding this comment.
Replaced the -o json example with awk parsing of the stable "ID:" line, per your suggested snippet.
There was a problem hiding this comment.
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.
| - `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`) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
SKILL.md now lists table/plain only, the --full -o json example is gone, and scripted extraction points at the stable ID: text line.
There was a problem hiding this comment.
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) | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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, sopage view --body-format xhtml --content-onlyis 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
adfcurrently validates only JSON syntax, so valid non-ADF values such asnull,[], or{}are passed to the API despite the new selector promising ADF input. Validate the ADF document boundary (at least a top-level object withtype: "doc", a supported version, and an arraycontent, 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 plainis 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 ascfl 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)) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 != "" |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
| - **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. |
There was a problem hiding this comment.
-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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
|
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. |
There was a problem hiding this comment.
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|xhtmlis documented as emitting an exact body, but this forwards the normal truncation settings to projection. Without--no-truncateor--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{ |
There was a problem hiding this comment.
--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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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|adfis 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\nfromrenderMessage, socfl page view ... --body-format xhtml --content-only | cfl page edit ... --body-format xhtmlchanges stored body bytes. SetNoNewlinefor 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).
--rawwas removed, and raw is no longer an unambiguous mode: XHTML and ADF must be selected separately with--body-format xhtmlor--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 |
| 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), |
There was a problem hiding this comment.
--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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
The script now probes ADF when XHTML is unavailable/empty and counts ADF-native pages as SKIP, restoring mixed-list behavior.
There was a problem hiding this comment.
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 | | |||
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
All four checklist/expectation sites updated to explicit --body-format xhtml/adf wording.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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-macrosis Markdown-only.cfl page view ID --body-format xhtml --show-macros(oradf) is rejected, but the adjacent flag table makes the options appear independently combinable. Qualify--show-macrosas Markdown-only/incompatible with ADF and XHTML, matching the documented--legacyrestriction.
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
SKIPand the script exits successfully without running its fidelity check.cfl page view --body-format adf --content-onlyintentionally 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 | |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Both flag-table rows now state --show-macros is Markdown-only and rejected with the exact body formats.
There was a problem hiding this comment.
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.
| # 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 |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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 jsoncheck 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 | |
There was a problem hiding this comment.
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.
…--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)
Implements items 1, 3, 4, and 7 of #455. Stacked on #457; will retarget to main when it merges.
--body-format markdown|adf|xhtml(omission = Markdown). Deletes--raw,--no-markdown,--storage.--legacyis valid only with Markdown input and is rejected otherwise.page edit --editoris representation-safe:.md/.json/.xhtmlbuffers with correct prefill; XHTML never transits the Markdown→ADF converter; metadata-only edits preserve body bytes.--body-format adf|xhtml.