-
Notifications
You must be signed in to change notification settings - Fork 0
Release automation + palette customization #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
@coderabbitai review |
✅ Actions performedReview triggered.
|
Summary by CodeRabbit
WalkthroughAdds repo tooling, CI, docs-generation pipeline (generator, CLI, transclusion), release automation, git hooks and helper scripts, planning data and generated documentation, TUI/mock data, tests, Makefile targets, and module dependency updates. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
actor Developer
participant Hook as pre-commit
participant Make as Makefile
participant CLI as docs-components
participant Gen as Generator
participant Trans as markdown-transclusion
Developer->>Hook: git commit
Hook->>Make: make fmt-check / make lint / make test / make docs
Make->>CLI: go run ./cmd/docs-components --repo ...
CLI->>Gen: Generate(ctx, options)
Gen-->>CLI: write components (md, graphs)
CLI->>Trans: RunTransclusion per document
Trans-->>CLI: rendered outputs
CLI-->>Make: success
Make-->>Hook: success -> commit proceeds
sequenceDiagram
autonumber
actor ReleaseEng
participant RelCLI as hubless release
participant Releaser
participant Git
ReleaseEng->>RelCLI: hubless release --version vX.Y.Z [--dry-run]
RelCLI->>Releaser: Run(ctx, Options)
alt runChecks (if not skipped)
Releaser->>Releaser: runChecks (fmt, lint, test, docs)
end
Releaser->>Releaser: ensureClean()
Releaser->>Releaser: ensureTagDoesNotExist(vX.Y.Z)
alt DryRun
Releaser-->>RelCLI: preview tag + notes
else create tag
Releaser->>Git: git tag -a vX.Y.Z -F notes.tmp
Git-->>Releaser: tag created
Releaser-->>RelCLI: success
end
RelCLI-->>ReleaseEng: next steps (push / release)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Poem
Pre-merge checks and finishing touches✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 84
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (7)
docs/TechSpec.md (1)
38-49
: Add languages to all fenced blocks and fix MD040 across the doc.Use text, bash, mermaid, ini, etc., as appropriate. This doc trips multiple MD040s.
docs/reference/update-progress-algorithm.md (1)
15-21
: Inconsistent env var name (GITMIND_ROOT vs HUBLESS_ROOT)Pick one. Section 3 switched to HUBLESS_ROOT; Inputs still list GITMIND_ROOT. Fix the Inputs section to avoid lying to readers.
- - Optional `--root` CLI flag or `GITMIND_ROOT` environment variable to locate the GitMind checkout. + - Optional `--root` CLI flag or `HUBLESS_ROOT` environment variable to locate the repository root.Also applies to: 23-28
docs/reference/implementation-skeleton.md (5)
60-84
: events.go snippet is missing imports; it won’t compile. Add time import.You use time.Time without importing time.
Apply this diff inside the code block:
-package domain +package domain + +import "time"
88-135
: issue.go snippet references undefined getString and misses time import. Add both.Replay uses getString but you never defined it; Issue.LastUpdated uses time.Time but there’s no import.
Apply this diff inside the code block:
-package domain +package domain + +import "time" @@ func Replay(id IssueID, events []Event) Issue { @@ } + +// getString returns payload[key] if it is a non-empty string, otherwise def. +func getString(m map[string]any, key string, def string) string { + if m == nil { + return def + } + if v, ok := m[key]; ok { + if s, ok := v.(string); ok && s != "" { + return s + } + } + return def +} + +// PriorityOrder provides a stable sort order for priorities. +func PriorityOrder(p Priority) int { + switch p { + case PriorityHigh: + return 0 + case PriorityMedium: + return 1 + case PriorityLow: + return 2 + default: + return 3 + } +}
140-188
: application/services.go snippet: missing imports for context/sort and missing package imports.You reference domain, ports, context, sort but import none. Add proper imports.
Apply this diff inside the code block:
-package application +package application + +import ( + "context" + "sort" + + "github.com/flyingrobots/hubless/internal/domain" + "github.com/flyingrobots/hubless/internal/ports" +)
193-201
: ports/repository.go snippet: missing imports for context/time and domain.Interface references context.Context, time.Time, and domain types; import them.
Apply this diff inside the code block:
-package ports +package ports + +import ( + "context" + "time" + + "github.com/flyingrobots/hubless/internal/domain" +)
230-262
: Add missing imports and stop swallowing svc.List errors — fix docs code blocks too.
- docs/reference/implementation-skeleton.md (lines 230–262): the snippet is missing imports (context, tea, list, viewport, application) and drops the error from svc.List. Apply this diff:
-package tui +package tui + +import ( + "context" + + tea "github.com/charmbracelet/bubbletea" + "github.com/charmbracelet/bubbles/list" + "github.com/charmbracelet/bubbles/viewport" + + "github.com/flyingrobots/hubless/internal/application" +) @@ func New(ctx context.Context, svc *application.Service, width, height int) Model { - issues, _ := svc.List(ctx) + issues, err := svc.List(ctx) + if err != nil { + // In a real app, handle/log; for a skeleton, degrade gracefully. + issues = nil + }
- Additional issue: other code blocks in docs/reference/implementation-skeleton.md reference time.Time without importing time — see lines 77–81, 106–110, 197–200. Add
import "time"
(or an import block) to those examples.Fix these now; don’t ship broken/compilable-unfriendly samples.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
⛔ Files ignored due to path filters (4)
@hubless/issues/generated/archive.md
is excluded by!**/generated/**
@hubless/issues/generated/tasks.md
is excluded by!**/generated/**
@hubless/roadmap/generated/README.md
is excluded by!**/generated/**
go.sum
is excluded by!**/*.sum
📒 Files selected for processing (60)
.editorconfig
(1 hunks).githooks/pre-commit
(1 hunks).github/workflows/docs.yml
(1 hunks).github/workflows/quality.yml
(1 hunks).golangci.yml
(1 hunks)@hubless/README.md
(1 hunks)@hubless/issues/stories/hubless-story-0007.json
(1 hunks)@hubless/issues/tasks.archive.md
(0 hunks)@hubless/issues/tasks/hubless-m0-task-0005.json
(1 hunks)@hubless/issues/templates/archive.md
(1 hunks)@hubless/issues/templates/tasks.md
(1 hunks)@hubless/roadmap/README.md
(0 hunks)@hubless/roadmap/features/hubless-feature-docs-components.json
(1 hunks)@hubless/roadmap/milestones/hubless-milestone-m0-foundations.json
(2 hunks)@hubless/roadmap/templates/README.md
(1 hunks)AGENTS.md
(1 hunks)CHANGELOG.md
(1 hunks)CHANGELOG.template.md
(1 hunks)CONTRIBUTING.md
(2 hunks)Dockerfile.release-test
(1 hunks)Makefile
(1 hunks)README.md
(1 hunks)cmd/docs-components/main.go
(1 hunks)cmd/release/main.go
(1 hunks)docs/PRD.md
(6 hunks)docs/TechSpec.md
(10 hunks)docs/components/issues/archived-stories.md
(1 hunks)docs/components/issues/archived-tasks.md
(1 hunks)docs/components/issues/changelog.md
(1 hunks)docs/components/issues/status-summary.md
(1 hunks)docs/components/issues/tasks-table.md
(1 hunks)docs/components/roadmap/dependencies-graph.md
(1 hunks)docs/components/roadmap/dependencies.md
(1 hunks)docs/components/roadmap/features-table.md
(1 hunks)docs/components/roadmap/milestones-table.md
(1 hunks)docs/components/roadmap/progress.md
(1 hunks)docs/components/roadmap/stories-table.md
(1 hunks)docs/reference/archive-structure.md
(1 hunks)docs/reference/docs-components-plan.md
(1 hunks)docs/reference/implementation-skeleton.md
(9 hunks)docs/reference/palettes.json
(1 hunks)docs/reference/palettes.schema.json
(1 hunks)docs/reference/release-notes.md
(1 hunks)docs/reference/release-notes.template.md
(1 hunks)docs/reference/rfcs/0001-release-automation.md
(1 hunks)docs/reference/rfcs/FEEDBACK-0001-release-automation.md
(1 hunks)docs/reference/update-progress-algorithm.md
(5 hunks)go.mod
(1 hunks)internal/docscomponents/generator.go
(1 hunks)internal/docscomponents/generator_test.go
(1 hunks)internal/docscomponents/transclusion.go
(1 hunks)internal/mock/data.go
(1 hunks)internal/release/releaser.go
(1 hunks)internal/ui/tui/mock/app.go
(1 hunks)internal/ui/tui/mock/profile.go
(1 hunks)internal/ui/tui/mock/styles.go
(1 hunks)scripts/install-git-hooks.sh
(1 hunks)scripts/render-docs.sh
(1 hunks)scripts/test-release-docker.sh
(1 hunks)scripts/verify-docs.sh
(1 hunks)
💤 Files with no reviewable changes (2)
- @hubless/issues/tasks.archive.md
- @hubless/roadmap/README.md
🧰 Additional context used
🧬 Code graph analysis (6)
internal/docscomponents/transclusion.go (1)
internal/release/releaser.go (1)
New
(28-39)
internal/ui/tui/mock/app.go (1)
internal/ui/tui/mock/styles.go (1)
Styles
(6-9)
internal/docscomponents/generator.go (1)
internal/release/releaser.go (1)
New
(28-39)
cmd/docs-components/main.go (2)
internal/docscomponents/generator.go (2)
NewGenerator
(34-71)GeneratorOptions
(19-24)internal/docscomponents/transclusion.go (2)
TransclusionOptions
(14-20)RunTransclusion
(23-76)
cmd/release/main.go (1)
internal/release/releaser.go (3)
New
(28-39)Options
(17-22)ErrVersionRequired
(14-14)
internal/docscomponents/generator_test.go (1)
internal/docscomponents/generator.go (2)
NewGenerator
(34-71)GeneratorOptions
(19-24)
🪛 markdownlint-cli2 (0.18.1)
docs/components/issues/archived-tasks.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/components/roadmap/stories-table.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/components/roadmap/milestones-table.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/components/roadmap/dependencies-graph.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/components/issues/archived-stories.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
CHANGELOG.md
25-25: Files should end with a single newline character
(MD047, single-trailing-newline)
docs/reference/release-notes.md
15-15: Files should end with a single newline character
(MD047, single-trailing-newline)
docs/reference/docs-components-plan.md
3-3: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
4-4: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
10-10: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
11-11: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
16-16: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
39-39: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
40-40: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
42-42: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
47-47: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
49-49: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
53-53: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
63-63: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
68-68: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
69-69: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
74-74: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
75-75: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
80-80: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
81-81: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
docs/reference/update-progress-algorithm.md
46-46: Spaces inside code span elements
(MD038, no-space-in-code)
86-86: Spaces inside code span elements
(MD038, no-space-in-code)
docs/components/roadmap/features-table.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/components/roadmap/progress.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/reference/rfcs/FEEDBACK-0001-release-automation.md
1-1: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/reference/rfcs/0001-release-automation.md
3-3: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
6-6: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
7-7: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
15-15: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
16-16: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
20-20: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
21-21: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
21-21: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
109-109: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
110-110: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
115-115: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
116-116: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
120-120: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
121-121: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
docs/components/issues/status-summary.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/components/issues/tasks-table.md
2-2: First line in a file should be a top-level heading
(MD041, first-line-heading, first-line-h1)
docs/TechSpec.md
71-71: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
78-78: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
99-99: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
163-163: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
177-177: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
189-189: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
README.md
4-4: Blank line inside blockquote
(MD028, no-blanks-blockquote)
13-13: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
13-13: Emphasis style
Expected: asterisk; Actual: underscore
(MD049, emphasis-style)
38-38: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
39-39: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
43-43: Trailing spaces
Expected: 0 or 2; Actual: 4
(MD009, no-trailing-spaces)
45-45: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🪛 YAMLlint (1.37.1)
.github/workflows/docs.yml
[warning] 3-3: truthy value should be one of [false, true]
(truthy)
[error] 5-5: too many spaces inside brackets
(brackets)
[error] 5-5: too many spaces inside brackets
(brackets)
.github/workflows/quality.yml
[warning] 3-3: truthy value should be one of [false, true]
(truthy)
[error] 5-5: too many spaces inside brackets
(brackets)
[error] 5-5: too many spaces inside brackets
(brackets)
🪛 Checkov (3.2.334)
Dockerfile.release-test
[low] 1-25: Ensure that HEALTHCHECK instructions have been added to container images
(CKV_DOCKER_2)
[low] 1-25: Ensure that a user for the container has been created
(CKV_DOCKER_3)
🪛 Shellcheck (0.11.0)
scripts/verify-docs.sh
[info] 32-32: Expansions inside ${..} need to be quoted separately, otherwise they match as patterns.
(SC2295)
🪛 checkmake (0.2.2)
Makefile
[warning] 1-1: Missing required phony target "all"
(minphony)
[warning] 1-1: Missing required phony target "clean"
(minphony)
🪛 actionlint (1.7.7)
.github/workflows/quality.yml
30-30: the runner of "golangci/golangci-lint-action@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🔇 Additional comments (32)
.editorconfig (3)
4-10
: Global space indentation will bleed into Go if the override isn’t fixed.This becomes a CI pain factory once gofmt/goimports run. After applying the Go override fix above, this is fine; otherwise you’ll get endless churn.
Run:
gofmt -l .
and ensure zero files reported.
16-18
: Makefile override is correct.Tabs for Makefiles are required. Good.
19-20
: Docs trailing whitespace exemption is sensible.Markdown often needs two-space breaks. Good call.
CHANGELOG.template.md (2)
5-8
: Template/transclusion looks sane.The reference to
docs/reference/release-notes.md
aligns with the generator flow.
11-11
: Path consistency check for archives.Ensure
@hubless/issues/generated/archive.md
actually resolves at build time (your other docs usedocs/components/...
). If the generator emits under that alias, all good; if not, align the path.go.mod (3)
5-8
: Direct deps look fine; make sure they match actual imports.Run
go mod tidy
after fixing thego
directive so indirects settle correctly.
10-28
: Sanity-check indirects and compat.After correcting the
go
directive, run:
go mod tidy
go build ./...
This will flush any stale indirects and ensure
x/sys
/x/text
versions are consistent withbubbletea
.
3-3
: Invalidgo
directive: patch versions aren’t allowed.
go.mod
’sgo
line must be a major.minor (e.g.,go 1.25
), not1.25.1
. If you need to pin a patch, use thetoolchain
directive.-go 1.25.1 +go 1.25 +toolchain go1.25.1Likely an incorrect or invalid review comment.
docs/components/roadmap/dependencies.md (3)
27-33
: Stories link pathing looks consistent.Uses
../issues/stories/...
as expected.
6-10
: Milestones/Features links are plausible if those JSONs are collocated.If those JSONs actually live under
@hubless/roadmap/...
instead, consider aligning link roots for consistency.
38-42
: Fix broken task links — use ../issues/tasks/... (docs/components/roadmap/dependencies.md:38-42)Links point to tasks/... relative to this file and will 404; change to ../issues/tasks/... or mirror the JSONs under docs/components/roadmap/tasks/. Automated link-check failed in the sandbox (script error) — confirm file locations or run the link-check locally.
-| [hubless/m0/task/0004](tasks/hubless-m0-task-0004.json) | hubless/m0/task/0001 | -| [hubless/m0/task/0005](tasks/hubless-m0-task-0005.json) | hubless/m0/task/0004 | -| [hubless/m1/task/0002](tasks/hubless-m1-task-0002.json) | hubless/m0/task/0001 | -| [hubless/m1/task/0003](tasks/hubless-m1-task-0003.json) | hubless/m1/task/0002 | +| [hubless/m0/task/0004](../issues/tasks/hubless-m0-task-0004.json) | hubless/m0/task/0001 | +| [hubless/m0/task/0005](../issues/tasks/hubless-m0-task-0005.json) | hubless/m0/task/0004 | +| [hubless/m1/task/0002](../issues/tasks/hubless-m1-task-0002.json) | hubless/m0/task/0001 | +| [hubless/m1/task/0003](../issues/tasks/hubless-m1-task-0003.json) | hubless/m1/task/0002 |Dockerfile.release-test (1)
1-1
: Base image pinning and version reality check.
golang:1.25
tag: confirm it exists in your registry at CI time. Consider digest pinning for reproducibility:-FROM golang:1.25 +# e.g., pin to a digest (example digest; update to the real one) +FROM golang:1.25@sha256:XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXscripts/test-release-docker.sh (1)
1-3
: Good: strict mode enabled.
set -euo pipefail
is the right default.docs/reference/archive-structure.md (1)
3-14
: Docs flow is clear and consistent with generator model.Solid overview; matches the JSON-source-first approach.
@hubless/roadmap/templates/README.md (1)
7-37
: Template is coherent; transclusion targets align with generated components.Looks good. Keep schema paths and filenames in lockstep with the generator.
AGENTS.md (1)
18-18
: Ensuremake hooks
exists and is idempotent.Confirm the Makefile target installs .githooks via scripts/install-git-hooks.sh and is safe to re-run.
docs/PRD.md (1)
95-102
: Table looks good; keep the scope/phasing crisp.Release phases are clear and incremental.
docs/reference/release-notes.template.md (1)
1-11
: Template looks sane.Header present, transclusions are clear. No blockers.
docs/components/roadmap/milestones-table.md (1)
4-9
: Verify milestone JSON links resolve — manual check required.
Automated verification failed in the sandbox (process-substitution / /dev/fd errors). Do not merge until everymilestones/*.json
link in docs/components/roadmap/milestones-table.md exists under docs/components/roadmap/milestones/. Run locally:
for rel in $(perl -nle 'while (/]((milestones/[^)]+.json))/g){print $1}' docs/components/roadmap/milestones-table.md); do p=docs/components/roadmap/$rel; [ -f "$p" ] && echo OK:$p || echo MISSING:$p; donedocs/components/roadmap/stories-table.md (1)
4-10
: Fix and validate all relative links now — DO NOT ship broken links.docs/components/roadmap/stories-table.md (lines 4–10) point to ../issues/stories/*.json; repo-wide verifier errored with "re.error: unknown extension ?! at position 13", so verification is inconclusive — confirm those JSON targets exist or rerun the verifier after fixing the regex.
@hubless/issues/templates/archive.md (1)
7-11
: FIX TRANSCLUSION PATHS — use ../../../docs/components/issues/... (NOT ../../)The two-level "../../docs/..." is incorrect (it resolves to @hubless/docs/..., which doesn't exist). The target files live at repo-root "docs/components/issues/..."; use a three-level relative path.
File: @hubless/issues/templates/archive.md
Lines: 7-11-![[docs/components/issues/archived-stories.md]] +![[../../../docs/components/issues/archived-stories.md]] -![[docs/components/issues/archived-tasks.md]] +![[../../../docs/components/issues/archived-tasks.md]]Verify resolution:
#!/usr/bin/env bash set -euo pipefail for target in "docs/components/issues/archived-stories.md" "docs/components/issues/archived-tasks.md"; do src="@hubless/issues/templates/archive.md" rel="../../../${target}" python - "$src" "$rel" "$target" <<'PY' import os,sys src, rel, repo_rel = sys.argv[1], sys.argv[2], sys.argv[3] p1 = os.path.normpath(os.path.join(os.path.dirname(src), rel)) p2 = os.path.normpath(os.path.join(os.getcwd(), repo_rel)) print(f"{repo_rel}\n corrected-relative-> {p1} exists={os.path.exists(p1)}\n repo-root-> {p2} exists={os.path.exists(p2)}") PY doneLikely an incorrect or invalid review comment.
docs/reference/palettes.json (1)
3-8
: Wrong — this is already wired: generator honors --palette-file / --graph-palette and maps palettetext
→ Mermaidcolor
.
- Flags: cmd/docs-components/main.go defines --graph-palette and --palette-file.
- Implementation: internal/docscomponents/generator.go loads/merges palettes (defaultPalettes + mergePaletteFile) and emits classDef using Fill/Stroke/Text:
classDef %s fill:%s,stroke:%s,color:%s,stroke-width:1px;
.- Tests: internal/docscomponents/generator_test.go verifies custom palette values are present in the generated graph.
If you want configurable stroke width: add
strokeWidth
to the palette schema and raw/paletteColor structs, parse it in mergePaletteFile, and use it in the classDef output; update tests accordingly.Likely an incorrect or invalid review comment.
@hubless/issues/stories/hubless-story-0007.json (1)
1-22
: LGTM.Schema looks consistent with other story docs. No blocking issues.
@hubless/issues/tasks/hubless-m0-task-0005.json (1)
1-88
: LGTM — schema looks consistent and complete.Status, badges, and timestamps line up. No nits.
@hubless/roadmap/milestones/hubless-milestone-m0-foundations.json (1)
6-9
: LGTM — milestone wiring is coherent.Timeframe, features, and tasks list are sane.
Also applies to: 20-22, 24-27
internal/docscomponents/generator_test.go (1)
14-38
: Solid end-to-end coverageGood fixture setup and validation across generated components. This will catch regressions in options normalization and file I/O paths.
Also applies to: 71-113
internal/mock/data.go (1)
7-18
: Clean, useful mocksGood, focused mock data with realistic timestamps and simple types. This will make UI demos sane.
If you want to avoid typos drifting into UIs, consider promoting
Status
andPriority
to typed string enums with helper constructors in a follow-up.Also applies to: 35-57, 59-174
@hubless/roadmap/features/hubless-feature-docs-components.json (1)
1-23
: Cross-reference integrity — verify the referenced milestone/story/feature/task IDs exist (auto-check failed)rg in the sandbox skipped files; I could not confirm the artifacts. Do not merge with dangling refs — ensure these IDs resolve or add/remove the references:
- hubless/milestone/m0-foundations → check @hubless/roadmap/milestones/hubless-milestone-m0-foundations.json
- hubless/story/0007 → check @hubless/issues/stories/hubless-story-0007.json
- hubless/feature/repo-foundations → check @hubless/roadmap/features/hubless-feature-repo-foundations.json (or wherever features are declared)
- hubless/m0/task/0005 → locate the task file under your tasks/m0 or @hubless/roadmap/tasks location
Verify these exist and update the feature file accordingly before merging.
internal/docscomponents/generator.go (4)
158-211
: Roadmap tables: no header when records are empty (because you error earlier). After making readers tolerant, keep headers even with zero rows.Your current code already writes headers unconditionally—good. With tolerant readers, you’ll generate empty tables (headers only). That’s the right behavior. No change needed; just confirming.
1090-1107
: Option normalization: default direction/palette is fine. Also default GraphClusters=false; document it.No code change; update docs/CLI help to mention defaults.
Add CLI help text accordingly.
922-943
: Progress bar rounding can overflow width with certain ratios; clamp before building the bar.You clamp after rounding, but also clamp ratio to [0,1]. It’s mostly fine; tighten the math to avoid off‑by‑one on pathological floats.
Apply this diff:
- filled := int(math.Round(ratio * width)) - if filled > width { - filled = width - } + filled := int(math.Round(math.Min(1, math.Max(0, ratio)) * width))Likely an incorrect or invalid review comment.
84-132
: Approved — Generate tolerates missing @Hubless dirs; tests pass.Ran the provided script (removed @Hubless; ran TestGenerator); all Generator tests passed — no code changes required. Ensure readers return nil/empty slices instead of errors and do not rely on old error behavior.
- `generated/` – Output Markdown rendered from templates (do not edit, regenerated via `make docs`). | ||
- `issues/` – stories & tasks following the task lifecycle. | ||
- `tasks/*.json` and `stories/*.json` – source of truth (hand maintained). | ||
- `templates/` – Markdown templates to document task/story tables (edit as needed). | ||
- `generated/` – Output Markdown rendered from templates (`tasks.md`, `archive.md`, etc.). Do not edit by hand. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Add a bold “DO NOT EDIT” banner to generated/ directories.
Double down on the warning to prevent accidental edits in reviews.
- - `generated/` – Output Markdown rendered from templates (do not edit, regenerated via `make docs`).
+ - `generated/` – Output Markdown rendered from templates.
+ - DO NOT EDIT: Regenerated via `make docs`; changes here will be overwritten.
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @hubless/README.md around lines 9 to 13, the generated/ directory description
needs a stronger warning: update the README text for each generated/ entry to
include a bold "DO NOT EDIT" banner (e.g., prepend "**DO NOT EDIT — generated
files**" or similar) and clarify that these files are regenerated via make docs;
also add instruction to include an identical bold banner comment at the top of
any generated files themselves so reviewers see the warning in diffs.
RUN apt-get update && apt-get install -y --no-install-recommends \ | ||
curl \ | ||
git \ | ||
nodejs \ | ||
npm \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
Security hardening: run as non-root.
You’re installing tools as root and then running an interactive shell as root. Create a non-root user and drop privileges.
RUN apt-get update && apt-get install -y --no-install-recommends \
curl \
git \
nodejs \
npm \
&& rm -rf /var/lib/apt/lists/*
+RUN useradd -m -u 10001 appuser
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Dockerfile.release-test around lines 5 to 11, the image installs packages and
leaves the container running as root; create a non-root user and drop
privileges: add a dedicated user and group (with a home directory), chown any
app/work directories and relevant files to that user, set WORKDIR to that home
or app dir, switch to the non-root user with USER before any RUN/CMD/ENTRYPOINT
that should run unprivileged, and ensure any subsequent package installs or npm
operations that must run as root are done before switching users; keep
permissions minimal and avoid giving sudo.
npm \ | ||
&& rm -rf /var/lib/apt/lists/* | ||
|
||
RUN npm install -g markdown-transclusion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pin npm dependency.
Global install without a version is non-reproducible. Pin it:
-RUN npm install -g markdown-transclusion
+RUN npm install -g markdown-transclusion@<known-good-version>
Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In Dockerfile.release-test around line 12, the RUN npm install -g
markdown-transclusion is non-reproducible because it installs the latest
package; change it to pin a specific version (for example
[email protected]) by updating the command to npm install -g
markdown-transclusion@<stable-version> or use a version variable/ARG and
reference a vetted version number so builds are deterministic.
**Old way:** | ||
ticket → | ||
website → | ||
bookkeeping → | ||
copy-paste → | ||
PR. | ||
**New way:** | ||
```bash |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Kill trailing spaces and surround fenced block with blank lines (MD009/MD031).
- **Old way:**
- ticket →
- website →
- bookkeeping →
- copy-paste →
- PR.
+ **Old way:**
+ ticket →
+ website →
+ bookkeeping →
+ copy-paste →
+ PR.
@@
- ```bash
+
+ ```bash
git hubless start issue 34
@@
- # PR updated, undrafted, review requested
+ # PR updated, undrafted, review requested
<details>
<summary>🧰 Tools</summary>
<details>
<summary>🪛 markdownlint-cli2 (0.18.1)</summary>
38-38: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
---
39-39: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
---
43-43: Trailing spaces
Expected: 0 or 2; Actual: 4
(MD009, no-trailing-spaces)
---
45-45: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
</details>
</details>
<details>
<summary>🤖 Prompt for AI Agents</summary>
In README.md around lines 38-45, remove trailing whitespace from the listed
lines and ensure the fenced code block is surrounded by blank lines: add an
empty line immediately before the opening bash and an empty line immediately after the closing
; also remove any extra spaces before the backticks so the
fence lines start at column 1.
</details>
<!-- fingerprinting:phantom:triton:chinchilla -->
<!-- This is an auto-generated comment by CodeRabbit -->
docker run --rm "$IMAGE_NAME" /bin/bash -lc "\ | ||
export PATH=/usr/local/go/bin:/go/bin:\$PATH && \ | ||
cd /app && \ | ||
git remote -v && \ | ||
go run ./cmd/release --version $VERSION --dry-run --skip-checks && \ | ||
go run ./cmd/release --version $VERSION --skip-checks && \ | ||
git tag --list && \ | ||
ls docs/reference | grep release-notes.md\ | ||
" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Make output debuggable and guard for tag collisions.
- Add
set -x
inside the container so failures show context. - If the tag already exists (reruns), the second
go run
may fail. Consider deleting existing local tag first in this isolated repo (optional).
-docker run --rm "$IMAGE_NAME" /bin/bash -lc "\
+docker run --rm "$IMAGE_NAME" /bin/bash -lc "\
export PATH=/usr/local/go/bin:/go/bin:\$PATH && \
+ set -x && \
cd /app && \
git remote -v && \
go run ./cmd/release --version $VERSION --dry-run --skip-checks && \
go run ./cmd/release --version $VERSION --skip-checks && \
git tag --list && \
ls docs/reference | grep release-notes.md\
"
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
docker run --rm "$IMAGE_NAME" /bin/bash -lc "\ | |
export PATH=/usr/local/go/bin:/go/bin:\$PATH && \ | |
cd /app && \ | |
git remote -v && \ | |
go run ./cmd/release --version $VERSION --dry-run --skip-checks && \ | |
go run ./cmd/release --version $VERSION --skip-checks && \ | |
git tag --list && \ | |
ls docs/reference | grep release-notes.md\ | |
" | |
docker run --rm "$IMAGE_NAME" /bin/bash -lc "\ | |
export PATH=/usr/local/go/bin:/go/bin:\$PATH && \ | |
set -x && \ | |
cd /app && \ | |
git remote -v && \ | |
go run ./cmd/release --version $VERSION --dry-run --skip-checks && \ | |
go run ./cmd/release --version $VERSION --skip-checks && \ | |
git tag --list && \ | |
ls docs/reference | grep release-notes.md\ | |
" |
🤖 Prompt for AI Agents
In scripts/test-release-docker.sh around lines 12 to 20, add debugging and a
guard for tag collisions: enable shell tracing inside the container by running
set -x before executing the sequence of commands so failures show the expanded
commands and context; and before invoking the non-dry-run release (the second go
run), delete any existing local tag for the release (e.g. run git tag -d
"v$VERSION" || true) so a previous tag in this isolated test repo won’t cause
the release to fail.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
docs docs-components release-docs: | ||
./scripts/render-docs.sh | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Add a sane default ‘all’, provide ‘clean’, and simplify gofmt usage.
Stop making contributors guess the entrypoint, and run with -s
while you’re at it.
+all: fmt-check lint test docs-verify
+
+clean:
+ @true # add cleanup of generated artifacts if/when you have any
+
fmt:
- find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' -print0 | xargs -0 gofmt -w
+ find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' -print0 | xargs -0 gofmt -s -w
Also applies to: 26-45
🤖 Prompt for AI Agents
In Makefile lines 3-5 (and similarly 26-45) add an explicit default target named
"all" that depends on docs docs-components release-docs so contributors don’t
have to guess the entrypoint; add a "clean" target that removes generated
documentation/artifacts; simplify the gofmt invocation to run with the -s flag
and write changes in-place (e.g. use gofmt -s -w on the project .go files
instead of the current more complex pipeline), and add .PHONY for all, clean,
docs, docs-components, release-docs to keep targets correct.
BIN="${MARKDOWN_TRANSCLUSION_BIN:-markdown-transclusion}" | ||
BASE_OVERRIDE="${MARKDOWN_TRANSCLUSION_BASE:-}" | ||
ARGS_ENV="${MARKDOWN_TRANSCLUSION_ARGS:-}" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fail early if the transclusion binary is missing. Don’t wait to explode later.
Add a cheap guard and clearer logging.
BIN="${MARKDOWN_TRANSCLUSION_BIN:-markdown-transclusion}"
BASE_OVERRIDE="${MARKDOWN_TRANSCLUSION_BASE:-}"
ARGS_ENV="${MARKDOWN_TRANSCLUSION_ARGS:-}"
+if ! command -v "${BIN%% *}" >/dev/null 2>&1; then
+ echo "ERROR: transclusion bin '${BIN}' not found on PATH" >&2
+ exit 127
+fi
+
cmd=(go run ./cmd/docs-components --repo "$ROOT_DIR" --transclusion-bin "$BIN")
Also applies to: 11-25
🤖 Prompt for AI Agents
In scripts/render-docs.sh around lines 5-8 (and similarly apply to lines 11-25),
the script sets BIN="${MARKDOWN_TRANSCLUSION_BIN:-markdown-transclusion}" but
doesn't verify the binary exists before continuing; add an early guard that
checks whether "$BIN" is available (e.g. command -v "$BIN" >/dev/null 2>&1 or
test -x "$(command -v "$BIN")") and if not, write a clear error message to
stderr including the expected binary name and exit with a non-zero status; keep
the existing variable logic intact and perform this check immediately after
those variable assignments so the script fails fast with a helpful log when the
transclusion binary is missing.
contains_placeholder() { | ||
local file="$1" | ||
if rg -n "!\\[\\[[^]]+\\]\\]" "$file" >/dev/null; then | ||
return 0 | ||
fi | ||
return 1 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ShellCheck SC2295; drop hard dep on ripgrep; quiet the matcher.
- Quote pattern removal in parameter expansion.
- Use rg -q if available; otherwise fall back to grep -Eq.
- Make matcher quiet and portable.
-contains_placeholder() {
- local file="$1"
- if rg -n "!\\[\\[[^]]+\\]\\]" "$file" >/dev/null; then
- return 0
- fi
- return 1
-}
+contains_placeholder() {
+ local file="$1"
+ local pattern='!\[\[[^]]+\]\]'
+ if command -v rg >/dev/null 2>&1; then
+ rg -q "$pattern" "$file"
+ else
+ grep -Eq "$pattern" "$file"
+ fi
+}
@@
- if contains_placeholder "$file"; then
- failures+=("${file#$ROOT_DIR/}")
+ if contains_placeholder "$file"; then
+ failures+=("${file#"$ROOT_DIR"/}")
fi
@@
-if ((${#missing[@]} > 0)); then
+if ((${#missing[@]} > 0)); then
printf 'verify-docs: missing generated targets:\n'
printf ' %s\n' "${missing[@]}"
exit 1
fi
@@
-if ((${#failures[@]} > 0)); then
+if ((${#failures[@]} > 0)); then
printf 'verify-docs: unresolved placeholders found in:\n'
printf ' %s\n' "${failures[@]}"
exit 1
fi
Also applies to: 30-37, 42-55
🤖 Prompt for AI Agents
In scripts/verify-docs.sh around lines 15 to 21 (and similarly 30-37, 42-55),
the contains_placeholder function currently uses an unquoted regex with ripgrep
and triggers ShellCheck SC2295 and a hard dependency on rg; replace the body to
first quote any pattern usage, detect whether rg exists and use rg -q when
present, otherwise fall back to grep -Eq, and ensure the command is run quietly
(no output) and returns the appropriate status; update all similar matcher
usages in the other ranges to follow this portable, quiet pattern.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Review continued from previous batch...
func (g *Generator) generateDependencyGraph(ctx context.Context, data roadmapArtifacts, tasks []recordWithPath[taskRecord]) error { | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
default: | ||
} | ||
|
||
var all []typedRecord | ||
|
||
for _, record := range data.Milestones { | ||
all = append(all, typedRecord{ | ||
id: record.data.ID, | ||
title: record.data.Title, | ||
typeLabel: "Milestone", | ||
deps: cleanValues(record.data.Dependencies), | ||
}) | ||
} | ||
for _, record := range data.Features { | ||
all = append(all, typedRecord{ | ||
id: record.data.ID, | ||
title: record.data.Title, | ||
typeLabel: "Feature", | ||
deps: cleanValues(record.data.Dependencies), | ||
}) | ||
} | ||
for _, record := range data.Stories { | ||
all = append(all, typedRecord{ | ||
id: record.data.ID, | ||
title: record.data.Title, | ||
typeLabel: "Story", | ||
deps: cleanValues(record.data.Dependencies), | ||
}) | ||
} | ||
for _, record := range tasks { | ||
all = append(all, typedRecord{ | ||
id: record.data.ID, | ||
title: record.data.Title, | ||
typeLabel: "Task", | ||
deps: cleanValues(record.data.Dependencies), | ||
}) | ||
} | ||
|
||
if len(all) == 0 { | ||
return nil | ||
} | ||
|
||
nodeIDs := map[string]string{} | ||
labels := map[string]string{} | ||
|
||
for idx, record := range all { | ||
if record.id == "" { | ||
continue | ||
} | ||
nodeName := fmt.Sprintf("n%d", idx) | ||
nodeIDs[record.id] = nodeName | ||
labels[nodeName] = escapeMermaidLabel(fmt.Sprintf("%s\\n%s", record.typeLabel, record.title)) | ||
} | ||
|
||
edges := make([][2]string, 0) | ||
for _, record := range all { | ||
src, ok := nodeIDs[record.id] | ||
if !ok { | ||
continue | ||
} | ||
for _, dep := range record.deps { | ||
if dst, ok := nodeIDs[dep]; ok { | ||
edges = append(edges, [2]string{src, dst}) | ||
} | ||
} | ||
} | ||
|
||
direction := strings.ToUpper(strings.TrimSpace(g.options.GraphDirection)) | ||
builder := &strings.Builder{} | ||
builder.WriteString(generatedNotice) | ||
builder.WriteString("\n") | ||
builder.WriteString("```mermaid\n") | ||
builder.WriteString(fmt.Sprintf("graph %s\n", direction)) | ||
|
||
nodeNames := make([]string, 0, len(labels)) | ||
for node := range labels { | ||
nodeNames = append(nodeNames, node) | ||
} | ||
sort.Strings(nodeNames) | ||
|
||
for _, node := range nodeNames { | ||
label := labels[node] | ||
fmt.Fprintf(builder, " %s[\"%s\"]\n", node, label) | ||
} | ||
|
||
palette := g.palettes[g.options.GraphPalette] | ||
if palette == nil { | ||
palette = g.palettes["evergreen"] | ||
} | ||
classNames := make([]string, 0, len(palette)) | ||
for className := range palette { | ||
classNames = append(classNames, className) | ||
} | ||
sort.Strings(classNames) | ||
for _, className := range classNames { | ||
color := palette[className] | ||
fmt.Fprintf(builder, " classDef %s fill:%s,stroke:%s,color:%s,stroke-width:1px;\n", className, color.Fill, color.Stroke, color.Text) | ||
} | ||
|
||
if g.options.GraphClusters { | ||
for _, cluster := range orderedTypes { | ||
nodes := collectNodesByType(cluster, all, nodeIDs) | ||
if len(nodes) == 0 { | ||
continue | ||
} | ||
fmt.Fprintf(builder, " subgraph %s\n", cluster) | ||
fmt.Fprintf(builder, " direction %s\n", direction) | ||
for _, node := range nodes { | ||
fmt.Fprintf(builder, " %s\n", node) | ||
} | ||
builder.WriteString(" end\n") | ||
} | ||
} | ||
|
||
classAssignments := make(map[string]string) | ||
for _, record := range all { | ||
if node, ok := nodeIDs[record.id]; ok { | ||
className := strings.ToLower(record.typeLabel) | ||
if _, ok := palette[className]; ok { | ||
classAssignments[node] = className | ||
} | ||
} | ||
} | ||
|
||
assignmentNodes := make([]string, 0, len(classAssignments)) | ||
for node := range classAssignments { | ||
assignmentNodes = append(assignmentNodes, node) | ||
} | ||
sort.Strings(assignmentNodes) | ||
for _, node := range assignmentNodes { | ||
fmt.Fprintf(builder, " class %s %s;\n", node, classAssignments[node]) | ||
} | ||
|
||
for _, edge := range edges { | ||
fmt.Fprintf(builder, " %s --> %s\n", edge[0], edge[1]) | ||
} | ||
|
||
builder.WriteString("```\n") | ||
|
||
outputPath := filepath.Join(g.componentsDir, "roadmap", "dependencies-graph.md") | ||
if err := g.writeFile(outputPath, builder.String()); err != nil { | ||
return fmt.Errorf("write dependency graph: %w", err) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Mermaid graph: you ignore duplicate IDs across types; collisions silently overwrite nodes. Detect and fail loudly.
If a Story and a Task share “ID: ABC-123”, nodeIDs clobbers and edges point to the wrong thing. That’s a nasty nondeterministic doc bug.
Apply this diff to detect duplicates and error:
- nodeIDs := map[string]string{}
+ nodeIDs := map[string]string{}
+ dup := map[string]int{}
@@
- for idx, record := range all {
+ for idx, record := range all {
if record.id == "" {
continue
}
- nodeName := fmt.Sprintf("n%d", idx)
- nodeIDs[record.id] = nodeName
+ nodeName := fmt.Sprintf("n%d", idx)
+ if _, exists := nodeIDs[record.id]; exists {
+ dup[record.id]++
+ } else {
+ nodeIDs[record.id] = nodeName
+ }
labels[nodeName] = escapeMermaidLabel(fmt.Sprintf("%s\\n%s", record.typeLabel, record.title))
}
+ if len(dup) > 0 {
+ keys := make([]string, 0, len(dup))
+ for k := range dup {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ if len(keys) > 10 {
+ keys = keys[:10]
+ }
+ return fmt.Errorf("duplicate IDs across artifacts/tasks: %s (showing up to 10)", strings.Join(keys, ", "))
+ }
func (g *Generator) generateTaskTable(ctx context.Context, records []recordWithPath[taskRecord]) error { | ||
select { | ||
case <-ctx.Done(): | ||
return ctx.Err() | ||
default: | ||
} | ||
|
||
builder := &strings.Builder{} | ||
builder.WriteString(generatedNotice) | ||
builder.WriteString("\n") | ||
builder.WriteString("| ID | Title | Status | Owner | Labels | Badges | Updated |\n") | ||
builder.WriteString("| --- | --- | --- | --- | --- | --- | --- |\n") | ||
|
||
for _, record := range records { | ||
owner := "_unassigned_" | ||
if record.data.Owner != nil && strings.TrimSpace(*record.data.Owner) != "" { | ||
owner = *record.data.Owner | ||
} | ||
|
||
labels := formatList(record.data.Labels) | ||
badges := formatList(record.data.Badges) | ||
|
||
updated := "—" | ||
if record.data.UpdatedAt != nil && strings.TrimSpace(*record.data.UpdatedAt) != "" { | ||
updated = *record.data.UpdatedAt | ||
} | ||
|
||
link := path.Join("tasks", filepath.Base(record.sourcePath)) | ||
fmt.Fprintf( | ||
builder, | ||
"| [%s](%s) | %s | %s | %s | %s | %s | %s |\n", | ||
record.data.ID, | ||
link, | ||
record.data.Title, | ||
record.data.Status, | ||
owner, | ||
labels, | ||
badges, | ||
updated, | ||
) | ||
} | ||
|
||
outputPath := filepath.Join(g.componentsDir, "issues", "tasks-table.md") | ||
if err := g.writeFile(outputPath, builder.String()); err != nil { | ||
return fmt.Errorf("write tasks table: %w", err) | ||
} | ||
|
||
return nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Tasks table: you print “—” for empty badges/labels but still render an extra space in changelog. Keep outputs consistent.
You already fixed it in changelog with a conditional; mirror that here to avoid stray “ — ” in embeddings. Cosmetic but user‑visible.
Apply this diff:
- labels := formatList(record.data.Labels)
- badges := formatList(record.data.Badges)
+ labels := formatList(record.data.Labels)
+ badges := formatList(record.data.Badges)
+ if badges == "—" {
+ badges = ""
+ }
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (g *Generator) generateTaskTable(ctx context.Context, records []recordWithPath[taskRecord]) error { | |
select { | |
case <-ctx.Done(): | |
return ctx.Err() | |
default: | |
} | |
builder := &strings.Builder{} | |
builder.WriteString(generatedNotice) | |
builder.WriteString("\n") | |
builder.WriteString("| ID | Title | Status | Owner | Labels | Badges | Updated |\n") | |
builder.WriteString("| --- | --- | --- | --- | --- | --- | --- |\n") | |
for _, record := range records { | |
owner := "_unassigned_" | |
if record.data.Owner != nil && strings.TrimSpace(*record.data.Owner) != "" { | |
owner = *record.data.Owner | |
} | |
labels := formatList(record.data.Labels) | |
badges := formatList(record.data.Badges) | |
updated := "—" | |
if record.data.UpdatedAt != nil && strings.TrimSpace(*record.data.UpdatedAt) != "" { | |
updated = *record.data.UpdatedAt | |
} | |
link := path.Join("tasks", filepath.Base(record.sourcePath)) | |
fmt.Fprintf( | |
builder, | |
"| [%s](%s) | %s | %s | %s | %s | %s | %s |\n", | |
record.data.ID, | |
link, | |
record.data.Title, | |
record.data.Status, | |
owner, | |
labels, | |
badges, | |
updated, | |
) | |
} | |
outputPath := filepath.Join(g.componentsDir, "issues", "tasks-table.md") | |
if err := g.writeFile(outputPath, builder.String()); err != nil { | |
return fmt.Errorf("write tasks table: %w", err) | |
} | |
return nil | |
} | |
func (g *Generator) generateTaskTable(ctx context.Context, records []recordWithPath[taskRecord]) error { | |
select { | |
case <-ctx.Done(): | |
return ctx.Err() | |
default: | |
} | |
builder := &strings.Builder{} | |
builder.WriteString(generatedNotice) | |
builder.WriteString("\n") | |
builder.WriteString("| ID | Title | Status | Owner | Labels | Badges | Updated |\n") | |
builder.WriteString("| --- | --- | --- | --- | --- | --- | --- |\n") | |
for _, record := range records { | |
owner := "_unassigned_" | |
if record.data.Owner != nil && strings.TrimSpace(*record.data.Owner) != "" { | |
owner = *record.data.Owner | |
} | |
labels := formatList(record.data.Labels) | |
badges := formatList(record.data.Badges) | |
if badges == "—" { | |
badges = "" | |
} | |
updated := "—" | |
if record.data.UpdatedAt != nil && strings.TrimSpace(*record.data.UpdatedAt) != "" { | |
updated = *record.data.UpdatedAt | |
} | |
link := path.Join("tasks", filepath.Base(record.sourcePath)) | |
fmt.Fprintf( | |
builder, | |
"| [%s](%s) | %s | %s | %s | %s | %s | %s |\n", | |
record.data.ID, | |
link, | |
record.data.Title, | |
record.data.Status, | |
owner, | |
labels, | |
badges, | |
updated, | |
) | |
} | |
outputPath := filepath.Join(g.componentsDir, "issues", "tasks-table.md") | |
if err := g.writeFile(outputPath, builder.String()); err != nil { | |
return fmt.Errorf("write tasks table: %w", err) | |
} | |
return nil | |
} |
func (g *Generator) loadRoadmapArtifacts() (roadmapArtifacts, error) { | ||
var result roadmapArtifacts | ||
|
||
milestones, err := g.readArtifactRecords(filepath.Join(g.repoRoot, "@hubless", "roadmap", "milestones")) | ||
if err != nil { | ||
return result, fmt.Errorf("load milestones: %w", err) | ||
} | ||
|
||
features, err := g.readArtifactRecords(filepath.Join(g.repoRoot, "@hubless", "roadmap", "features")) | ||
if err != nil { | ||
return result, fmt.Errorf("load features: %w", err) | ||
} | ||
|
||
stories, err := g.readArtifactRecords(filepath.Join(g.repoRoot, "@hubless", "issues", "stories")) | ||
if err != nil { | ||
return result, fmt.Errorf("load stories: %w", err) | ||
} | ||
|
||
result.Milestones = milestones | ||
result.Features = features | ||
result.Stories = stories | ||
|
||
return result, nil | ||
} | ||
|
||
func (g *Generator) readArtifactRecords(dir string) ([]recordWithPath[artifactRecord], error) { | ||
if _, err := os.Stat(dir); err != nil { | ||
return nil, err | ||
} | ||
|
||
files, err := filepath.Glob(filepath.Join(dir, "*.json")) | ||
if err != nil { | ||
return nil, fmt.Errorf("glob %s: %w", dir, err) | ||
} | ||
|
||
if len(files) == 0 { | ||
return nil, fmt.Errorf("no JSON files found in %s", dir) | ||
} | ||
|
||
sort.Strings(files) | ||
|
||
results := make([]recordWithPath[artifactRecord], 0, len(files)) | ||
for _, file := range files { | ||
data, err := os.ReadFile(file) | ||
if err != nil { | ||
return nil, fmt.Errorf("read %s: %w", file, err) | ||
} | ||
|
||
var record artifactRecord | ||
if err := json.Unmarshal(data, &record); err != nil { | ||
return nil, fmt.Errorf("decode %s: %w", file, err) | ||
} | ||
|
||
if record.ID == "" || record.Title == "" || record.Status == "" { | ||
return nil, fmt.Errorf("record %s missing required fields", file) | ||
} | ||
|
||
results = append(results, recordWithPath[artifactRecord]{sourcePath: file, data: record}) | ||
} | ||
|
||
return results, nil | ||
} | ||
|
||
func (g *Generator) readTaskRecords(dir string) ([]recordWithPath[taskRecord], error) { | ||
if _, err := os.Stat(dir); err != nil { | ||
return nil, err | ||
} | ||
|
||
files, err := filepath.Glob(filepath.Join(dir, "*.json")) | ||
if err != nil { | ||
return nil, fmt.Errorf("glob %s: %w", dir, err) | ||
} | ||
|
||
if len(files) == 0 { | ||
return nil, fmt.Errorf("no JSON files found in %s", dir) | ||
} | ||
|
||
sort.Strings(files) | ||
|
||
results := make([]recordWithPath[taskRecord], 0, len(files)) | ||
for _, file := range files { | ||
data, err := os.ReadFile(file) | ||
if err != nil { | ||
return nil, fmt.Errorf("read %s: %w", file, err) | ||
} | ||
|
||
var record taskRecord | ||
if err := json.Unmarshal(data, &record); err != nil { | ||
return nil, fmt.Errorf("decode %s: %w", file, err) | ||
} | ||
|
||
if record.ID == "" || record.Title == "" || record.Status == "" { | ||
return nil, fmt.Errorf("task %s missing required fields", file) | ||
} | ||
|
||
results = append(results, recordWithPath[taskRecord]{sourcePath: file, data: record}) | ||
} | ||
|
||
return results, nil | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hard‑fail on missing dirs and empty folders is hostile. Treat absent/empty as empty datasets.
Currently you error if @hubless/roadmap/* or @hubless/issues/* folders are missing or empty. That bricks first‑time runs and CI docs jobs. Return empty slices instead.
Apply these diffs:
func (g *Generator) readArtifactRecords(dir string) ([]recordWithPath[artifactRecord], error) {
- if _, err := os.Stat(dir); err != nil {
- return nil, err
- }
+ if _, err := os.Stat(dir); err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, nil
+ }
+ return nil, err
+ }
@@
- if len(files) == 0 {
- return nil, fmt.Errorf("no JSON files found in %s", dir)
- }
+ if len(files) == 0 {
+ return nil, nil
+ }
@@
- return results, nil
+ return results, nil
}
func (g *Generator) readTaskRecords(dir string) ([]recordWithPath[taskRecord], error) {
- if _, err := os.Stat(dir); err != nil {
- return nil, err
- }
+ if _, err := os.Stat(dir); err != nil {
+ if errors.Is(err, os.ErrNotExist) {
+ return nil, nil
+ }
+ return nil, err
+ }
@@
- if len(files) == 0 {
- return nil, fmt.Errorf("no JSON files found in %s", dir)
- }
+ if len(files) == 0 {
+ return nil, nil
+ }
@@
- return results, nil
+ return results, nil
}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
func (g *Generator) loadRoadmapArtifacts() (roadmapArtifacts, error) { | |
var result roadmapArtifacts | |
milestones, err := g.readArtifactRecords(filepath.Join(g.repoRoot, "@hubless", "roadmap", "milestones")) | |
if err != nil { | |
return result, fmt.Errorf("load milestones: %w", err) | |
} | |
features, err := g.readArtifactRecords(filepath.Join(g.repoRoot, "@hubless", "roadmap", "features")) | |
if err != nil { | |
return result, fmt.Errorf("load features: %w", err) | |
} | |
stories, err := g.readArtifactRecords(filepath.Join(g.repoRoot, "@hubless", "issues", "stories")) | |
if err != nil { | |
return result, fmt.Errorf("load stories: %w", err) | |
} | |
result.Milestones = milestones | |
result.Features = features | |
result.Stories = stories | |
return result, nil | |
} | |
func (g *Generator) readArtifactRecords(dir string) ([]recordWithPath[artifactRecord], error) { | |
if _, err := os.Stat(dir); err != nil { | |
return nil, err | |
} | |
files, err := filepath.Glob(filepath.Join(dir, "*.json")) | |
if err != nil { | |
return nil, fmt.Errorf("glob %s: %w", dir, err) | |
} | |
if len(files) == 0 { | |
return nil, fmt.Errorf("no JSON files found in %s", dir) | |
} | |
sort.Strings(files) | |
results := make([]recordWithPath[artifactRecord], 0, len(files)) | |
for _, file := range files { | |
data, err := os.ReadFile(file) | |
if err != nil { | |
return nil, fmt.Errorf("read %s: %w", file, err) | |
} | |
var record artifactRecord | |
if err := json.Unmarshal(data, &record); err != nil { | |
return nil, fmt.Errorf("decode %s: %w", file, err) | |
} | |
if record.ID == "" || record.Title == "" || record.Status == "" { | |
return nil, fmt.Errorf("record %s missing required fields", file) | |
} | |
results = append(results, recordWithPath[artifactRecord]{sourcePath: file, data: record}) | |
} | |
return results, nil | |
} | |
func (g *Generator) readTaskRecords(dir string) ([]recordWithPath[taskRecord], error) { | |
if _, err := os.Stat(dir); err != nil { | |
return nil, err | |
} | |
files, err := filepath.Glob(filepath.Join(dir, "*.json")) | |
if err != nil { | |
return nil, fmt.Errorf("glob %s: %w", dir, err) | |
} | |
if len(files) == 0 { | |
return nil, fmt.Errorf("no JSON files found in %s", dir) | |
} | |
sort.Strings(files) | |
results := make([]recordWithPath[taskRecord], 0, len(files)) | |
for _, file := range files { | |
data, err := os.ReadFile(file) | |
if err != nil { | |
return nil, fmt.Errorf("read %s: %w", file, err) | |
} | |
var record taskRecord | |
if err := json.Unmarshal(data, &record); err != nil { | |
return nil, fmt.Errorf("decode %s: %w", file, err) | |
} | |
if record.ID == "" || record.Title == "" || record.Status == "" { | |
return nil, fmt.Errorf("task %s missing required fields", file) | |
} | |
results = append(results, recordWithPath[taskRecord]{sourcePath: file, data: record}) | |
} | |
return results, nil | |
} | |
func (g *Generator) readArtifactRecords(dir string) ([]recordWithPath[artifactRecord], error) { | |
if _, err := os.Stat(dir); err != nil { | |
if errors.Is(err, os.ErrNotExist) { | |
return nil, nil | |
} | |
return nil, err | |
} | |
files, err := filepath.Glob(filepath.Join(dir, "*.json")) | |
if err != nil { | |
return nil, fmt.Errorf("glob %s: %w", dir, err) | |
} | |
if len(files) == 0 { | |
return nil, nil | |
} | |
sort.Strings(files) | |
results := make([]recordWithPath[artifactRecord], 0, len(files)) | |
for _, file := range files { | |
data, err := os.ReadFile(file) | |
if err != nil { | |
return nil, fmt.Errorf("read %s: %w", file, err) | |
} | |
var record artifactRecord | |
if err := json.Unmarshal(data, &record); err != nil { | |
return nil, fmt.Errorf("decode %s: %w", file, err) | |
} | |
if record.ID == "" || record.Title == "" || record.Status == "" { | |
return nil, fmt.Errorf("record %s missing required fields", file) | |
} | |
results = append(results, recordWithPath[artifactRecord]{sourcePath: file, data: record}) | |
} | |
return results, nil | |
} | |
func (g *Generator) readTaskRecords(dir string) ([]recordWithPath[taskRecord], error) { | |
if _, err := os.Stat(dir); err != nil { | |
if errors.Is(err, os.ErrNotExist) { | |
return nil, nil | |
} | |
return nil, err | |
} | |
files, err := filepath.Glob(filepath.Join(dir, "*.json")) | |
if err != nil { | |
return nil, fmt.Errorf("glob %s: %w", dir, err) | |
} | |
if len(files) == 0 { | |
return nil, nil | |
} | |
sort.Strings(files) | |
results := make([]recordWithPath[taskRecord], 0, len(files)) | |
for _, file := range files { | |
data, err := os.ReadFile(file) | |
if err != nil { | |
return nil, fmt.Errorf("read %s: %w", file, err) | |
} | |
var record taskRecord | |
if err := json.Unmarshal(data, &record); err != nil { | |
return nil, fmt.Errorf("decode %s: %w", file, err) | |
} | |
if record.ID == "" || record.Title == "" || record.Status == "" { | |
return nil, fmt.Errorf("task %s missing required fields", file) | |
} | |
results = append(results, recordWithPath[taskRecord]{sourcePath: file, data: record}) | |
} | |
return results, nil | |
} |
🤖 Prompt for AI Agents
In internal/docscomponents/generator.go around lines 672–771 the
readArtifactRecords and readTaskRecords functions currently hard‑fail when the
target directory is missing or contains no JSON files; change them to treat
missing dirs and empty folders as empty datasets by returning an empty slice and
nil error instead of an error. Specifically: if os.Stat(dir) returns an error
and os.IsNotExist(err) then return an empty slice of the appropriate
recordWithPath type and nil; for other os.Stat errors keep returning the error;
also when len(files) == 0 return an empty slice and nil (not an error). Keep
other error handling (read/unmarshal/field checks) unchanged.
Docstrings generation was requested by @flyingrobots. * #1 (comment) The following files were modified: * `cmd/docs-components/main.go` * `cmd/release/main.go` * `internal/docscomponents/generator.go` * `internal/docscomponents/transclusion.go` * `internal/mock/data.go` * `internal/release/releaser.go` * `internal/ui/tui/mock/app.go` * `internal/ui/tui/mock/profile.go` * `internal/ui/tui/mock/styles.go` * `scripts/verify-docs.sh`
Note Generated docstrings for this pull request at #2 |
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
📝 Add docstrings to `docs-release-pipeline`
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 22
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (8)
docs/reference/implementation-skeleton.md (8)
15-38
: Project layout is already stale vs this PR. Update the tree.You add a release CLI, docs-components generator, palettes, and scripts in this PR, yet the skeleton ignores them. Don’t mislead readers.
Apply this diff to reflect the actual structure:
```bash hubless/ ├─ cmd/ -│ └─ hubless/ -│ └─ main.go # composition root +│ ├─ hubless/ +│ │ └─ main.go # composition root +│ ├─ release/ # release automation CLI +│ │ └─ main.go +│ └─ docs-components/ # docs generator CLI +│ └─ main.go ├─ internal/ │ ├─ domain/ # pure domain types and logic │ │ ├─ events.go │ │ └─ issue.go │ ├─ application/ # use cases │ │ └─ services.go │ ├─ ports/ # boundary interfaces (in/out) │ │ └─ repository.go -│ ├─ adapters/ +│ ├─ adapters/ │ │ └─ gitstore/ │ │ └─ git_store.go -│ └─ ui/ +│ ├─ ui/ │ └─ tui/ # Bubbletea implementation │ ├─ model.go │ ├─ listview.go │ └─ styles.go +│ └─ docscomponents/ # generator internals +│ └─ ... ├─ go.mod -└─ Makefile # build/test helpers +├─ Makefile # build/test helpers +├─ docs/ +│ ├─ components/ # transclusion blocks +│ ├─ palettes/ # palette schemas/data +│ └─ reference/ +├─ scripts/ +│ └─ test-release-docker.sh +└─ .goreleaser.yaml # if used by release--- `59-84`: **Missing imports make even copy-paste exploration annoying. Add minimal imports.** Yes, you said “may omit imports,” but adding one line dramatically improves UX. ```diff package domain +import "time"
89-135
: You callgetString
like it exists. It doesn’t. Provide the stub here.Stop gaslighting readers with phantom helpers.
Append this to the snippet:
func Replay(id IssueID, events []Event) Issue { ... return issue } + +// getString returns m[key] if it is a string, otherwise fallback. +func getString(m map[string]any, key, fallback string) string { + if m == nil { + return fallback + } + if v, ok := m[key]; ok { + if s, ok := v.(string); ok && s != "" { + return s + } + } + return fallback +}
140-188
: Sorter relies ondomain.PriorityOrder
which you never define. Add it or drop the sort.Right now this is dead-on-arrival for anyone trying to follow along.
Add the ordering to the domain section:
package domain @@ type Priority string @@ const ( PriorityHigh Priority = "high" PriorityMedium Priority = "medium" PriorityLow Priority = "low" ) + +// PriorityOrder defines ordering: high < medium < low. +func PriorityOrder(p Priority) int { + switch p { + case PriorityHigh: + return 0 + case PriorityMedium: + return 1 + case PriorityLow: + return 2 + default: + return 3 + } +}
193-201
: Why the hell isNow()
on the store interface? Either justify it or cut it.Time comes from the adapter or a clock abstraction, not your persistence interface unless you have a very specific reason.
type EventStore interface { ListIssues(ctx context.Context) ([]domain.IssueID, error) LoadEvents(ctx context.Context, id domain.IssueID) ([]domain.Event, error) AppendEvent(ctx context.Context, event domain.Event) (string, error) - Now() time.Time }
If you need a clock, define
type Clock interface { Now() time.Time }
separately and inject it.
206-225
: Git adapter snippet is magical hand-waving. Mark placeholders explicitly.Readers aren’t mind-readers. Call out the fake bits so nobody assumes this is production-grade.
func (s *Store) AppendEvent(ctx context.Context, evt domain.Event) (string, error) { - tree, err := s.gitWithInput("", "mktree") + // NOTE: placeholder call; real impl must write blobs/trees first. + tree, err := s.gitWithInput("", "mktree") @@ - msg := buildCommitMessage(evt) - parent := s.currentRefHead(evt.Issue) + // NOTE: buildCommitMessage/currentRefHead/author/timestamp/updateRef are placeholders. + msg := buildCommitMessage(evt) // TODO: implement + parent := s.currentRefHead(evt.Issue) // TODO: handle missing ref @@ - if err := s.updateRef(ref, strings.TrimSpace(string(oid)), parent); err != nil { + if err := s.updateRef(ref, strings.TrimSpace(string(oid)), parent); err != nil { // TODO: atomic update return "", err }Also add
import ( "fmt"; "strings" )
to the snippet header to avoid useless errors.
230-262
: TUI snippet references ghosts:loadDetail
,newList
,issueItem
. Either stub them or stop calling them.Don’t ship broken pseudo-APIs in docs.
Minimal fix to avoid undefined symbols in the example:
func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ - case "enter": - return m.loadDetail(), nil + case "enter": + // TODO: implement loadDetail to fetch and render selection. + return m, nil } }Optionally append stubs for readability:
type issueItem struct{ application.IssueSummary } func newList(items []list.Item, w, h int) list.Model { return list.New(items, list.NewDefaultDelegate(), w, h) }
266-269
: Next steps ignore the biggest additions of this PR. List them.Stop burying the lede—docs and release automation are the headline.
- Flesh out unit tests for domain replay and adapters. - Expand the Git adapter with catalog and feed updates. - Integrate TUI commands with mutation operations (`CreateIssue`, `ChangeStatus`, `Comment`). + - Document and harden the release CLI (`cmd/release`): version bumping, signing, push controls, dry-run. + - Wire docs generator (`cmd/docs-components`, `internal/docscomponents`) into Makefile and CI. + - Finalize palette schema and add examples under `docs/palettes/`; validate in generator tests. + - Add end-to-end dockerized release tests (`scripts/test-release-docker.sh`) to CI.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: ASSERTIVE
Plan: Pro
📒 Files selected for processing (38)
.editorconfig
(1 hunks).githooks/pre-commit
(1 hunks).github/workflows/docs.yml
(1 hunks).github/workflows/quality.yml
(1 hunks).golangci.yml
(1 hunks)@hubless/issues/templates/tasks.md
(1 hunks)AGENTS.md
(1 hunks)CHANGELOG.md
(1 hunks)CONTRIBUTING.md
(2 hunks)Dockerfile.release-test
(1 hunks)Makefile
(1 hunks)README.md
(1 hunks)cmd/docs-components/main.go
(1 hunks)cmd/release/main.go
(1 hunks)docs/components/issues/archived-stories.md
(1 hunks)docs/components/issues/archived-tasks.md
(1 hunks)docs/components/issues/changelog.md
(1 hunks)docs/components/issues/status-summary.md
(1 hunks)docs/components/issues/tasks-table.md
(1 hunks)docs/components/roadmap/dependencies-graph.md
(1 hunks)docs/components/roadmap/features-table.md
(1 hunks)docs/components/roadmap/milestones-table.md
(1 hunks)docs/components/roadmap/progress.md
(1 hunks)docs/components/roadmap/stories-table.md
(1 hunks)docs/reference/implementation-skeleton.md
(9 hunks)docs/reference/release-notes.md
(1 hunks)docs/reference/rfcs/FEEDBACK-0001-release-automation.md
(1 hunks)internal/docscomponents/generator.go
(1 hunks)internal/docscomponents/generator_test.go
(1 hunks)internal/docscomponents/transclusion.go
(1 hunks)internal/mock/data.go
(1 hunks)internal/release/releaser.go
(1 hunks)internal/ui/tui/mock/app.go
(1 hunks)internal/ui/tui/mock/profile.go
(1 hunks)internal/ui/tui/mock/styles.go
(1 hunks)scripts/install-git-hooks.sh
(1 hunks)scripts/test-release-docker.sh
(1 hunks)scripts/verify-docs.sh
(1 hunks)
🧰 Additional context used
🧬 Code graph analysis (6)
cmd/release/main.go (1)
internal/release/releaser.go (3)
New
(32-43)Options
(17-22)ErrVersionRequired
(14-14)
internal/docscomponents/transclusion.go (1)
internal/release/releaser.go (1)
New
(32-43)
internal/ui/tui/mock/app.go (1)
internal/ui/tui/mock/styles.go (1)
Styles
(6-9)
cmd/docs-components/main.go (2)
internal/docscomponents/generator.go (2)
NewGenerator
(39-76)GeneratorOptions
(19-24)internal/docscomponents/transclusion.go (2)
TransclusionOptions
(14-20)RunTransclusion
(29-82)
internal/docscomponents/generator_test.go (1)
internal/docscomponents/generator.go (2)
NewGenerator
(39-76)GeneratorOptions
(19-24)
internal/docscomponents/generator.go (1)
internal/release/releaser.go (1)
New
(32-43)
🪛 markdownlint-cli2 (0.18.1)
docs/components/roadmap/dependencies-graph.md
82-82: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
82-82: Fenced code blocks should have a language specified
(MD040, fenced-code-language)
82-82: Code block style
Expected: indented; Actual: fenced
(MD046, code-block-style)
docs/reference/rfcs/FEEDBACK-0001-release-automation.md
1-1: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Below
(MD022, blanks-around-headings)
@hubless/issues/templates/tasks.md
21-21: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
22-22: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
README.md
4-4: Blank line inside blockquote
(MD028, no-blanks-blockquote)
38-38: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
39-39: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
43-43: Trailing spaces
Expected: 0 or 2; Actual: 4
(MD009, no-trailing-spaces)
45-45: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🪛 Checkov (3.2.334)
Dockerfile.release-test
[low] 1-26: Ensure that a user for the container has been created
(CKV_DOCKER_3)
🪛 Shellcheck (0.11.0)
scripts/verify-docs.sh
[info] 33-33: Expansions inside ${..} need to be quoted separately, otherwise they match as patterns.
(SC2295)
🪛 actionlint (1.7.7)
.github/workflows/quality.yml
30-30: the runner of "golangci/golangci-lint-action@v3" action is too old to run on GitHub Actions. update the action's version to fix this issue
(action)
🪛 GitHub Check: quality
internal/ui/tui/mock/profile.go
[failure] 31-31:
expected declaration, found '}'
🪛 GitHub Actions: Quality Checks
internal/ui/tui/mock/profile.go
[error] 31-31: expected declaration, found '}'
🪛 GitHub Check: docs
internal/docscomponents/generator.go
[failure] 1138-1138:
undefined: time
[failure] 1132-1132:
undefined: time
[failure] 1131-1131:
undefined: strconv
[failure] 1130-1130:
undefined: strconv
[failure] 1120-1120:
undefined: time
[failure] 1111-1111:
undefined: time
[failure] 1107-1107:
undefined: time
[failure] 1104-1104:
undefined: time
🪛 GitHub Actions: Docs Pipeline
internal/docscomponents/generator.go
[error] 1104-1104: undefined: time
🔇 Additional comments (34)
docs/reference/implementation-skeleton.md (2)
11-11
: Good: disclaimer fixed to stop pretending these compile.This finally matches reality. Keep it that way across future edits.
42-53
: Document reality: add CLI deps or state CLIs are stdlib-only.No imports of common Go CLI frameworks (github.com/spf13/cobra, github.com/urfave/cli, github.com/alecthomas/kong) were detected — update docs/reference/implementation-skeleton.md (lines 42–53) to either add the actual CLI dependency to the go.mod snippet or explicitly state the release/docs CLIs are implemented with the standard library (flag).
.editorconfig (1)
12-19
: EditorConfig for Go/Makefile looks correct.Tabs for Go and Makefile, sane defaults elsewhere. Ship it.
.golangci.yml (1)
1-23
: Good baseline; upgrade action separately.Config is solid: key linters enabled, sane settings. No blockers here.
scripts/install-git-hooks.sh (1)
12-17
: Hooks installer is correct and scoped.Guards work-tree, uses --local, clear output. Good.
docs/components/issues/status-summary.md (1)
1-7
: LGTM: Generated header + table look sane.No lint gotchas; counts format is clean.
docs/components/issues/archived-stories.md (1)
1-5
: LGTM: Clear empty state beats fake dashes.Header suppression present; table reads fine.
docs/components/roadmap/stories-table.md (1)
1-11
: LGTM: Stable table scaffold.Generated header present, links resolve relatively, statuses consistent.
docs/components/roadmap/milestones-table.md (1)
1-10
: LGTM: Header suppression + table OK.No formatting violations.
docs/components/roadmap/progress.md (1)
1-8
: LGTM: Progress table renders cleanly.Generated header present; bars and totals consistent.
CONTRIBUTING.md (1)
44-51
: Stop hand‑waving and add a sanity check for docs tooling.You expect env vars to be set but never show a single verification step.
- Install or clone [`markdown-transclusion`](https://github.com/flyingrobots/markdown-transclusion) (Node ≥20). Set `MARKDOWN_TRANSCLUSION_BIN` to the executable (`markdown-transclusion` if installed globally, or `node`) and `MARKDOWN_TRANSCLUSION_ARGS` to the CLI script path when using a local clone (e.g., `/path/to/markdown-transclusion/dist/cli.js`). + - Install or clone [`markdown-transclusion`](https://github.com/flyingrobots/markdown-transclusion) (Node ≥20). Set `MARKDOWN_TRANSCLUSION_BIN` to the executable (`markdown-transclusion` if installed globally, or `node`) and `MARKDOWN_TRANSCLUSION_ARGS` to the CLI script path when using a local clone (e.g., `/path/to/markdown-transclusion/dist/cli.js`). + - Verify setup: + ```bash + node -v && npm -v + command -v "${MARKDOWN_TRANSCLUSION_BIN:-markdown-transclusion}" || { echo "missing markdown-transclusion (or node)"; exit 1; } + ```README.md (1)
3-7
: Kill the pointless blank line between blockquotes (MD028).Keep the callout contiguous with the quote or drop the first
>
— but don’t half‑ass both.-# Hubless - -> *Imagine GitHub… but in your repo. No hub; just Git.* - -> [!INFO] +# Hubless + +> *Imagine GitHub… but in your repo. No hub; just Git.* +> [!INFO] > **EARLY DAYS.** I just started this project yesterday. Expect rapid iteration, rough edges, and breaking changes. > If you want boring stability, wait. If you want to see Git-native project state come alive, jump in now.docs/reference/release-notes.md (1)
1-15
: Release notes generator looks fine.No functional issues found for this segment.
.githooks/pre-commit (1)
1-31
: Pre-commit gate is solid.Good: strict mode, tool checks, fail on dirty docs. Ship it.
Consider adding a quick “only run docs regen if sources changed” later for speed (optional).
docs/components/roadmap/features-table.md (1)
5-11
: Fix broken feature links or emit the JSON targets. NOW.These links point to docs/components/roadmap/features/.json, but the data appears under hubless/roadmap/features/.json. Either update the generator to emit JSON into docs/components/roadmap/features, or fix the links to point to the actual files. Stop shipping dead links.
Run to verify:
#!/usr/bin/env bash set -euo pipefail base="docs/components/roadmap" file="$base/features-table.md" perl -nE 'while (/\]\((features\/[^)]+\.json)\)/g) { print "$. $1\n" }' "$file" | while read -r ln rel; do path="$base/$rel" [[ -f "$path" ]] || { echo "MISSING: $path (from $file:$ln)"; exit 1; } done echo "All feature links resolve."Dockerfile.release-test (1)
1-26
: Stop building root-only snowflakes. Run as non-root and pin tools.go.mod contains "go 1.25.1" — update base image to golang:1.25.1, pin markdown-transclusion, and drop privileges as shown.
-FROM golang:1.25 +FROM golang:1.25.1 @@ -RUN npm install -g markdown-transclusion +ARG MT_VER=1 +RUN npm install -g "markdown-transclusion@^${MT_VER}" @@ -WORKDIR /app -COPY . /app +RUN useradd -m -u 10001 appuser +WORKDIR /app +COPY --chown=appuser:appuser . /app @@ -RUN rm -rf .git && \ - git init && \ - git config user.name "Release Tester" && \ - git config user.email "[email protected]" && \ - git add . && \ - git commit -m "container snapshot" && \ - git remote remove origin || true +USER appuser +RUN rm -rf .git && \ + git init && \ + git config user.name "Release Tester" && \ + git config user.email "[email protected]" && \ + git add . && \ + git commit -m "container snapshot" && \ + git remote remove origin || truescripts/test-release-docker.sh (1)
12-21
: Harden the in‑container script and stop grepping ls like it's 1995.Enable strict mode inside the container and use a proper file test. Also guard reruns by nuking a preexisting tag before the real run.
[ suggest_recommended_refactor ]docker run --rm "$IMAGE_NAME" /bin/bash -lc "\ export PATH=/usr/local/go/bin:/go/bin:\$PATH && \ - set -x && \ + set -euo pipefail && set -x && \ cd /app && \ git remote -v && \ go run ./cmd/release --version $VERSION --dry-run --skip-checks && \ + git tag -d \"v$VERSION\" >/dev/null 2>&1 || git tag -d \"$VERSION\" >/dev/null 2>&1 || true && \ go run ./cmd/release --version $VERSION --skip-checks && \ git tag --list && \ - ls docs/reference | grep release-notes.md\ + test -f docs/reference/release-notes.md\ "internal/ui/tui/mock/profile.go (2)
22-29
: Magic numbers for breakpoints? Come on.At minimum, name them. Better: import shared layout constants so they don’t drift.
- switch { - case width < 100: + const ( + smMax = 100 + mdMax = 140 + ) + switch { + case width < smMax: return layoutProfile{id: "sm", name: "small"} - case width < 140: + case width < mdMax: return layoutProfile{id: "md", name: "medium"} default: return layoutProfile{id: "lg", name: "large"} }If you have central breakpoints, import and use those instead of local consts.
20-21
: Duplicate/misplaced comment noise.You already described breakpoints above. Kill the duplicate line.
-// layoutProfile encapsulates responsive settings derived from Stickers breakpoints (in terminal columns).
Likely an incorrect or invalid review comment.
cmd/docs-components/main.go (2)
79-91
: Defaulting palette-file to a repo path is a foot‑gun that hard‑fails other repos. Fix it.Make it opt‑in and only load when the file exists. This was already called out; why is it still broken?
- flag.StringVar(&graphPalette, "graph-palette", "evergreen", "Mermaid palette for dependency graph (evergreen, infrared, zerothrow)") - flag.StringVar(&paletteFile, "palette-file", "docs/reference/palettes.json", "Optional palette definition file (JSON)") + flag.StringVar(&graphPalette, "graph-palette", "evergreen", "Mermaid palette for dependency graph (evergreen, infrared, zerothrow)") + flag.StringVar(&paletteFile, "palette-file", "", "Optional palette definition file (JSON)") @@ - generator, err := docscomponents.NewGenerator(repoRoot, componentsDir, docscomponents.GeneratorOptions{ + // If a palette file was provided, resolve relative to repo and ignore if missing. + if paletteFile != "" && !strings.HasPrefix(paletteFile, "/") { + candidate := filepath.Join(repoRoot, paletteFile) + if _, statErr := os.Stat(candidate); statErr == nil { + paletteFile = candidate + } else { + log.Printf("warning: palette file %q not found; falling back to built-in palettes", candidate) + paletteFile = "" + } + } + generator, err := docscomponents.NewGenerator(repoRoot, componentsDir, docscomponents.GeneratorOptions{ GraphDirection: graphDirection, GraphClusters: graphClusters, GraphPalette: graphPalette, PaletteFile: paletteFile, })And import the missing package:
import ( "context" "flag" "log" "os" "strings" + "path/filepath"
152-158
: Env arg parsing isn’t shell‑aware. Quoted args will be split wrong.Use a shlex parser; fall back to Fields on error.
-func parseArgs(raw string) []string { - fields := strings.Fields(raw) - return append([]string(nil), fields...) -} +func parseArgs(raw string) []string { + if strings.TrimSpace(raw) == "" { + return nil + } + fields, err := shlex.Split(raw) + if err != nil { + return append([]string(nil), strings.Fields(raw)...) + } + return append([]string(nil), fields...) +}Add the import:
import ( @@ "strings" + "github.com/google/shlex" )
#!/bin/bash set -euo pipefail rg -n 'palette-file' cmd/docs-components/main.go rg -n 'parseArgs' cmd/docs-components/main.go # Build to ensure imports and flags are correct. go build ./cmd/docs-componentsinternal/mock/data.go (1)
1-187
: LGTM.Types and mock factories are clean and deterministic.
internal/ui/tui/mock/app.go (1)
20-40
: Stop copying the model on every update; use pointer receivers.You mutate fields on a value receiver and return a copy. That’s wasteful and brittle. Bubble Tea is fine with pointer models.
Apply:
-func NewModel(width, height int, sections []mockStatusSection, issues []mockIssue, board []mockBoardColumn) AppModel { +func NewModel(width, height int, sections []mockStatusSection, issues []mockIssue, board []mockBoardColumn) *AppModel { prof := profileForWidth(width) - return AppModel{ + return &AppModel{ @@ -func (m AppModel) Init() tea.Cmd { +func (m *AppModel) Init() tea.Cmd { @@ -func (m AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { +func (m *AppModel) Update(msg tea.Msg) (tea.Model, tea.Cmd) { @@ -func (m AppModel) View() string { +func (m *AppModel) View() string { @@ -func (m AppModel) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { +func (m *AppModel) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {Run to find call sites needing updates:
#!/bin/bash rg -n --type=go -C2 '\bNewModel\('Also applies to: 51-65, 67-87, 89-99, 101-107
internal/docscomponents/transclusion.go (1)
29-41
: Fail fast on missing binary and missing input; surface clearer errors.Stop punting errors to the child process. Resolve the bin and check the input exists before exec.
bin := strings.TrimSpace(opts.Bin) if bin == "" { bin = "markdown-transclusion" } + if _, err := exec.LookPath(bin); err != nil { + return fmt.Errorf("resolve %s: %w", bin, err) + } @@ absInput, err := makeAbsoluteWithBase(opts.InputPath, absBasePath) if err != nil { return fmt.Errorf("resolve input path: %w", err) } + if _, err := os.Stat(absInput); err != nil { + return fmt.Errorf("input %s: %w", absInput, err) + }Also applies to: 56-64, 70-82
internal/release/releaser.go (2)
28-43
: Don’t assume repoRoot is valid — verify directory and Git repo upfront.Fail early with a real error instead of crashing later.
func New(repoRoot string) (*Releaser, error) { @@ absRoot, err := filepath.Abs(repoRoot) if err != nil { return nil, fmt.Errorf("resolve repo root: %w", err) } + fi, statErr := os.Stat(absRoot) + if statErr != nil || !fi.IsDir() { + return nil, fmt.Errorf("repo root not found or not a directory: %w", statErr) + } + if err := exec.Command("git", "-C", absRoot, "rev-parse", "--is-inside-work-tree").Run(); err != nil { + return nil, fmt.Errorf("not a git repository at %s: %w", absRoot, err) + } return &Releaser{repoRoot: absRoot}, nil }
45-115
: Verify/implement CLI "push" controls — don't claim them if absent.No evidence the CLI exposes or wires a push/sign flag. Either add a Push bool to Options and wire CLI flags (cmd/release) into Releaser.Run to perform git push/sign (see internal/release/releaser.go Run 45–115), or update the PR/help to state the push output is only an informational suggestion (dry-run).
internal/docscomponents/generator.go (7)
33-39
: Constructor side effects: stop mkdir in NewGenerator.Don’t mutate the world in constructors. You already mkdirAll in writeFile.
-// componentsDir to repoRoot/docs/components when empty), ensures the components directory exists, +// componentsDir to repoRoot/docs/components when empty), @@ - if err := os.MkdirAll(absComponentsDir, 0o755); err != nil { - return nil, fmt.Errorf("ensure components dir: %w", err) - }Also applies to: 58-60
702-715
: Be hostile to users, eh? Missing/empty data dirs hard‑fail generation. Treat them as empty datasets.First‑time runs and CI will break for no good reason.
func (g *Generator) readArtifactRecords(dir string) ([]recordWithPath[artifactRecord], error) { - if _, err := os.Stat(dir); err != nil { - return nil, err - } + if _, err := os.Stat(dir); err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + return nil, err + } @@ - if len(files) == 0 { - return nil, fmt.Errorf("no JSON files found in %s", dir) - } + if len(files) == 0 { + return nil, nil + } @@ func (g *Generator) readTaskRecords(dir string) ([]recordWithPath[taskRecord], error) { - if _, err := os.Stat(dir); err != nil { - return nil, err - } + if _, err := os.Stat(dir); err != nil { + if errors.Is(err, os.ErrNotExist) { + return nil, nil + } + return nil, err + } @@ - if len(files) == 0 { - return nil, fmt.Errorf("no JSON files found in %s", dir) - } + if len(files) == 0 { + return nil, nil + }Also applies to: 740-752
373-383
: Mermaid graph node IDs collide silently across types; detect duplicates and fail loudly.Otherwise edges point to the wrong thing and you won’t notice.
nodeIDs := map[string]string{} labels := map[string]string{} + dup := map[string]int{} @@ - nodeName := fmt.Sprintf("n%d", idx) - nodeIDs[record.id] = nodeName + nodeName := fmt.Sprintf("n%d", idx) + if _, exists := nodeIDs[record.id]; exists { + dup[record.id]++ + } else { + nodeIDs[record.id] = nodeName + } labels[nodeName] = escapeMermaidLabel(fmt.Sprintf("%s\\n%s", record.typeLabel, record.title)) } + if len(dup) > 0 { + keys := make([]string, 0, len(dup)) + for k := range dup { keys = append(keys, k) } + sort.Strings(keys) + if len(keys) > 10 { keys = keys[:10] } + return fmt.Errorf("duplicate IDs across artifacts/tasks: %s (showing up to 10)", strings.Join(keys, ", ")) + }Also applies to: 445-454
937-951
: Palette file handling is too strict; unknown classes nuke the run. Skip unknowns.Docs authors fat‑finger one key and your whole pipeline dies. Be forgiving by default.
- if !isKnownPaletteClass(key) { - return fmt.Errorf("unknown palette class %q in palette %q", className, paletteName) - } + if !isKnownPaletteClass(key) { + // Ignore unknown classes to keep generation resilient. + continue + }
1160-1171
: Mermaid label escaping misses braces and angle brackets. Add them.Users put braces in titles; Mermaid chokes.
func escapeMermaidLabel(label string) string { replacer := strings.NewReplacer( "\\", "\\\\", "\"", "\\\"", "[", "\\[", "]", "\\]", + "{", "\\{", + "}", "\\}", + "<", "\\<", + ">", "\\>", ) return replacer.Replace(label) }
535-556
: Normalize status case once; stop lowercasing in the comparator.Minor tidy that avoids repeated work.
- counts := map[string]int{} + counts := map[string]int{} for _, record := range records { - status := strings.TrimSpace(record.data.Status) + status := strings.ToUpper(strings.TrimSpace(record.data.Status)) if status == "" { status = "UNKNOWN" } counts[status]++ } @@ - return strings.ToLower(statuses[i]) < strings.ToLower(statuses[j]) + return statuses[i] < statuses[j]
141-215
: Roadmap tables: missing records must not crash output — verification requiredRan the suggested rg in the sandbox; ripgrep returned "No files were searched" so I couldn't verify here. Run docs generation locally after applying the directory‑tolerant changes and confirm generation succeeds when the roadmap and issues dirs are absent (to debug ripgrep run: rg -n '@hubless/(roadmap|issues)' -S --debug). Attach the generation output.
Makefile (1)
1-1
: You declared phony targets ‘all’ and ‘clean’ but never defined them. Add them, and fix gofmt.Default entrypoint should work out of the box. Also use -s for gofmt. Quote NOTES.
.PHONY: all clean docs docs-components docs-test docs-verify fmt fmt-check lint test hooks release-docs release release-dry + +all: fmt-check lint test docs-verify + +clean: + @true @@ fmt: - find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' -print0 | xargs -0 gofmt -w + find . -name '*.go' -not -path './vendor/*' -not -path './.git/*' -print0 | xargs -0 gofmt -s -w @@ - go run ./cmd/release --version $(VERSION) $(if $(NOTES),--notes $(NOTES)) + go run ./cmd/release --version $(VERSION) $(if $(NOTES),--notes "$(NOTES)") @@ - go run ./cmd/release --version $(VERSION) --dry-run $(if $(NOTES),--notes $(NOTES)) + go run ./cmd/release --version $(VERSION) --dry-run $(if $(NOTES),--notes "$(NOTES)")
name: Docs Pipeline | ||
|
||
'on': | ||
push: | ||
branches: [main] | ||
pull_request: | ||
permissions: | ||
contents: read | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Stop wasting cycles; make CI reproducible and smarter.
- Kill global npm; use npx to avoid version drift.
- Add concurrency to cancel superseded runs.
- Cache Go modules.
Apply:
name: Docs Pipeline
'on':
push:
branches: [main]
pull_request:
+concurrency:
+ group: docs-${{ github.ref }}
+ cancel-in-progress: true
@@
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version-file: go.mod
+ cache: true
+ cache-dependency-path: go.sum
@@
- - name: Install markdown-transclusion CLI
- run: npm install -g markdown-transclusion@^1
- # Alternatively:
- # - name: Generate docs
- # run: npx --yes markdown-transclusion --version && make docs
- # env:
- # MARKDOWN_TRANSCLUSION_BIN: npx --yes markdown-transclusion
- - name: Generate docs
- env:
- MARKDOWN_TRANSCLUSION_BIN: markdown-transclusion
- run: make docs
+ - name: Generate docs
+ run: npx --yes markdown-transclusion --version && make docs
+ env:
+ MARKDOWN_TRANSCLUSION_BIN: npx --yes markdown-transclusion
Also applies to: 16-23, 24-36
🤖 Prompt for AI Agents
.github/workflows/docs.yml around lines 1-9 (also applies to 16-23 and 24-36):
replace any use of a globally installed npm CLI with npx invocations to pin tool
versions at execution time (e.g., change "npm run ..." or global tool calls to
"npx <tool>@<version> ..." or invoke scripts via "npx npm@latest -- <args>" to
avoid version drift), add a top-level concurrency block to the workflow to
cancel superseded runs (concurrency: { group: "docs-ci-${{ github.ref }}",
cancel-in-progress: true }), and add Go module caching by adding a cache step
(use actions/cache) before running go commands with path: ${{ env.GOMODCACHE }}
or ~/.cache/go-build and key: go-mod-${{ matrix.go-version }}-${{
hashFiles('**/go.sum') }} (restore before go env download/install and save
after) so CI is reproducible and faster.
- name: Set up Go | ||
uses: actions/setup-go@v5 | ||
with: | ||
go-version: '1.25' | ||
|
||
- name: Install markdown-transclusion |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
This workflow will break. Fix Go version, set up Node, upgrade golangci action.
You claim Go 1.25 (doesn’t exist here), install npm tools without Node, and use a stale action. Clean it up.
- name: Set up Go
uses: actions/setup-go@v5
with:
- go-version: '1.25'
+ go-version: '1.22.x'
+ check-latest: true
+ cache: true
+
+ - name: Set up Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
@@
- - name: Run golangci-lint
- uses: golangci/golangci-lint-action@v3
+ - name: Run golangci-lint
+ uses: golangci/golangci-lint-action@v6
with:
version: v1.59.1
install-mode: binary
args: ./...
- env:
- GOTOOLCHAIN: go1.22.7
Also applies to: 23-25, 29-37
🏁 Script executed:
#!/usr/bin/env bash
# Sanity-check that Go versions are consistent across repo
rg -nP "(setup-go@|go-version:|GOTOOLCHAIN|language:\s*Go)" -g ".github/**" -n -C1
Length of output: 148
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "PWD: $(pwd)"
echo "---- top-level ----"
ls -la | sed -n '1,200p' || true
echo
echo "---- .github/workflows ----"
ls -la .github/workflows || true
echo
if [ -f .github/workflows/quality.yml ]; then
echo "---- .github/workflows/quality.yml (full) ----"
sed -n '1,240p' .github/workflows/quality.yml
echo "---- lines 18-23 ----"
sed -n '18,23p' .github/workflows/quality.yml
else
echo "quality.yml not found; searching repo for relevant workflow keywords..."
fi
echo
echo "---- rg search for Go/Node/golangci references ----"
rg -n --hidden -S 'setup-go@|go-version:|GOTOOLCHAIN|language:\s*Go|setup-node@|node-version:|golangci-lint-action|markdown-transclusion' || true
Length of output: 7780
Fix now — workflow will fail: wrong Go version, missing Node setup, stale golangci action
- .github/workflows/quality.yml sets go-version: '1.25' but the job itself uses GOTOOLCHAIN: go1.22.7 → align to the repo/toolchain (e.g., go1.22.x or whatever go.mod declares).
- It runs npm install -g markdown-transclusion without setting up Node — add actions/setup-node@v4 with node-version: '20' before the npm step (docs.yml already does this).
- golangci/golangci-lint-action@v3 is outdated here — bump to @v6 and remove the hard-coded GOTOOLCHAIN once setup-go is correct.
Location: .github/workflows/quality.yml — Set up Go / Install markdown-transclusion (lines ~18–24) and Run golangci-lint block (lines ~30–37).
🤖 Prompt for AI Agents
.github/workflows/quality.yml lines ~18–24 and ~30–37: the workflow sets
go-version: '1.25' while the job uses GOTOOLCHAIN go1.22.7, calls npm without
setting up Node, and uses an outdated golangci action; update go-version to
match the repo/toolchain (e.g., go1.22.x or whatever go.mod specifies), remove
the hard-coded GOTOOLCHAIN env in the golangci-lint step, insert an
actions/setup-node@v4 step with node-version: '20' immediately before the
markdown-transclusion npm install step, and bump
golangci/golangci-lint-action@v3 to @v6 so the lint step uses the proper
toolchain and recent action version.
3. Once a task is `DONE` and has badges **Tested**, **Documented**, **Shipped**, move the JSON file to `tasks.archive/` and record it in `tasks.archive.md`. | ||
## Anatomy of a Task |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Fix markdownlint violations (MD032, MD022).
Lists and headings need blank lines. Add one after the list before the next heading.
-3. Once a task is `DONE` and has badges **Tested**, **Documented**, **Shipped**, move the JSON file to `tasks.archive/` and record it in `tasks.archive.md`.
-## Anatomy of a Task
+3. Once a task is `DONE` and has badges **Tested**, **Documented**, **Shipped**, move the JSON file to `tasks.archive/` and record it in `tasks.archive.md`.
+
+## Anatomy of a Task
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
3. Once a task is `DONE` and has badges **Tested**, **Documented**, **Shipped**, move the JSON file to `tasks.archive/` and record it in `tasks.archive.md`. | |
## Anatomy of a Task | |
3. Once a task is `DONE` and has badges **Tested**, **Documented**, **Shipped**, move the JSON file to `tasks.archive/` and record it in `tasks.archive.md`. | |
## Anatomy of a Task |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
21-21: Lists should be surrounded by blank lines
(MD032, blanks-around-lists)
22-22: Headings should be surrounded by blank lines
Expected: 1; Actual: 0; Above
(MD022, blanks-around-headings)
🤖 Prompt for AI Agents
In @hubless/issues/templates/tasks.md around lines 21 to 22, the list item
ending with "record it in `tasks.archive.md`." is immediately followed by the
heading "## Anatomy of a Task" which triggers markdownlint MD032/MD022; add a
single blank line between the end of the list and the heading so there is an
empty line after the list before the next heading.
1. Task added to `@hubless/issues/tasks/<id>.json`; the generated rollup (`@hubless/issues/generated/tasks.md`) should show it as `PLANNED` after regeneration. | ||
2. Start task? status = "STARTED" | ||
3. Task blocked? status = "BLOCKED" | ||
4. Task finished? status = "DONE" | ||
5. Once status = "DONE", Tasks need the following badges: (i) Tested (ii) Documented (iii) Shipped; only then shall we remove them from the tasks.md file, and add it to the "tasks.archive.md" file | ||
5. Once status = "DONE" with badges (i) Tested (ii) Documented (iii) Shipped, the generator removes the item from the tasks rollup and adds it to the archive automatically on `make docs`. This also refreshes `CHANGELOG.md` and release notes. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
Docs align with generator; ensure Go version matches CI.
Process text is fine now. Keep the stated “Go 1.22” consistent with the workflow after you fix it.
Also applies to: 41-41
🏁 Script executed:
#!/usr/bin/env bash
# Confirm AGENTS.md and workflows agree on Go minor version
echo "AGENTS.md:"; rg -n "Go 1\." AGENTS.md
echo "Workflows:"; rg -n "go-version:\s*'1\.[0-9]+\.x'" .github/workflows
Length of output: 139
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== AGENTS.md matches =="
rg -n "Go 1\." AGENTS.md || true
echo
echo "== AGENTS.md excerpt (around line 41) =="
if [ -f AGENTS.md ]; then
nl -ba AGENTS.md | sed -n '36,46p' || true
else
echo "AGENTS.md not found"
fi
echo
echo "== Search .github/workflows for Go usage / setup-go / go-version / matrix entries =="
if [ -d .github/workflows ]; then
rg -n --hidden -S "go-version|actions/setup-go|setup-go@|\\bgo:\\s*\\[|\\bgo:\\s*[0-9]|matrix:|golang" .github/workflows -C3 || true
echo
echo "== Search for literal '1.22' in workflows =="
rg -n --hidden -S "1\\.22" .github/workflows || true
echo
echo "== List workflow files =="
ls -la .github/workflows || true
else
echo ".github/workflows not found"
fi
Length of output: 2191
🏁 Script executed:
#!/usr/bin/env bash
set -euo pipefail
echo "== AGENTS.md (lines 36-46) =="
awk '{printf "%6d %s\n", NR, $0}' AGENTS.md | sed -n '36,46p' || true
echo
echo "== go.mod (lines 1-20) =="
if [ -f go.mod ]; then
awk '{printf "%6d %s\n", NR, $0}' go.mod | sed -n '1,20p' || true
else
echo "go.mod not found"
fi
echo
echo "== .github/workflows/quality.yml (full file) =="
if [ -f .github/workflows/quality.yml ]; then
awk '{printf "%6d %s\n", NR, $0}' .github/workflows/quality.yml || true
else
echo "quality.yml not found"
fi
echo
echo "== .github/workflows/docs.yml (full file) =="
if [ -f .github/workflows/docs.yml ]; then
awk '{printf "%6d %s\n", NR, $0}' .github/workflows/docs.yml || true
else
echo "docs.yml not found"
fi
Length of output: 4761
Update AGENTS.md Go version to match go.mod/workflows — stop shipping stale docs.
AGENTS.md:41 currently says "Go 1.22" but go.mod sets "go 1.25.1" and .github/workflows/quality.yml uses actions/setup-go go-version: '1.25' (line 21) while docs.yml reads go.mod (lines 16–19). Change AGENTS.md to "Go 1.25 (go.mod: 1.25.1)". Also review GOTOOLCHAIN: go1.22.7 in quality.yml (line 36) — either document it as an intentional golangci-lint pin or update it to 1.25.
## Latest Release Snapshot | ||
|
||
# Hubless Release Notes |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Don’t put a second H1 in the middle of the file.
One top‑level heading per page. Fix the template so the generated output uses H2 here.
Change in the source template (CHANGELOG.template.md), not this generated file:
-# Hubless Release Notes
+## Hubless Release Notes
Re‑run: make docs
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
# Hubless Release Notes | |
## Hubless Release Notes |
🤖 Prompt for AI Agents
In CHANGELOG.md around line 7 there is a second top-level H1 which should not be
added to generated output — update the source template CHANGELOG.template.md to
use an H2 (replace the stray leading "#" with "##" for this heading), commit the
change to the template (not the generated file), then re-run make docs to
regenerate CHANGELOG.md.
return layoutProfile{id: "lg", name: "large"} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
❓ Verification inconclusive
Build is red because you left a stray ‘}’. Fix it. Now.
This is a blocking syntax error: “expected declaration, found '}'”.
-}
Run locally after the fix:
🏁 Script executed:
#!/bin/bash
set -euo pipefail
gofmt -s -w .
go build ./...
go test ./...
Length of output: 69
Remove stray '}' at internal/ui/tui/mock/profile.go:31 — build failing
internal/ui/tui/mock/profile.go:31 contains an extra closing brace causing: "expected declaration, found '}'". Delete the stray }
.
-}
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
} |
🧰 Tools
🪛 GitHub Check: quality
[failure] 31-31:
expected declaration, found '}'
🪛 GitHub Actions: Quality Checks
[error] 31-31: expected declaration, found '}'
🤖 Prompt for AI Agents
internal/ui/tui/mock/profile.go around line 31: there is a stray closing brace
'}' causing a compile error "expected declaration, found '}'". Remove that extra
'}' so the file ends with the correct declarations/closing braces only; ensure
surrounding braces remain balanced and run `go vet`/`go build` to confirm the
file compiles.
// NewStyles constructs and returns a Styles value with the mock TUI's default styling. | ||
// | ||
// The returned Styles sets: | ||
// - Statusline: foreground color #00B3A4 and bold text. | ||
// - Footer: foreground color #6B9F7F. | ||
func NewStyles() Styles { | ||
return Styles{ | ||
Statusline: lipgloss.NewStyle().Foreground(lipgloss.Color("#00B3A4")).Bold(true), | ||
Footer: lipgloss.NewStyle().Foreground(lipgloss.Color("#6B9F7F")), | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Hardcoded colors? In a palette PR? Come on.
Stop baking hex codes. Either make it theme-aware or accept a palette.
Option A (theme-aware now):
return Styles{
- Statusline: lipgloss.NewStyle().Foreground(lipgloss.Color("#00B3A4")).Bold(true),
- Footer: lipgloss.NewStyle().Foreground(lipgloss.Color("#6B9F7F")),
+ Statusline: lipgloss.NewStyle().
+ Foreground(lipgloss.AdaptiveColor{Light: "#00B3A4", Dark: "#00B3A4"}).
+ Bold(true),
+ Footer: lipgloss.NewStyle().
+ Foreground(lipgloss.AdaptiveColor{Light: "#6B9F7F", Dark: "#6B9F7F"}),
}
Option B (clean API; keep backward compat): add
type Palette interface {
Accent() lipgloss.TerminalColor
Subtle() lipgloss.TerminalColor
}
func NewStylesFromPalette(p Palette) Styles {
return Styles{
Statusline: lipgloss.NewStyle().Foreground(p.Accent()).Bold(true),
Footer: lipgloss.NewStyle().Foreground(p.Subtle()),
}
}
Then wire mocks to real palette.
🤖 Prompt for AI Agents
internal/ui/tui/mock/styles.go around lines 11 to 21: currently the mock styles
hardcode hex color strings which breaks palette/theme integration; replace this
by providing a palette-aware API: keep the existing NewStyles for backward
compatibility but implement a NewStylesFromPalette(p Palette) that returns
Styles using p.Accent() for Statusline (bold) and p.Subtle() for Footer, and
have the default NewStyles call NewStylesFromPalette with a sensible default
palette implementation; also add the Palette interface (Accent() and Subtle()
returning lipgloss.TerminalColor) and update mock wiring to construct styles via
the palette-based constructor so mocks use the real palette.
**Old way:** | ||
ticket → | ||
website → | ||
bookkeeping → | ||
copy-paste → | ||
PR. | ||
**New way:** | ||
```bash | ||
git hubless start issue 34 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion
🧩 Analysis chain
Strip trailing spaces and surround the fence (MD009/MD031).
Your whitespace is sloppy; fix it and stop making linters cry.
- **Boring stuff just happens.**
- **Old way:**
- ticket →
- website →
- bookkeeping →
- copy-paste →
- PR.
+ **Boring stuff just happens.**
+ **Old way:**
+ ticket →
+ website →
+ bookkeeping →
+ copy-paste →
+ PR.
**New way:**
- ```bash
+
+ ```bash
git hubless start issue 34
Run to confirm no trailing spaces remain:
🏁 Script executed:
#!/bin/bash
rg -nP '\s+$' README.md
Length of output: 741
Strip trailing whitespace and surround the fenced code block (MD009 / MD031)
- Remove trailing spaces in README.md at lines: 17, 22, 23, 25, 26, 28, 29, 31, 32, 34, 37–44.
- Add a blank line before the opening
bash and after the closing
so the fenced block is properly surrounded. - Re-run rg -nP '\s+$' README.md (or your markdown linter) to confirm the file is clean.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
38-38: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
39-39: Trailing spaces
Expected: 0 or 2; Actual: 1
(MD009, no-trailing-spaces)
43-43: Trailing spaces
Expected: 0 or 2; Actual: 4
(MD009, no-trailing-spaces)
45-45: Fenced code blocks should be surrounded by blank lines
(MD031, blanks-around-fences)
🤖 Prompt for AI Agents
In README.md around lines 17, 22, 23, 25, 26, 28, 29, 31, 32, 34, and 37–44,
remove all trailing whitespace on the noted lines and add an empty line
immediately before the opening ```bash and another empty line immediately after
the closing ``` so the fenced code block is properly surrounded; then re-run rg
-nP '\s+$' README.md (or your markdown linter) to verify no trailing spaces
remain.
|
||
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" | ||
IMAGE_NAME="hubless-release-test" | ||
VERSION="0.0.1" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick
Let VERSION be overridden.
Hard‑coding this is lazy. Respect env override.
-VERSION="0.0.1"
+VERSION="${VERSION:-0.0.1}"
📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
VERSION="0.0.1" | |
VERSION="${VERSION:-0.0.1}" |
🤖 Prompt for AI Agents
In scripts/test-release-docker.sh around line 6, VERSION is hard-coded as
"0.0.1" which prevents overriding via environment; change it to default from
environment by assigning VERSION="${VERSION:-0.0.1}" so callers can override
with an env var while preserving the default.
# contains_placeholder returns 0 if the specified file contains an unresolved placeholder of the form `![[...]]`; otherwise returns 1. | ||
contains_placeholder() { | ||
local file="$1" | ||
if rg -n "!\\[\\[[^]]+\\]\\]" "$file" >/dev/null; then | ||
return 0 | ||
fi | ||
return 1 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fix ShellCheck SC2295, drop hard rg dep, make matcher quiet/portable.
Unquoted parameter expansion and unconditional rg usage are sloppy. Make it portable and correct.
contains_placeholder() {
local file="$1"
- if rg -n "!\\[\\[[^]]+\\]\\]" "$file" >/dev/null; then
- return 0
- fi
- return 1
+ local pattern='!\[\[[^]]+\]\]'
+ if command -v rg >/dev/null 2>&1; then
+ rg -q "$pattern" "$file"
+ else
+ grep -Eq "$pattern" "$file"
+ fi
}
@@
- if contains_placeholder "$file"; then
- failures+=("${file#$ROOT_DIR/}")
+ if contains_placeholder "$file"; then
+ failures+=("${file#"$ROOT_DIR"/}")
fi
@@
- if contains_placeholder "$path"; then
- failures+=("${target}")
+ if contains_placeholder "$path"; then
+ failures+=("$target")
fi
@@
-if ((${#failures[@]} > 0)); then
+if ((${#failures[@]} > 0)); then
printf 'verify-docs: unresolved placeholders found in:\n'
- printf ' %s\n' "${failures[@]}"
+ printf ' %s\n' "${failures[@]}"
exit 1
fi
Also applies to: 31-35, 37-39, 49-53
🤖 Prompt for AI Agents
In scripts/verify-docs.sh around lines 15-22, the function uses an unquoted
parameter and relies on ripgrep (rg); replace the rg invocation with a portable,
quiet grep invocation and ensure the filename parameter is quoted, and simplify
the function to return grep's exit status; apply the same changes to the other
occurrences at lines 31-35, 37-39, and 49-53 so all placeholder checks use grep
-qE (or grep -q -E) with a properly escaped pattern and quoted "$file".
Summary
Testing
go test ./cmd/... ./internal/docscomponents ./internal/release
make docs
./scripts/test-release-docker.sh