Skip to content

refactor: task artifacts changes - #52

Merged
pedronauck merged 10 commits into
mainfrom
pn/task-changes
Apr 5, 2026
Merged

pedronauck merged 10 commits into
mainfrom
pn/task-changes

Conversation

@pedronauck

@pedronauck pedronauck commented Apr 5, 2026 •

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features

    • Added validate-tasks CLI (text/json) with fix prompts and JSON output.
    • Added preflight validation for start with --skip-validation and --force and an interactive validation form.
    • Migration now reports V1→V2 migrated counts and lists unmapped-type files; improved migration/type remapping and title extraction.
    • Task type registry, validation rules, and automated fix-prompt generation.
    • UI timeline shows task title and optional task-type badge.
  • Bug Fixes

    • Improved CLI process exit code handling for accurate statuses.

@pedronauck pedronauck self-assigned this Apr 5, 2026
@coderabbitai

coderabbitai Bot commented Apr 5, 2026 •

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: ded86b29-cb73-4294-9460-0cb5c5f941a2

📥 Commits

Reviewing files that changed from the base of the PR and between f6cf6ab and 93bd9be.

📒 Files selected for processing (4)
  • internal/core/agent/session_helpers_test.go
  • internal/core/agent/tool_call_format.go
  • internal/core/prompt/prompt_test.go
  • internal/core/prompt/templates.go

Disabled knowledge base sources:

  • Linear integration is disabled

You can enable these sources in your CodeRabbit configuration.


Walkthrough

Adds task metadata validation (validator, registry, fix-prompt), interactive/non-interactive preflight integrated into start (skip/force), v1→v2 migration and type remapping with unmapped-file tracking, threads task title/type into planning/UI, and improves CLI exit-code and signal handling.

Changes

Cohort / File(s) Summary
CLI entry & exit / context
cmd/compozy/main.go, command/command.go, internal/cli/exit.go, internal/cli/command_context.go, internal/cli/command_context_test.go, internal/cli/exit_test.go
Introduce ExitCode(err) and commandExitError for process exit codes; update main to use exit codes; add signalCommandContext to derive contexts that honor OS signals; add tests.
Root CLI, flags & new subcommand
internal/cli/root.go, internal/cli/root_test.go, internal/cli/testdata/start_help.golden, internal/cli/form_test.go, internal/cli/validate_tasks.go, internal/cli/validate_tasks_test.go, internal/cli/root_command_execution_test.go, internal/cli/migrate_command_test.go
Add validate-tasks subcommand (text/json), add --skip-validation and --force to start, wire preflight into start flow, update help and add execution tests.
Preflight orchestration & UI
internal/core/run/preflight.go, internal/core/run/preflight_test.go, internal/core/run/validation_form.go, internal/core/run/validation_form_test.go
Implement preflight decision flow (Skip/Force/interactive/non-interactive), failure reporting/fix-prompt rendering, Bubble Tea validation modal and tests.
Task validation, registry & fix-prompt
internal/core/tasks/validate.go, internal/core/tasks/validate_test.go, internal/core/tasks/types.go, internal/core/tasks/types_test.go, internal/core/tasks/fix_prompt.go, internal/core/tasks/fix_prompt_test.go, internal/core/tasks/testdata/*
Add TypeRegistry, task validator (Report/Issue), JSON/text writers, deterministic fix-prompt generation, and comprehensive tests/golden data.
Migration, scanning & remap/title utilities
internal/core/migrate.go, internal/core/migrate_test.go, internal/core/tasks/type_remap.go, internal/core/tasks/type_remap_test.go, internal/core/tasks/title.go, internal/core/tasks/title_test.go
Thread TypeRegistry into migration, detect v1 frontmatter, migrate v1→v2, count V1→V2 migrations, track unmapped-type files, add legacy-type remapping and H1 title extraction utilities and tests.
Model & parsing shape changes
internal/core/model/model.go, internal/core/model/model_test.go, internal/core/prompt/common.go, internal/core/prompt/prompt_test.go, internal/core/prompt/prd.go
Replace v1 fields (domain/scope) with title/type in model/frontmatter; add ErrV1TaskMetadata sentinel; update parsing and prompt rendering/tests.
Plan/prepare/job plumbing
internal/core/plan/input.go, internal/core/plan/prepare.go, internal/core/plan/prepare_test.go
Treat v1 parse errors as migration-directed; parse task metadata into jobs; populate TaskTitle/TaskType; wrap memory prep errors with task path; tests updated.
Runtime UI & job metadata
internal/core/run/types.go, internal/core/run/ui_model.go, internal/core/run/ui_styles.go, internal/core/run/ui_update.go, internal/core/run/ui_view.go, internal/core/run/*_test.go
Thread TaskTitle/TaskType through internal job/UI, add timeline header/badge styles and layout changes, and update UI tests.
Tasks store & helpers
internal/core/tasks/store.go, internal/core/tasks/store_test.go, various test helpers across internal/core/*_test.go
Treat v1 parse errors as legacy artifacts with migrate guidance; update many test fixtures to v2 frontmatter (title/type) and add tests verifying legacy-free docs.
Workspace config: tasks.types
internal/core/workspace/config.go, internal/core/workspace/config_test.go, internal/cli/workspace_config.go
Add [tasks].types to ProjectConfig, validate via TypeRegistry (nil allowed, empty list rejected), and ensure migrate command loads project config; tests cover loading/validation.
Small internal/formatting and helpers
internal/core/prompt/templates.go, internal/core/agent/tool_call_format.go, internal/cli/command_context.go
Introduce reasoning-effort constants, centralize "Find" tool name constant, and add signal-aware command context helper; include small test updates.

Sequence Diagram(s)

sequenceDiagram
    participant CLI as CLI (start)
    participant Config as Workspace Config
    participant Registry as TypeRegistry
    participant Preflight as Preflight Checker
    participant Tasks as Tasks.Validator
    participant Form as Validation Form UI
    participant Runner as Workflow Runner

    CLI->>Config: Load workspace config (tasks.types)
    Config-->>CLI: ProjectConfig
    CLI->>Registry: NewRegistry(config.Tasks.Types)
    Registry-->>CLI: TypeRegistry

    CLI->>Preflight: PreflightCheckConfig(tasksDir, registry, skip/force, interactive?)
    Preflight->>Tasks: Validate(ctx, tasksDir, registry)
    Tasks-->>Preflight: Report (OK?/Issues)
    alt Report.OK
        Preflight-->>CLI: PreflightOK
    else Report has issues and interactive
        Preflight->>Form: Launch validation form (report, fixPrompt)
        Form-->>Preflight: User decision (Continue/Abort/Copy)
        Preflight-->>CLI: PreflightContinued or PreflightAborted
    else Report has issues and non-interactive and force
        Preflight-->>CLI: PreflightForced
    else Report has issues and non-interactive
        Preflight->>CLI: Write failure summary + fix-prompt to stderr
        Preflight-->>CLI: PreflightAborted
    end

    alt Preflight OK/Continued/Forced
        CLI->>Runner: Execute workflow
        Runner-->>CLI: result
    else Preflight Aborted
        CLI-->>CLI: Exit with code from ExitCode(err)
    end
Loading
sequenceDiagram
    participant CLI as CLI (validate-tasks)
    participant Config as Workspace Config
    participant Registry as TypeRegistry
    participant Validator as Tasks.Validate
    participant Formatter as Output Formatter
    participant Stdout as Stdout

    CLI->>Config: Resolve workspace config
    Config-->>CLI: ProjectConfig
    CLI->>Registry: NewRegistry(config.Tasks.Types)
    Registry-->>CLI: TypeRegistry
    CLI->>Validator: Validate(ctx, tasksDir, registry)
    Validator-->>CLI: Report (Scanned, Issues)
    CLI->>Formatter: Format report (json/text, include fix_prompt if not OK)
    Formatter-->>Stdout: Rendered output
    alt Report.OK
        CLI-->>CLI: Exit 0
    else validation issues
        CLI-->>CLI: Exit 1
    else runtime error
        CLI-->>CLI: Exit 2
    end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title is extremely vague and does not convey the specific nature or scope of the changes. While technically related to task artifacts, it provides no meaningful information about what was refactored or why. Replace with a more descriptive title that clearly summarizes the main change, such as 'refactor: migrate task metadata from legacy to v2 schema' or 'refactor: add task validation and metadata migration'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pn/task-changes

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
internal/core/run/ui_view.go (1)

474-493: ⚠️ Potential issue | 🟡 Minor

Account for the extra spacer row in the transcript height budget.

When taskTitle is present, this adds a third header line above the viewport but keeps the old fixed SetHeight budget. The timeline panel becomes one row taller and can clip the bottom footer/help area on normal terminal sizes. Reduce the transcript viewport height whenever the spacer is rendered.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/run/ui_view.go` around lines 474 - 493, The transcript viewport
height isn't reduced when the optional spacer row for a non-empty taskTitle is
added in renderTimelinePanel, causing the panel to grow by one line and
potentially clip the footer; update renderTimelinePanel so that before calling
m.transcriptViewport.SetHeight(...) you compute the height budget as base :=
max(m.contentHeight-4, logViewportMinHeight) and if job != nil &&
strings.TrimSpace(job.taskTitle) != "" then subtract 1 (e.g., base = max(base-1,
logViewportMinHeight)); then call m.transcriptViewport.SetHeight(base) and
proceed with setting content and restoring offsets so the extra spacer is
accounted for without overflowing the footer.
🧹 Nitpick comments (10)
internal/core/model/model_test.go (1)

212-252: Test verifies struct shape via reflection—consider compile-time alternatives.

This test uses reflection to assert field presence/absence on struct types. While the intent (catching schema drift) is valid, Go's type system already provides compile-time checks. A simpler approach would be to access the fields directly in test code—compilation will fail if fields are missing or renamed:

func TestTaskMetadataUsesV2Fields(t *testing.T) {
    t.Parallel()
    // Compile-time verification: these lines won't compile if fields are removed
    _ = model.TaskEntry{}.Title
    _ = model.TaskEntry{}.TaskType
    _ = model.TaskFileMeta{}.Title
    // ...
}

Additionally, test case names should follow the "Should..." pattern per coding guidelines.

As per coding guidelines: "MUST use t.Run("Should...") pattern for ALL test cases" and "Do not use reflection without performance justification".

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/model/model_test.go` around lines 212 - 252, Replace the
runtime reflection checks in TestTaskMetadataUsesV2Fields with compile-time
field accesses: remove the reflect.Type loops and instead directly reference the
required fields on model.TaskEntry and model.TaskFileMeta (e.g., access .Title,
.Status, .TaskType, .Complexity, .Dependencies) so compilation will fail if
fields are removed; also ensure the forbidden fields are not referenced and
rename each t.Run subtest name to follow the "Should..." pattern (e.g., "Should
include Title and drop Domain/Scope") to comply with test naming guidelines.
internal/core/plan/input.go (1)

308-313: Consolidate duplicate wrapTaskParseError implementation. This function is identical to the one in internal/core/tasks/store.go. Extract it to a shared location (e.g., a helper package) to avoid duplication and ensure consistent error handling.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/plan/input.go` around lines 308 - 313, Duplicate
wrapTaskParseError implementations should be consolidated into a single shared
helper: create a new helper (e.g., package internal/core/helpers) and move the
logic into an exported function WrapTaskParseError(path string, err error) error
that preserves the same behavior (checking errors.Is against
prompt.ErrLegacyTaskMetadata and prompt.ErrV1TaskMetadata and returning the same
formatted messages). Replace the wrapTaskParseError definitions in both places
with calls to helpers.WrapTaskParseError and update imports accordingly (remove
the local function and import the helper package in files that used it, e.g.,
the previous internal/core/plan and internal/core/tasks usages).
internal/core/tasks/types.go (1)

22-22: Add a comment clarifying the minimum 2-character slug requirement.

The regex ^[a-z][a-z0-9_-]{1,31}$ enforces slugs of 2-32 characters (1 leading letter + 1-31 trailing chars). Single-character types like "a" are rejected. Adding a brief comment explaining this constraint would clarify the intent, especially since all builtin types are multi-character but this design choice is not documented.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/tasks/types.go` at line 22, The regexp variable
taskTypeSlugPattern enforces a 2–32 character slug (one leading lowercase letter
plus 1–31 of [a-z0-9_-]) which rejects single-character types; add a brief
inline comment above taskTypeSlugPattern describing this minimum 2-character
requirement and the intent (e.g., "must start with a lowercase letter, total
length 2–32, single-character slugs like 'a' are invalid") so readers understand
the design choice and the regex components.
internal/core/workspace/config_test.go (1)

465-475: Consider using slices.Equal instead of custom equalStrings helper.

The equalStrings function duplicates slices.Equal from the standard library (Go 1.21+). Since types_test.go in the same PR already imports and uses slices, this could be simplified.

♻️ Suggested simplification
+import "slices"
+
 // In test assertions, replace:
-if !equalStrings(*cfg.Tasks.Types, tt.wantTypes) {
+if !slices.Equal(*cfg.Tasks.Types, tt.wantTypes) {

-func equalStrings(got, want []string) bool {
-	if len(got) != len(want) {
-		return false
-	}
-	for idx := range got {
-		if got[idx] != want[idx] {
-			return false
-		}
-	}
-	return true
-}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/workspace/config_test.go` around lines 465 - 475, The file
defines a custom helper equalStrings that duplicates standard library
functionality; replace all uses of equalStrings with slices.Equal from the
standard library and remove the equalStrings function definition; also add the
"slices" import to this test file (or reuse the existing slices import if
present in related tests like types_test.go) so comparisons use
slices.Equal(got, want) instead of the custom loop.
internal/core/tasks/type_remap_test.go (1)

5-50: Good registry-based remapping tests, but missing nil registry case.

The test covers key scenarios well. However, given the nil pointer issue flagged in type_remap.go, consider adding a test case for nil registry to verify the expected behavior (should return "").

🧪 Suggested additional test
func TestRemapLegacyTaskTypeWithNilRegistry(t *testing.T) {
	t.Parallel()

	tests := []struct {
		name    string
		rawType string
		want    string
	}{
		{
			name:    "known remap without registry returns empty",
			rawType: "Refactor",
			want:    "", // or "refactor" depending on desired behavior
		},
		{
			name:    "unknown type without registry returns empty",
			rawType: "backend",
			want:    "",
		},
	}

	for _, tt := range tests {
		tt := tt
		t.Run(tt.name, func(t *testing.T) {
			t.Parallel()
			if got := RemapLegacyTaskType(tt.rawType, nil); got != tt.want {
				t.Fatalf("unexpected remap for %q with nil registry\nwant: %q\ngot:  %q", tt.rawType, tt.want, got)
			}
		})
	}
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/tasks/type_remap_test.go` around lines 5 - 50, Add a new test
function TestRemapLegacyTaskTypeWithNilRegistry that calls RemapLegacyTaskType
with a nil registry and asserts it returns the empty string; implement it as a
parallel table-driven test (use t.Parallel, table of cases with rawType
"Refactor" and "backend" and want ""), run each case via t.Run and compare got
:= RemapLegacyTaskType(tt.rawType, nil) to tt.want and fail with a clear message
on mismatch so the nil-registry behavior is covered and prevents the nil pointer
regression.
internal/core/prompt/prompt_test.go (1)

505-535: Consider splitting mixed assertion styles in TestTaskMetadataHelpers.

This test mixes table-driven pattern (for IsTaskCompleted) with inline assertions (for ExtractTaskNumber and SafeFileName). For consistency and better failure isolation, consider extracting the inline assertions into separate subtests or their own test functions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/prompt/prompt_test.go` around lines 505 - 535,
TestTaskMetadataHelpers mixes a table-driven subtest for IsTaskCompleted with
two inline assertions for ExtractTaskNumber and SafeFileName, which reduces
isolation; move those inline checks into their own subtests or separate test
functions (e.g., add t.Run("ExtractTaskNumber", ...) and t.Run("SafeFileName",
...) inside TestTaskMetadataHelpers or create TestExtractTaskNumber and
TestSafeFileName) and call t.Parallel() inside each new subtest/test so failures
are isolated and parallelism is preserved; reference IsTaskCompleted,
ExtractTaskNumber, SafeFileName and the existing TestTaskMetadataHelpers when
making the change.
internal/core/tasks/fix_prompt_test.go (1)

52-54: Consider using filepath.Join for cross-platform compatibility.

The filepathJoin helper uses a hardcoded / separator. While this works for testdata paths on all platforms, using filepath.Join would be more idiomatic.

♻️ Suggested change
+import "path/filepath"
+
-func filepathJoin(parts ...string) string {
-	return strings.Join(parts, "/")
-}
+func filepathJoin(parts ...string) string {
+	return filepath.Join(parts...)
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/tasks/fix_prompt_test.go` around lines 52 - 54, The helper
filepathJoin currently joins parts with a hardcoded "/" which is non-idiomatic;
update the filepathJoin function to delegate to path/filepath's filepath.Join
(i.e., return filepath.Join(parts...)) and add the import for "path/filepath"
(remove or keep "strings" if still used elsewhere) so tests use the OS-native
separator via the filepath.Join function.
internal/core/migrate_test.go (2)

537-545: Consider extracting shared test helpers to avoid duplication.

mustMigrationRegistry creates a default registry for tests. The preflight tests also need a similar helper (testValidationRegistry). Consider whether these could be consolidated in a shared test utilities package to reduce duplication across test files.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/migrate_test.go` around lines 537 - 545, The helper
mustMigrationRegistry duplicates setup logic used by preflight tests; extract a
shared test helper (e.g., NewTestRegistry or testValidationRegistry) into a test
utilities package and have tests call that instead of duplicating code.
Concretely, move the registry creation logic that calls
taskscore.NewRegistry(nil) and the t.Fatalf error handling into a new helper
function (name it NewTestRegistry or testValidationRegistry), update callers
(tests referencing mustMigrationRegistry and the preflight tests) to import and
call the shared helper, and adjust imports to use the new test utilities package
so duplication is removed while preserving the same behavior.

48-57: Loop variable capture is redundant in Go 1.22+.

The tt := tt pattern on line 49 (and similarly on line 111) was needed in older Go versions to capture the loop variable correctly in closures. Since Go 1.22, the loop variable semantics changed and this is no longer necessary.

♻️ Remove redundant loop variable capture
 	for _, tt := range tests {
-		tt := tt
 		t.Run(tt.name, func(t *testing.T) {
 			t.Parallel()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/migrate_test.go` around lines 48 - 57, Remove the redundant
loop-variable capture lines (`tt := tt`) inside the test loops; update the
table-driven tests that call t.Run(tt.name, ...) and use tt.body / tt.want (and
the second occurrence at the other test block around line 111) to rely on Go
1.22+ loop semantics, simply iterating `for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { ... }) }` while keeping calls to
taskscore.ExtractTaskBodyTitle(tt.body) and assertions unchanged.
internal/core/tasks/validate.go (1)

46-48: Consider using ctx.Err() instead of context.Cause() for pre-check.

context.Cause(ctx) returns the underlying cause of cancellation, but for a simple "is the context done?" check, ctx.Err() is more idiomatic. context.Cause is typically used when you need to distinguish between different cancellation reasons.

♻️ Use ctx.Err() for simpler cancellation check
-	if err := context.Cause(ctx); err != nil {
+	if err := ctx.Err(); err != nil {
 		return Report{}, fmt.Errorf("validate tasks: %w", err)
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/tasks/validate.go` around lines 46 - 48, The current pre-check
in validate tasks uses context.Cause(ctx) which is intended for extracting
cancellation causes; replace that call with the idiomatic ctx.Err() check in the
same location (the block that currently calls context.Cause(ctx) and returns
fmt.Errorf("validate tasks: %w", err)) so the code simply checks if ctx.Err() !=
nil and returns the wrapped error; update the conditional around
context.Cause(ctx) to use ctx.Err() while keeping the existing error wrapping
and return behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/cli/exit.go`:
- Around line 8-10: The Error() method on commandExitError can panic if its
e.err field is nil; modify commandExitError.Error to defensively handle a nil
e.err (e.g., return an empty string or a default message) instead of directly
calling e.err.Error(), and/or update any factory like withExitCode to ensure
e.err is never nil; reference the commandExitError type and its Error() method
and adjust callers such as withExitCode to either pass a non-nil error or rely
on the new nil-safe behavior.

In `@internal/cli/root.go`:
- Around line 485-498: migrationFixPrompt currently builds the Issue message
with an empty type string, producing `type "" must be one of: ...`; update
migrationFixPrompt to include the actual unmapped type value instead of the
empty string by reading it from the migration result or file metadata (e.g.,
look up the type for the path in core.MigrationResult — e.g., a map or struct
like result.MigratedFiles[path].Type or a result.FileType map — or, if not
available, parse the file at the given path to extract the type) and use that
variable in the fmt.Sprintf call that sets tasks.Issue.Message (keep the rest of
the loop and the Report creation the same, only replace the literal "" with the
actual type value).

In `@internal/cli/validate_tasks.go`:
- Around line 110-121: The validator trims whitespace when checking s.format in
validateFormat() but doesn't persist that normalized value, causing run() to see
the original untrimmed string; update validateFormat() to normalize and store
the trimmed value (e.g., assign strings.TrimSpace(s.format) back to s.format)
before the switch so subsequent logic in run() and branches like
validateTasksFormatText / validateTasksFormatJSON operate on the normalized
format.
- Around line 211-224: resolveTaskWorkflowDir currently calls filepath.Abs on
resolvedTasksDir which resolves relative paths against the process CWD; instead,
if resolvedTasksDir is not absolute (use filepath.IsAbs), join it with
workspaceRoot (e.g. filepath.Join(workspaceRoot, resolvedTasksDir)) and then
call filepath.Abs on that combined path so relative --tasks-dir values are
resolved from the workspaceRoot; update references in resolveTaskWorkflowDir
(resolvedTasksDir, workspaceRoot, model.TaskDirectoryForWorkspace) and preserve
the existing error wrapping from fmt.Errorf("resolve tasks dir: %w", err).
- Around line 66-68: The run method on validateTasksCommandState currently uses
context.Background() when creating the cancelable context with
signal.NotifyContext; change it to start from cmd.Context() (with a fallback to
context.Background() if cmd.Context() is nil) so parent cancellations/deadlines
propagate, i.e., obtain the base context via cmd.Context(), then call
signal.NotifyContext(baseCtx, os.Interrupt, syscall.SIGTERM) and keep the
existing defer stop() logic.

In `@internal/core/plan/prepare.go`:
- Around line 143-145: The call to memory.Prepare currently returns the raw
error which loses task context; update the error return to wrap the underlying
error with task/workflow information using fmt.Errorf, e.g. wrap the err from
memory.Prepare(cfg.TasksDir, batchIssues[0].Name) with a message that includes
batchIssues[0].Name (or other identifying fields) before returning so callers
see "memory.Prepare for <task>: %w".

In `@internal/core/run/preflight_test.go`:
- Around line 183-203: The test TestRunValidationFormUsesInjectedInputAndOutput
mutates package-level variables validationFormInput and validationFormOutput
while calling runValidationForm, which can race with other parallel tests;
remove t.Parallel() from this test (delete the t.Parallel() call) so the test
runs serially, keep the t.Cleanup restore logic intact, and rerun tests to
ensure no races; alternatively, if you prefer concurrency safety instead, guard
access to validationFormInput/validationFormOutput with a sync.Mutex or refactor
runValidationForm to accept io.Reader/io.Writer via its config so tests can
inject per-test I/O without touching package-level state.

In `@internal/core/run/preflight.go`:
- Around line 16-19: The package-level mutable vars validationFormInput and
validationFormOutput are causing potential test races; add io.Reader and
io.Writer fields (e.g., ValidationInput io.Reader, ValidationOutput io.Writer)
to PreflightConfig, update runValidationForm to use cfg.ValidationInput and
cfg.ValidationOutput when non-nil (falling back to os.Stdin/os.Stdout), remove
or stop using the package-level vars, and update
TestRunValidationFormUsesInjectedInputAndOutput to inject the test buffers via
PreflightConfig instead of mutating the globals.

In `@internal/core/run/validation_form.go`:
- Around line 48-69: The Update method currently only handles quit keys and
doesn't support scrolling, so long reports run off-screen; add state to
validationFormModel (e.g., issueOffset int and issueViewportHeight int) and in
the WindowSizeMsg branch compute issueViewportHeight = max(0, m.height -
reservedLines) where reservedLines accounts for header/footer/help lines, then
clamp issueOffset; extend the KeyPressMsg handling to support scroll keys
("up"/"k" -> issueOffset--, "down"/"j" -> issueOffset++, "pgup" -> issueOffset
-= issueViewportHeight, "pgdown" -> issueOffset += issueViewportHeight,
optionally "home"/"end") and always clamp issueOffset between 0 and
len(m.issues)-issueViewportHeight; update the rendering code that lists issues
to slice by issueOffset..issueOffset+issueViewportHeight so the visible region
is bounded; keep existing behaviors around writeFixPrompt(), setting m.decision
(PreflightContinued/PreflightAborted) and quitting unchanged.

In `@internal/core/tasks/type_remap.go`:
- Around line 43-47: The loop over registry.Values() can panic if registry is
nil; add a nil check for registry before iterating in the fallback branch (the
code using registry.Values() and comparing to normalized), or short-circuit to
the legacyTypeRemap/return path when registry == nil; update the function that
performs the remap so the IsAllowed check and the subsequent for _, candidate :=
range registry.Values() are protected by the same registry != nil guard
(reference symbols: registry, Values(), normalized, legacyTypeRemap, IsAllowed).

---

Outside diff comments:
In `@internal/core/run/ui_view.go`:
- Around line 474-493: The transcript viewport height isn't reduced when the
optional spacer row for a non-empty taskTitle is added in renderTimelinePanel,
causing the panel to grow by one line and potentially clip the footer; update
renderTimelinePanel so that before calling m.transcriptViewport.SetHeight(...)
you compute the height budget as base := max(m.contentHeight-4,
logViewportMinHeight) and if job != nil && strings.TrimSpace(job.taskTitle) !=
"" then subtract 1 (e.g., base = max(base-1, logViewportMinHeight)); then call
m.transcriptViewport.SetHeight(base) and proceed with setting content and
restoring offsets so the extra spacer is accounted for without overflowing the
footer.

---

Nitpick comments:
In `@internal/core/migrate_test.go`:
- Around line 537-545: The helper mustMigrationRegistry duplicates setup logic
used by preflight tests; extract a shared test helper (e.g., NewTestRegistry or
testValidationRegistry) into a test utilities package and have tests call that
instead of duplicating code. Concretely, move the registry creation logic that
calls taskscore.NewRegistry(nil) and the t.Fatalf error handling into a new
helper function (name it NewTestRegistry or testValidationRegistry), update
callers (tests referencing mustMigrationRegistry and the preflight tests) to
import and call the shared helper, and adjust imports to use the new test
utilities package so duplication is removed while preserving the same behavior.
- Around line 48-57: Remove the redundant loop-variable capture lines (`tt :=
tt`) inside the test loops; update the table-driven tests that call
t.Run(tt.name, ...) and use tt.body / tt.want (and the second occurrence at the
other test block around line 111) to rely on Go 1.22+ loop semantics, simply
iterating `for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { ... })
}` while keeping calls to taskscore.ExtractTaskBodyTitle(tt.body) and assertions
unchanged.

In `@internal/core/model/model_test.go`:
- Around line 212-252: Replace the runtime reflection checks in
TestTaskMetadataUsesV2Fields with compile-time field accesses: remove the
reflect.Type loops and instead directly reference the required fields on
model.TaskEntry and model.TaskFileMeta (e.g., access .Title, .Status, .TaskType,
.Complexity, .Dependencies) so compilation will fail if fields are removed; also
ensure the forbidden fields are not referenced and rename each t.Run subtest
name to follow the "Should..." pattern (e.g., "Should include Title and drop
Domain/Scope") to comply with test naming guidelines.

In `@internal/core/plan/input.go`:
- Around line 308-313: Duplicate wrapTaskParseError implementations should be
consolidated into a single shared helper: create a new helper (e.g., package
internal/core/helpers) and move the logic into an exported function
WrapTaskParseError(path string, err error) error that preserves the same
behavior (checking errors.Is against prompt.ErrLegacyTaskMetadata and
prompt.ErrV1TaskMetadata and returning the same formatted messages). Replace the
wrapTaskParseError definitions in both places with calls to
helpers.WrapTaskParseError and update imports accordingly (remove the local
function and import the helper package in files that used it, e.g., the previous
internal/core/plan and internal/core/tasks usages).

In `@internal/core/prompt/prompt_test.go`:
- Around line 505-535: TestTaskMetadataHelpers mixes a table-driven subtest for
IsTaskCompleted with two inline assertions for ExtractTaskNumber and
SafeFileName, which reduces isolation; move those inline checks into their own
subtests or separate test functions (e.g., add t.Run("ExtractTaskNumber", ...)
and t.Run("SafeFileName", ...) inside TestTaskMetadataHelpers or create
TestExtractTaskNumber and TestSafeFileName) and call t.Parallel() inside each
new subtest/test so failures are isolated and parallelism is preserved;
reference IsTaskCompleted, ExtractTaskNumber, SafeFileName and the existing
TestTaskMetadataHelpers when making the change.

In `@internal/core/tasks/fix_prompt_test.go`:
- Around line 52-54: The helper filepathJoin currently joins parts with a
hardcoded "/" which is non-idiomatic; update the filepathJoin function to
delegate to path/filepath's filepath.Join (i.e., return filepath.Join(parts...))
and add the import for "path/filepath" (remove or keep "strings" if still used
elsewhere) so tests use the OS-native separator via the filepath.Join function.

In `@internal/core/tasks/type_remap_test.go`:
- Around line 5-50: Add a new test function
TestRemapLegacyTaskTypeWithNilRegistry that calls RemapLegacyTaskType with a nil
registry and asserts it returns the empty string; implement it as a parallel
table-driven test (use t.Parallel, table of cases with rawType "Refactor" and
"backend" and want ""), run each case via t.Run and compare got :=
RemapLegacyTaskType(tt.rawType, nil) to tt.want and fail with a clear message on
mismatch so the nil-registry behavior is covered and prevents the nil pointer
regression.

In `@internal/core/tasks/types.go`:
- Line 22: The regexp variable taskTypeSlugPattern enforces a 2–32 character
slug (one leading lowercase letter plus 1–31 of [a-z0-9_-]) which rejects
single-character types; add a brief inline comment above taskTypeSlugPattern
describing this minimum 2-character requirement and the intent (e.g., "must
start with a lowercase letter, total length 2–32, single-character slugs like
'a' are invalid") so readers understand the design choice and the regex
components.

In `@internal/core/tasks/validate.go`:
- Around line 46-48: The current pre-check in validate tasks uses
context.Cause(ctx) which is intended for extracting cancellation causes; replace
that call with the idiomatic ctx.Err() check in the same location (the block
that currently calls context.Cause(ctx) and returns fmt.Errorf("validate tasks:
%w", err)) so the code simply checks if ctx.Err() != nil and returns the wrapped
error; update the conditional around context.Cause(ctx) to use ctx.Err() while
keeping the existing error wrapping and return behavior.

In `@internal/core/workspace/config_test.go`:
- Around line 465-475: The file defines a custom helper equalStrings that
duplicates standard library functionality; replace all uses of equalStrings with
slices.Equal from the standard library and remove the equalStrings function
definition; also add the "slices" import to this test file (or reuse the
existing slices import if present in related tests like types_test.go) so
comparisons use slices.Equal(got, want) instead of the custom loop.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 25395540-22e7-476a-8abc-002bc7aff7c7

📥 Commits

Reviewing files that changed from the base of the PR and between 630c0ed and 93ef960.

⛔ Files ignored due to path filters (29)
  • .agents/skills/cy-create-tasks/SKILL.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/cy-create-tasks/references/task-context-schema.md is excluded by !**/*.md, !.agents/**
  • .agents/skills/cy-create-tasks/references/task-template.md is excluded by !**/*.md, !.agents/**
  • .compozy/tasks/acp-integration/_meta.md is excluded by !**/*.md
  • .compozy/tasks/acp-integration/task_01.md is excluded by !**/*.md
  • .compozy/tasks/acp-integration/task_02.md is excluded by !**/*.md
  • .compozy/tasks/acp-integration/task_03.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/_meta.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/_tasks.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/MEMORY.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/task_01.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/task_02.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/task_03.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/task_04.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/task_05.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/memory/task_06.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/reviews-001/_meta.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/reviews-001/issue_001.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/reviews-001/issue_002.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/task_01.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/task_02.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/task_03.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/task_04.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/task_05.md is excluded by !**/*.md
  • .compozy/tasks/tasks-changes/task_06.md is excluded by !**/*.md
  • README.md is excluded by !**/*.md
  • skills/cy-create-tasks/SKILL.md is excluded by !**/*.md
  • skills/cy-create-tasks/references/task-context-schema.md is excluded by !**/*.md
  • skills/cy-create-tasks/references/task-template.md is excluded by !**/*.md
📒 Files selected for processing (54)
  • cmd/compozy/main.go
  • command/command.go
  • internal/cli/exit.go
  • internal/cli/form_test.go
  • internal/cli/migrate_command_test.go
  • internal/cli/root.go
  • internal/cli/root_command_execution_test.go
  • internal/cli/root_test.go
  • internal/cli/testdata/start_help.golden
  • internal/cli/validate_tasks.go
  • internal/cli/validate_tasks_test.go
  • internal/cli/workspace_config.go
  • internal/core/api.go
  • internal/core/archive_test.go
  • internal/core/migrate.go
  • internal/core/migrate_test.go
  • internal/core/model/model.go
  • internal/core/model/model_test.go
  • internal/core/plan/input.go
  • internal/core/plan/prepare.go
  • internal/core/plan/prepare_test.go
  • internal/core/prompt/common.go
  • internal/core/prompt/prd.go
  • internal/core/prompt/prompt_test.go
  • internal/core/run/execution_test.go
  • internal/core/run/preflight.go
  • internal/core/run/preflight_test.go
  • internal/core/run/types.go
  • internal/core/run/ui_model.go
  • internal/core/run/ui_styles.go
  • internal/core/run/ui_update.go
  • internal/core/run/ui_update_test.go
  • internal/core/run/ui_view.go
  • internal/core/run/ui_view_test.go
  • internal/core/run/validation_form.go
  • internal/core/run/validation_form_test.go
  • internal/core/sync_test.go
  • internal/core/tasks/fix_prompt.go
  • internal/core/tasks/fix_prompt_test.go
  • internal/core/tasks/store.go
  • internal/core/tasks/store_test.go
  • internal/core/tasks/testdata/fix_prompt.golden
  • internal/core/tasks/testdata/validate_title_h1.golden
  • internal/core/tasks/title.go
  • internal/core/tasks/type_remap.go
  • internal/core/tasks/type_remap_test.go
  • internal/core/tasks/types.go
  • internal/core/tasks/types_test.go
  • internal/core/tasks/validate.go
  • internal/core/tasks/validate_test.go
  • internal/core/workspace/config.go
  • internal/core/workspace/config_test.go
  • test/public_api_test.go
  • test/skills_bundle_test.go

Comment thread internal/cli/exit.go
Comment thread internal/cli/root.go
Comment thread internal/cli/validate_tasks.go
Comment thread internal/cli/validate_tasks.go
Comment thread internal/cli/validate_tasks.go
Comment thread internal/core/plan/prepare.go Outdated
Comment thread internal/core/run/preflight_test.go
Comment thread internal/core/run/preflight.go Outdated
Comment thread internal/core/run/validation_form.go
Comment thread internal/core/tasks/type_remap.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant