Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
d549e18
Align live E2E contract with sync test plan
rgonek Mar 9, 2026
9a3b5c3
Update .gitignore to include workspace specifications and add new wor…
rgonek Mar 9, 2026
0123b37
fix(sync): publish attachments with resolved media ids
rgonek Mar 9, 2026
daff575
docs(test-log): add execution split for new sessions in follow-up plan
rgonek Mar 9, 2026
f4ace02
fix(push): align preflight validation with real push
rgonek Mar 9, 2026
1754436
fix(pull): reconcile incremental remote create and update events
rgonek Mar 9, 2026
b433881
fix(push): preserve local edits during pull-merge conflicts
rgonek Mar 9, 2026
55d87af
fix(converter): preserve plain text iso dates on round-trip
rgonek Mar 9, 2026
9fbcee6
feat(compat): surface folder fallback cause and archive semantics
rgonek Mar 9, 2026
f9e83bf
test(release): gate sandbox baseline and codify checklist
rgonek Mar 9, 2026
1285294
fix(sync): stabilize live sync e2e workflows
rgonek Mar 9, 2026
e251887
feat: harden production readiness follow-ups
rgonek Mar 10, 2026
b8342ac
fix: align live e2e content-state handling
rgonek Mar 10, 2026
a51cec7
fix: run live content-status e2e with tenant states
rgonek Mar 10, 2026
f84750c
feat(sync): implement push mutation logic for page upsert and deletion
rgonek Mar 10, 2026
d626a73
Refactor push tests: Move helper functions to a new test helpers file
rgonek Mar 10, 2026
d47e1bc
Add comprehensive tests for pull functionality in sync package
rgonek Mar 10, 2026
cc77157
Fix push preflight and dry-run regressions
rgonek Mar 10, 2026
0038eed
Fix CI lint findings
rgonek Mar 10, 2026
da937d3
feat(sync): enhance folder handling and diagnostics for push operations
rgonek Mar 10, 2026
9b8ec60
Fix live sync workflow regressions
rgonek Mar 10, 2026
2a4fb2e
Align local lint with CI
rgonek Mar 10, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ go.work.sum
workspace/
test-output/

!openspec/specs/workspace/

# Local Confluence sync state
.confluence-state.json
.confluence-search-index/
Expand Down
10 changes: 8 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -139,8 +139,14 @@ Validation failures must stop `push` immediately.
- **NEVER perform real tests (e.g. `conf pull` or `conf push`) targeting real Confluence spaces within the repository root.** This prevents accidental commits of synced Markdown content.
- **Agent Sandbox**: Use a temporary directory *outside* of the repository for full end-to-end integration tests with real data.
- E2E tests must run only against explicit sandbox configuration:
- `CONF_E2E_SANDBOX_SPACE_KEY` (required for all E2E workflows)
- `CONF_E2E_CONFLICT_PAGE_ID` (required for conflict workflow coverage)
- `CONF_E2E_DOMAIN`
- `CONF_E2E_EMAIL`
- `CONF_E2E_API_TOKEN`
- `CONF_E2E_PRIMARY_SPACE_KEY`
- `CONF_E2E_SECONDARY_SPACE_KEY`
- No other environment variables should be required to run `make test-e2e`
- Core E2E tests should create and clean up their own scratch pages instead of mutating shared seeded content
- Capability-specific live E2E suites (for example folder-fallback coverage) must still skip when the required tenant behavior is unavailable
- Never hardcode production page IDs or space keys in test code.
- If you must use a subdirectory for small tests, use the `workspace/` or `test-output/` directories (both gitignored).
- **Cleanup**: Always delete test content from `workspace/` or `test-output/` after completing a test session to keep the environment clean.
Expand Down
19 changes: 13 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ MAIN := ./cmd/conf
GO := go
GOFLAGS :=

.PHONY: build install test coverage-check fmt fmt-check lint clean
.PHONY: build install test test-unit test-e2e release-check coverage-check fmt fmt-check lint clean

## build: compile the conf binary
build:
Expand All @@ -13,17 +13,23 @@ build:
install:
$(GO) install $(MAIN)

## test: run all unit tests
test:
## test: run the default local test suite
test: test-unit

## test-unit: run all non-E2E tests
test-unit:
$(GO) test ./...

## coverage-check: enforce package coverage minimums
coverage-check:
$(GO) run ./tools/coveragecheck

## test-e2e: run all end-to-end tests (requires credentials)
## test-e2e: run all end-to-end tests (requires CONF_E2E_DOMAIN, CONF_E2E_EMAIL, CONF_E2E_API_TOKEN, CONF_E2E_PRIMARY_SPACE_KEY, CONF_E2E_SECONDARY_SPACE_KEY)
test-e2e: build
$(GO) test -v -tags=e2e ./cmd -run TestWorkflow
$(GO) test -v -tags=e2e ./cmd -run '^TestWorkflow_'

## release-check: run the release gate, including live sandbox E2E coverage
release-check: fmt-check lint test-unit test-e2e


## fmt: format all Go source files
Expand All @@ -34,9 +40,10 @@ fmt:
fmt-check:
$(GO) run ./tools/gofmtcheck

## lint: run static checks
## lint: run the same static checks used in CI
lint:
$(GO) vet ./...
golangci-lint run

## clean: remove build artifacts
clean:
Expand Down
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Write docs like code. Publish to Confluence with confidence. ✍️
## Why teams use `conf` ✨
- 📝 Markdown-first authoring with Confluence as the destination.
- 🛡️ Safe sync model with validation before remote writes.
- 👀 Clear preview step via `conf diff` before push.
- 👀 Clear preview step via `conf diff` for tracked pages and `conf push --preflight` for brand-new files.
- 🔎 Local full-text search across synced Markdown with SQLite or Bleve backends.
- 🤖 Works in local repos and automation pipelines.

Expand Down Expand Up @@ -38,7 +38,7 @@ conf init
`conf init` prepares Git metadata, `.gitignore`, and `.env` scaffolding, and creates an initial commit when it initializes a new Git repository.
If `ATLASSIAN_*` or legacy `CONFLUENCE_*` credentials are already set in the environment, `conf init` writes `.env` from them without prompting.

`conf pull` mirrors Confluence hierarchy locally by placing folders and child pages in nested directories. Pages with children use `<Page>/<Page>.md` so they are distinct from pure folders. Leaf-page title renames can keep the existing Markdown path when the effective parent directory is unchanged, but pages that own subtree directories move when their self-owned directory segment changes. Hierarchy moves and ancestor/path-segment sanitization changes are surfaced as `PAGE_PATH_MOVED` notes in `conf pull`/`conf diff`, and `conf status` previews tracked moves before the next pull.
`conf pull` mirrors Confluence hierarchy locally by placing folders and child pages in nested directories. Pages with children use `<Page>/<Page>.md` so they are distinct from pure folders. Incremental pulls reconcile remote creates, updates, and deletes without requiring `--force`. Leaf-page title renames can keep the existing Markdown path when the effective parent directory is unchanged, but pages that own subtree directories move when their self-owned directory segment changes. Hierarchy moves and ancestor/path-segment sanitization changes are surfaced as `PAGE_PATH_MOVED` notes in `conf pull`/`conf diff`, and `conf status` previews tracked moves before the next pull.

## Quick flow 🔄
> ⚠️ **IMPORTANT**: If you are developing `conf` itself, NEVER run sync commands against real Confluence spaces in the repository root. This prevents accidental commits of synced documentation. Use a separate sandbox folder.
Expand All @@ -56,6 +56,9 @@ conf validate ENG
# 3) Preview local vs remote
conf diff ENG

# Preview a brand-new file before its first push
conf push .\ENG\New-Page.md --preflight

# 4) Push local changes
conf push ENG --on-conflict=cancel
```
Expand All @@ -66,15 +69,19 @@ conf push ENG --on-conflict=cancel
- Target rule: `.md` suffix means file mode; otherwise space mode (`SPACE_KEY`)
- Required auth: `ATLASSIAN_DOMAIN`, `ATLASSIAN_EMAIL`, `ATLASSIAN_API_TOKEN`
- Extension support: PlantUML is the only first-class rendered extension handler; Mermaid is preserved as code, and raw `adf:extension` / unknown macro handling is best-effort and should be sandbox-validated before relying on it
- Status scope: `conf status` reports Markdown page drift only; use `git status` or `conf diff` for attachment-only changes
- Cross-space links are preserved as readable remote links rather than rewritten to local Markdown paths
- Removing tracked Markdown pages archives the corresponding remote page; follow-up pull removes the archived page from tracked local state
- `pull` and `push` are serialized per repository with a workspace lock, so concurrent mutating runs fail fast with a clear lock message
- `push` failures retain recovery refs and print exact `conf recover`, `git switch`, and cleanup commands for the retained run
- Status scope: `conf status` reports Markdown page drift only; use `git status` for local asset changes or `conf diff` for attachment-aware remote inspection. There is no attachment-aware `conf status` mode yet
- Label rules: labels are trimmed, lowercased, deduplicated, and sorted; empty labels and labels containing whitespace are rejected
- Search filters: `--space`, repeatable `--label`, `--heading`, `--created-by`, `--updated-by`, date bounds, and `--result-detail`
- Git remote is optional (local Git is enough)

## Docs 📚
- Usage and command reference: `docs/usage.md`
- Feature and tenant compatibility matrix: `docs/compatibility.md`
- Automation, CI behavior, and live sandbox smoke-test runbook: `docs/automation.md`
- Automation, CI behavior, live sandbox release checklist, and smoke-test runbook: `docs/automation.md`
- Changelog: `CHANGELOG.md`
- Security policy: `SECURITY.md`
- Support policy: `SUPPORT.md`
Expand All @@ -92,5 +99,7 @@ conf push ENG --on-conflict=cancel
## Development 🧑‍💻
- `make build`
- `make test`
- `make test-e2e` (requires explicit `CONF_E2E_*` sandbox environment)
- `make release-check` (runs `fmt-check`, `lint`, `test`, and live sandbox E2E as the release gate)
- `make fmt`
- `make lint`
28 changes: 28 additions & 0 deletions cmd/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ func runDiff(cmd *cobra.Command, target config.Target) (runErr error) {
if err := ensureWorkspaceSyncReady("diff"); err != nil {
return err
}
if err := ensureDiffTargetSupportsRemoteComparison(target); err != nil {
return err
}
initialCtx, err := resolveInitialPullContext(target)
if err != nil {
return err
Expand Down Expand Up @@ -209,6 +212,31 @@ func runDiff(cmd *cobra.Command, target config.Target) (runErr error) {
return err
}

func ensureDiffTargetSupportsRemoteComparison(target config.Target) error {
if !target.IsFile() {
return nil
}

absPath, err := filepath.Abs(target.Value)
if err != nil {
return err
}

doc, err := fs.ReadMarkdownDocument(absPath)
if err != nil {
return fmt.Errorf("read target file %s: %w", target.Value, err)
}
if strings.TrimSpace(doc.Frontmatter.ID) != "" {
return nil
}

return fmt.Errorf(
"target file %s has no id, so diff cannot compare it to an existing remote page; for a brand-new page preview, run `conf push --preflight %s`",
target.Value,
target.Value,
)
}

func runDiffFileMode(
ctx context.Context,
out io.Writer,
Expand Down
Loading