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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions .planning/STATE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
gsd_state_version: 1.0
milestone: v1.2
milestone_name: Workflow, Parity & Release Infrastructure
status: completed
status: executing
stopped_at: Completed 18-02-PLAN.md
last_updated: "2026-03-28T18:26:28.046Z"
last_updated: "2026-03-28T18:40:40.131Z"
last_activity: 2026-03-28
progress:
total_phases: 7
completed_phases: 6
completed_phases: 7
total_plans: 19
completed_plans: 17
completed_plans: 19
percent: 100
---

Expand All @@ -21,13 +21,13 @@ progress:
See: .planning/PROJECT.md (updated 2026-03-28)

**Core value:** Give AI agents reliable, structured JSON access to Confluence content through a CLI
**Current focus:** Phase 16schema-gendocs
**Current focus:** Phase 18documentation-site

## Current Position

Phase: 18
Plan: Not started
Status: Phase 16 complete
Status: Executing Phase 18
Last activity: 2026-03-28

Progress: [██████████] 100% (2/2 plans in phase 16)
Expand Down
2 changes: 1 addition & 1 deletion cmd/avatar.go
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ func runAvatarAnalyze(cmd *cobra.Command, args []string) error {
return &cferrors.AlreadyWrittenError{Code: cferrors.ExitValidation}
}

pages, err := avatar.FetchUserPages(c, userFlag)
pages, err := avatar.FetchUserPages(cmd.Context(), c, userFlag)
if err != nil {
// Classify error: 401/unauthorized/auth → ExitAuth, else ExitError.
errStr := strings.ToLower(err.Error())
Expand Down
2 changes: 1 addition & 1 deletion cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ var rootCmd = &cobra.Command{

// Switch base URL to Atlassian API proxy.
resolved.BaseURL = fmt.Sprintf(
"https://api.atlassian.com/ex/confluence/%s/wiki/rest/api/v2",
"https://api.atlassian.com/ex/confluence/%s/wiki/api/v2",
effectiveCloudID,
)
}
Expand Down
2 changes: 1 addition & 1 deletion cmd/search.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ func fetchV1(cmd *cobra.Command, c *client.Client, fullURL string) ([]byte, int)
}
defer resp.Body.Close()

body, err := io.ReadAll(resp.Body)
body, err := io.ReadAll(io.LimitReader(resp.Body, 10<<20))
if err != nil {
apiErr := &cferrors.APIError{ErrorType: "connection_error", Message: "reading response body: " + err.Error()}
apiErr.WriteJSON(c.Stderr)
Expand Down
4 changes: 2 additions & 2 deletions internal/avatar/fetch.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ type contentPage struct {
// FetchUserPages fetches Confluence pages created by accountID.
// It uses the v1 content API with CQL search and returns up to 200 pages.
// The Body field of each PageRecord contains plain text (HTML stripped).
func FetchUserPages(c *client.Client, accountID string) ([]PageRecord, error) {
func FetchUserPages(ctx context.Context, c *client.Client, accountID string) ([]PageRecord, error) {
cql := fmt.Sprintf(`creator = "%s" AND type = page ORDER BY lastModified DESC`,
escapeCQLString(accountID))

Expand All @@ -77,7 +77,7 @@ func FetchUserPages(c *client.Client, accountID string) ([]PageRecord, error) {
const maxPages = 200

for nextURL != "" && len(records) < maxPages {
body, err := fetchContentV1(context.TODO(), c, nextURL)
body, err := fetchContentV1(ctx, c, nextURL)
if err != nil {
return nil, err
}
Expand Down
7 changes: 4 additions & 3 deletions internal/avatar/fetch_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package avatar_test

import (
"context"
"encoding/json"
"fmt"
"io"
Expand Down Expand Up @@ -144,7 +145,7 @@ func TestFetchUserPages_HappyPath(t *testing.T) {
defer srv.Close()

c := newTestClient(srv.URL + "/wiki/api/v2")
records, err := avatar.FetchUserPages(c, accountID)
records, err := avatar.FetchUserPages(context.Background(), c, accountID)
if err != nil {
t.Fatalf("FetchUserPages returned error: %v", err)
}
Expand Down Expand Up @@ -191,7 +192,7 @@ func TestFetchUserPages_EmptyResults(t *testing.T) {
defer srv.Close()

c := newTestClient(srv.URL + "/wiki/api/v2")
records, err := avatar.FetchUserPages(c, "user123")
records, err := avatar.FetchUserPages(context.Background(), c, "user123")
if err != nil {
t.Fatalf("FetchUserPages returned error: %v", err)
}
Expand All @@ -210,7 +211,7 @@ func TestFetchUserPages_HTTP401(t *testing.T) {
defer srv.Close()

c := newTestClient(srv.URL + "/wiki/api/v2")
records, err := avatar.FetchUserPages(c, "user123")
records, err := avatar.FetchUserPages(context.Background(), c, "user123")
if err == nil {
t.Fatalf("expected error on 401, got nil (records: %v)", records)
}
Expand Down
5 changes: 5 additions & 0 deletions internal/oauth2/threelo.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,11 @@ func waitForCallback(listener net.Listener, expectedState string, timeout time.D
return
}
code := r.URL.Query().Get("code")
if code == "" {
errCh <- fmt.Errorf("missing code parameter in callback")
http.Error(w, "Authorization failed: missing code parameter.", http.StatusBadRequest)
return
}
fmt.Fprint(w, "Authorization successful! You may close this window.")
codeCh <- code
})
Expand Down
2 changes: 1 addition & 1 deletion internal/template/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ func Save(name string, tmpl *Template) error {
return fmt.Errorf("marshal template: %w", err)
}

if err := os.WriteFile(path, data, 0o644); err != nil {
if err := os.WriteFile(path, data, 0o600); err != nil {
return fmt.Errorf("write template: %w", err)
}
return nil
Expand Down
Loading