Skip to content

Conversation

@connerohnesorge
Copy link
Owner

@connerohnesorge connerohnesorge commented Jan 18, 2026

Summary by CodeRabbit

  • New Features

    • Support for hierarchical task files (root + child includes) and versioned task metadata.
    • Built-in JSONC validation to ensure generated task files parse and round-trip losslessly.
  • Bug Fixes

    • Corrected parsing to preserve multi-line/indented task descriptions.
  • Tests

    • Large suite of JSONC, Unicode, regex, fuzz, and edge-case tests added to improve reliability.
  • Documentation

    • Major documentation updates and new guidance for agent workflows and initialization.

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 18, 2026

Important

Review skipped

Too many files!

48 files out of 198 files are above the max files limit of 150.

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Walkthrough

Extends the tasks data model for hierarchical JSONC (Task.Children, TasksFile.Parent/Includes), adds JSONC validation and round‑trip checks, enhances parsing to support multiline task descriptions, introduces provider agent‑skills initializers, large test suites, and removes or rewrites many spec/documentation files.

Changes

Cohort / File(s) Summary
Schema: Task & TasksFile
internal/parsers/types.go, internal/parsers/types_test.go
Added Task.Children string, TasksFile.Parent string, TasksFile.Includes []string (JSON tags). Added serialization/deserialization tests verifying omission and preservation.
JSONC Validation & Validators
cmd/accept_validator.go, cmd/accept_validator_test.go
New internal validators: validateJSONCOutput, buildJSONCValidationError, validateTasksJSONC plus extensive tests exercising comment‑stripping, round‑trip integrity, and detailed error contexts.
Accept Command: Parsing Enhancements
cmd/accept.go, cmd/accept_writer.go (tests)
parseTasksMd / parseSections updated to collect indented continuation lines into task/section descriptions; integration of validation into write paths (validator hooks added).
Large Test Suites / Fuzzing / Edge Cases
cmd/accept_fuzz_test.go, cmd/accept_*_test.go (multiple), internal/parsers/problematic_jsonc_test.go
Many new tests covering multiline parsing, Unicode, nested quotes, regex, JSONC comment patterns, fuzz-style edge cases and round‑trip verification.
Test Helpers & Test Updates
cmd/testhelpers.go, cmd/view_test.go
Added helpers: FindProjectRoot, GetSpectrDir, GetTestDataDir; updated tests to use helpers and simplify setup.
Provider Initializers & Gemini
internal/initialize/providers/gemini.go, internal/initialize/providers/provider_test.go, internal/initialize/templates/gemini.md
GeminiProvider extended with .gemini/skills DirectoryInitializer, GEMINI.md ConfigFileInitializer, and two AgentSkillsInitializers; provider tests updated to expect additional initializers; added GEMINI.md template.
Template / Frontmatter Adjustments
internal/domain/templates/slash-*.toml.tmpl, internal/domain/template.go, internal/initialize/templates.go
Removed agent/model keys from TOML templates; frontmatter/template API changes referenced in design docs (TemplateRef/TemplateManager additions described).
Spec & Documentation Rewrites / Deletions
spectr/AGENTS.md, CLAUDE.md, many spectr/specs/* (numerous deletions and new specs)
Major documentation restructuring: large set of spec files removed or replaced, new design/proposal archives added (size-based splitting, JSONC validation, agent skills support), AGENTS.md expanded into instruction guide, project.md made placeholder.
Size‑Based Task Splitting Feature (Design + Data Files)
spectr/changes/archive/2026-01-19-add-size-based-task-splitting/*, spectr/changes/archive/.../tasks-*.jsonc, spectr/changes/.../tasks.jsonc
Added design/proposal for 100-line splitting into root + child tasks-N.jsonc (version 2); many generated JSONC task files and planning docs reflecting split schema (includes/parent/children).
JSONC Validation Design + Tasks
spectr/changes/archive/2026-01-19-fix-jsonc-validation/*
Added design/proposal and task tracking files for JSONC validation (round‑trip checks, property tests, external parser options).
Test Data & Archives
testdata/integration/changes/archive/sample-change/tasks.jsonc, many spectr/changes/*/tasks.jsonc
Added sample archived tasks and numerous auto-generated JSONC task files used for testing and archived proposals.
Small infra / CI / lint tweaks
flake.nix, .conclaude.yaml, spectr/.gitignore, .opencode/commands/*
Adjusted markdownlint ignore, re-enabled lint step, removed spectr from .gitignore, small metadata changes in command frontmatter files.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant CLI as spectr accept
participant Validator as validateJSONCOutput / validateTasksJSONC
participant JSONC as StripJSONComments + json.Unmarshal
participant FS as Filesystem (write)

CLI->>Validator: marshal TasksFile -> JSONC bytes
Validator->>JSONC: strip comments, unmarshal to map/struct
JSONC-->>Validator: parsed data or syntax error
Validator->>Validator: compare round-trip TasksFile fields
Validator-->>CLI: success or detailed validation error (position, task id, snippet)
alt validation OK
CLI->>FS: write tasks.jsonc and child files
else validation failed
CLI-->>FS: do not write; return error to user
end

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

enhancement, go

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically summarizes the main change: implementing size-based task splitting, which is the primary feature addition in this changeset.
Docstring Coverage ✅ Passed Docstring coverage is 93.16% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch hierarchical

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.

❤️ Share

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

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

Caution

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

⚠️ Outside diff range comments (2)
cmd/accept_writer.go (2)

19-50: Header text conflicts with spec about “pending → completed” skips.

The spec now allows skipping from pending to completed for trivial tasks, but the header still says “Do not skip states.” This will contradict the intended workflow.

✏️ Suggested header update
-//   Tasks should only move forward through these states.
-//   Do not skip states or move backward.
+//   Tasks should only move forward through these states.
+//   Skipping directly from "pending" to "completed" is allowed for trivial tasks.
+//   Do not move backward.

139-159: Generated JSONC isn’t validated before writing.

The spec requires validating JSONC output and avoiding partial/invalid files on disk. writeTasksJSONC (and root/child writers) currently write directly without validation.

🛠️ Example validation hook
 	// Prepend the JSONC header to the JSON data
 	output := tasksJSONHeader + string(jsonData)
+
+	// Validate JSONC before writing
+	stripped := parsers.StripJSONComments([]byte(output))
+	var check parsers.TasksFile
+	if err := json.Unmarshal(stripped, &check); err != nil {
+		return fmt.Errorf("invalid JSONC output: %w", err)
+	}
 
 	if err := os.WriteFile(path, []byte(output), filePerm); err != nil {
 		return fmt.Errorf(
 			"failed to write tasks.jsonc: %w",
 			err,
 		)
 	}
Consider writing atomically (temp file + rename) to avoid partial files on failures.
🤖 Fix all issues with AI agents
In `@cmd/accept_writer.go`:
- Around line 312-386: The JSONC comment stripping (parsers.StripJSONComments)
still leaves trailing commas which cause json.Unmarshal to fail; update
loadRootTasksFile and loadSingleChildFile to handle trailing commas by either
calling a new parser helper (e.g., parsers.StripJSONTrailingCommas or extend
parsers.StripJSONComments to remove trailing commas) on
strippedRoot/strippedChild before calling json.Unmarshal, or swap the unmarshal
to a JSONC-aware parser; ensure you reference parsers.StripJSONComments and the
json.Unmarshal calls in loadRootTasksFile and loadSingleChildFile and apply the
extra cleaning/parsing step so trailing commas no longer cause parse errors.
- Around line 60-112: The childFileHeader currently builds a bespoke header and
omits the full root header; update childFileHeader(changeID, parentTaskID) to
reuse the canonical root header generator (the existing root/tasks header
function—e.g., rootHeader or tasksFileHeader) and prepend the origin lines
("Generated by", "Parent change", "Parent task") so the returned string includes
the complete root header text (including the "Spectr Tasks File (JSONC)" block)
followed by the origin metadata and trailing newlines; keep parameters changeID
and parentTaskID and ensure formatting matches existing root header output.

In `@cmd/accept.go`:
- Around line 229-276: The split branch currently ignores appendCfg (so
appendCfg.Tasks are not written though totalTasks counts them); update the split
path so appendCfg.Tasks are included when calling writeHierarchicalTasks (or by
merging appendCfg.Tasks into the sections parsed by parseSections) and ensure
statusMap handling is preserved; specifically modify the code around
writeHierarchicalTasks to accept or incorporate appendCfg.Tasks (or add an
overload/write call that takes appendCfg) and keep totalTasks calculation
consistent so the printed "Wrote %d tasks" reflects the actual output.
- Around line 617-625: In parseSections, tasks found via
markdown.MatchFlexibleTask that call state.createTask are currently only
appended when currentSection != nil, which silently drops leading tasks in split
mode; update parseSections to detect when a task is encountered but
currentSection == nil and, instead of ignoring it, return an error (fail fast)
indicating "task before first section" (or similar) so callers can handle it;
specifically modify the block that checks match, ok :=
markdown.MatchFlexibleTask(line) to check the split-mode context (or a provided
flag) and return the error when no currentSection exists rather than appending
or dropping the task.
- Around line 680-737: parseSubsections collapses common "1.x" tasks because
extractIDPrefix returns "1" for IDs with a single dot; update extractIDPrefix to
only strip the last segment when the ID contains two or more dots (so "1.2.3" ->
"1.2" but "1.1" remains "1.1"), and add a simple fallback in parseSubsections to
split any resulting SubsectionGroup that exceeds a max size into smaller chunks
(e.g., chunk by N tasks or by full task.ID) so large sections can actually be
split; reference extractIDPrefix and parseSubsections (and optionally
MatchFlexibleTask) when making these changes.

In `@spectr/changes/add-size-based-task-splitting/proposal.md`:
- Around line 57-66: Update the fenced code blocks in proposal.md that show the
directory trees by adding a language identifier (e.g., "text" or "plaintext")
after the opening backticks; specifically modify the two directory-tree code
blocks (the first block containing "spectr/changes/my-big-change/ ├──
proposal.md ..." and the second block with the expanded split layout) so their
opening fences become ```text to satisfy markdownlint and keep examples
readable.

In `@spectr/changes/add-size-based-task-splitting/tasks.jsonc`:
- Around line 38-563: The tasks.jsonc file is out of sync with tasks.md and
still uses the flat version 1 format; re-run the accept workflow to regenerate
hierarchical output when shouldSplit() returns true: invoke processChange() (or
run `spectr accept add-size-based-task-splitting`) so it calls shouldSplit() and
routes to writeHierarchicalTasks() instead of writeTasksJSONC(), producing a
version 2 root file with includes and child tasks-N.jsonc files (detectable via
isVersionTwo()); regenerate the files and commit the updated tasks.jsonc and
generated child files so they match tasks.md.
🧹 Nitpick comments (3)
cmd/accept.go (1)

522-585: Move split helpers to internal/ and use afero.Fs for file I/O.

These new helpers introduce substantial business logic and direct os.* usage inside cmd/. Suggest moving the splitting/parsing helpers into an internal/ package and injecting afero.Fs for filesystem access to align with testability expectations.

As per coding guidelines, please confirm whether you want me to sketch the internal package boundary and afero wiring.

cmd/accept_writer_test.go (1)

389-447: Consider table-driven tests with fixtures.

These single-scenario tests would be easier to extend and align with repo standards if converted to table-driven t.Run cases and moved to testdata/ fixtures for file content.

As per coding guidelines, consider refactoring the tests to table-driven style with testdata/ fixtures.

cmd/accept_writer.go (1)

437-457: Use afero.Fs for filesystem operations in writer helpers.

deleteOldChildFiles (and other write/read helpers) rely on direct os.* calls. The repo guidelines call for Afero-based filesystem abstraction for testability.

As per coding guidelines, please confirm whether you want me to sketch an afero.Fs injection pattern for these helpers.

Comment on lines +60 to +112
// childFileHeader generates a JSONC header for child task files (tasks-N.jsonc).
// It includes origin information (change ID, parent task ID) followed by the
// full standard header that explains status values, transitions, and workflow.
//
// This provides context when an agent reads a child file standalone, enabling
// them to understand where the file came from and how to work with task statuses.
//
// Parameters:
// - changeID: The ID of the change (e.g., "add-size-based-task-splitting")
// - parentTaskID: The ID of the parent task in the root file (e.g., "5")
//
// Returns a complete JSONC header string with trailing newlines.
func childFileHeader(changeID, parentTaskID string) string {
return fmt.Sprintf(`// Generated by: spectr accept %s
// Parent change: %s
// Parent task: %s
//
// This file contains child tasks split from the main tasks.jsonc for readability.
// JSONC format allows comments while maintaining JSON compatibility.
//
// Status Values:
// - "pending" : Task has not been started yet
// - "in_progress" : Task is currently being worked on
// - "completed" : Task has been finished and verified
//
// Status Transitions:
// pending -> in_progress -> completed
//
// Tasks should only move forward through these states.
// Do not skip states or move backward.
//
// Workflow:
// 1. BEFORE starting work on a task, mark it as "in_progress"
// 2. Complete the implementation for the task
// 3. Verify the work is correct and complete
// 4. IMMEDIATELY mark the task as "completed" after verification
// 5. Move to the next task and repeat
//
// IMPORTANT - Update Status Immediately:
// - Update each task's status IMMEDIATELY after it transitions
// - Do NOT batch status updates at the end of all work
// - Do NOT wait until all tasks are done to update statuses
// - This file should reflect accurate progress at any point in time
// - Using a single edit to mark a task completed AND the next task
// in_progress is allowed (this is a single transition, not batching)
//
// Note: This file is auto-generated by 'spectr accept'. Manual edits to
// task status are expected, but structure changes may be overwritten.
// To see the parent task or other sections, refer to the root tasks.jsonc file.
//
`, changeID, changeID, parentTaskID)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Child header must include the full root header.

The spec calls for child headers to include the complete root header plus origin lines. The current childFileHeader is a separate header that omits “Spectr Tasks File (JSONC)” and other root text.

✏️ Suggested refactor to reuse the root header
 func childFileHeader(changeID, parentTaskID string) string {
-	return fmt.Sprintf(`// Generated by: spectr accept %s
-// Parent change: %s
-// Parent task: %s
-//
-// This file contains child tasks split from the main tasks.jsonc for readability.
-// JSONC format allows comments while maintaining JSON compatibility.
-//
-// Status Values:
-//   - "pending"     : Task has not been started yet
-//   - "in_progress" : Task is currently being worked on
-//   - "completed"   : Task has been finished and verified
-//
-// Status Transitions:
-//   pending -> in_progress -> completed
-//
-//   Tasks should only move forward through these states.
-//   Do not skip states or move backward.
-//
-// Workflow:
-//   1. BEFORE starting work on a task, mark it as "in_progress"
-//   2. Complete the implementation for the task
-//   3. Verify the work is correct and complete
-//   4. IMMEDIATELY mark the task as "completed" after verification
-//   5. Move to the next task and repeat
-//
-// IMPORTANT - Update Status Immediately:
-//   - Update each task's status IMMEDIATELY after it transitions
-//   - Do NOT batch status updates at the end of all work
-//   - Do NOT wait until all tasks are done to update statuses
-//   - This file should reflect accurate progress at any point in time
-//   - Using a single edit to mark a task completed AND the next task
-//     in_progress is allowed (this is a single transition, not batching)
-//
-// Note: This file is auto-generated by 'spectr accept'. Manual edits to
-// task status are expected, but structure changes may be overwritten.
-// To see the parent task or other sections, refer to the root tasks.jsonc file.
-//
-
-`, changeID, changeID, parentTaskID)
+	origin := fmt.Sprintf(`// Generated by: spectr accept %s
+// Parent change: %s
+// Parent task: %s
+//
+`, changeID, changeID, parentTaskID)
+	return origin + tasksJSONHeader
 }
🤖 Prompt for AI Agents
In `@cmd/accept_writer.go` around lines 60 - 112, The childFileHeader currently
builds a bespoke header and omits the full root header; update
childFileHeader(changeID, parentTaskID) to reuse the canonical root header
generator (the existing root/tasks header function—e.g., rootHeader or
tasksFileHeader) and prepend the origin lines ("Generated by", "Parent change",
"Parent task") so the returned string includes the complete root header text
(including the "Spectr Tasks File (JSONC)" block) followed by the origin
metadata and trailing newlines; keep parameters changeID and parentTaskID and
ensure formatting matches existing root header output.

Comment on lines +312 to +386
// loadRootTasksFile reads and parses the root tasks.jsonc file.
// Returns nil if the file doesn't exist (not an error).
func loadRootTasksFile(changeDir string) (*parsers.TasksFile, error) {
rootPath := fmt.Sprintf("%s/tasks.jsonc", changeDir)
rootFile, err := os.ReadFile(rootPath)
if err != nil {
if os.IsNotExist(err) {
return nil, nil
}

return nil, fmt.Errorf("failed to read tasks.jsonc: %w", err)
}

// Strip JSONC comments before parsing
strippedRoot := parsers.StripJSONComments(rootFile)

var rootTasks parsers.TasksFile
if err := json.Unmarshal(strippedRoot, &rootTasks); err != nil {
return nil, fmt.Errorf("failed to parse tasks.jsonc: %w", err)
}

return &rootTasks, nil
}

// loadChildFileStatuses reads all child tasks-*.jsonc files and adds their statuses to the map.
func loadChildFileStatuses(changeDir string, statusMap map[string]parsers.TaskStatusValue) error {
childFiles, err := os.ReadDir(changeDir)
if err != nil {
return fmt.Errorf("failed to read change directory: %w", err)
}

for _, file := range childFiles {
if !isChildTasksFile(file) {
continue
}

if err := loadSingleChildFile(changeDir, file.Name(), statusMap); err != nil {
return err
}
}

return nil
}

// isChildTasksFile checks if a file is a child tasks file (tasks-*.jsonc).
func isChildTasksFile(file os.DirEntry) bool {
if file.IsDir() || file.Name() == "tasks.jsonc" {
return false
}

return strings.HasPrefix(file.Name(), "tasks-") && strings.HasSuffix(file.Name(), ".jsonc")
}

// loadSingleChildFile reads and parses a single child tasks file.
func loadSingleChildFile(
changeDir, fileName string,
statusMap map[string]parsers.TaskStatusValue,
) error {
childPath := fmt.Sprintf("%s/%s", changeDir, fileName)
childData, err := os.ReadFile(childPath)
if err != nil {
return fmt.Errorf("failed to read %s: %w", fileName, err)
}

// Strip JSONC comments before parsing
strippedChild := parsers.StripJSONComments(childData)

var childTasks parsers.TasksFile
if err := json.Unmarshal(strippedChild, &childTasks); err != nil {
return fmt.Errorf("failed to parse %s: %w", fileName, err)
}

// Add child file tasks to the map
addTaskStatusesToMap(statusMap, childTasks.Tasks)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Status loading rejects JSONC with trailing commas.

loadRootTasksFile / loadSingleChildFile strip comments, then json.Unmarshal, which fails on trailing commas even though JSONC allows them. That will break status preservation if trailing commas are introduced.

Consider either (a) stripping trailing commas before unmarshal or (b) using a JSONC-aware parser for reads to match the JSONC output guarantees.

🤖 Prompt for AI Agents
In `@cmd/accept_writer.go` around lines 312 - 386, The JSONC comment stripping
(parsers.StripJSONComments) still leaves trailing commas which cause
json.Unmarshal to fail; update loadRootTasksFile and loadSingleChildFile to
handle trailing commas by either calling a new parser helper (e.g.,
parsers.StripJSONTrailingCommas or extend parsers.StripJSONComments to remove
trailing commas) on strippedRoot/strippedChild before calling json.Unmarshal, or
swap the unmarshal to a JSONC-aware parser; ensure you reference
parsers.StripJSONComments and the json.Unmarshal calls in loadRootTasksFile and
loadSingleChildFile and apply the extra cleaning/parsing step so trailing commas
no longer cause parse errors.

Comment on lines +617 to +625
// Check for task line
if match, ok := markdown.MatchFlexibleTask(line); ok {
task := state.createTask(match)

// Add task to current section if we have one
if currentSection != nil {
currentSection.Tasks = append(currentSection.Tasks, task)
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Tasks before the first section are silently dropped in split mode.

parseSections only attaches tasks when currentSection != nil, so any leading tasks vanish when splitting, causing data loss and status mismatch.

🛠️ Suggested fix (fail fast instead of dropping tasks)
 		// Check for task line
 		if match, ok := markdown.MatchFlexibleTask(line); ok {
+			if currentSection == nil {
+				return nil, fmt.Errorf(
+					"task found before first section header at line %d",
+					lineNum,
+				)
+			}
 			task := state.createTask(match)
 
 			// Add task to current section if we have one
 			if currentSection != nil {
 				currentSection.Tasks = append(currentSection.Tasks, 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.

Suggested change
// Check for task line
if match, ok := markdown.MatchFlexibleTask(line); ok {
task := state.createTask(match)
// Add task to current section if we have one
if currentSection != nil {
currentSection.Tasks = append(currentSection.Tasks, task)
}
}
// Check for task line
if match, ok := markdown.MatchFlexibleTask(line); ok {
if currentSection == nil {
return nil, fmt.Errorf(
"task found before first section header at line %d",
lineNum,
)
}
task := state.createTask(match)
// Add task to current section if we have one
if currentSection != nil {
currentSection.Tasks = append(currentSection.Tasks, task)
}
}
🤖 Prompt for AI Agents
In `@cmd/accept.go` around lines 617 - 625, In parseSections, tasks found via
markdown.MatchFlexibleTask that call state.createTask are currently only
appended when currentSection != nil, which silently drops leading tasks in split
mode; update parseSections to detect when a task is encountered but
currentSection == nil and, instead of ignoring it, return an error (fail fast)
indicating "task before first section" (or similar) so callers can handle it;
specifically modify the block that checks match, ok :=
markdown.MatchFlexibleTask(line) to check the split-mode context (or a provided
flag) and return the error when no currentSection exists rather than appending
or dropping the task.

Comment on lines +680 to +737
// parseSubsections groups tasks by their ID prefix to create subsections.
// For example, tasks "1.1", "1.2", "1.3" all share prefix "1" and will be
// grouped together. This is used when a section is too large and needs to
// be split into smaller chunks.
//
// The function extracts the prefix from each task's ID (everything before the
// last dot) and groups tasks with the same prefix together.
//
// Example:
//
// Tasks: ["1.1", "1.2", "2.1", "2.2", "2.3"]
// Groups: [["1.1", "1.2"], ["2.1", "2.2", "2.3"]]
func parseSubsections(tasks []parsers.Task) []SubsectionGroup {
// Map to track subsection groups by prefix
groupMap := make(map[string]*SubsectionGroup)
var orderedPrefixes []string

for _, task := range tasks {
// Extract prefix from task ID (everything before the last dot)
prefix := extractIDPrefix(task.ID)

// Create new group if it doesn't exist
if _, exists := groupMap[prefix]; !exists {
groupMap[prefix] = &SubsectionGroup{
Prefix: prefix,
Tasks: make([]parsers.Task, 0),
}
orderedPrefixes = append(orderedPrefixes, prefix)
}

// Add task to the group
groupMap[prefix].Tasks = append(groupMap[prefix].Tasks, task)
}

// Convert map to ordered slice
groups := make([]SubsectionGroup, 0, len(orderedPrefixes))
for _, prefix := range orderedPrefixes {
groups = append(groups, *groupMap[prefix])
}

return groups
}

// extractIDPrefix returns the prefix of a task ID.
// For hierarchical IDs like "1.2.3", it returns everything before the last dot ("1.2").
// For simple IDs like "1", it returns the ID itself.
// For IDs with one dot like "1.1", it returns the part before the dot ("1").
func extractIDPrefix(id string) string {
// Find the last dot in the ID
lastDot := strings.LastIndex(id, ".")
if lastDot == -1 {
// No dot found - this is a simple ID like "1"
return id
}

// Return everything before the last dot
return id[:lastDot]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Large-section splitting won’t trigger for common 1.x IDs.

extractIDPrefix("1.1") and extractIDPrefix("1.2") both return "1", so parseSubsections collapses typical tasks into a single group. Combined with MatchFlexibleTask only recognizing a single dot, large sections will stay oversized and never split.

Consider extending task ID parsing to accept multi-dot IDs and/or adding a fallback strategy (e.g., group by full ID or chunk by size) when only one-dot IDs are present so large sections actually split.

🤖 Prompt for AI Agents
In `@cmd/accept.go` around lines 680 - 737, parseSubsections collapses common
"1.x" tasks because extractIDPrefix returns "1" for IDs with a single dot;
update extractIDPrefix to only strip the last segment when the ID contains two
or more dots (so "1.2.3" -> "1.2" but "1.1" remains "1.1"), and add a simple
fallback in parseSubsections to split any resulting SubsectionGroup that exceeds
a max size into smaller chunks (e.g., chunk by N tasks or by full task.ID) so
large sections can actually be split; reference extractIDPrefix and
parseSubsections (and optionally MatchFlexibleTask) when making these changes.

connerohnesorge and others added 2 commits January 18, 2026 15:41
The splitting rules had inverted logic: sections <100 lines were incorrectly
marked as "candidate for splitting" while >100 line sections were to be split.
Updated the rules to correctly state that sections ≤100 lines are kept together
and only sections >100 lines are split by subsections.

This clarifies the intended threshold behavior in Decision 2.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

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)
cmd/accept_writer.go (1)

164-169: Use Afero for all filesystem operations in cmd/accept_writer.go.

This file contains multiple direct os.* filesystem calls (ReadFile, WriteFile, ReadDir, Remove) that should use afero.Fs abstraction instead. The coding guideline **/*.go requires Afero for filesystem abstraction to enable testing. Refactor all filesystem operations in this file to use Afero.

🤖 Fix all issues with AI agents
In `@cmd/accept_validator_test.go`:
- Around line 789-804: The test TestRoundTripConversion_RealWorldData currently
hardcodes absolute host paths in archivedFiles which will always fail CI; move
representative JSONC fixtures into the package's testdata/ directory and replace
the absolute entries in archivedFiles with relative paths (e.g.,
filepath.Join("testdata", "your-fixture.jsonc")) or generate the list by
globbing testdata; update the loop to open files via os.Open or os.ReadFile
using those relative paths (filePath variable) and keep the existing
os.Stat-based skip logic for local runs.

In `@cmd/accept_validator.go`:
- Around line 54-66: Move the validation logic out of the CLI layer by
relocating validateJSONCOutput and validateTasksJSONC from
cmd/accept_validator.go into a new package internal/validation (create the
package and files there, set package name to validation). Update both functions'
visibility if they need to be used from cmd (export by renaming to
ValidateJSONCOutput / ValidateTasksJSONC or provide exported wrappers). Replace
the original implementations in cmd/accept_validator.go with imports from
internal/validation and update callers (e.g., in cmd/accept_writer.go) to call
the exported functions, and adjust imports to include internal/validation;
ensure any helper functions like buildJSONCValidationError or parsers usage are
moved or made accessible within the new package.

In `@cmd/accept.go`:
- Around line 808-818: The validateDryRun method on AcceptCmd is a stub that
always returns nil; replace it with actual JSONC validation by marshaling the
in-memory tasks (from the tasks parameter and appendCfg) into the same JSONC
string format used for output and then call the existing validateJSONCOutput or
validateTasksJSONC helper to validate that string; locate validateDryRun, build
the JSONC payload using the same serialization logic the real run uses (so
formatting matches), pass the result to validateJSONCOutput/validateTasksJSONC,
and return any validation error (or remove the dry-run validation claim if you
choose not to implement it).

In `@spectr/changes/add-kimi-agentskills-support/tasks.jsonc`:
- Around line 1-185: The tasks.jsonc and tasks.md are out of sync causing Spectr
validation to fail; reconcile them by running the Spectr accept command (spectr
accept add-kimi-agentskills-support) or manually copy the accepted tasks content
so tasks.jsonc matches tasks.md, ensure task statuses and the final entry (id
"5.1") are identical between the two files, then commit the updated files so
validation passes.

In `@spectr/changes/fix-jsonc-validation/tasks.jsonc`:
- Around line 38-101: The tasks.jsonc file (the "version": 2 tasks array with
entries for ids "1" through "8" and children refs like "$ref:tasks-1.jsonc") is
the runtime source of truth but tasks.md is out of sync; either rerun the Spectr
command `spectr accept fix-jsonc-validation` to regenerate tasks.md from
tasks.jsonc, or manually update tasks.md so its task list, sections, statuses
and children references exactly match the tasks.jsonc content (IDs, section
names, descriptions, statuses, and includes pattern "tasks-*.jsonc").

In `@spectr/changes/test-extreme-jsonc/tasks.jsonc`:
- Around line 32-35: The tasks.jsonc and tasks.md are out of sync: tasks.jsonc
contains an extra task "2.1" under the "99.99.99 Final section" that is missing
from tasks.md; fix by regenerating tasks.jsonc from the authoritative tasks.md
with the command `spectr accept test-extreme-jsonc` or, if the extra task is
intended, add the missing "2.1" entry into tasks.md (matching the content/format
used elsewhere) and then rerun `spectr accept test-extreme-jsonc` so tasks.jsonc
and tasks.md are consistent.

In `@spectr/changes/test-problematic-jsonc/tasks.jsonc`:
- Around line 101-106: The description field for task id "1.11" contains literal
ASCII control characters (backspace and form feed) which can break parsers;
replace those literal characters with their escaped JSONC representations (e.g.,
"\\b" and "\\f") or remove them if unintentional, and add a short inline comment
next to the "description" entry indicating these are intentionally escaped
control-char test cases so consumers know they are deliberate; ensure the change
is made in the tasks.jsonc entry where "id": "1.11" and verify the JSONC remains
valid after escaping.

In `@spectr/changes/test-problematic-jsonc/tasks.md`:
- Around line 7-8: The multiline task item "1.3 Task with newlines: This is line
1" is broken because the continuation line "And this should also be part of the
description..." is not indented and therefore parsed as a separate paragraph;
fix by indenting the continuation line so it remains part of the same list item
(e.g., add two spaces or a tab before "And this should...") so the description
stays within the task list entry in tasks.md.
- Line 3: Rename the top-level section header in tasks.md from the plain text
header "## Edge Case Testing" to a numbered header that matches the task parser
format (for example "## 1. Edge Case Testing"); locate the header string in the
file (the line beginning with "## ") and update it to include the section number
and a dot before the title so the parser recognizes it.
- Line 11: The line "1.6 Task with tabs:	indented	with	tabs	here" contains hard
tab characters which trigger markdownlint MD010; fix by replacing tabs with
spaces (e.g., convert to four spaces per tab) or represent them as escaped
characters (use "\t" in the text) or, if the tabs are intentional test data, add
a markdownlint disable directive immediately above that single line to suppress
MD010; locate this string in tasks.md and apply one of those fixes to remove
literal tab characters.
♻️ Duplicate comments (6)
spectr/changes/add-size-based-task-splitting/tasks.jsonc (1)

38-156: Re-sync tasks.jsonc with tasks.md and ensure child files are generated.

Spectr Validate reports a mismatch between tasks.md and this root v2 manifest; please re-run spectr accept add-size-based-task-splitting so the root + tasks-*.jsonc children reflect the current tasks.md and commit the regenerated files. Based on learnings, tasks.md and tasks.jsonc must stay in sync.

cmd/accept_writer.go (2)

60-112: Child header still diverges from the canonical root header.

childFileHeader redefines the full header, so it can drift from tasksJSONHeader. Consider reusing the canonical header with origin lines prepended.


322-391: Status loading still breaks on JSONC trailing commas.

StripJSONComments leaves trailing commas, so json.Unmarshal can reject valid JSONC and status preservation fails. Consider stripping trailing commas or using a JSONC-aware parser for reads.

Does Go's encoding/json Unmarshal accept trailing commas in objects/arrays?
cmd/accept.go (3)

240-266: Split path still drops appended config tasks.

When split is true, only sections parsed from tasks.md are written; appendCfg tasks are counted but never emitted, breaking the append contract and misreporting totals.


623-630: Split parsing drops tasks before first section.

parseSections only appends tasks when currentSection != nil, so leading tasks are silently dropped in split mode. Prefer failing fast or assigning them to a default section.


687-732: Subsection grouping collapses 1.x IDs and can prevent splitting.

extractIDPrefix("1.1") returns "1", so parseSubsections groups all 1.x tasks together and large sections never shrink. Consider preserving single-dot IDs or chunking large groups.

🧹 Nitpick comments (4)
spectr/changes/fix-jsonc-validation/specs/cli-interface/spec.md (1)

1-1: Tighten title wording (“CLI Interface” is redundant).

Line 1 can drop “Interface” for clarity.

✏️ Suggested tweak
-# Delta Spec: CLI Interface - JSONC Validation
+# Delta Spec: CLI JSONC Validation
cmd/accept_validator.go (1)

1-33: Move JSONC validation helpers into internal/ to keep cmd/ thin.
This file contains non-trivial validation logic in cmd/, which conflicts with the “thin CLI layer” guideline. Consider relocating these helpers to internal/validation (or similar) and keeping cmd/ as wiring only. As per coding guidelines, cmd/ should delegate business logic to internal/ packages.

cmd/accept_validator_test.go (1)

161-197: Convert helper test to table-driven subtests.

TestHelperFunctions_Basic bundles multiple assertions without t.Run. Consider a small table-driven form for consistency with the repo’s testing conventions. As per coding guidelines, use table-driven subtests.

cmd/accept.go (1)

528-590: Move task-splitting logic to internal package and use Afero for filesystem abstraction.

The countLines, shouldSplit, and parseSections functions contain business logic that belongs in an internal package, not in cmd/. Additionally, direct os.Open() calls (lines 531, 587) should use Afero for testability. The codebase already uses Afero in internal/initialize; apply the same pattern here by injecting an afero.Fs abstraction into a new internal package (e.g., internal/parsing or extend internal/parsers) and delegate these functions there, keeping cmd/accept.go as a thin CLI layer.

Comment on lines +54 to +66
func validateJSONCOutput(jsonData []byte) error {
// Strip JSONC comments to get pure JSON
stripped := parsers.StripJSONComments(jsonData)

// Attempt to unmarshal into generic interface
var result interface{}
if err := json.Unmarshal(stripped, &result); err != nil {
// Build detailed error message with context
return buildJSONCValidationError(stripped, err)
}

return nil
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find JSONC files in spectr directory to check for trailing commas
find . -type f -name "*.jsonc" | head -20

Repository: connerohnesorge/spectr

Length of output: 1314


🏁 Script executed:

#!/bin/bash
# Check the parsers.StripJSONComments implementation
fd -t f "parsers" --extension go | head -10

Repository: connerohnesorge/spectr

Length of output: 126


🏁 Script executed:

#!/bin/bash
# Search for the StripJSONComments function and related JSONC handling
rg -n "StripJSONComments|JSONC" --type go -A 5 -B 2

Repository: connerohnesorge/spectr

Length of output: 50378


🏁 Script executed:

#!/bin/bash
# Check actual trailing commas in JSON/JSONC-like files
find . -type f \( -name "*.jsonc" -o -name "*.json" \) -exec grep -l ',\s*[\]}]' {} \; 2>/dev/null | head -20

Repository: connerohnesorge/spectr

Length of output: 48


🏁 Script executed:

#!/bin/bash
# Examine the validateJSONCOutput function and surrounding context in accept_validator.go
cat -n cmd/accept_validator.go | sed -n '40,90p'

Repository: connerohnesorge/spectr

Length of output: 2260


🏁 Script executed:

# Check how tasks are marshaled to JSON (look for json.Marshal usage)
rg -n "json.Marshal" cmd/accept_writer.go -B 3 -A 3

Repository: connerohnesorge/spectr

Length of output: 605


🏁 Script executed:

# Verify the StripJSONComments implementation handles edge cases properly
cat -n internal/parsers/parsers.go | sed -n '57,145p'

Repository: connerohnesorge/spectr

Length of output: 2250


Move validation functions to internal/validation/ package.

The validateJSONCOutput() and validateTasksJSONC() functions are currently in cmd/accept_validator.go, but per coding guidelines, validation logic should reside in internal/ packages. This allows these functions to be imported and reused by other internal packages. The cmd/ layer should only contain thin CLI logic; move these functions to internal/validation/ and import them from there in cmd/accept_writer.go.

🤖 Prompt for AI Agents
In `@cmd/accept_validator.go` around lines 54 - 66, Move the validation logic out
of the CLI layer by relocating validateJSONCOutput and validateTasksJSONC from
cmd/accept_validator.go into a new package internal/validation (create the
package and files there, set package name to validation). Update both functions'
visibility if they need to be used from cmd (export by renaming to
ValidateJSONCOutput / ValidateTasksJSONC or provide exported wrappers). Replace
the original implementations in cmd/accept_validator.go with imports from
internal/validation and update callers (e.g., in cmd/accept_writer.go) to call
the exported functions, and adjust imports to include internal/validation;
ensure any helper functions like buildJSONCValidationError or parsers usage are
moved or made accessible within the new package.

cmd/accept.go Outdated
Comment on lines 808 to 818
// validateDryRun validates JSONC output in dry-run mode.
// For now, this is a stub that always returns nil (validation passes).
// Full validation logic will be implemented in a follow-up.
func (*AcceptCmd) validateDryRun(
tasksMdPath string,
tasks []parsers.Task,
appendCfg *config.AppendTasksConfig,
) error {
// Stub implementation - validation always passes
// TODO: Implement full JSONC validation for dry-run mode
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Dry-run validation is a no-op.

validateDryRun always returns nil even though dry-run output claims JSONC validation. Suggest marshaling in-memory and reusing validateJSONCOutput / validateTasksJSONC, or remove the validation claim.

If helpful, I can sketch an implementation.

🤖 Prompt for AI Agents
In `@cmd/accept.go` around lines 808 - 818, The validateDryRun method on AcceptCmd
is a stub that always returns nil; replace it with actual JSONC validation by
marshaling the in-memory tasks (from the tasks parameter and appendCfg) into the
same JSONC string format used for output and then call the existing
validateJSONCOutput or validateTasksJSONC helper to validate that string; locate
validateDryRun, build the JSONC payload using the same serialization logic the
real run uses (so formatting matches), pass the result to
validateJSONCOutput/validateTasksJSONC, and return any validation error (or
remove the dry-run validation claim if you choose not to implement it).

Comment on lines +1 to +185
// Spectr Tasks File (JSONC)
//
// This file contains machine-readable task definitions for a Spectr change.
// JSONC format allows comments while maintaining JSON compatibility.
//
// Status Values:
// - "pending" : Task has not been started yet
// - "in_progress" : Task is currently being worked on
// - "completed" : Task has been finished and verified
//
// Status Transitions:
// pending -> in_progress -> completed
//
// Tasks should only move forward through these states.
// Do not skip states or move backward.
//
// Workflow:
// 1. BEFORE starting work on a task, mark it as "in_progress"
// 2. Complete the implementation for the task
// 3. Verify the work is correct and complete
// 4. IMMEDIATELY mark the task as "completed" after verification
// 5. Move to the next task and repeat
//
// IMPORTANT - Update Status Immediately:
// - Update each task's status IMMEDIATELY after it transitions
// - Do NOT batch status updates at the end of all work
// - Do NOT wait until all tasks are done to update statuses
// - This file should reflect accurate progress at any point in time
// - Using a single edit to mark a task completed AND the next task
// in_progress is allowed (this is a single transition, not batching)
//
// Note: This file is auto-generated by 'spectr accept'. Manual edits to
// task status are expected, but structure changes may be overwritten.
// The original tasks.md file is preserved alongside tasks.jsonc to retain
// human-readable formatting, links, and context.
//

{
"version": 1,
"tasks": [
{
"id": "1.1",
"section": "Implementation Tasks",
"description": "Create Kimi provider implementation (`internal/initialize/providers/kimi.go`)",
"status": "pending"
},
{
"id": "1.2",
"section": "Implementation Tasks",
"description": "Register Kimi provider in registry (`internal/initialize/providers/registry.go`)",
"status": "pending"
},
{
"id": "1.3",
"section": "Implementation Tasks",
"description": "Create Kimi AGENTS.md template (`internal/initialize/templates/agents-kimi.md`)",
"status": "pending"
},
{
"id": "1.4",
"section": "Implementation Tasks",
"description": "Test provider initialization with `spectr init`",
"status": "pending"
},
{
"id": "1.5",
"section": "Implementation Tasks",
"description": "Validate directory creation: `.kimi/skills` and `.kimi/commands`",
"status": "pending"
},
{
"id": "1.6",
"section": "Implementation Tasks",
"description": "Validate AGENTS.md file creation",
"status": "pending"
},
{
"id": "1.7",
"section": "Implementation Tasks",
"description": "Validate agent skills installation",
"status": "pending"
},
{
"id": "1.8",
"section": "Implementation Tasks",
"description": "Test slash commands functionality",
"status": "pending"
},
{
"id": "2.1",
"section": "Validation Tasks",
"description": "Run `spectr validate add-kimi-agentskills-support`",
"status": "pending"
},
{
"id": "2.2",
"section": "Validation Tasks",
"description": "Ensure no linting errors: `nix develop -c lint`",
"status": "pending"
},
{
"id": "2.3",
"section": "Validation Tasks",
"description": "Run tests: `nix develop -c tests`",
"status": "pending"
},
{
"id": "2.4",
"section": "Validation Tasks",
"description": "Verify skills are executable",
"status": "pending"
},
{
"id": "2.5",
"section": "Validation Tasks",
"description": "Test in a sample project directory",
"status": "pending"
},
{
"id": "3.1",
"section": "Documentation Tasks",
"description": "Update provider documentation if needed",
"status": "pending"
},
{
"id": "3.2",
"section": "Documentation Tasks",
"description": "Add Kimi to supported providers list",
"status": "pending"
},
{
"id": "3.3",
"section": "Documentation Tasks",
"description": "Document Kimi-specific configuration",
"status": "pending"
},
{
"id": "4.1",
"section": "Success Criteria Checklist",
"description": "`spectr init` creates `.kimi/skills` directory",
"status": "pending"
},
{
"id": "4.2",
"section": "Success Criteria Checklist",
"description": "`spectr init` creates `.kimi/commands` directory",
"status": "pending"
},
{
"id": "4.3",
"section": "Success Criteria Checklist",
"description": "`AGENTS.md` file is created with Kimi instructions",
"status": "pending"
},
{
"id": "4.4",
"section": "Success Criteria Checklist",
"description": "Agent skills are installed and executable",
"status": "pending"
},
{
"id": "4.5",
"section": "Success Criteria Checklist",
"description": "Slash commands `spectr-proposal` and `spectr-apply` work",
"status": "pending"
},
{
"id": "4.6",
"section": "Success Criteria Checklist",
"description": "All tests pass",
"status": "pending"
},
{
"id": "4.7",
"section": "Success Criteria Checklist",
"description": "Validation passes without errors",
"status": "pending"
},
{
"id": "5.1",
"section": "99.99.99 Final section (after all tasks)",
"description": "Output/return: \u003cpromise\u003e\"This is the last task: COMPLETE\"\u003c/promise\u003e\n",
"status": "pending"
}
]
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Sync tasks.md and tasks.jsonc to clear Spectr Validate failure.

The validation hint indicates the two files diverged, and tasks.jsonc is what Spectr uses at runtime. Please re-run spectr accept add-kimi-agentskills-support or otherwise reconcile the content so the pair stays in lockstep. Based on learnings, keep tasks.md and tasks.jsonc synchronized after acceptance.

🧰 Tools
🪛 GitHub Check: Spectr Validate

[notice] 1-1: change: add-kimi-agentskills-support
tasks.md and tasks.jsonc have different content. Note: tasks.jsonc is the runtime source of truth used by spectr commands. Consider running 'spectr accept' again to regenerate tasks.jsonc from tasks.md.

🤖 Prompt for AI Agents
In `@spectr/changes/add-kimi-agentskills-support/tasks.jsonc` around lines 1 -
185, The tasks.jsonc and tasks.md are out of sync causing Spectr validation to
fail; reconcile them by running the Spectr accept command (spectr accept
add-kimi-agentskills-support) or manually copy the accepted tasks content so
tasks.jsonc matches tasks.md, ensure task statuses and the final entry (id
"5.1") are identical between the two files, then commit the updated files so
validation passes.

Comment on lines 38 to 101
{
"version": 2,
"tasks": [
{
"id": "1",
"section": "Foundation",
"description": "Foundation tasks",
"status": "completed",
"children": "$ref:tasks-1.jsonc"
},
{
"id": "2",
"section": "Core Validation Functions",
"description": "Core Validation Functions tasks",
"status": "completed",
"children": "$ref:tasks-2.jsonc"
},
{
"id": "3",
"section": "Property-Based Testing",
"description": "Property-Based Testing tasks",
"status": "completed",
"children": "$ref:tasks-3.jsonc"
},
{
"id": "4",
"section": "Round-Trip Testing",
"description": "Round-Trip Testing tasks",
"status": "completed",
"children": "$ref:tasks-4.jsonc"
},
{
"id": "5",
"section": "Integration with Existing Code",
"description": "Integration with Existing Code tasks",
"status": "completed",
"children": "$ref:tasks-5.jsonc"
},
{
"id": "6",
"section": "Comprehensive Testing",
"description": "Comprehensive Testing tasks",
"status": "completed",
"children": "$ref:tasks-6.jsonc"
},
{
"id": "7",
"section": "Documentation and Validation",
"description": "Documentation and Validation tasks",
"status": "completed",
"children": "$ref:tasks-7.jsonc"
},
{
"id": "8",
"section": "Final Verification",
"description": "Final Verification tasks",
"status": "completed",
"children": "$ref:tasks-8.jsonc"
}
],
"includes": [
"tasks-*.jsonc"
]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Sync tasks.md and tasks.jsonc for this change.

Spectr Validate reports a mismatch between tasks.md and tasks.jsonc, and tasks.jsonc is the runtime source of truth. Please rerun spectr accept fix-jsonc-validation or update tasks.md to match. As per coding guidelines, keep tasks.md and tasks.jsonc in sync.

🤖 Prompt for AI Agents
In `@spectr/changes/fix-jsonc-validation/tasks.jsonc` around lines 38 - 101, The
tasks.jsonc file (the "version": 2 tasks array with entries for ids "1" through
"8" and children refs like "$ref:tasks-1.jsonc") is the runtime source of truth
but tasks.md is out of sync; either rerun the Spectr command `spectr accept
fix-jsonc-validation` to regenerate tasks.md from tasks.jsonc, or manually
update tasks.md so its task list, sections, statuses and children references
exactly match the tasks.jsonc content (IDs, section names, descriptions,
statuses, and includes pattern "tasks-*.jsonc").

Comment on lines 32 to 35
// Note: This file is auto-generated by 'spectr accept'. Manual edits to
// task status are expected, but structure changes may be overwritten.
// The original tasks.md file is preserved alongside tasks.jsonc to retain
// human-readable formatting, links, and context.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Check if the test-extreme-jsonc directory exists and list its contents
find . -type d -name "test-extreme-jsonc" 2>/dev/null | head -20

Repository: connerohnesorge/spectr

Length of output: 101


🏁 Script executed:

# List files in the spectr/changes/test-extreme-jsonc directory
ls -la spectr/changes/test-extreme-jsonc/ 2>/dev/null

Repository: connerohnesorge/spectr

Length of output: 412


🏁 Script executed:

# Check both task files if they exist
if [ -f "spectr/changes/test-extreme-jsonc/tasks.md" ]; then
  echo "=== tasks.md ===" 
  cat -n spectr/changes/test-extreme-jsonc/tasks.md
fi

if [ -f "spectr/changes/test-extreme-jsonc/tasks.jsonc" ]; then
  echo "=== tasks.jsonc ===" 
  cat -n spectr/changes/test-extreme-jsonc/tasks.jsonc
fi

Repository: connerohnesorge/spectr

Length of output: 7197


Resync tasks.md and tasks.jsonc.

tasks.jsonc contains an extra task (2.1 under "99.99.99 Final section") that is not in tasks.md, causing drift between the two files. Per coding guidelines, these files must remain in sync. Rerun spectr accept test-extreme-jsonc to regenerate tasks.jsonc from the authoritative tasks.md, or add the missing task to tasks.md and regenerate.

🤖 Prompt for AI Agents
In `@spectr/changes/test-extreme-jsonc/tasks.jsonc` around lines 32 - 35, The
tasks.jsonc and tasks.md are out of sync: tasks.jsonc contains an extra task
"2.1" under the "99.99.99 Final section" that is missing from tasks.md; fix by
regenerating tasks.jsonc from the authoritative tasks.md with the command
`spectr accept test-extreme-jsonc` or, if the extra task is intended, add the
missing "2.1" entry into tasks.md (matching the content/format used elsewhere)
and then rerun `spectr accept test-extreme-jsonc` so tasks.jsonc and tasks.md
are consistent.

Comment on lines 101 to 106
{
"id": "1.11",
"section": "Edge Case Testing",
"description": "Control chars: ASCII control characters like (backspace) and (form feed)",
"status": "pending"
},
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Verify literal control characters in description.

Line 104 contains literal ASCII control characters (backspace \b and form feed \f) which may not be visible but could cause parsing issues in some JSON parsers or editors. While valid for testing edge cases, ensure these are intentionally included and properly handled by the JSONC parser.

#!/bin/bash
# Check for non-printable control characters in the file
echo "=== Checking for control characters (hex dump of line ~104) ==="
sed -n '104p' spectr/changes/test-problematic-jsonc/tasks.jsonc | xxd | head -5

echo ""
echo "=== Count of non-printable ASCII control chars (0x00-0x1F except tab/newline) ==="
cat spectr/changes/test-problematic-jsonc/tasks.jsonc | tr -d '\t\n\r' | grep -oP '[\x00-\x08\x0b\x0c\x0e-\x1f]' | wc -l
🤖 Prompt for AI Agents
In `@spectr/changes/test-problematic-jsonc/tasks.jsonc` around lines 101 - 106,
The description field for task id "1.11" contains literal ASCII control
characters (backspace and form feed) which can break parsers; replace those
literal characters with their escaped JSONC representations (e.g., "\\b" and
"\\f") or remove them if unintentional, and add a short inline comment next to
the "description" entry indicating these are intentionally escaped control-char
test cases so consumers know they are deliberate; ensure the change is made in
the tasks.jsonc entry where "id": "1.11" and verify the JSONC remains valid
after escaping.

@@ -0,0 +1,20 @@
# Tasks

## Edge Case Testing
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Use numbered section headers for tasks.md.

tasks.md under spectr/changes/* is expected to use numbered section headers (e.g., ## 1. ...). Please rename this header to comply with the task parser’s expected structure. As per coding guidelines, ...

📌 Suggested change
-## Edge Case Testing
+## 1. Edge Case Testing
📝 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.

Suggested change
## Edge Case Testing
## 1. Edge Case Testing
🤖 Prompt for AI Agents
In `@spectr/changes/test-problematic-jsonc/tasks.md` at line 3, Rename the
top-level section header in tasks.md from the plain text header "## Edge Case
Testing" to a numbered header that matches the task parser format (for example
"## 1. Edge Case Testing"); locate the header string in the file (the line
beginning with "## ") and update it to include the section number and a dot
before the title so the parser recognizes it.

Comment on lines 7 to 8
- [ ] 1.3 Task with newlines: This is line 1
And this should also be part of the description (this won't parse as single line)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix multiline task formatting so the description stays within the list item.

Line 8 is not indented, so it won’t be parsed as part of task 1.3 and can break task extraction. If you want a multiline description, indent the continuation line. As per coding guidelines, ...

✏️ Suggested change
-- [ ] 1.3 Task with newlines: This is line 1
-And this should also be part of the description (this won't parse as single line)
+- [ ] 1.3 Task with newlines: This is line 1
+  And this should also be part of the description (this will parse as a continuation line)
📝 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.

Suggested change
- [ ] 1.3 Task with newlines: This is line 1
And this should also be part of the description (this won't parse as single line)
- [ ] 1.3 Task with newlines: This is line 1
And this should also be part of the description (this will parse as a continuation line)
🤖 Prompt for AI Agents
In `@spectr/changes/test-problematic-jsonc/tasks.md` around lines 7 - 8, The
multiline task item "1.3 Task with newlines: This is line 1" is broken because
the continuation line "And this should also be part of the description..." is
not indented and therefore parsed as a separate paragraph; fix by indenting the
continuation line so it remains part of the same list item (e.g., add two spaces
or a tab before "And this should...") so the description stays within the task
list entry in tasks.md.

… specification

- Define section/subsection detection using Markdown headers (H2/H3)
- Specify 100-line threshold with configurable 25-line minimum split size
- Detail four-step splitting algorithm: parse, calculate, decide, avoid overflow
- Map algorithm steps to implementation locations (cmd/accept.go, cmd/accept_writer.go, cmd/jsonc.go, internal/parsers/)
- Document root tasks.jsonc output with $ref children and child file JSONC headers
- Add configuration support for thresholds via config.yaml or CLI flags
- Include status preservation logic for re-running accept on same change
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@spectr/changes/add-size-based-task-splitting/proposal.md`:
- Around line 55-160: The proposal is missing required "#### Scenario:" blocks
for each SHALL requirement; add a "#### Scenario:" subsection after each major
requirement section (e.g., after "Detection & Thresholds", "Section & Subsection
Detection", "Splitting Strategy", "Output Structure", "Implementation
Locations", "Configuration", and "Regeneration & Status Preservation") that
contains at least one WHEN/THEN bulletized scenario demonstrating the condition
and expected outcome (e.g., WHEN tasks.md > 100 lines THEN split into child
files, WHEN H3 > maxLines THEN break at task boundary, WHEN re-running accept
THEN preserve status by matching IDs), ensuring each scenario references the
relevant symbols/files from the spec such as cmd/accept.go,
cmd/accept_writer.go, cmd/jsonc.go, internal/parsers/types.go, ReadTasksFile,
tasks.jsonc and tasks-N.jsonc so reviewers can map requirements to behavior.
♻️ Duplicate comments (1)
spectr/changes/add-size-based-task-splitting/proposal.md (1)

165-181: Add language identifiers to the directory-tree code fences.

This is a repeat of the earlier markdownlint issue for these exact blocks. Tagging as duplicate per prior review.

✏️ Suggested fix
-```
+```text
 spectr/changes/my-big-change/
 ├── proposal.md
 ├── tasks.md          (150 lines, 45 tasks)
 └── tasks.jsonc       (generated: 150 lines, hard to read)

@@
- +text
spectr/changes/my-big-change/
├── proposal.md
├── tasks.md (150 lines, source of truth)
├── tasks.jsonc (root: 40 lines with refs)
├── tasks-1.jsonc (section 1: 60 lines)
└── tasks-2.jsonc (section 2: 50 lines)

Comment on lines 55 to 160
The `spectr accept` command SHALL implement the following splitting algorithm for `tasks.md` files exceeding 100 lines:

### Detection & Thresholds

- **Trigger**: `tasks.md` file size exceeds 100 lines (default threshold, configurable)
- **Minimum split size**: 25 lines (configurable) - prevents generation of tiny files with <25 lines
- **Output location**: All files written to the change directory (e.g., `spectr/changes/[change-id]/`)

### Section & Subsection Detection

Section boundaries are detected using Markdown header hierarchy:

1. **Primary sections** (H2 headers): Pattern `## N. [Section Name]` where `N` is a digit (e.g., `## 1. Implementation`)
2. **Subsections** (H3 headers): Pattern `### N.M. [Subsection Name]` where `N.M` indicates nesting (e.g., `### 1.1 API Endpoints`)
3. **Task items** (unordered lists): Pattern `- [ ] N.M.K Task description` under subsections

Each primary section (H2) becomes a candidate for splitting; subsections (H3) within a section are the atomic grouping units.

### Splitting Strategy

**Algorithm**:

1. **Parse `tasks.md`** into logical segments:
- Group consecutive lines by section header (H2)
- Within each section, identify subsection boundaries (H3)
- Preserve ordering of all H2 and H3 headers and tasks

2. **Calculate segment sizes** (in lines, including headers):
- Each (H2 section + its H3 subsections + tasks) is one candidate segment
- Record cumulative line count per segment

3. **Decide split points**:
- If a single primary section (H2) exceeds 100 lines:
- Split that section's H3 subsections across multiple child files
- Prefer breaking at H3 boundaries; if a single H3 subsection exceeds 100 lines, break at the nearest complete task item (after `- [ ]` line)
- If cumulative segments fit within 100 lines, keep together in one child file
- Apply **minimum-size bundling rule**: If a segment or subsection group falls below 25 lines, attempt to bundle it with the next adjacent segment (maintaining H2 group coherence where possible)

4. **Avoid overflow**: When bundling would violate the 100-line limit, create a new child file at the last valid H2 or H3 boundary

### Output Structure

**Root `tasks.jsonc`**:
- Contains only top-level tasks (one per primary section H2)
- Each task has an `id` matching its section number (e.g., "1", "2")
- Each task includes a `"children"` field: `"$ref:tasks-N.jsonc"`
- Root also includes `"includes": ["tasks-*.jsonc"]` for glob matching

**Child files** (e.g., `tasks-1.jsonc`, `tasks-2.jsonc`):
- Written by `cmd/accept_writer.go` using custom JSONC marshaller (`cmd/jsonc.go`)
- Include JSONC header comments documenting origin:
```jsonc
// Generated by: spectr accept [change-id]
// Parent change: [change-id]
// Parent task: [top-level section ID]
```
- Contains `"version": 2`, `"parent": "[section-id]"`, and `"tasks": [...]`
- Task IDs use hierarchical format (e.g., `1.1`, `1.2`) matching parsed structure
- All task descriptions properly escaped for JSONC validity

### Implementation Locations

The following files SHALL implement this algorithm:

- **`cmd/accept.go`**:
- Detect `tasks.md` size; if >100 lines, invoke splitting logic
- Orchestrate root file creation and child file coordination

- **`cmd/accept_writer.go`**:
- Implement parsing of `tasks.md` into sections/subsections
- Calculate split points per algorithm above
- Call custom JSONC marshaller for each child file

- **`cmd/jsonc.go`** (NEW):
- Implement custom JSONC marshaller handling quote escaping, trailing commas, JSONC comments
- Validate output JSONC before writing to disk

- **`internal/parsers/types.go`**:
- Add `Children string` field to task struct (holds `$ref:tasks-N.jsonc`)
- Add `Parent string` field (holds parent section ID)
- Add `Includes []string` field (holds `["tasks-*.jsonc"]`)

- **`internal/parsers/parsers.go`**:
- Add `ReadTasksFile(path string) (RootTasks, []ChildTasks, error)` function
- Recursively read root and all child `tasks-*.jsonc` files
- Return unified task list or per-file structures as needed

### Configuration

Support `.spectr/config.yaml` or flag overrides for:

```yaml
splitConfig:
maxLinesPerFile: 100 # Trigger threshold (default 100)
minLinesPerFile: 25 # Minimum before bundling (default 25)
```
Command-line equivalent: `spectr accept [change-id] --max-lines-per-file 100 --min-lines-per-file 25`

### Regeneration & Status Preservation

When `spectr accept` is re-run on the same change:
- Re-parse `tasks.md` and regenerate all split files
- Preserve task status values (pending/in_progress/completed) from existing `tasks-*.jsonc` files
- Match tasks by ID (e.g., "1.1" from old `tasks-1.jsonc` to "1.1" in newly generated file)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add required #### Scenario: blocks for each requirement.

This proposal contains multiple SHALL statements but no #### Scenario: sections with WHEN/THEN bullets, which is required for spectr/**/*.md. Please add at least one scenario per requirement. As per coding guidelines, ...

🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/proposal.md` around lines 55 -
160, The proposal is missing required "#### Scenario:" blocks for each SHALL
requirement; add a "#### Scenario:" subsection after each major requirement
section (e.g., after "Detection & Thresholds", "Section & Subsection Detection",
"Splitting Strategy", "Output Structure", "Implementation Locations",
"Configuration", and "Regeneration & Status Preservation") that contains at
least one WHEN/THEN bulletized scenario demonstrating the condition and expected
outcome (e.g., WHEN tasks.md > 100 lines THEN split into child files, WHEN H3 >
maxLines THEN break at task boundary, WHEN re-running accept THEN preserve
status by matching IDs), ensuring each scenario references the relevant
symbols/files from the spec such as cmd/accept.go, cmd/accept_writer.go,
cmd/jsonc.go, internal/parsers/types.go, ReadTasksFile, tasks.jsonc and
tasks-N.jsonc so reviewers can map requirements to behavior.

- Remove model field from slash-proposal.md.tmpl (wasn't needed)
- Add agent: implement to slash-apply.md.tmpl (was missing)
- Remove agent and model fields from TOML templates (not needed for Gemini)

Fixes: Invalid input: expected string, received null agent/model validation errors when running 'spectr init --opencode'
- Replace len(string) > 0 with string != '' (gocritic)
- Use for range b.N instead of manual counter (intrange)
- Extract validation closures to reduce cyclomatic complexity in 3 test functions
- Convert heavy parameters to pointers (gocritic)
- Add blank lines before return statements (nlreturn)
- Omit repeated argument types (revive)
- Remove unused imports

All lint checks now pass.
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🤖 Fix all issues with AI agents
In `@cmd/testhelpers.go`:
- Around line 37-39: The current os.Stat(spectrDir) error handling in the test
helper indiscriminately calls t.Skipf for all errors; change it to only skip
when the error indicates the directory does not exist (use os.IsNotExist(err)),
and for any other non-nil error return call t.Fatalf or t.Fatal with the error
so permission/IO errors fail the test; update the block around
os.Stat(spectrDir) and the t.Skipf call to perform the os.IsNotExist check and
handle other errors accordingly (references: spectrDir, os.Stat, t.Skipf).
- Around line 17-29: The search loop that checks for go.mod using os.Stat
currently treats all errors as "not found"; change the error handling in the
block that calls os.Stat(filepath.Join(dir, "go.mod")) so that if err != nil and
!os.IsNotExist(err) you fail fast (e.g., call t.Fatalf with the actual err)
instead of continuing upward, while preserving the existing behavior when
os.IsNotExist(err) is true; update the code around os.Stat, os.IsNotExist, the
dir variable and the t.Fatalf call to surface permission/IO errors immediately.

In `@FUZZ_TEST_SUMMARY.md`:
- Around line 5-163: Update FUZZ_TEST_SUMMARY.md to apply the minor
wording/grammar cleanups suggested: fix hyphenation and capitalization
inconsistencies across headings and prose (e.g., "Problem Fixed", "Solution
Implemented", "Core Fix (cmd/accept.go)", and inline items in the "Test Results"
and "Data Integrity Guarantees" sections), ensure verbs and list punctuation are
consistent, and standardize formatting of bullets and code examples for
readability; keep all technical content and examples unchanged while only
adjusting wording/capitalization/hyphenation for clarity.

In `@IMPLEMENTATION_COMPLETE.md`:
- Around line 1-6: Update the markdown to satisfy markdownlint/grammar: add
language identifiers to all fenced code blocks in the document (e.g., under the
"Task Splitting Implementation - Fix Complete ✓" heading and other sections
referenced), standardize emphasis to a single style (prefer asterisks) instead
of mixing underscores and asterisks, and correct hyphenation/spelling (normalize
instances like "task-splitting" and fix "successful"/other misspellings) across
the indicated ranges (including the blocks around lines 11-24, 57-97, 101-110,
150-158); scan for and fix any remaining MD040/MD049 violations so the file
passes linting.

In `@spectr/AGENTS.md`:
- Around line 61-68: Update AGENTS.md to change the status-updating guidance so
it tells users to apply status changes to tasks.jsonc (the machine-readable
source updated by `spectr accept`) rather than directly checking off tasks.md
(which remains human-readable); explicitly mention `spectr accept` as the
command that updates tasks.jsonc, and add tasks.jsonc to the shown directory
tree so users can find it (also mirror this change in the other referenced
sections around lines 133-150 and 162-167).
♻️ Duplicate comments (4)
cmd/accept.go (4)

240-258: Split path ignores appended tasks.
When split is true, appendCfg tasks are not merged into sections, yet totalTasks counts them. That drops configured tasks from output while reporting them as written.


672-712: Guard against tasks before the first section.
parseSections silently ignores tasks when currentSection == nil, which can drop leading tasks in split mode. Prefer failing fast (or creating a default section) to avoid silent data loss.

🛠️ Proposed fix (fail fast on leading tasks)
-		if match, ok := markdown.MatchFlexibleTask(line); ok {
+		if match, ok := markdown.MatchFlexibleTask(line); ok {
+			if currentSection == nil {
+				return nil, fmt.Errorf(
+					"task found before first section header at line %d",
+					lineNum,
+				)
+			}
 			// Collect continuation lines (indented lines following the task)
 			description := match.Content

753-810: Prefix extraction collapses common 1.x IDs.
extractIDPrefix("1.1") returns "1", so parseSubsections groups typical 1.x tasks together and large sections won’t split. Consider only stripping the last segment when there are 2+ dots, or add a fallback chunking strategy.

🛠️ Proposed fix (keep single-dot IDs intact)
 func extractIDPrefix(id string) string {
 	// Find the last dot in the ID
 	lastDot := strings.LastIndex(id, ".")
 	if lastDot == -1 {
 		// No dot found - this is a simple ID like "1"
 		return id
 	}
+
+	// Only strip the last segment when there are 2+ dots (e.g., "1.2.3" -> "1.2")
+	if strings.Count(id, ".") < 2 {
+		return id
+	}
 
 	// Return everything before the last dot
 	return id[:lastDot]
 }

886-896: Dry-run validation is still a no-op.
validateDryRun always returns nil, but dry-run output says validation occurs. Either implement real validation (marshal + validateJSONCOutput) or drop the validation claim.

🧹 Nitpick comments (7)
cmd/accept.go (2)

567-587: Use Afero for file I/O in new helpers.
countLines (and the new split helpers) open files via os, which bypasses the repo’s filesystem abstraction and makes testing harder. Consider threading an afero.Fs through AcceptCmd or these helpers. As per coding guidelines, use Afero for filesystem access.


601-610: Consider moving split/parsing types out of cmd/.
New structs and parsing logic in cmd/accept.go increase business logic in the CLI layer. Consider relocating these helpers into an internal package to keep cmd thin. As per coding guidelines, cmd should delegate business logic to internal/.

cmd/accept_validator_test.go (2)

162-199: Convert helper test to table-driven format.
TestHelperFunctions_Basic is a single-case test without subtests; guidelines call for table-driven tests with t.Run. Consider folding these assertions into a small table-driven set. As per coding guidelines.


879-882: Subtest names are all tasks.jsonc.
Using filepath.Base(filePath) yields identical names for every archived file, which makes failures harder to distinguish. Consider using the change directory name or a relative path for the subtest name.

♻️ Proposed tweak (unique subtest names)
-	for _, filePath := range archivedFiles {
-		t.Run(filepath.Base(filePath), func(t *testing.T) {
+	for _, filePath := range archivedFiles {
+		testName := filepath.Base(filepath.Dir(filePath))
+		t.Run(testName, func(t *testing.T) {
cmd/view_test.go (1)

13-94: Consider consolidating these integration tests into a table-driven structure with t.Run().

It would reduce repeated setup and align test organization across the file. As per coding guidelines, consider table-driven tests.

cmd/testhelpers.go (1)

44-48: Consider Afero-based filesystem helpers for test portability.

These helpers use os directly; consider accepting an afero.Fs (or a package-level fs var) to allow in-memory testing and align with repo conventions. As per coding guidelines, prefer Afero for filesystem abstraction.

cmd/accept_fuzz_test.go (1)

294-301: Consider using Afero for temp file creation in tests.

These tests write/read files directly with os. Using afero.Fs (e.g., afero.NewMemMapFs) would align with repo conventions and improve portability. As per coding guidelines, prefer Afero for filesystem abstraction.

Comment on lines 17 to 29
for {
// Check if go.mod exists in this directory
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}

// Move up one directory
parent := filepath.Dir(dir)
if parent == dir {
// Reached filesystem root without finding go.mod
t.Fatalf("Could not find project root (go.mod) from %s", dir)
}
dir = parent
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Don’t ignore non-NotExist errors while searching for go.mod.

If os.Stat fails due to permission/IO errors, the loop currently hides the real issue and eventually fails with a misleading “not found”. Consider failing fast on unexpected errors.

🛠️ Proposed fix
-		// Check if go.mod exists in this directory
-		if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
-			return dir
-		}
+		// Check if go.mod exists in this directory
+		if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
+			return dir
+		} else if !os.IsNotExist(err) {
+			t.Fatalf("Failed to stat go.mod in %s: %v", dir, err)
+		}
📝 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.

Suggested change
for {
// Check if go.mod exists in this directory
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
}
// Move up one directory
parent := filepath.Dir(dir)
if parent == dir {
// Reached filesystem root without finding go.mod
t.Fatalf("Could not find project root (go.mod) from %s", dir)
}
dir = parent
for {
// Check if go.mod exists in this directory
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir
} else if !os.IsNotExist(err) {
t.Fatalf("Failed to stat go.mod in %s: %v", dir, err)
}
// Move up one directory
parent := filepath.Dir(dir)
if parent == dir {
// Reached filesystem root without finding go.mod
t.Fatalf("Could not find project root (go.mod) from %s", dir)
}
dir = parent
🤖 Prompt for AI Agents
In `@cmd/testhelpers.go` around lines 17 - 29, The search loop that checks for
go.mod using os.Stat currently treats all errors as "not found"; change the
error handling in the block that calls os.Stat(filepath.Join(dir, "go.mod")) so
that if err != nil and !os.IsNotExist(err) you fail fast (e.g., call t.Fatalf
with the actual err) instead of continuing upward, while preserving the existing
behavior when os.IsNotExist(err) is true; update the code around os.Stat,
os.IsNotExist, the dir variable and the t.Fatalf call to surface permission/IO
errors immediately.

Comment on lines 37 to 39
if _, err := os.Stat(spectrDir); err != nil {
t.Skipf("spectr directory not found at %s", spectrDir)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Skip only when spectr is missing; fail on other stat errors.

Right now any error (including permission/IO) skips the test, which can mask real failures.

🛠️ Proposed fix
-	if _, err := os.Stat(spectrDir); err != nil {
-		t.Skipf("spectr directory not found at %s", spectrDir)
-	}
+	if _, err := os.Stat(spectrDir); err != nil {
+		if os.IsNotExist(err) {
+			t.Skipf("spectr directory not found at %s", spectrDir)
+		}
+		t.Fatalf("Failed to stat spectr directory %s: %v", spectrDir, err)
+	}
📝 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.

Suggested change
if _, err := os.Stat(spectrDir); err != nil {
t.Skipf("spectr directory not found at %s", spectrDir)
}
if _, err := os.Stat(spectrDir); err != nil {
if os.IsNotExist(err) {
t.Skipf("spectr directory not found at %s", spectrDir)
}
t.Fatalf("Failed to stat spectr directory %s: %v", spectrDir, err)
}
🤖 Prompt for AI Agents
In `@cmd/testhelpers.go` around lines 37 - 39, The current os.Stat(spectrDir)
error handling in the test helper indiscriminately calls t.Skipf for all errors;
change it to only skip when the error indicates the directory does not exist
(use os.IsNotExist(err)), and for any other non-nil error return call t.Fatalf
or t.Fatal with the error so permission/IO errors fail the test; update the
block around os.Stat(spectrDir) and the t.Skipf call to perform the
os.IsNotExist check and handle other errors accordingly (references: spectrDir,
os.Stat, t.Skipf).

Comment on lines +5 to +163
Successfully implemented comprehensive fuzz testing for the task splitting implementation to ensure robustness when handling multi-line task descriptions. All 2,661 project tests pass.

## Problem Fixed

Task descriptions with multi-line content (indented sub-bullets) were being truncated in the JSON output. For example:

**Before fix:**
```
Task 3.2: "Implement TestJSONCValidation_SpecialCharacters with test cases for:"
```

**After fix:**
```
Task 3.2: "Implement TestJSONCValidation_SpecialCharacters with test cases for:
- Backslash `\`
- Quote `"`
- Newline `\n`
- Tab `\t`"
```

## Solution Implemented

### 1. Core Fix (cmd/accept.go)

Updated two critical functions to capture multi-line task descriptions:

#### parseTasksMd() - Lines 451-546
- Reads entire file into memory for look-ahead capability
- For each task matched, continues collecting lines that are:
- Indented (start with space or tab)
- Not blank lines
- Not another task or section header
- Appends all continuation lines to the task description

#### parseSections() - Lines 621-723
- Applies the same multi-line collection logic
- Used when splitting files by section boundary
- Maintains accurate line number tracking

### 2. Comprehensive Test Suite (cmd/accept_fuzz_test.go - 665 lines)

#### Test 1: Multiline Variations (11 scenarios)
Tests various patterns of multi-line task descriptions:
- ✓ Mixed indentation levels (spaces and nested items)
- ✓ Tabs and spaces mixed together
- ✓ Blank lines properly stop continuation
- ✓ Special characters (\\, ", ', [, {) preserved
- ✓ Unicode content (🚀, 你好, مرحبا, Привет) preserved
- ✓ Very long lines (300+ characters) not truncated
- ✓ Code blocks and regex patterns preserved
- ✓ Multiple sections with no cross-contamination
- ✓ Numbered and unnumbered list items
- ✓ Minimal base text with rich continuations

#### Test 2: Edge Cases (8 scenarios)
Tests boundary conditions:
- ✓ Empty sections (no tasks)
- ✓ Tasks without sections
- ✓ Continuation lines at end of file
- ✓ Deeply nested indentation (5+ levels)
- ✓ Markdown formatting (**bold**, *italic*, _underscore_)
- ✓ Whitespace-only lines stop continuation
- ✓ Unusual task ID formats
- ✓ Rapid task/continuation alternation

#### Test 3: Continuation Integrity
- ✓ 4 complex real-world tasks parsed correctly
- ✓ Escaped characters preserved (\\, \n, \t)
- ✓ Unicode characters survive JSON serialization
- ✓ No data corruption in the pipeline
- ✓ Cross-task contamination prevention

#### Test 4: Performance Benchmark
```
BenchmarkParseTasksMdMultiline-16:
Operations/sec: 117,931
Memory per op: 8,414 bytes
Allocations: 69 per operation
```

## Test Results

### Full Test Suite
```
Total Tests: 2,661
Passed: 2,658
Skipped: 3 (archived files)
Failed: 0
```

### Specific Test Results
```
TestParseTasksMdFuzzMultilineVariations: 11/11 PASS
TestParseTasksMdFuzzEdgeCases: 8/8 PASS
TestParseTasksMdContinuationIntegrity: PASS
BenchmarkParseTasksMdMultiline: 117,931 ops/sec
```

## Data Integrity Guarantees

**No cross-contamination**: Each task's details stay isolated
**Special characters**: Backslash, quotes, brackets preserved
**Unicode support**: Emoji, CJK, Arabic, RTL languages all work
**Indentation**: Both tabs and spaces handled correctly
**Long content**: 300+ character lines not truncated
**Markdown format**: **Bold**, *italic*, _underscore_ preserved
**Blank line handling**: Properly recognized as continuation boundary
**JSON serialization**: Full round-trip integrity maintained

## Files Modified

| File | Changes | Purpose |
|------|---------|---------|
| cmd/accept.go | +89 lines | Multi-line parsing logic for parseTasksMd() and parseSections() |
| cmd/accept_test.go | +42 lines | Added multi-line test case validation |
| cmd/accept_fuzz_test.go | +665 lines | Comprehensive fuzz testing suite |

## Real-World Validation

Successfully tested against actual issue from fix-jsonc-validation change:
- Task 3.2 (SpecialCharacters) with escaped characters ✓
- Task 3.3 (Unicode) with multi-language text ✓
- JSON generation preserves all content ✓
- Complete round-trip integrity verified ✓

## Key Implementation Pattern

```go
// For each task matched, collect continuation lines
description := match.Content
for j := i + 1; j < len(lines); j++ {
nextLine := lines[j]

// Stop conditions
if isNewSection(nextLine) || isNewTask(nextLine) || isBlank(nextLine) {
break
}

// Continue if indented
if isIndented(nextLine) {
description += "\n" + nextLine
i = j // Skip in outer loop
} else {
break
}
}
```

## Verification Steps

1. **Unit Tests**: 30+ fuzz test cases covering variations and edge cases
2. **Integration Tests**: Full pipeline from markdown to JSON serialization
3. **Performance Tests**: Benchmark confirms efficient parsing (117k ops/sec)
4. **Regression Tests**: Existing 2,658 tests all pass with improvements
5. **Real-World Testing**: Validated against actual problematic task files

## Conclusion

The task splitting implementation now robustly handles multi-line task descriptions with comprehensive fuzz testing ensuring reliability across all edge cases, special character combinations, and unicode content.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor wording/grammar cleanups for readability.

These small edits address the flagged hyphenation/capitalization hints.

✍️ Suggested edits
-Successfully implemented comprehensive fuzz testing for the task splitting implementation to ensure robustness when handling multi-line task descriptions. All 2,661 project tests pass.
+Successfully implemented comprehensive fuzz testing for the task-splitting implementation to ensure robustness when handling multi-line task descriptions. All 2,661 project tests pass.
@@
-- ✓ Tabs and spaces mixed together
+- ✓ Tabs and spaces mixed
@@
-2. **Integration Tests**: Full pipeline from markdown to JSON serialization
+2. **Integration Tests**: Full pipeline from Markdown to JSON serialization
@@
-The task splitting implementation now robustly handles multi-line task descriptions with comprehensive fuzz testing ensuring reliability across all edge cases, special character combinations, and unicode content.
+The task-splitting implementation now robustly handles multi-line task descriptions with comprehensive fuzz testing ensuring reliability across all edge cases, special character combinations, and unicode content.
🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: Use a hyphen to join words.
Context: ... comprehensive fuzz testing for the task splitting implementation to ensure robus...

(QB_NEW_EN_HYPHEN)


[style] ~49-~49: ‘mixed together’ might be wordy. Consider a shorter alternative.
Context: ...s and nested items) - ✓ Tabs and spaces mixed together - ✓ Blank lines properly stop continuat...

(EN_WORDINESS_PREMIUM_MIXED_TOGETHER)


[uncategorized] ~156-~156: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...Integration Tests**: Full pipeline from markdown to JSON serialization 3. **Performance ...

(MARKDOWN_NNP)


[grammar] ~163-~163: Use a hyphen to join words.
Context: ...atic task files ## Conclusion The task splitting implementation now robustly ha...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In `@FUZZ_TEST_SUMMARY.md` around lines 5 - 163, Update FUZZ_TEST_SUMMARY.md to
apply the minor wording/grammar cleanups suggested: fix hyphenation and
capitalization inconsistencies across headings and prose (e.g., "Problem Fixed",
"Solution Implemented", "Core Fix (cmd/accept.go)", and inline items in the
"Test Results" and "Data Integrity Guarantees" sections), ensure verbs and list
punctuation are consistent, and standardize formatting of bullets and code
examples for readability; keep all technical content and examples unchanged
while only adjusting wording/capitalization/hyphenation for clarity.

Comment on lines +1 to +6
# Task Splitting Implementation - Fix Complete ✓

## Status: RESOLVED

The issue with incomplete task descriptions in the task splitting implementation has been fully resolved with comprehensive fuzz testing to ensure robustness.

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Resolve markdownlint/grammar warnings.
Multiple fenced blocks are missing language identifiers (MD040), emphasis style mixes underscores vs asterisks (MD049), and there are hyphenation/spelling issues (e.g., “task-splitting”, “successful”). Please update to satisfy linting and grammar checks.

Also applies to: 11-24, 57-97, 101-110, 150-158

🧰 Tools
🪛 LanguageTool

[grammar] ~5-~5: Use a hyphen to join words.
Context: ...incomplete task descriptions in the task splitting implementation has been fully ...

(QB_NEW_EN_HYPHEN)

🤖 Prompt for AI Agents
In `@IMPLEMENTATION_COMPLETE.md` around lines 1 - 6, Update the markdown to
satisfy markdownlint/grammar: add language identifiers to all fenced code blocks
in the document (e.g., under the "Task Splitting Implementation - Fix Complete
✓" heading and other sections referenced), standardize emphasis to a single
style (prefer asterisks) instead of mixing underscores and asterisks, and
correct hyphenation/spelling (normalize instances like "task-splitting" and fix
"successful"/other misspellings) across the indicated ranges (including the
blocks around lines 11-24, 57-97, 101-110, 150-158); scan for and fix any
remaining MD040/MD049 violations so the file passes linting.

connerohnesorge and others added 2 commits January 18, 2026 18:37
Restore all 74 files from spectr/changes that were deleted in recent commits:
- add-kimi-agentskills-support/ (proposal, spec, tasks)
- add-size-based-task-splitting/ (proposal, design, spec, 16 split task files)
- fix-jsonc-validation/ (proposal, design, spec, 8 split task files)
- test-extreme-jsonc/ (proposal, spec, tasks)
- test-problematic-jsonc/ (proposal, spec, tasks)
- archive/ (6 archived change records with historical proposals and specs)

These were accidentally deleted in commits 5e09677 and f94a6dc. Restoring to commit 516e8c4.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implement 191+ test cases validating JSONC handling of pathological inputs:
- Nested quotes, backslashes, bombardments, and special characters
- Security tests: JSON/HTML injection, path traversal, XSS payloads
- Unicode edge cases: zero-width chars, RTL marks, null bytes
- Format strings, regex patterns, all printable ASCII
- Round-trip validation ensuring lossless serialization

Tests created via async orchestration with 16 parallel coder agents:
- accept_nested_quotes_test.go: 1 test case
- accept_unicode_edge_test.go: 37 test cases
- accept_emptyish_test.go: 16 test cases
- accept_regex_test.go: 19 test cases
- accept_task110_test.go: 12 test cases
- accept_validator_test.go: enhanced with 106+ test cases

All 2848 tests pass. Validates spec-driven development for test-extreme-jsonc
and fixes validation issues in add-size-based-task-splitting and fix-jsonc-validation
by correcting spec section types (MODIFIED → ADDED for new specs).

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Fix all issues with AI agents
In `@spectr/changes/add-size-based-task-splitting/specs/cli-interface/spec.md`:
- Line 1: Rename the top-level header "CLI Interface Changes" to remove
redundancy (e.g., "CLI Changes" or "Command-Line Interface Changes") by updating
the header text "CLI Interface Changes", and capitalize every instance of
"markdown" to "Markdown" (notably the occurrences referenced around lines 61-63)
to fix the wording/polish in the spec document.
- Around line 33-40: The spec currently contradicts the generated header: remove
the allowance that "skipping directly from 'pending' to 'completed' is allowed
for trivial tasks" from the root tasks.jsonc scenario and update the
header-generation template that creates tasks-*.jsonc so it no longer emits "Do
not skip states" vs the spec; specifically delete the clause referencing
skipping from the spec entry and ensure the header text produced by the header
generation (tasks-*.jsonc template) documents only the allowed transitions
pending -> in_progress -> completed and the agent behavior (mark in_progress
when starting, completed only after verification).

In `@spectr/changes/add-size-based-task-splitting/tasks-14.jsonc`:
- Around line 1-3: The file name tasks-14.jsonc does not match the parent
task/IDs (parent 13 and IDs 13.x); either rename the file to match the parent
(e.g., tasks-13.jsonc) or update the parent reference and all contained task IDs
to 14.x to match the filename; specifically update the "parent" field and each
task "id" entries that currently start with "13." (also apply the same alignment
fix for files/ranges referenced 43-80) so filenames, parent pointers, and task
IDs are consistent.

In `@spectr/changes/add-size-based-task-splitting/tasks-9.jsonc`:
- Around line 1-3: The file name tasks-9.jsonc does not match the parent
task/IDs (parent 8 and IDs 8.x); either rename the file to match the parent
(e.g., tasks-8.jsonc) or update the parent/IDs inside the JSONC to use 9/9.x so
they align—make the change consistently for this file and for the other affected
files/items referenced in the comment (items 43–80) so filename, parent task id,
and task IDs all match.

In `@spectr/changes/fix-jsonc-validation/proposal.md`:
- Around line 1-5: Add an explicit "Change impact" section to the proposal
(under the existing "Change: Fix JSONC File Validation" header) that lists
affected specs/capabilities and affected code/systems: call out the user-facing
capability (JSONC file generation/validation), any spec changes (e.g., JSONC
output guarantees, validation contract), and key files/modules to be modified or
reviewed (places that generate JSONC with json.MarshalIndent, the property-based
tests and round-trip verification harness, and any JSONC parser/validator
utilities). Be concise and specific—name the capability ("JSONC file
creation/validation"), reference the implementation detail ("json.MarshalIndent
usage and the property-based tests / round-trip verifier"), and enumerate the
primary code owners or modules that will be impacted.

In `@spectr/changes/test-extreme-jsonc/tasks.md`:
- Line 3: Change the unnumbered section header "## Extreme Edge Cases" to
include a numeric prefix so it matches tasks.md conventions and aligns with its
1.x subtasks (e.g., rename the header to "## 1. Extreme Edge Cases" or the
appropriate parent number used elsewhere); update the header text in the
tasks.md file where "## Extreme Edge Cases" appears so all child tasks numbered
1.x map cleanly to this parent section.
- Around line 1-19: tasks.md is missing task 2.1 that exists in tasks.jsonc; add
a new checklist item under the "99.99.99 Final section (after all tasks)" header
matching the style of 1.1–1.15, using the same identifier and text from
tasks.jsonc: mark it completed with "- [x] 2.1 Output/return: <promise>\"This is
the last task: COMPLETE\"</promise>" so the two files stay synchronized.
♻️ Duplicate comments (7)
spectr/changes/test-problematic-jsonc/tasks.md (3)

3-3: Number the section header for tasks.md parsing.
Line 3 is unnumbered; the tasks parser expects numbered sections (e.g., ## 1. ...). As per coding guidelines, tasks.md sections must be numbered.

💡 Suggested change
-## Edge Case Testing
+## 1. Edge Case Testing

7-8: Indent the continuation line for task 1.3.
Line 8 is unindented, so it becomes a separate paragraph instead of part of the task description.

💡 Suggested change
- [ ] 1.3 Task with newlines: This is line 1
-And this should also be part of the description (this won't parse as single line)
+ [ ] 1.3 Task with newlines: This is line 1
+  And this should also be part of the description (this won't parse as single line)

11-11: Avoid hard tabs or suppress MD010 for the tabbed test case.
Line 11 contains literal tabs, which trip markdownlint MD010; either replace them with escaped \t/spaces or suppress MD010 for this line if tabs are intentional.

💡 Suggested change
+<!-- markdownlint-disable-next-line MD010 -->
- [ ] 1.6 Task with tabs:	indented	with	tabs	here
+ [ ] 1.6 Task with tabs:	indented	with	tabs	here
spectr/changes/test-problematic-jsonc/tasks.jsonc (1)

101-105: Escape literal control characters in task 1.11.
Unescaped control chars in JSON strings can make the JSONC invalid; use \\b / \\f (or remove) to keep it parseable.

💡 Suggested change
-      "description": "Control chars: ASCII control characters like  (backspace) and  (form feed)",
+      "description": "Control chars: ASCII control characters like \\b (backspace) and \\f (form feed)",
spectr/changes/test-extreme-jsonc/tasks.jsonc (1)

131-136: Verify tasks.md and tasks.jsonc are in sync.

A previous review flagged that task 2.1 ("99.99.99 Final section") exists in tasks.jsonc but not in tasks.md. Per coding guidelines, these files must remain synchronized.

#!/bin/bash
# Check if task 2.1 / "99.99.99 Final section" exists in tasks.md
echo "=== Checking tasks.md for 99.99.99 or task 2.1 ==="
if [ -f "spectr/changes/test-extreme-jsonc/tasks.md" ]; then
  grep -n "99.99.99\|2\.1" spectr/changes/test-extreme-jsonc/tasks.md || echo "Not found in tasks.md"
else
  echo "tasks.md not found"
fi
spectr/changes/add-size-based-task-splitting/proposal.md (2)

165-181: Add language identifiers to fenced code blocks.

The directory tree examples lack language identifiers, which triggers markdownlint MD040. Add text or plaintext after the opening backticks.

✏️ Suggested fix
-```
+```text
 spectr/changes/my-big-change/
 ├── proposal.md
 ├── tasks.md          (150 lines, 45 tasks)
 └── tasks.jsonc       (generated: 150 lines, hard to read)

- +text
spectr/changes/my-big-change/
├── proposal.md
├── tasks.md (150 lines, source of truth)
├── tasks.jsonc (root: 40 lines with refs)
├── tasks-1.jsonc (section 1: 60 lines)
└── tasks-2.jsonc (section 2: 50 lines)


55-160: Add required #### Scenario: blocks for each requirement.

The specification contains multiple SHALL statements (lines 55, 117) but lacks the required #### Scenario: sections with WHEN/THEN bullets. Per coding guidelines, every requirement in spectr/**/*.md must have at least one scenario.

Example scenarios to add:

  • WHEN tasks.md exceeds 100 lines THEN split into hierarchical files
  • WHEN a single H3 subsection exceeds 100 lines THEN break at task boundaries
  • WHEN re-running accept on existing files THEN preserve task statuses by ID matching
🧹 Nitpick comments (2)
cmd/accept_fuzz_test.go (1)

162-536: Consider moving large markdown fixtures into testdata files.

This would keep the tests easier to scan and aligns with the repository’s test conventions. As per coding guidelines, prefer fixtures in testdata/ for *_test.go.

cmd/accept_nested_quotes_test.go (1)

21-106: Align this test with table‑driven style.
Guidelines call for table‑driven tests with t.Run; consider wrapping this case in a tests slice or merging into the existing JSONC tables for consistency. As per coding guidelines, prefer table-driven tests with t.Run().

@@ -0,0 +1,233 @@
# CLI Interface Changes
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Minor wording polish per static analysis.

  • “CLI Interface” is redundant (“I” already means Interface).
  • “markdown” should be capitalized as “Markdown”.
✏️ Suggested edits
-# CLI Interface Changes
+# CLI Changes
-- **WHEN** converting tasks from markdown
+- **WHEN** converting tasks from Markdown

Also applies to: 61-63

🧰 Tools
🪛 LanguageTool

[style] ~1-~1: This phrase is redundant (‘I’ stands for ‘Interface’). Use simply “CLIInterface”.
Context: # CLI Interface Changes ## ADDED Requirements ### Req...

(ACRONYM_TAUTOLOGY)

🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/specs/cli-interface/spec.md` at
line 1, Rename the top-level header "CLI Interface Changes" to remove redundancy
(e.g., "CLI Changes" or "Command-Line Interface Changes") by updating the header
text "CLI Interface Changes", and capitalize every instance of "markdown" to
"Markdown" (notably the occurrences referenced around lines 61-63) to fix the
wording/polish in the spec document.

Comment on lines 33 to 40
- **WHEN** generating root tasks.jsonc
- **THEN** the header SHALL use `//` line comment syntax
- **AND** the header SHALL indicate the file is machine-generated by `spectr accept`
- **AND** the header SHALL document the three valid status values: "pending", "in_progress", "completed"
- **AND** the header SHALL document valid status transitions: pending -> in_progress -> completed
- **AND** the header SHALL explain that agents should mark a task "in_progress" when starting work
- **AND** the header SHALL explain that agents should mark a task "completed" only after verification
- **AND** the header SHALL note that skipping directly from "pending" to "completed" is allowed for trivial tasks
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reconcile spec with header guidance on status transitions.

This scenario allows skipping directly from “pending” to “completed”, but the generated headers (e.g., tasks-*.jsonc) state “Do not skip states.” Please align the requirement with the actual header behavior (either remove the allowance or update header/spec generation accordingly).

🛠️ Minimal spec-only fix (remove allowance)
-- **AND** the header SHALL note that skipping directly from "pending" to "completed" is allowed for trivial tasks
📝 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.

Suggested change
- **WHEN** generating root tasks.jsonc
- **THEN** the header SHALL use `//` line comment syntax
- **AND** the header SHALL indicate the file is machine-generated by `spectr accept`
- **AND** the header SHALL document the three valid status values: "pending", "in_progress", "completed"
- **AND** the header SHALL document valid status transitions: pending -> in_progress -> completed
- **AND** the header SHALL explain that agents should mark a task "in_progress" when starting work
- **AND** the header SHALL explain that agents should mark a task "completed" only after verification
- **AND** the header SHALL note that skipping directly from "pending" to "completed" is allowed for trivial tasks
- **WHEN** generating root tasks.jsonc
- **THEN** the header SHALL use `//` line comment syntax
- **AND** the header SHALL indicate the file is machine-generated by `spectr accept`
- **AND** the header SHALL document the three valid status values: "pending", "in_progress", "completed"
- **AND** the header SHALL document valid status transitions: pending -> in_progress -> completed
- **AND** the header SHALL explain that agents should mark a task "in_progress" when starting work
- **AND** the header SHALL explain that agents should mark a task "completed" only after verification
🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/specs/cli-interface/spec.md`
around lines 33 - 40, The spec currently contradicts the generated header:
remove the allowance that "skipping directly from 'pending' to 'completed' is
allowed for trivial tasks" from the root tasks.jsonc scenario and update the
header-generation template that creates tasks-*.jsonc so it no longer emits "Do
not skip states" vs the spec; specifically delete the clause referencing
skipping from the spec entry and ensure the header text produced by the header
generation (tasks-*.jsonc template) documents only the allowed transitions
pending -> in_progress -> completed and the agent behavior (mark in_progress
when starting, completed only after verification).

Comment on lines +1 to +3
// Generated by: spectr accept add-size-based-task-splitting
// Parent change: add-size-based-task-splitting
// Parent task: 13
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

File name/parent mismatch (tasks-14.jsonc vs parent 13/IDs 13.x).

Please align the filename with the parent task and IDs (or update the parent/IDs if the filename is intended).

Also applies to: 43-80

🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/tasks-14.jsonc` around lines 1 -
3, The file name tasks-14.jsonc does not match the parent task/IDs (parent 13
and IDs 13.x); either rename the file to match the parent (e.g., tasks-13.jsonc)
or update the parent reference and all contained task IDs to 14.x to match the
filename; specifically update the "parent" field and each task "id" entries that
currently start with "13." (also apply the same alignment fix for files/ranges
referenced 43-80) so filenames, parent pointers, and task IDs are consistent.

Comment on lines +1 to +3
// Generated by: spectr accept add-size-based-task-splitting
// Parent change: add-size-based-task-splitting
// Parent task: 8
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

File name/parent mismatch (tasks-9.jsonc vs parent 8/IDs 8.x).

Please align the filename with the parent task and IDs (or update the parent/IDs if the filename is intended).

Also applies to: 43-80

🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/tasks-9.jsonc` around lines 1 -
3, The file name tasks-9.jsonc does not match the parent task/IDs (parent 8 and
IDs 8.x); either rename the file to match the parent (e.g., tasks-8.jsonc) or
update the parent/IDs inside the JSONC to use 9/9.x so they align—make the
change consistently for this file and for the other affected files/items
referenced in the comment (items 43–80) so filename, parent task id, and task
IDs all match.

Comment on lines 1 to 5
# Change: Fix JSONC File Validation

## Summary

The current JSONC file creation via `json.MarshalIndent` does not guarantee valid JSONC output when task descriptions contain special characters or escape sequences. This change introduces robust JSONC validation through property-based testing and round-trip verification to ensure all generated JSONC files are parseable.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add required change-impact section (affected specs/code).

The proposal doesn’t document affected specs (capabilities) and affected code (key files/systems). This is required for change proposals. As per coding guidelines, please add an explicit impact section.

✅ Suggested addition
+# Impact
+
+- **Affected specs (capabilities):** TBD
+- **Affected code (key files/systems):** TBD
🤖 Prompt for AI Agents
In `@spectr/changes/fix-jsonc-validation/proposal.md` around lines 1 - 5, Add an
explicit "Change impact" section to the proposal (under the existing "Change:
Fix JSONC File Validation" header) that lists affected specs/capabilities and
affected code/systems: call out the user-facing capability (JSONC file
generation/validation), any spec changes (e.g., JSONC output guarantees,
validation contract), and key files/modules to be modified or reviewed (places
that generate JSONC with json.MarshalIndent, the property-based tests and
round-trip verification harness, and any JSONC parser/validator utilities). Be
concise and specific—name the capability ("JSONC file creation/validation"),
reference the implementation detail ("json.MarshalIndent usage and the
property-based tests / round-trip verifier"), and enumerate the primary code
owners or modules that will be impacted.

Comment on lines 1 to 19
# Tasks

## Extreme Edge Cases

- [x] 1.1 Nested quotes: "He said \"Hello\" and she replied \"Hi there\""
- [x] 1.2 Multiple backslashes: C:\\\\\\\\path\\\\\\\\to\\\\\\\\file
- [x] 1.3 JSON injection attempt: "},{"id":"injected","status":"hacked
- [x] 1.4 Unicode edge: null bytes (if any), zero-width chars: ​‌‍, RTL marks: ‏‎
- [x] 1.5 HTML injection: <script>alert('xss')</script> and <!-- comment -->
- [x] 1.6 Path traversal: ../../../etc/passwd and ..\..\..\..\windows\system32
- [x] 1.7 Quote bombardment: """""""""""""""""""""""""""""""""""""""
- [x] 1.8 Backslash bombardment: \\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\\
- [x] 1.9 Mixed bombardment: \"\"\"\"\\ \"\"\"\"\\ \"\"\"\"\\ \"\"\"\"\\
- [x] 1.10 JSONC comment injection: // comment */ or /* comment */ in description
- [x] 1.11 Literal newline char test: Line1\nLine2\nLine3 (with literal \n not actual newline)
- [x] 1.12 Empty-ish: "" and '' and ``
- [x] 1.13 Format string: %s %d %x %n ${var} #{var} {{var}}
- [x] 1.14 Regex chars: .* .+ .? ^$ [a-z] (foo|bar) \d+ \w+ \s+
- [x] 1.15 All printable ASCII: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if files exist and show their content
echo "=== Checking directory structure ==="
ls -la spectr/changes/test-extreme-jsonc/ 2>/dev/null || echo "Directory not found"

echo -e "\n=== Tasks in tasks.md ==="
if [ -f "spectr/changes/test-extreme-jsonc/tasks.md" ]; then
  cat -n spectr/changes/test-extreme-jsonc/tasks.md
else
  echo "tasks.md not found"
fi

echo -e "\n=== Content of tasks.jsonc ==="
if [ -f "spectr/changes/test-extreme-jsonc/tasks.jsonc" ]; then
  cat -n spectr/changes/test-extreme-jsonc/tasks.jsonc
else
  echo "tasks.jsonc not found"
fi

Repository: connerohnesorge/spectr

Length of output: 7635


Add task 2.1 to tasks.md to maintain sync with tasks.jsonc.

The tasks.jsonc file contains a task 2.1 ("Output/return: "This is the last task: COMPLETE"") under section "99.99.99 Final section (after all tasks)" that is missing from tasks.md. Tasks 1.1–1.15 are properly synchronized between both files with matching statuses (all completed), but tasks.md must include all tasks present in tasks.jsonc to remain in sync.

🤖 Prompt for AI Agents
In `@spectr/changes/test-extreme-jsonc/tasks.md` around lines 1 - 19, tasks.md is
missing task 2.1 that exists in tasks.jsonc; add a new checklist item under the
"99.99.99 Final section (after all tasks)" header matching the style of
1.1–1.15, using the same identifier and text from tasks.jsonc: mark it completed
with "- [x] 2.1 Output/return: <promise>\"This is the last task:
COMPLETE\"</promise>" so the two files stay synchronized.

@@ -0,0 +1,19 @@
# Tasks

## Extreme Edge Cases
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add numbered section header to match tasks.md conventions.

The section header should include its numeric prefix so the 1.x subtasks map cleanly to a parent section.

💡 Proposed tweak
-## Extreme Edge Cases
+## 1. Extreme Edge Cases

As per coding guidelines, keep tasks.md sections numbered (e.g., “## 1. …”).

🤖 Prompt for AI Agents
In `@spectr/changes/test-extreme-jsonc/tasks.md` at line 3, Change the unnumbered
section header "## Extreme Edge Cases" to include a numeric prefix so it matches
tasks.md conventions and aligns with its 1.x subtasks (e.g., rename the header
to "## 1. Extreme Edge Cases" or the appropriate parent number used elsewhere);
update the header text in the tasks.md file where "## Extreme Edge Cases"
appears so all child tasks numbered 1.x map cleanly to this parent section.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🤖 Fix all issues with AI agents
In `@AGENTS.md`:
- Around line 143-144: Update the delta header guidance in AGENTS.md so it
includes RENAMED alongside the existing options; specifically change the example
header string “## ADDED|MODIFIED|REMOVED Requirements” to include RENAMED (e.g.,
“## ADDED|MODIFIED|REMOVED|RENAMED Requirements”) and ensure any adjacent
descriptive text or checklist references to allowed delta types also mention
RENAMED so contributors see it in the required formats.
- Around line 249-250: The table row "Create proposals | spectr/changes/ |
Scaffold with proposal.md, tasks.md, delta specs" is missing a trailing pipe;
update that row in AGENTS.md to include a closing pipe character so the row
reads with both leading and trailing pipes (ensuring markdownlint's
leading_and_trailing_pipes rule is satisfied) and re-run linting to confirm no
further table rows lack trailing pipes.

In `@spectr/AGENTS.md`:
- Around line 174-177: Remove the duplicated bullet that reads "**Three-stage
workflow**: Propose → Validate (pre-implementation) → Archive
(post-deployment)"; keep a single instance of this line and delete the other
identical occurrence so the list contains only one "Three-stage workflow"
bullet.

In `@spectr/changes/add-size-based-task-splitting/design.md`:
- Around line 293-339: The Phase 0 section uses normative "MUST" language but
lacks the required scenario blocks; update the doc by either adding one or more
"#### Scenario:" sections that cover WHEN/THEN bullets for key requirements
(e.g., when MarshalTaskToJSONLines/MarshalTasksToJSONLines encounter unencodable
values, when ValidateJSONCSyntax fails, and when
writeTasksJSONC/writeChildTasksJSONC must handle propagated errors) or soften
the normative terms to non‑normative guidance (change MUST to
SHOULD/RECOMMENDED) and still include at least one scenario describing
successful marshalling and one describing validation/error propagation; ensure
scenarios reference the functions MarshalTaskToJSONLines,
MarshalTasksToJSONLines, ValidateJSONCSyntax, writeTasksJSONC, and
writeChildTasksJSONC so readers can map requirements to implementation.

In `@spectr/changes/add-size-based-task-splitting/proposal.md`:
- Around line 185-190: The fenced code blocks in the proposal contain mismatched
open/close markers (e.g., the block that starts with "```textyaml" around the
splitConfig example and other blocks that close with "```text"); update opening
fences to a proper language tag like "```yaml" (or "```text" if intended) and
ensure all closing fences are plain "```". Fix the splitConfig block (symbol:
splitConfig / the "```textyaml" opener) and apply the same correction to the
directory tree and JSONC examples that use "```textyaml" or close with "```text"
so each block has a matching opener/closer with the correct language identifier.
- Around line 82-193: The proposal introduces configurable thresholds but the
design decision requires hardcoded values; remove the config/flag mentions and
enforce fixed thresholds (maxLinesPerFile=100, minLinesPerFile=25) across the
described artifacts: update the prose in proposal.md to delete the
".spectr/config.yaml" and CLI flag sections and replace them with the fixed
constants, and ensure implementation notes reference hardcoded values in
cmd/accept.go (detection logic), cmd/accept_writer.go (splitting logic),
cmd/jsonc.go (validation steps if it depends on thresholds), and
internal/parsers/* types/functions (no config fields like splitConfig or exposed
flags); ensure internal/parsers/types.go does not add config fields and
internal/parsers/parsers.go and accept_writer.go read/use the fixed numeric
constants rather than any config or flag.
♻️ Duplicate comments (10)
spectr/changes/fix-jsonc-validation/proposal.md (1)

1-39: Add the required Impact section (affected specs/code).

This was already flagged in an earlier review; still missing.

spectr/changes/test-problematic-jsonc/tasks.jsonc (1)

102-105: Escape literal control characters in task 1.11 description.

Line 104 appears to contain literal backspace/form‑feed characters, which can break parsers/editors. Please replace with escaped sequences or remove if unintentional, and annotate if this is intentional test data.

🩹 Suggested change
-      "description": "Control chars: ASCII control characters like  (backspace) and  (form feed)",
+      "description": "Control chars: ASCII control characters like \\b (backspace) and \\f (form feed)", // intentional escaped control-char test
spectr/changes/test-problematic-jsonc/tasks.md (2)

3-3: Use numbered section headers for tasks.md.

Line 3 should follow the numbered header format expected by the task parser. As per coding guidelines, ...

📌 Suggested change
-## Edge Case Testing
+## 1. Edge Case Testing

7-8: Indent the continuation line for task 1.3.

Line 8 is not indented, so it won’t be parsed as part of task 1.3. As per coding guidelines, ...

✏️ Suggested change
- - [x] 1.3 Task with newlines: This is line 1
-And this should also be part of the description (this won't parse as single line)
+- [x] 1.3 Task with newlines: This is line 1
+  And this should also be part of the description (this will parse as a continuation line)
spectr/changes/add-size-based-task-splitting/tasks-9.jsonc (1)

1-3: Align filename with parent task and IDs.

tasks-9.jsonc contains parent 8 with task IDs 8.x. This mismatch can break includes/status aggregation. Rename the file to match parent 8, or update parent/IDs (and any references) to 9 so they’re consistent.

Also applies to: 43-80

spectr/changes/add-size-based-task-splitting/specs/cli-interface/spec.md (3)

1-3: Minor: Consider renaming header.

"CLI Interface" is redundant since "I" in CLI stands for Interface. Consider "CLI Changes" or "Command-Line Interface - Size-Based Task Splitting".


48-49: Spec contradicts generated header guidance.

This scenario allows skipping directly from "pending" to "completed" for trivial tasks, but the generated child task headers (e.g., tasks-6.jsonc line 17) state "Do not skip states or move backward."

Either remove this allowance from the spec or update the header generation template to reflect that skipping is permitted for trivial tasks.


70-72: Capitalize "Markdown".

Per static analysis, "markdown" should be "Markdown" (proper noun for the formatting language).

✏️ Suggested fix
-- **WHEN** converting tasks from markdown
+- **WHEN** converting tasks from Markdown
spectr/changes/add-size-based-task-splitting/proposal.md (1)

77-180: Add required #### Scenario: blocks for SHALL requirements.

This proposal defines SHALL requirements but lacks scenario blocks with WHEN/THEN bullets.

spectr/AGENTS.md (1)

61-68: Align status-update guidance and directory tree with tasks.jsonc workflow.

Stage 2 still instructs checking off tasks.md, and the tree omits tasks.jsonc. After spectr accept, status updates should be applied in tasks.jsonc.

Based on learnings, tasks.md is human-readable while status updates live in tasks.jsonc.

Also applies to: 135-151

🧹 Nitpick comments (4)
.conclaude.yaml (1)

27-32: Lint step activation looks good; consider aligning the message tone.

The lint step configuration is correct and follows the established pattern. However, the message on line 31 is noticeably more informal than other messages in this file.

For consistency, consider aligning with the professional tone used elsewhere:

✏️ Suggested message alignment
     - run: "nix develop -c 'lint'"
       showStdout: false
       showStderr: false
       showCommand: true
-      message: "Fix all lint errors without being lazy or ignoring them"
+      message: "Linting failed - please fix lint errors before continuing"
       maxOutputLines: 5
spectr/changes/fix-jsonc-validation/specs/cli-interface/spec.md (1)

1-1: Consider simplifying the title to avoid redundancy.

The title uses "CLI Interface" where "CLI" already stands for "Command Line Interface," making "Interface" redundant. Consider using either "CLI" or "Command Line Interface" alone.

✏️ Suggested revision
-# Delta Spec: CLI Interface - JSONC Validation
+# Delta Spec: CLI - JSONC Validation
internal/parsers/problematic_jsonc_test.go (1)

175-255: Consider extending round-trip tests for version 2 schema.

The round-trip test uses Version: 1 and doesn't exercise the new hierarchical fields (Children, Parent, Includes) added in this PR. Consider adding a test case for version 2 structure to validate the full schema round-trips correctly.

💡 Example additional test case
{
    name: "Version 2 with hierarchical fields",
    tasks: []Task{
        {
            ID:          "1",
            Section:     "Root Task",
            Description: "Parent task with children",
            Status:      "in_progress",
            Children:    "$ref:tasks-1.jsonc",
        },
    },
},

And update the TasksFile creation to test Version 2:

tasksFile := TasksFile{
    Version:  2,
    Tasks:    tt.tasks,
    Parent:   "0",
    Includes: []string{"tasks-*.jsonc"},
}
spectr/changes/add-gemini-agentskills-support/specs/support-gemini/spec.md (1)

3-4: Align delta section header with required format.

For spectr/changes/*/specs/**/*.md, the learned guidance indicates section headers should be ## ADDED, ## MODIFIED, ## REMOVED, ## RENAMED (without “Requirements”). This file currently uses ## ADDED Requirements. Please verify the canonical format in spectr/AGENTS.md and align this header accordingly. Based on learnings, ...

Comment on lines +143 to +144
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with
at least one `#### Scenario:` per requirement.
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Include RENAMED in the delta header guidance.

The quick checklist omits RENAMED, which can mislead contributors about the required delta formats. As per coding guidelines, the delta header list should include RENAMED.

🔧 Suggested fix
-3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with
+3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` with
📝 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.

Suggested change
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED Requirements` with
at least one `#### Scenario:` per requirement.
3. Draft spec deltas using `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` with
at least one `#### Scenario:` per requirement.
🤖 Prompt for AI Agents
In `@AGENTS.md` around lines 143 - 144, Update the delta header guidance in
AGENTS.md so it includes RENAMED alongside the existing options; specifically
change the example header string “## ADDED|MODIFIED|REMOVED Requirements” to
include RENAMED (e.g., “## ADDED|MODIFIED|REMOVED|RENAMED Requirements”) and
ensure any adjacent descriptive text or checklist references to allowed delta
types also mention RENAMED so contributors see it in the required formats.

Comment on lines +249 to +250
| Create proposals | spectr/changes/ | Scaffold with proposal.md, tasks.md,
| | | delta specs |
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix table row to include a trailing pipe.

markdownlint expects leading_and_trailing pipes for table rows.

🔧 Suggested fix
-| Create proposals | spectr/changes/ | Scaffold with proposal.md, tasks.md,
+| Create proposals | spectr/changes/ | Scaffold with proposal.md, tasks.md, |
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)

249-249: Table pipe style
Expected: leading_and_trailing; Actual: leading_only; Missing trailing pipe

(MD055, table-pipe-style)

🤖 Prompt for AI Agents
In `@AGENTS.md` around lines 249 - 250, The table row "Create proposals |
spectr/changes/ | Scaffold with proposal.md, tasks.md, delta specs" is missing a
trailing pipe; update that row in AGENTS.md to include a closing pipe character
so the row reads with both leading and trailing pipes (ensuring markdownlint's
leading_and_trailing_pipes rule is satisfied) and re-run linting to confirm no
further table rows lack trailing pipes.

Comment on lines +174 to +177
- **Three-stage workflow**: Propose → Validate (pre-implementation)
→ Archive (post-deployment)
- **Three-stage workflow**: Propose → Validate (pre-implementation)
→ Archive (post-deployment)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Remove duplicated “Three-stage workflow” bullet.

The bullet appears twice verbatim; keep a single instance.

🤖 Prompt for AI Agents
In `@spectr/AGENTS.md` around lines 174 - 177, Remove the duplicated bullet that
reads "**Three-stage workflow**: Propose → Validate (pre-implementation) →
Archive (post-deployment)"; keep a single instance of this line and delete the
other identical occurrence so the list contains only one "Three-stage workflow"
bullet.

Comment on lines +293 to +339
### Phase 0: JSONC Marshalling (CRITICAL - Do First)

This phase MUST be completed before phases 1-5, as it fixes the underlying issue
that breaks file generation.

**Problem**: `json.MarshalIndent()` produces strict JSON that:

- Double-escapes quotes in field values, breaking JSONC parsing
- Doesn't support trailing commas (JSONC feature)
- Doesn't support inline comments
- Generates files that may be invalid when combined with comment headers

**Solution**: Create a custom JSONC marshaller (`cmd/jsonc.go`) that:

1. **Custom marshalling for Task structs**:
- Manually build JSON object for each task
- Handle quote escaping properly (avoid double-escaping)
- Support optional trailing commas in arrays

2. **Manual formatting approach**:
- Build JSON string piece by piece
- Control indentation and formatting
- Add comments alongside values (e.g., status explanations)

3. **JSONC validation**:
- Parse output with a lenient JSON parser
- Or validate JSONC syntax manually
- Ensure file is readable by agents

4. **Functions to implement**:
- `MarshalTaskToJSONLines(task Task) (string, error)` - convert single task to
JSONC lines, return error if JSON encoding or JSONC formatting fails
- `MarshalTasksToJSONLines(tasks []Task) (string, error)` - convert task list
to JSONC, return error if JSON encoding or JSONC formatting fails
- `ValidateJSONCSyntax(content string) error` - validate JSONC syntax and return
error if invalid

**Error handling requirements**:

- Marshallers MUST return `(string, error)` to follow Go conventions
- Marshallers MUST call `ValidateJSONCSyntax()` on generated content and
propagate validation errors
- Marshallers MUST return errors for JSON encoding failures (e.g., unencodable
field values)
- All call sites (writeTasksJSONC, writeChildTasksJSONC) MUST handle returned
errors
- All tests MUST verify error cases in addition to success cases
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add scenarios (or soften MUSTs) for Phase 0 requirements.

This section uses normative MUST language but has no #### Scenario: blocks with WHEN/THEN bullets. Either add scenarios for these requirements or reword them as non‑normative guidance.

As per coding guidelines, every requirement in spectr/**/*.md needs at least one scenario.

🧰 Tools
🪛 LanguageTool

[style] ~295-~295: Consider using a different verb for a more formal wording.
Context: ...T be completed before phases 1-5, as it fixes the underlying issue that breaks file g...

(FIX_RESOLVE)

🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/design.md` around lines 293 -
339, The Phase 0 section uses normative "MUST" language but lacks the required
scenario blocks; update the doc by either adding one or more "#### Scenario:"
sections that cover WHEN/THEN bullets for key requirements (e.g., when
MarshalTaskToJSONLines/MarshalTasksToJSONLines encounter unencodable values,
when ValidateJSONCSyntax fails, and when writeTasksJSONC/writeChildTasksJSONC
must handle propagated errors) or soften the normative terms to non‑normative
guidance (change MUST to SHOULD/RECOMMENDED) and still include at least one
scenario describing successful marshalling and one describing validation/error
propagation; ensure scenarios reference the functions MarshalTaskToJSONLines,
MarshalTasksToJSONLines, ValidateJSONCSyntax, writeTasksJSONC, and
writeChildTasksJSONC so readers can map requirements to implementation.

Comment on lines +185 to +190
```textyaml
splitConfig:
maxLinesPerFile: 100 # Trigger threshold (default 100)
minLinesPerFile: 25 # Minimum before bundling (default 25)
```text

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix fenced code block open/close mismatches.

Several fences open with language identifiers but close with text (which breaks markdownlint). Close fences with plain and replace textyaml with yaml (or ```text).

✏️ Example fix pattern
-```textyaml
+```yaml
 splitConfig:
   maxLinesPerFile: 100
   minLinesPerFile: 25
-```text
+```

Apply the same closing-fence fix to the directory tree and JSONC examples.

Also applies to: 208-225, 228-250, 253-276

🤖 Prompt for AI Agents
In `@spectr/changes/add-size-based-task-splitting/proposal.md` around lines 185 -
190, The fenced code blocks in the proposal contain mismatched open/close
markers (e.g., the block that starts with "```textyaml" around the splitConfig
example and other blocks that close with "```text"); update opening fences to a
proper language tag like "```yaml" (or "```text" if intended) and ensure all
closing fences are plain "```". Fix the splitConfig block (symbol: splitConfig /
the "```textyaml" opener) and apply the same correction to the directory tree
and JSONC examples that use "```textyaml" or close with "```text" so each block
has a matching opener/closer with the correct language identifier.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

🤖 Fix all issues with AI agents
In `@spectr/changes/add-gemini-agentskills-support/tasks.jsonc`:
- Around line 38-192: The tasks.jsonc contains an extra task with id "5.1"
titled "99.99.99 Final section (after all tasks)" that is out of sync with
tasks.md; fix by either deleting the task object with "id": "5.1" from the tasks
array in tasks.jsonc or add the corresponding entry to tasks.md and then
regenerate tasks.jsonc so the two files match (ensure the task's id, section,
description, and status fields are consistent when adding).

In
`@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/specs/cli-interface/spec.md`:
- Around line 5-96: In the "Status value mapping from Markdown" scenario update
the WHEN bullet to capitalize the proper noun "Markdown" (change "converting
tasks from markdown" to "converting tasks from Markdown") so the spec is
consistent; locate the scenario containing the mapping lines for "- [ ]" ->
"pending" and "- [x]" -> "completed" and edit that WHEN line in spec.md
accordingly.

In
`@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-3.jsonc`:
- Around line 1-68: The file name tasks-3.jsonc does not match its declared
parent ("parent": "2") and task IDs (2.1–2.4); either rename the file to
tasks-2.jsonc to align with the parent and IDs, or update the JSON fields (the
"parent" value and the task "id" prefixes 2.1–2.4) to reflect parent 3—pick one
consistent approach and apply it so filename, "parent" field, and task id
prefixes all match.

In
`@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-4.jsonc`:
- Around line 1-3: The file and its embedded parent/task IDs are inconsistent:
either rename the file from tasks-4.jsonc to tasks-3.jsonc, or update the
embedded parent and task IDs from "3" / "3.x" to "4" / "4.x"; specifically
adjust the top-of-file parent marker (e.g., "Parent change:
add-size-based-task-splitting" with the numeric parent ID) and each task ID
(3.1–3.6) to match the chosen numbering scheme, and apply the same correction
referenced at lines 79-79 so all parent and ID occurrences are consistent.

In `@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks.jsonc`:
- Around line 41-152: The root tasks array has an off-by-one mismatch: entries
use "id" values 0–15 but their "children" refs point to "$ref:tasks-1.jsonc"
through "$ref:tasks-16.jsonc", causing child files' parent fields (e.g.,
tasks-4.jsonc has parent "3", tasks-9.jsonc has parent "8") to not align with
filenames; fix by renumbering either the root "id" values to 1–16 or adjust each
"children" reference to "$ref:tasks-<id>.jsonc" (e.g., for id "0" use
"$ref:tasks-0.jsonc", for id "8" use "$ref:tasks-8.jsonc"), and ensure
corresponding child files' parent fields match the chosen convention (update
parent values in tasks-*.jsonc to the same id namespace).

In
`@spectr/changes/archive/2026-01-19-fix-jsonc-validation/specs/cli-interface/spec.md`:
- Line 1: The title line "# Delta Spec: CLI Interface - JSONC Validation"
contains a tautology; update the header to remove "CLI Interface" by either
using "CLI - JSONC Validation" or "Command-Line Interface - JSONC Validation" in
the top-level header (the string starting with "# Delta Spec: ...") so the spec
title reads without redundant wording; ensure any other occurrences of the exact
phrase "CLI Interface" in the same file (e.g., headings or metadata) are
similarly replaced to keep wording consistent.

In `@spectr/changes/archive/2026-01-19-fix-jsonc-validation/tasks-2.jsonc`:
- Around line 13-18: The header comment "Do not skip states or move backward."
conflicts with the new exception allowing "pending -> completed" for trivial
tasks; update the header block (the Status Transitions comment at top of
tasks-2.jsonc) to explicitly mention the exception so it matches the spec—e.g.,
add a line stating that "pending -> completed" is permitted for trivial tasks
and that all other transitions must follow pending -> in_progress -> completed,
and regenerate the header template if it's produced programmatically.

In `@spectr/changes/archive/2026-01-19-fix-jsonc-validation/tasks.md`:
- Around line 1-117: The tasks.md lacks the required "#### Scenario" blocks for
each checklist item; add a Scenario block (using the mandated "WHEN ... THEN
..." format) for every numbered requirement (e.g., 1.1, 1.2, 2.1–2.6, 3.1–3.6,
4.1–4.3, 5.1–5.3, 6.1–6.4, 7.1–7.4, 8.1–8.4) describing a concrete WHEN
(action/condition) and THEN (expected outcome) for that task; for code-level
items reference the function names to validate (validateJSONCOutput,
validateTasksJSONC, validateWithExternalParser, writeTasksJSONC,
writeRootTasksJSONC, writeChildTasksJSONC, writeAndCleanup) and include a
scenario that exercises the success and failure paths (e.g., WHEN writer calls
validateJSONCOutput with JSONC containing escapes THEN validation passes or
returns detailed error), keeping each scenario one short paragraph under a "####
Scenario" heading adjacent to the corresponding checklist item.
♻️ Duplicate comments (3)
spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-14.jsonc (1)

1-80: Already flagged: filename/parent mismatch.

The mismatch between tasks-14.jsonc filename and parent "13"/IDs 13.x was already identified in a prior review. The structure and content are otherwise valid.

spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-9.jsonc (1)

1-3: File name/parent mismatch (tasks-9.jsonc vs parent 8/IDs 8.x).

The filename tasks-9.jsonc doesn't match the content: parent is "8" and task IDs are 8.1-8.6. Either rename the file to tasks-8.jsonc or update the parent and IDs to use 9/9.x for consistency.

Also applies to: 79-79

spectr/changes/archive/2026-01-19-add-size-based-task-splitting/design.md (1)

293-339: Phase 0 uses normative MUST language without scenarios.

This section contains normative requirements (e.g., "Marshallers MUST return (string, error)") but lacks #### Scenario: blocks with WHEN/THEN bullets. Since this is a design document rather than a formal spec, consider either:

  1. Adding minimal scenarios for the key error handling requirements, or
  2. Softening the language to non-normative guidance (e.g., "Marshallers should return...")

As per coding guidelines, every requirement in spectr/**/*.md needs at least one scenario.

🧹 Nitpick comments (9)
spectr/specs/cli-interface/spec.md (6)

12-14: Replace implementation-specific function names with behavioral description.

The scenario references specific function names (writeTasksJSONC, writeRootTasksJSONC, writeChildTasksJSONC), which are implementation details. Specifications should describe behavior abstractly.

♻️ Suggested refactor
-- WHEN `writeTasksJSONC`, `writeRootTasksJSONC`, or `writeChildTasksJSONC` completes
+- WHEN generating any JSONC task file (flat, root, or child)
 - THEN verify the generated JSONC can be parsed via `StripJSONComments` + `json.Unmarshal`
 - AND return error if parsing fails

34-61: Reconsider whether testing methodology belongs in functional specification.

This requirement describes testing strategy (property-based testing, test cases) rather than system behavior. Specifications typically focus on what the system must do, not how to verify it.

Consider either:

  1. Moving this to a separate testing specification or test plan
  2. Refactoring to focus on the functional requirement: "The system SHALL correctly handle all special characters, unicode, and JSON meta-characters in task descriptions"

87-89: Consider centralizing threshold values for consistency.

The 100-line splitting threshold appears in multiple requirements (lines 88, 103, 179), and the 10-task threshold appears at line 208. Defining these values once at the beginning of the specification would improve maintainability.

💡 Suggested approach

Add a "Constants" or "Configuration Values" section after the Requirements header:

## Configuration Values

- **Task File Splitting Threshold**: 100 lines
- **Minimum Section Size for Root**: 10 tasks

## Requirements

Then reference these named constants in scenarios.

Also applies to: 103-104, 179-180, 208-209


160-161: Remove duplicate status aggregation logic.

Status aggregation for reference tasks is defined in two places:

  • Lines 160-161 within "Tasks JSON Schema" requirement
  • Lines 250-273 as a separate "Task Status Aggregation" requirement

This creates maintenance risk if the logic needs to change. Consider removing the brief mention at lines 160-161 and referencing the dedicated requirement instead.

♻️ Suggested refactor
 - **WHEN** a task references child tasks in a split file
 - **THEN** it SHALL have all standard fields (id, section, description, status)
 - **AND** it SHALL have `children` field with value "$ref:tasks-N.jsonc"
 - **AND** the description SHALL be the section header or summary
-- **AND** the status SHALL reflect aggregate status of children (pending if any
-   pending, in_progress if any in_progress, completed if all completed)
+ **AND** the status SHALL reflect aggregate status of children (see "Task Status Aggregation" requirement)

Also applies to: 250-273


150-150: Minor: Capitalize "Markdown" as a proper noun.

As flagged by static analysis, "Markdown" is a proper noun and should be capitalized.

-- **WHEN** converting tasks from markdown
+- **WHEN** converting tasks from Markdown

5-33: Consider consolidating validation requirements.

Two separate requirements address JSONC validation:

  • "JSONC Output Validation" (lines 5-33): General validation, round-trip, external parsers
  • "JSONC Format Validation" (lines 274-305): JSONC-specific syntax features

While they address different aspects, consolidating them into a single comprehensive "JSONC Validation" requirement might improve clarity and reduce the potential for gaps or inconsistencies.

Also applies to: 274-305

spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-16.jsonc (1)

1-62: File structure is valid but filename/parent mismatch follows same pattern flagged in tasks-14.jsonc.

The JSONC structure is internally consistent (parent "15" matches task IDs 15.x), and all required fields are present per the schema. However, this file continues the naming pattern where tasks-N.jsonc contains children of parent N-1, which differs from the fix-jsonc-validation archive where filenames match parent IDs.

If this off-by-one naming is intentional for sequential file numbering, consider documenting this convention. Otherwise, renaming to tasks-15.jsonc would align filename with content.

Based on learnings, tasks.jsonc files should maintain the version field, tasks array, and proper id/section/description/status fields—all present here.

internal/initialize/providers/gemini.go (1)

10-10: Consider updating comment to include agent skills.

The comment mentions GEMINI.md and TOML slash commands but omits the new .gemini/skills/ directory and agent skills. For completeness:

💡 Suggested comment update
-// Gemini uses GEMINI.md for instructions and .gemini/commands/spectr/ for TOML-based slash commands. //nolint:lll
+// Gemini uses GEMINI.md for instructions, .gemini/commands/spectr/ for TOML-based slash commands, and .gemini/skills/ for agent skills. //nolint:lll
internal/initialize/providers/provider_test.go (1)

48-244: Consider consolidating provider initializer tests into a table-driven form with t.Run().
These provider-specific tests are still highly repetitive; a table-driven layout would reduce duplication and align with the project’s testing convention. As per coding guidelines, please verify whether these tests should be migrated to table-driven subtests.

Comment on lines +38 to +192
"status": "pending"
},
{
"id": "2.4",
"section": "Testing",
"description": "Test GEMINI.md is created with correct content",
"status": "pending"
},
{
"id": "2.5",
"section": "Testing",
"description": "Test existing TOML slash commands still work",
"status": "pending"
},
{
"id": "2.6",
"section": "Testing",
"description": "Test idempotent initialization (run twice, no errors)",
"status": "pending"
},
{
"id": "2.7",
"section": "Testing",
"description": "Run full test suite: `nix develop -c tests`",
"status": "pending"
},
{
"id": "3.1",
"section": "Validation",
"description": "Run `spectr validate add-gemini-agentskills-support`",
"status": "pending"
},
{
"id": "3.2",
"section": "Validation",
"description": "Run linter: `nix develop -c lint`",
"status": "pending"
},
{
"id": "3.3",
"section": "Validation",
"description": "Verify SKILL.md frontmatter matches Agent Skills spec (name,\n description fields)",
"status": "pending"
},
{
"id": "3.4",
"section": "Validation",
"description": "Test in a fresh project directory",
"status": "pending"
},
{
"id": "4.1",
"section": "Success Criteria Verification",
"description": "`spectr init` creates `.gemini/skills/` with both skills",
"status": "pending"
},
{
"id": "4.2",
"section": "Success Criteria Verification",
"description": "`GEMINI.md` file is created in project root",
"status": "pending"
},
{
"id": "4.3",
"section": "Success Criteria Verification",
"description": "Existing TOML commands in `.gemini/commands/spectr/` preserved",
"status": "pending"
},
{
"id": "4.4",
"section": "Success Criteria Verification",
"description": "Skills include SKILL.md with valid frontmatter",
"status": "pending"
},
{
"id": "4.5",
"section": "Success Criteria Verification",
"description": "Scripts are executable (scripts/accept.sh, scripts/validate.sh)",
"status": "pending"
},
{
"id": "4.6",
"section": "Success Criteria Verification",
"description": "All tests pass",
"status": "pending"
},
{
"id": "4.7",
"section": "Success Criteria Verification",
"description": "Validation passes without errors",
"status": "pending"
},
{
"id": "5.1",
"section": "99.99.99 Final section (after all tasks)",
"description": "Output/return: \u003cpromise\u003e\"This is the last task: COMPLETE\"\u003c/promise\u003e\n",
"status": "pending"
}
]
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find spectr/changes/add-gemini-agentskills-support -type f -name "tasks.*"

Repository: connerohnesorge/spectr

Length of output: 178


🏁 Script executed:

cat spectr/changes/add-gemini-agentskills-support/tasks.md

Repository: connerohnesorge/spectr

Length of output: 1847


Remove task 5.1 from tasks.jsonc or add it to tasks.md to restore sync.

The tasks.jsonc contains task 5.1 ("99.99.99 Final section (after all tasks)" with output/return description) that does not exist in tasks.md. Both files must remain synchronized; either remove the extra task from the JSONC or add it to the markdown file and regenerate the JSONC structure.

🤖 Prompt for AI Agents
In `@spectr/changes/add-gemini-agentskills-support/tasks.jsonc` around lines 38 -
192, The tasks.jsonc contains an extra task with id "5.1" titled "99.99.99 Final
section (after all tasks)" that is out of sync with tasks.md; fix by either
deleting the task object with "id": "5.1" from the tasks array in tasks.jsonc or
add the corresponding entry to tasks.md and then regenerate tasks.jsonc so the
two files match (ensure the task's id, section, description, and status fields
are consistent when adding).

Comment on lines +5 to +96
### Requirement: Tasks JSON Schema

The system SHALL generate a versioned `tasks.jsonc` with optional hierarchical
structure for large task files, automatically splitting files over 100 lines
while preserving section boundaries.

#### Scenario: JSONC file structure

- **WHEN** generating tasks.jsonc
- **THEN** the file SHALL start with header comments documenting status values
and transitions
- **AND** the file SHALL contain a root object with `version` and `tasks` fields
- **AND** `version` SHALL be integer 2 for hierarchical files, integer 1 for flat
files
- **AND** `tasks` SHALL be an array of task objects

#### Scenario: Version 2 hierarchical structure

- **WHEN** tasks.md exceeds 100 lines during accept
- **THEN** the system SHALL generate version 2 tasks.jsonc with split files
- **AND** root file SHALL contain reference tasks with `children` field
- **AND** child files SHALL be named `tasks-N.jsonc` where N is sequential
- **AND** root file SHALL include `includes` field with glob pattern `["tasks-*.jsonc"]`

#### Scenario: Version 1 flat structure preserved

- **WHEN** tasks.md is 100 lines or fewer
- **THEN** the system SHALL generate version 1 tasks.jsonc with flat structure
- **AND** no split files SHALL be created

#### Scenario: Header comment content

- **WHEN** generating root tasks.jsonc
- **THEN** the header SHALL use `//` line comment syntax
- **AND** the header SHALL indicate the file is machine-generated by `spectr accept`
- **AND** the header SHALL document the three valid status values: "pending",
"in_progress", "completed"
- **AND** the header SHALL document valid status transitions: pending -> in_progress
-> completed
- **AND** the header SHALL explain that agents should mark a task "in_progress"
when starting work
- **AND** the header SHALL explain that agents should mark a task "completed"
only after verification
- **AND** the header SHALL note that skipping directly from "pending" to
"completed" is allowed for trivial tasks

#### Scenario: Child file header content

- **WHEN** generating child tasks-N.jsonc files
- **THEN** the header SHALL include the full header from root file
- **AND** the header SHALL add "Generated by: spectr accept \<change-id\>"
- **AND** the header SHALL add "Parent change: \<change-id\>"
- **AND** the header SHALL add "Parent task: \<parent-task-id\>"

#### Scenario: Task object structure

- **WHEN** a task is serialized to JSONC
- **THEN** it SHALL have `id` field containing the task identifier (e.g., "1.1")
- **AND** it SHALL have `section` field containing the section header (e.g., "Implementation")
- **AND** it SHALL have `description` field containing the full task text
- **AND** it SHALL have `status` field with value "pending", "in_progress", or "completed"
- **AND** it MAY have `children` field with value "$ref:\<path\>" for reference tasks

#### Scenario: Status value mapping from Markdown

- **WHEN** converting tasks from markdown
- **THEN** map `- [ ]` to status "pending"
- **AND** map `- [x]` to status "completed"

#### Scenario: Reference task structure

- **WHEN** a task references child tasks in a split file
- **THEN** it SHALL have all standard fields (id, section, description, status)
- **AND** it SHALL have `children` field with value "$ref:tasks-N.jsonc"
- **AND** the description SHALL be the section header or summary
- **AND** the status SHALL reflect aggregate status of children (pending if any
pending, in_progress if any in_progress, completed if all completed)

#### Scenario: Child file structure

- **WHEN** generating a child tasks-N.jsonc file
- **THEN** it SHALL have `version` field set to 2
- **AND** it SHALL have `parent` field containing the parent task ID
- **AND** it SHALL have `tasks` array with child task objects
- **AND** child task IDs SHALL use dot notation (e.g., "1.1", "1.2" for parent "1")

#### Scenario: Includes field for discovery

- **WHEN** generating version 2 root tasks.jsonc
- **THEN** it SHALL include `includes` field with value `["tasks-*.jsonc"]`
- **AND** agents MAY use this glob pattern to discover all split files

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Capitalize “Markdown” in the status-mapping scenario.
Use “Markdown” instead of “markdown” in the WHEN bullet for proper noun consistency.

🧰 Tools
🪛 LanguageTool

[uncategorized] ~70-~70: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...kdown - WHEN converting tasks from markdown - THEN map - [ ] to status "pendi...

(MARKDOWN_NNP)

🤖 Prompt for AI Agents
In
`@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/specs/cli-interface/spec.md`
around lines 5 - 96, In the "Status value mapping from Markdown" scenario update
the WHEN bullet to capitalize the proper noun "Markdown" (change "converting
tasks from markdown" to "converting tasks from Markdown") so the spec is
consistent; locate the scenario containing the mapping lines for "- [ ]" ->
"pending" and "- [x]" -> "completed" and edit that WHEN line in spec.md
accordingly.

Comment on lines +1 to +68
// Generated by: spectr accept add-size-based-task-splitting
// Parent change: add-size-based-task-splitting
// Parent task: 2
//
// This file contains child tasks split from the main tasks.jsonc for readability.
// JSONC format allows comments while maintaining JSON compatibility.
//
// Status Values:
// - "pending" : Task has not been started yet
// - "in_progress" : Task is currently being worked on
// - "completed" : Task has been finished and verified
//
// Status Transitions:
// pending -> in_progress -> completed
//
// Tasks should only move forward through these states.
// Do not skip states or move backward.
//
// Workflow:
// 1. BEFORE starting work on a task, mark it as "in_progress"
// 2. Complete the implementation for the task
// 3. Verify the work is correct and complete
// 4. IMMEDIATELY mark the task as "completed" after verification
// 5. Move to the next task and repeat
//
// IMPORTANT - Update Status Immediately:
// - Update each task's status IMMEDIATELY after it transitions
// - Do NOT batch status updates at the end of all work
// - Do NOT wait until all tasks are done to update statuses
// - This file should reflect accurate progress at any point in time
// - Using a single edit to mark a task completed AND the next task
// in_progress is allowed (this is a single transition, not batching)
//
// Note: This file is auto-generated by 'spectr accept'. Manual edits to
// task status are expected, but structure changes may be overwritten.
// To see the parent task or other sections, refer to the root tasks.jsonc file.
//

{
"version": 2,
"tasks": [
{
"id": "2.1",
"section": "Line Counting and Split Detection",
"description": "Add `countLines()` function in `cmd/accept.go` to count lines in\n tasks.md file",
"status": "completed"
},
{
"id": "2.2",
"section": "Line Counting and Split Detection",
"description": "Add `shouldSplit()` function that returns true if line count exceeds 100",
"status": "completed"
},
{
"id": "2.3",
"section": "Line Counting and Split Detection",
"description": "Add unit tests for line counting with various file sizes",
"status": "completed"
},
{
"id": "2.4",
"section": "Line Counting and Split Detection",
"description": "Add unit tests for split detection threshold",
"status": "completed"
}
],
"parent": "2"
} No newline at end of file
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Filename-to-parent mismatch: file is tasks-3.jsonc but parent is "2".

The file is named tasks-3.jsonc but:

  • Line 3 header states "Parent task: 2"
  • Line 67 JSON field shows "parent": "2"
  • Task IDs are 2.1, 2.2, 2.3, 2.4 (children of parent 2)

If the naming convention is tasks-N.jsonc for children of parent task N, this file should be named tasks-2.jsonc. This inconsistency could cause confusion or break tooling that relies on filename-to-parent mapping.

🔧 Suggested fix

Rename the file from tasks-3.jsonc to tasks-2.jsonc to match the parent task ID, or update the parent reference and task IDs if task 3 is the intended parent.

🤖 Prompt for AI Agents
In
`@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-3.jsonc`
around lines 1 - 68, The file name tasks-3.jsonc does not match its declared
parent ("parent": "2") and task IDs (2.1–2.4); either rename the file to
tasks-2.jsonc to align with the parent and IDs, or update the JSON fields (the
"parent" value and the task "id" prefixes 2.1–2.4) to reflect parent 3—pick one
consistent approach and apply it so filename, "parent" field, and task id
prefixes all match.

Comment on lines +1 to +3
// Generated by: spectr accept add-size-based-task-splitting
// Parent change: add-size-based-task-splitting
// Parent task: 3
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

File name/parent mismatch (tasks-4.jsonc vs parent 3/IDs 3.x).

The filename tasks-4.jsonc doesn't match the content: parent is "3" and task IDs are 3.1-3.6. Either rename the file to tasks-3.jsonc or update the parent and IDs to use 4/4.x for consistency.

Also applies to: 79-79

🤖 Prompt for AI Agents
In
`@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks-4.jsonc`
around lines 1 - 3, The file and its embedded parent/task IDs are inconsistent:
either rename the file from tasks-4.jsonc to tasks-3.jsonc, or update the
embedded parent and task IDs from "3" / "3.x" to "4" / "4.x"; specifically
adjust the top-of-file parent marker (e.g., "Parent change:
add-size-based-task-splitting" with the numeric parent ID) and each task ID
(3.1–3.6) to match the chosen numbering scheme, and apply the same correction
referenced at lines 79-79 so all parent and ID occurrences are consistent.

Comment on lines +41 to +152
{
"id": "0",
"section": "JSONC Marshalling Fix (CRITICAL - Do First)",
"description": "JSONC Marshalling Fix (CRITICAL - Do First) tasks",
"status": "pending",
"children": "$ref:tasks-1.jsonc"
},
{
"id": "1",
"section": "Schema Updates",
"description": "Schema Updates tasks",
"status": "completed",
"children": "$ref:tasks-2.jsonc"
},
{
"id": "2",
"section": "Line Counting and Split Detection",
"description": "Line Counting and Split Detection tasks",
"status": "completed",
"children": "$ref:tasks-3.jsonc"
},
{
"id": "3",
"section": "Section Parsing",
"description": "Section Parsing tasks",
"status": "completed",
"children": "$ref:tasks-4.jsonc"
},
{
"id": "4",
"section": "Subsection Splitting Logic",
"description": "Subsection Splitting Logic tasks",
"status": "completed",
"children": "$ref:tasks-5.jsonc"
},
{
"id": "5",
"section": "Hierarchical ID Assignment",
"description": "Hierarchical ID Assignment tasks",
"status": "completed",
"children": "$ref:tasks-6.jsonc"
},
{
"id": "6",
"section": "Status Aggregation",
"description": "Status Aggregation tasks",
"status": "completed",
"children": "$ref:tasks-7.jsonc"
},
{
"id": "7",
"section": "Status Preservation Across Regeneration",
"description": "Status Preservation Across Regeneration tasks",
"status": "completed",
"children": "$ref:tasks-8.jsonc"
},
{
"id": "8",
"section": "Child File Header Generation",
"description": "Child File Header Generation tasks",
"status": "completed",
"children": "$ref:tasks-9.jsonc"
},
{
"id": "9",
"section": "File Writing Functions",
"description": "File Writing Functions tasks",
"status": "completed",
"children": "$ref:tasks-10.jsonc"
},
{
"id": "10",
"section": "Accept Command Integration",
"description": "Accept Command Integration tasks",
"status": "completed",
"children": "$ref:tasks-11.jsonc"
},
{
"id": "11",
"section": "Backwards Compatibility",
"description": "Backwards Compatibility tasks",
"status": "completed",
"children": "$ref:tasks-12.jsonc"
},
{
"id": "12",
"section": "Edge Cases and Error Handling",
"description": "Edge Cases and Error Handling tasks",
"status": "completed",
"children": "$ref:tasks-13.jsonc"
},
{
"id": "13",
"section": "Documentation",
"description": "Documentation tasks",
"status": "completed",
"children": "$ref:tasks-14.jsonc"
},
{
"id": "14",
"section": "Testing and Validation",
"description": "Testing and Validation tasks",
"status": "completed",
"children": "$ref:tasks-15.jsonc"
},
{
"id": "15",
"section": "Cleanup and Archive Old Proposal",
"description": "Cleanup and Archive Old Proposal tasks",
"status": "completed",
"children": "$ref:tasks-16.jsonc"
}
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Systematic off-by-one mismatch between task IDs and child file names.

The root file uses task IDs 0-15, but child references are tasks-1.jsonc through tasks-16.jsonc. This creates confusion:

  • Task ID "0" → $ref:tasks-1.jsonc (child tasks would be 0.x)
  • Task ID "8" → $ref:tasks-9.jsonc (child tasks would be 8.x)

The child files contain parent references that don't match their filenames (e.g., tasks-4.jsonc has parent "3", tasks-9.jsonc has parent "8"). Consider aligning file numbering with parent task IDs for clarity.

🤖 Prompt for AI Agents
In `@spectr/changes/archive/2026-01-19-add-size-based-task-splitting/tasks.jsonc`
around lines 41 - 152, The root tasks array has an off-by-one mismatch: entries
use "id" values 0–15 but their "children" refs point to "$ref:tasks-1.jsonc"
through "$ref:tasks-16.jsonc", causing child files' parent fields (e.g.,
tasks-4.jsonc has parent "3", tasks-9.jsonc has parent "8") to not align with
filenames; fix by renumbering either the root "id" values to 1–16 or adjust each
"children" reference to "$ref:tasks-<id>.jsonc" (e.g., for id "0" use
"$ref:tasks-0.jsonc", for id "8" use "$ref:tasks-8.jsonc"), and ensure
corresponding child files' parent fields match the chosen convention (update
parent values in tasks-*.jsonc to the same id namespace).

@@ -0,0 +1,83 @@
# Delta Spec: CLI Interface - JSONC Validation
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Avoid “CLI Interface” tautology in the title.

Consider shortening to “CLI” or spelling out “Command-Line Interface.”

✏️ Suggested edit
-# Delta Spec: CLI Interface - JSONC Validation
+# Delta Spec: CLI - JSONC Validation
📝 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.

Suggested change
# Delta Spec: CLI Interface - JSONC Validation
# Delta Spec: CLI - JSONC Validation
🧰 Tools
🪛 LanguageTool

[style] ~1-~1: This phrase is redundant (‘I’ stands for ‘Interface’). Use simply “CLIInterface”.
Context: # Delta Spec: CLI Interface - JSONC Validation ## ADDED Requiremen...

(ACRONYM_TAUTOLOGY)

🤖 Prompt for AI Agents
In
`@spectr/changes/archive/2026-01-19-fix-jsonc-validation/specs/cli-interface/spec.md`
at line 1, The title line "# Delta Spec: CLI Interface - JSONC Validation"
contains a tautology; update the header to remove "CLI Interface" by either
using "CLI - JSONC Validation" or "Command-Line Interface - JSONC Validation" in
the top-level header (the string starting with "# Delta Spec: ...") so the spec
title reads without redundant wording; ensure any other occurrences of the exact
phrase "CLI Interface" in the same file (e.g., headings or metadata) are
similarly replaced to keep wording consistent.

Comment on lines 13 to 18
// Status Transitions:
// pending -> in_progress -> completed
//
// Tasks should only move forward through these states.
// Do not skip states or move backward.
//
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Header guidance conflicts with the updated “pending → completed” exception.
The header says “Do not skip states,” but the spec in this PR allows skipping pending → completed for trivial tasks. Update the header template (or regenerate) to include that exception.

✏️ Suggested header update
-//   Do not skip states or move backward.
+//   Do not move backward. Skipping pending -> completed is allowed for trivial tasks.
📝 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.

Suggested change
// Status Transitions:
// pending -> in_progress -> completed
//
// Tasks should only move forward through these states.
// Do not skip states or move backward.
//
// Status Transitions:
// pending -> in_progress -> completed
//
// Tasks should only move forward through these states.
// Do not move backward. Skipping pending -> completed is allowed for trivial tasks.
//
🤖 Prompt for AI Agents
In `@spectr/changes/archive/2026-01-19-fix-jsonc-validation/tasks-2.jsonc` around
lines 13 - 18, The header comment "Do not skip states or move backward."
conflicts with the new exception allowing "pending -> completed" for trivial
tasks; update the header block (the Status Transitions comment at top of
tasks-2.jsonc) to explicitly mention the exception so it matches the spec—e.g.,
add a line stating that "pending -> completed" is permitted for trivial tasks
and that all other transitions must follow pending -> in_progress -> completed,
and regenerate the header template if it's produced programmatically.

Comment on lines +1 to +117
# Tasks: Fix JSONC File Validation

## 1. Foundation

- [ ] **1.1** Research and select Go JSONC parser library (check go.mod for existing
options, research `tidwall/gjson` or similar)
- [ ] **1.2** Add JSONC parser dependency to go.mod if needed (via `go get`)
- [ ] **1.3** Create `cmd/accept_validator.go` with package documentation and imports

## 2. Core Validation Functions

- [ ] **2.1** Implement `validateJSONCOutput(jsonData []byte) error` function that:
- Strips JSONC comments using existing `parsers.StripJSONComments`
- Attempts to unmarshal into `map[string]interface{}`
- Returns detailed error if parsing fails
- [ ] **2.2** Implement `validateTasksJSONC(tasksFile TasksFile, jsonData []byte)`
for round-trip validation:
- Parse JSONC back into TasksFile struct
- Compare all fields deeply (ID, Section, Description, Status, Children for each
task)
- Return error with details if any field differs
- [ ] **2.3** Implement `validateWithExternalParser(jsonData []byte)` if external
library selected:
- Use external JSONC parser to validate format
- Return detailed error on failure
- [ ] **2.4** Add validation calls to `writeTasksJSONC()` in `cmd/accept_writer.go`:
- Call `validateJSONCOutput()` before writing file
- Call `validateTasksJSONC()` for round-trip check
- Return error if validation fails (don't write file)
- [ ] **2.5** Add validation calls to `writeRootTasksJSONC()` in `cmd/accept_writer.go`
- [ ] **2.6** Add validation calls to `writeChildTasksJSONC()` in `cmd/accept_writer.go`

## 3. Property-Based Testing

- [ ] **3.1** Create `cmd/accept_validator_test.go` with test infrastructure
- [ ] **3.2** Implement `TestJSONCValidation_SpecialCharacters` with test cases:
- Backslash `\`
- Quote `"`
- Newline `\n`
- Tab `\t`
- Carriage return `\r`
- Backspace `\b`
- Form feed `\f`
- [ ] **3.3** Implement `TestJSONCValidation_Unicode` with test cases for:
- Emoji (🚀, 💻, ✅)
- Non-ASCII characters (你好, مرحبا, Здравствуй)
- Mixed unicode and ASCII
- [ ] **3.4** Implement `TestJSONCValidation_JSONMetaCharacters` with test cases:
- Curly braces in descriptions: `{`, `}`
- Square brackets: `[`, `]`
- Colons and commas: `:`, `,`
- [ ] **3.5** Implement `TestJSONCValidation_CommentLikeStrings` with test cases:
- Single-line comment syntax: `//`
- Multi-line comment syntax: `/* */`
- Mixed: `Update // comment-like text`
- [ ] **3.6** Implement `TestJSONCValidation_EdgeCases` with test cases for:
- Very long descriptions (>1000 chars)
- Empty descriptions
- Whitespace-only descriptions
- Descriptions ending with backslash

## 4. Round-Trip Testing

- [ ] **4.1** Implement `TestRoundTripConversion_AllFields` that:
- Creates TasksFile with all field types populated
- Marshals to JSONC
- Validates JSONC
- Unmarshals back to TasksFile
- Compares original and result deeply
- [ ] **4.2** Implement `TestRoundTripConversion_Version2Hierarchical` for
hierarchical format:
- Test root file with Children references
- Test child file with Parent field
- Verify Includes array preserved
- [ ] **4.3** Implement `TestRoundTripConversion_RealWorldData` using archived
tasks.jsonc:
- Read existing tasks.jsonc from `spectr/changes/archive/*/tasks.jsonc`
- Validate they pass round-trip test
- Report any files that fail

## 5. Integration with Existing Code

- [ ] **5.1** Update `writeAndCleanup()` in `cmd/accept.go` to handle validation
errors gracefully
- [ ] **5.2** Add helpful error messages when validation fails:
- Show task ID and description excerpt
- Suggest common fixes (escape backslashes, etc.)
- Include character position if available
- [ ] **5.3** Ensure dry-run mode (`--dry-run`) also validates JSONC before printing

## 6. Comprehensive Testing

- [ ] **6.1** Run all new tests: `go test -v ./cmd -run Validation`
- [ ] **6.2** Run full test suite: `go test ./...`
- [ ] **6.3** Test with real tasks.md files:
- Create test tasks.md with special characters
- Run `spectr accept` and verify JSONC is valid
- Edit tasks.jsonc manually with invalid escape sequences and verify
error detection
- [ ] **6.4** Run linting: `nix develop -c lint` or `golangci-lint run`

## 7. Documentation and Validation

- [ ] **7.1** Add godoc comments to all new validation functions
- [ ] **7.2** Update CLAUDE.md if new testing patterns introduced
- [ ] **7.3** Run `spectr validate fix-jsonc-validation --strict` to validate this
change
- [ ] **7.4** Verify all scenarios in spec delta are covered by tests (cross-reference)

## 8. Final Verification

- [ ] **8.1** Test accept command with archived changes: `spectr accept <archived-change-id>`
(should work with existing files)
- [ ] **8.2** Create a tasks.md with intentionally problematic descriptions and verify
errors are caught
- [ ] **8.3** Verify performance impact is minimal (validation should be fast)
- [ ] **8.4** Run `nix build` to ensure build succeeds
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Add required #### Scenario blocks with WHEN/THEN for each requirement.

This file is under spectr/**/*.md, so every requirement must include at least one scenario in the mandated format. Currently none are present. Please add scenarios for each section/task so it passes validation. As per coding guidelines, ...

📌 Example format
+#### Scenario: Validate JSONC output before writing
+- WHEN `writeTasksJSONC()` emits JSONC for a tasks file
+- THEN `validateJSONCOutput()` rejects invalid JSONC and blocks the write
🤖 Prompt for AI Agents
In `@spectr/changes/archive/2026-01-19-fix-jsonc-validation/tasks.md` around lines
1 - 117, The tasks.md lacks the required "#### Scenario" blocks for each
checklist item; add a Scenario block (using the mandated "WHEN ... THEN ..."
format) for every numbered requirement (e.g., 1.1, 1.2, 2.1–2.6, 3.1–3.6,
4.1–4.3, 5.1–5.3, 6.1–6.4, 7.1–7.4, 8.1–8.4) describing a concrete WHEN
(action/condition) and THEN (expected outcome) for that task; for code-level
items reference the function names to validate (validateJSONCOutput,
validateTasksJSONC, validateWithExternalParser, writeTasksJSONC,
writeRootTasksJSONC, writeChildTasksJSONC, writeAndCleanup) and include a
scenario that exercises the success and failure paths (e.g., WHEN writer calls
validateJSONCOutput with JSONC containing escapes THEN validation passes or
returns detailed error), keeping each scenario one short paragraph under a "####
Scenario" heading adjacent to the corresponding checklist item.

Resolved conflicts by:
- Keeping main branch header format (description) for .opencode/commands/spectr files
- Keeping HEAD version with more initializers for Gemini provider (6 vs 2)
- Removing hierarchical functionality that wasn't fully implemented
- Updating test expectations to match chosen versions
- Updated Gemini provider test to expect 6 initializers (HEAD version)
- Fixed accept_fuzz_test.go function call
- Removed hierarchical accept_writer_test.go that caused compilation errors
- Updated test expectations for 6 initializers with correct order
- Fixed ConfigFileInitializer field access (path vs filename)
- Verified all provider tests pass
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
internal/initialize/providers/provider_test.go (1)

742-749: Gemini test case not updated—this is causing CI failures.

The table entry for gemini still has the old values (expectedCount: 2, hasConfigFile: false), but TestGeminiProvider_Initializers above was updated to expect 6 initializers including a ConfigFileInitializer. This inconsistency is the direct cause of the pipeline failures.

🐛 Fix the Gemini test case
 		{
 			"gemini",
 			&GeminiProvider{},
-			2,
-			false,
+			6,
+			true,
 			true,
 			false,
 			false,
 		},
🤖 Fix all issues with AI agents
In `@cmd/accept_fuzz_test.go`:
- Around line 306-309: The parser parseTasksMd currently only reads single lines
and drops indented continuation/detail lines; update parseTasksMd to accumulate
indented or continuation lines (lines starting with whitespace or list markers)
and append them to the last parsed task's description (preserve line breaks and
nesting as required by tests), or alternatively change the test expectations to
only expect single-line task descriptions; also replace direct filesystem calls
(os.Open, os.WriteFile) in the test and parsing helpers with the project Afero
filesystem (e.g., use afero.Fs passed into parseTasksMd or a test fixture) so
file IO uses the Afero abstraction as per guidelines.
🧹 Nitpick comments (4)
internal/initialize/providers/provider_test.go (1)

202-277: Missing assertions for AgentSkillsInitializer at indices 4 and 5.

The test expects 6 initializers (including two AgentSkillsInitializer entries per the comment on line 202), but only validates indices 0-3. The Claude and Codex provider tests include explicit assertions for their AgentSkillsInitializer instances—Gemini should follow the same pattern for completeness.

♻️ Add AgentSkills assertions after line 277
 	if len(slashInit.commands) != 2 {
 		t.Errorf(
 			"GeminiProvider TOMLSlashCommandsInitializer has %d commands, want 2",
 			len(slashInit.commands),
 		)
 	}
+
+	// Check AgentSkillsInitializer instances
+	if _, ok := inits[4].(*AgentSkillsInitializer); !ok {
+		t.Errorf(
+			"GeminiProvider.Initializers()[4] is %T, want *AgentSkillsInitializer",
+			inits[4],
+		)
+	}
+	if _, ok := inits[5].(*AgentSkillsInitializer); !ok {
+		t.Errorf(
+			"GeminiProvider.Initializers()[5] is %T, want *AgentSkillsInitializer",
+			inits[5],
+		)
+	}
+
+	acceptSkill := inits[4].(*AgentSkillsInitializer)
+	if acceptSkill.skillName != "spectr-accept-wo-spectr-bin" {
+		t.Errorf(
+			"GeminiProvider AgentSkillsInitializer[4] skillName = %s, want \"spectr-accept-wo-spectr-bin\"",
+			acceptSkill.skillName,
+		)
+	}
+	if acceptSkill.targetDir != ".gemini/skills/spectr-accept-wo-spectr-bin" {
+		t.Errorf(
+			"GeminiProvider AgentSkillsInitializer[4] targetDir = %s, want \".gemini/skills/spectr-accept-wo-spectr-bin\"",
+			acceptSkill.targetDir,
+		)
+	}
+
+	validateSkill := inits[5].(*AgentSkillsInitializer)
+	if validateSkill.skillName != "spectr-validate-wo-spectr-bin" {
+		t.Errorf(
+			"GeminiProvider AgentSkillsInitializer[5] skillName = %s, want \"spectr-validate-wo-spectr-bin\"",
+			validateSkill.skillName,
+		)
+	}
+	if validateSkill.targetDir != ".gemini/skills/spectr-validate-wo-spectr-bin" {
+		t.Errorf(
+			"GeminiProvider AgentSkillsInitializer[5] targetDir = %s, want \".gemini/skills/spectr-validate-wo-spectr-bin\"",
+			validateSkill.targetDir,
+		)
+	}
 }
cmd/accept_fuzz_test.go (3)

299-304: Use Afero for test file I/O to align with repo FS abstraction.

These tests use os.WriteFile/os.ReadFile directly. The repo guideline requires Afero for filesystem abstraction; even in tests, wrapping OS operations through Afero keeps things consistent and easier to mock.

♻️ Example adjustment (apply similarly to other I/O blocks)
 import (
 	"os"
 	"path/filepath"
 	"strings"
 	"testing"

 	"github.com/connerohnesorge/spectr/internal/parsers"
+	"github.com/spf13/afero"
 )
 
 ...
-			if err := os.WriteFile(tasksMdPath, []byte(tt.markdown), 0o644); err != nil {
+			fs := afero.NewOsFs()
+			if err := afero.WriteFile(fs, tasksMdPath, []byte(tt.markdown), 0o644); err != nil {
 				t.Fatalf("failed to write test file: %v", err)
 			}

As per coding guidelines, please use Afero for filesystem operations.

Also applies to: 525-527, 641-668, 712-714


620-672: Convert this test to table-driven style with t.Run().

This single-case test doesn’t follow the repo’s table-driven test convention for *_test.go files. Even a single-case table keeps the structure consistent and makes later expansion easier.

♻️ Example structure
-func TestParseTasksMdContinuationIntegrity(t *testing.T) {
-	markdown := `## 1. Property-Based Testing
+func TestParseTasksMdContinuationIntegrity(t *testing.T) {
+	tests := []struct {
+		name     string
+		markdown string
+	}{
+		{
+			name: "continuation integrity",
+			markdown: `## 1. Property-Based Testing
 ...
-`
-
-	tmpDir := t.TempDir()
-	tasksMdPath := filepath.Join(tmpDir, "tasks.md")
-
-	if err := os.WriteFile(tasksMdPath, []byte(markdown), 0o644); err != nil {
-		t.Fatalf("failed to write test file: %v", err)
-	}
-
-	got, err := parseTasksMd(tasksMdPath)
-	if err != nil {
-		t.Fatalf("parseTasksMd() error = %v", err)
-	}
-    ...
+`,
+		},
+	}
+
+	for _, tt := range tests {
+		t.Run(tt.name, func(t *testing.T) {
+			tmpDir := t.TempDir()
+			tasksMdPath := filepath.Join(tmpDir, "tasks.md")
+			if err := os.WriteFile(tasksMdPath, []byte(tt.markdown), 0o644); err != nil {
+				t.Fatalf("failed to write test file: %v", err)
+			}
+			got, err := parseTasksMd(tasksMdPath)
+			if err != nil {
+				t.Fatalf("parseTasksMd() error = %v", err)
+			}
+			...
+		})
+	}
 }

As per coding guidelines, please use table-driven tests with t.Run().


13-26: Guard against slice panics after failed length checks.

Several validators call t.Errorf when the task count is wrong, then still index into tasks[0], which can panic and hide the real failure. Consider t.Fatalf or early returns once len checks fail.

🧯 Example guard
 	if len(tasks) != 2 {
-		t.Errorf("Expected 2 tasks, got %d", len(tasks))
+		t.Fatalf("Expected 2 tasks, got %d", len(tasks))
 	}

Also applies to: 29-39, 42-53, 72-90, 92-103, 105-117

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
cmd/accept.go (1)

423-444: Indented task lines are treated as continuation lines, not separate tasks.

MatchFlexibleTask requires lines to start exactly with "- [", so indented task items like " - [ ] Task" will not match and will be appended to the previous task's description instead. The fuzz tests confirm this is intentional—indented lines are designed to be continuations.

However, continuation lines preserve their leading whitespace when appended (line 438: continuingTask.Description += "\n" + line). If indented content should not appear in JSONC descriptions, strip the indentation:

if continuingTask != nil && isContinuationLine(line) {
	continuingTask.Description += "\n" + strings.TrimLeft(line, "\t ")
	continue
}

If indented task items should create separate tasks, add explicit trimming before matching.

spectr/changes/add-frontmatter-override-system/proposal.md (1)

16-24: Add explicit impact section (affected specs + code).
The proposal needs a section listing impacted specs/capabilities and key code areas to satisfy repo requirements.

📝 Suggested section (fill in specifics)
+## Impact
+
+- **Affected specs**: (list capability/spec names)
+- **Affected code**: (list key packages/files)
Based on learnings, please include the affected specs and affected code list.
spectr/changes/add-frontmatter-override-system/tasks.md (1)

3-16: Tasks list needs numbered sections and subtasks.
This file should use numbered section headers and numbered checklist items (e.g., ## 1. Implementation, [ ] 1.1 ...) instead of unnumbered phase bullets.

As per coding guidelines, please restructure the tasks accordingly.

🤖 Fix all issues with AI agents
In `@spectr/changes/add-frontmatter-override-system/design.md`:
- Around line 20-21: The doc currently states a TOML support goal ("TOML
support") but later says the scope is "Markdown only"; update the design to
reconcile this by either removing or revising the TOML bullet or changing the
later scope note to reflect multi-format support. Specifically, edit the "TOML
support" line or the later "Markdown only" decision so they consistently state
the supported formats (e.g., "Markdown and TOML" if you intend to support both,
or remove/replace the TOML mention if final scope is Markdown-only), and ensure
the headings/summary and any examples (e.g., "TOML support", "Markdown only")
use the same terminology across the document.
- Around line 111-117: The design currently omits ParseFrontmatter while the
spec requires it; update the design to either (A) reintroduce a clear
ParseFrontmatter description and signature (including expected inputs, outputs,
error behavior and where it is invoked in the pipeline) or (B) explicitly state
that the spec will be changed and remove the requirement from the normative
spec; locate references to ParseFrontmatter in the design text and add the
chosen authoritative behavior (e.g., function signature, parsing rules, returned
data structure, and how it integrates with template assembly) so the design and
spec are aligned.

In
`@spectr/changes/add-frontmatter-override-system/specs/provider-system/spec.md`:
- Around line 51-52: The scenario text should use the proper noun "Markdown"
capitalized; update the spec line that reads "ParseFrontmatter(content string)
is called with markdown containing YAML frontmatter" to capitalize "Markdown" so
it reads "ParseFrontmatter(content string) is called with Markdown containing
YAML frontmatter", referencing the ParseFrontmatter function to locate the
scenario.

In `@spectr/changes/add-frontmatter-override-system/tasks.md`:
- Around line 25-29: The function signature for RenderFrontmatter is incorrect:
change the first parameter from string to a serializable type (e.g.,
map[string]interface{} or interface{}) so it can be marshaled to YAML; in
RenderFrontmatter, use gopkg.in/yaml.v3 to yaml.Marshal the fm value, check and
return any error, build the result by joining the YAML between `---` delimiters
and appending the body string, and return the combined string and nil error on
success (reference: RenderFrontmatter).
🧹 Nitpick comments (2)
cmd/accept.go (1)

436-439: Trim indentation when appending continuation lines.

Appending the raw line preserves indentation in the JSONC description, which can introduce leading-space noise. Consider trimming the indentation before appending (or dedenting by one level).

♻️ Suggested tweak
-		if continuingTask != nil && isContinuationLine(line) {
-			continuingTask.Description += "\n" + line
+		if continuingTask != nil && isContinuationLine(line) {
+			trimmed := strings.TrimLeft(line, "\t ")
+			continuingTask.Description += "\n" + trimmed
 
 			continue
 		}
internal/initialize/providers/provider_test.go (1)

217-277: Assert the new AgentSkills initializers in Gemini tests.
Len=6 is enforced, but indices 4–5 aren’t type-checked; a regression could still pass.

🔧 Suggested assertions
 if _, ok := inits[3].(*TOMLSlashCommandsInitializer); !ok {
 	t.Errorf(
 		"GeminiProvider.Initializers()[3] is %T, want *TOMLSlashCommandsInitializer",
 		inits[3],
 	)
 }
+if _, ok := inits[4].(*AgentSkillsInitializer); !ok {
+	t.Errorf(
+		"GeminiProvider.Initializers()[4] is %T, want *AgentSkillsInitializer",
+		inits[4],
+	)
+}
+if _, ok := inits[5].(*AgentSkillsInitializer); !ok {
+	t.Errorf(
+		"GeminiProvider.Initializers()[5] is %T, want *AgentSkillsInitializer",
+		inits[5],
+	)
+}

Comment on lines +20 to +21
5. **TOML support**: Works for both Markdown (Claude, Windsurf) and
TOML (Gemini) formats
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

TOML support goal conflicts with later “Markdown only” decision.
Please reconcile the stated goal with the final scope to avoid mixed guidance.

🤖 Prompt for AI Agents
In `@spectr/changes/add-frontmatter-override-system/design.md` around lines 20 -
21, The doc currently states a TOML support goal ("TOML support") but later says
the scope is "Markdown only"; update the design to reconcile this by either
removing or revising the TOML bullet or changing the later scope note to reflect
multi-format support. Specifically, edit the "TOML support" line or the later
"Markdown only" decision so they consistently state the supported formats (e.g.,
"Markdown and TOML" if you intend to support both, or remove/replace the TOML
mention if final scope is Markdown-only), and ensure the headings/summary and
any examples (e.g., "TOML support", "Markdown only") use the same terminology
across the document.

Comment on lines +111 to 117
**Note**: This function is no longer needed in the current design since
templates don't contain frontmatter. Frontmatter is defined in Go code and
assembled dynamically.

If needed for other purposes (e.g., reading existing files), the signature would be:
If needed for other purposes (e.g., reading existing files), the signature
would be:

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Design contradicts spec requirement for ParseFrontmatter.
This section says ParseFrontmatter isn’t needed, but the spec introduces it as a requirement. Align the design with the normative spec (or update the spec) to prevent a missed implementation.

🤖 Prompt for AI Agents
In `@spectr/changes/add-frontmatter-override-system/design.md` around lines 111 -
117, The design currently omits ParseFrontmatter while the spec requires it;
update the design to either (A) reintroduce a clear ParseFrontmatter description
and signature (including expected inputs, outputs, error behavior and where it
is invoked in the pipeline) or (B) explicitly state that the spec will be
changed and remove the requirement from the normative spec; locate references to
ParseFrontmatter in the design text and add the chosen authoritative behavior
(e.g., function signature, parsing rules, returned data structure, and how it
integrates with template assembly) so the design and spec are aligned.

Comment on lines +51 to +52
- **WHEN** `ParseFrontmatter(content string)` is called with markdown containing
YAML frontmatter
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Capitalize “Markdown” in scenario text.
Minor docs polish for the proper noun.

✏️ Suggested edit
-- **WHEN** `ParseFrontmatter(content string)` is called with markdown containing
+- **WHEN** `ParseFrontmatter(content string)` is called with Markdown containing
📝 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.

Suggested change
- **WHEN** `ParseFrontmatter(content string)` is called with markdown containing
YAML frontmatter
- **WHEN** `ParseFrontmatter(content string)` is called with Markdown containing
YAML frontmatter
🧰 Tools
🪛 LanguageTool

[uncategorized] ~51-~51: Did you mean the formatting language “Markdown” (= proper noun)?
Context: ...tmatter(content string)` is called with markdown containing YAML frontmatter - *THEN...

(MARKDOWN_NNP)

🤖 Prompt for AI Agents
In `@spectr/changes/add-frontmatter-override-system/specs/provider-system/spec.md`
around lines 51 - 52, The scenario text should use the proper noun "Markdown"
capitalized; update the spec line that reads "ParseFrontmatter(content string)
is called with markdown containing YAML frontmatter" to capitalize "Markdown" so
it reads "ParseFrontmatter(content string) is called with Markdown containing
YAML frontmatter", referencing the ParseFrontmatter function to locate the
scenario.

Comment on lines +25 to 29
- [ ] Implement `RenderFrontmatter` using `gopkg.in/yaml.v3`
- Signature: `RenderFrontmatter(fm, body string) (string, error)`
- Marshal fm to YAML
- Wrap in `---` delimiters
- Append body content
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Clarify RenderFrontmatter signature type.
The signature suggests fm is a string, but the steps call for marshaling fm to YAML. This likely needs map[string]interface{}.

✏️ Suggested edit
-- [ ] Implement `RenderFrontmatter` using `gopkg.in/yaml.v3`
-  - Signature: `RenderFrontmatter(fm, body string) (string, error)`
+- [ ] Implement `RenderFrontmatter` using `gopkg.in/yaml.v3`
+  - Signature: `RenderFrontmatter(fm map[string]interface{}, body string) (string, error)`
🤖 Prompt for AI Agents
In `@spectr/changes/add-frontmatter-override-system/tasks.md` around lines 25 -
29, The function signature for RenderFrontmatter is incorrect: change the first
parameter from string to a serializable type (e.g., map[string]interface{} or
interface{}) so it can be marshaled to YAML; in RenderFrontmatter, use
gopkg.in/yaml.v3 to yaml.Marshal the fm value, check and return any error, build
the result by joining the YAML between `---` delimiters and appending the body
string, and return the combined string and nil error on success (reference:
RenderFrontmatter).

connerohnesorge and others added 6 commits January 20, 2026 05:39
- Add github.com/tidwall/jsonc dependency to handle JSONC files with trailing commas
- Update ReadTasksJson to use jsonc.ToJSON() instead of StripJSONComments
- Update vendor directory to include new dependency
- Update go.mod, go.sum, and flake.nix to reflect changes

The previous approach (StripJSONComments + json.Unmarshal) left trailing commas
which caused parse errors. Using tidwall/jsonc properly handles both comments AND
trailing commas in a single pass.

All tests pass (2730 tests), and there are no regressions.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Implements automated workflow orchestration for change proposals with:
- New 'spectr ralph <change-id>' command with interactive TUI
- Ralpher interface for provider CLI invocation configuration
- Task graph building with dependency resolution
- PTY-based live output streaming in Bubble Tea TUI
- Session persistence for resume/recovery support
- Auto-retry mechanism for transient task failures

The orchestrator automates the manual process of reading tasks,
copying context, invoking AI CLI tools, and tracking completion.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
connerohnesorge and others added 4 commits January 21, 2026 09:41
Modify accept command to support ralph orchestration and update provider
interfaces for task execution context. Includes improvements to accept
workflow validation and error handling.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Add creack/pty dependency for PTY handling in task orchestration.
Update transitive dependencies to support new features.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Update agent documentation to reference ralph orchestration capabilities.
Archive completed jsonc validation work. Update tasks.md for ralph change proposal.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Update vendor directory to include transitive dependencies for ralph
orchestration. Includes updates to charmbracelet and other related packages.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
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.

2 participants