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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 6 additions & 7 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,21 +136,20 @@ Matches or exceeds industry standards of kubectl, terraform, and gh CLI:
- Conflict detection and resolution workflow
- TestPullService_ConflictResolvedWithForce test
- **Milestone 1**: Canonical markdown schema and tooling
- Parser rejection of legacy # STORY: format
- ticketr migrate command with dry-run and --write modes
- 11 new tests (3 parser + 7 migration + 1 service)
- docs/migration-guide.md (175 lines)
- Parser rejection of `# STORY:` heading with actionable error
- Manual update guidance documented in README/WORKFLOW
- 4 new tests (parser rejection cases + service guardrail)
- examples/README.md (155 lines)
- **Milestone 0**: Repository recon and standards alignment
- Canonical # TICKET: format established
- Legacy # STORY: format deprecated with clear errors
- testdata/legacy_story/ fixtures for regression tests
- `# STORY:` heading rejected with clear errors
- testdata/unsupported_story/ fixtures for regression tests
- docs/README.md documenting test strategy

### Changed
- Renamed from jira-story-creator to ticketr
- All examples migrated to canonical # TICKET: format
- Enhanced README with migration guidance
- Enhanced README with schema guidance

### Fixed
- Spurious change detection from non-deterministic map iteration
Expand Down
21 changes: 10 additions & 11 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,9 @@ internal/
│ ├── jira/ # JIRA API integration
│ └── filesystem/ # Markdown file I/O
├── parser/ # Markdown parser
├── renderer/ # Markdown rendering utilities
├── state/ # State management (.ticketr.state)
├── logging/ # File logging
└── migration/ # Legacy format migration
└── logging/ # File logging
```

**Key principles:**
Expand Down Expand Up @@ -182,15 +182,14 @@ bash tests/smoke/smoke_test.sh
```

**Scenarios tested:**
1. Legacy file migration
2. Push dry-run validation
3. Pull with missing file
4. State file creation and persistence
5. Log file creation
6. Help command functionality
7. Concurrent file operations safety
1. Push dry-run validation
2. Pull with missing file
3. State file creation and persistence
4. Log file creation
5. Help command functionality
6. Concurrent file operations safety

**Expected:** 7/7 scenarios passing, 13/13 checks passing
**Expected:** 6/6 scenarios passing, 12/12 checks passing

See [`tests/smoke/README.md`](tests/smoke/README.md) for detailed smoke test documentation.

Expand Down Expand Up @@ -318,7 +317,7 @@ All documentation must follow the [Documentation Style Guide](docs/style-guide.m
- Add TODO comments for future work

**Markdown Formatting:**
- Use kebab-case file names (`migration-guide.md`)
- Use kebab-case file names (e.g., `state-management.md`)
- Always specify language hints in code blocks (```bash, ```go, etc.)
- Use relative links for internal documentation
- Follow heading hierarchy (single H1, proper H2/H3/H4 nesting)
Expand Down
5 changes: 1 addition & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Manage JIRA tickets using Markdown files with bidirectional sync. Version contro
[![License](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
[![Docker](https://img.shields.io/badge/Docker-Ready-2496ED?style=flat&logo=docker)](Dockerfile)

> **v2.0 Breaking Change:** Legacy `# STORY:` format deprecated. Use `ticketr migrate` to upgrade. See [Migration Guide](docs/migration-guide.md).
> **Note:** Ticketr requires `# TICKET:` headings. Files using `# STORY:` must be updated before use.

## Features

Expand Down Expand Up @@ -100,8 +100,6 @@ ticketr push tickets.md --force-partial-upload
# Discover JIRA schema/fields
ticketr schema > .ticketr.yaml

# Migrate legacy format
ticketr migrate old-tickets.md --write
```

## Key Concepts
Expand Down Expand Up @@ -218,7 +216,6 @@ See [examples/](examples/) directory for:
- [WORKFLOW.md](docs/WORKFLOW.md) - End-to-end usage guide
- [ARCHITECTURE.md](docs/ARCHITECTURE.md) - Technical architecture
- [TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md) - Common issues
- [migration-guide.md](docs/migration-guide.md) - v1.x → v2.0 migration
- [state-management.md](docs/state-management.md) - Change detection
- [release-process.md](docs/release-process.md) - Release management
- [CONTRIBUTING.md](CONTRIBUTING.md) - Contribution guide
Expand Down
1 change: 0 additions & 1 deletion SUPPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ Before seeking support, please check our comprehensive documentation:
- **[Quick Start Guide](https://github.com/karolswdev/ticktr/wiki/Quick-Start-Tutorial)** - Get started in 5 minutes
- **[Documentation](docs/)** - Complete documentation suite
- [WORKFLOW.md](docs/WORKFLOW.md) - End-to-end workflows and examples
- [Migration Guide](docs/migration-guide.md) - Migrating from legacy format
- [State Management](docs/state-management.md) - Understanding .ticketr.state
- [Release Process](docs/release-process.md) - Version and release information
- **[Examples](examples/)** - Ready-to-use templates and examples
Expand Down
121 changes: 1 addition & 120 deletions cmd/ticketr/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,13 @@ import (
"fmt"
"log"
"os"
"path/filepath"
"strings"

"github.com/karolswdev/ticktr/internal/adapters/filesystem"
"github.com/karolswdev/ticktr/internal/adapters/jira"
"github.com/karolswdev/ticktr/internal/core/services"
"github.com/karolswdev/ticktr/internal/core/validation"
"github.com/karolswdev/ticktr/internal/logging"
"github.com/karolswdev/ticktr/internal/migration"
"github.com/karolswdev/ticktr/internal/state"
"github.com/spf13/cobra"
"github.com/spf13/viper"
Expand All @@ -32,9 +30,6 @@ var (
pullOutput string
pullForce bool

// Migrate command flags
writeFlag bool

rootCmd = &cobra.Command{
Use: "ticketr",
Short: "A tool for managing JIRA tickets as code",
Expand Down Expand Up @@ -67,22 +62,6 @@ to overwrite local changes with remote changes when conflicts occur.`,
Run: runSchema,
}

migrateCmd = &cobra.Command{
Use: "migrate [file]",
Short: "Convert legacy # STORY: format to # TICKET:",
Long: `Migrates Markdown files from deprecated # STORY: schema to canonical # TICKET: schema.

By default, runs in dry-run mode showing preview of changes.
Use --write flag to apply changes to files.

Examples:
ticketr migrate path/to/story.md # Preview changes
ticketr migrate path/to/story.md --write # Apply changes
ticketr migrate examples/*.md --write # Batch migration`,
Args: cobra.MinimumNArgs(1),
Run: runMigrate,
}

// Legacy commands for backward compatibility
legacyCmd = &cobra.Command{
Use: "legacy",
Expand All @@ -108,14 +87,10 @@ func init() {
pullCmd.Flags().StringVarP(&pullOutput, "output", "o", "pulled_tickets.md", "output file path")
pullCmd.Flags().BoolVar(&pullForce, "force", false, "Force overwrite local changes with remote changes when conflicts are detected")

// Migrate command flags
migrateCmd.Flags().BoolVarP(&writeFlag, "write", "w", false, "Write changes to files")

// Add commands to root
rootCmd.AddCommand(pushCmd)
rootCmd.AddCommand(pullCmd)
rootCmd.AddCommand(schemaCmd)
rootCmd.AddCommand(migrateCmd)
rootCmd.AddCommand(legacyCmd)

// Legacy flags for backward compatibility
Expand Down Expand Up @@ -529,100 +504,6 @@ func processFieldForSchema(field map[string]interface{}, customFieldsMap map[str
}
}

// runMigrate handles the migrate command
func runMigrate(cmd *cobra.Command, args []string) {
// Create migrator with DryRun based on writeFlag
migrator := &migration.Migrator{
DryRun: !writeFlag,
}

// Track overall success/failure
hasErrors := false
totalFiles := 0
totalChanges := 0

// Process each file argument (supports glob patterns)
for _, pattern := range args {
// Expand glob pattern
matches, err := filepath.Glob(pattern)
if err != nil {
fmt.Printf("Error processing pattern '%s': %v\n", pattern, err)
hasErrors = true
continue
}

if len(matches) == 0 {
// No glob match, treat as literal file path
matches = []string{pattern}
}

// Process each matched file
for _, filePath := range matches {
totalFiles++

// Get absolute path for display
absPath, err := filepath.Abs(filePath)
if err != nil {
absPath = filePath
}

// Perform migration
content, changed, err := migrator.MigrateFile(filePath)
if err != nil {
fmt.Printf("Error migrating %s: %v\n", absPath, err)
hasErrors = true
continue
}

if !changed {
if verbose {
fmt.Printf("No changes needed: %s\n", absPath)
}
continue
}

totalChanges++

// If dry-run, show preview
if migrator.DryRun {
originalContent, _ := os.ReadFile(filePath)
preview := migrator.PreviewDiff(absPath, string(originalContent), content)
fmt.Println(preview)
} else {
// Write the migration
err = migrator.WriteMigration(filePath, content)
if err != nil {
fmt.Printf("Error writing %s: %v\n", absPath, err)
hasErrors = true
continue
}

// Count how many replacements were made
changeCount := strings.Count(content, "# TICKET:") - strings.Count(string(content), "# STORY:")
if changeCount < 0 {
changeCount = -changeCount
}

fmt.Printf("Migrated: %s (%d change(s))\n", absPath, changeCount)
}
}
}

// Print summary
if totalFiles == 0 {
fmt.Println("No files matched the provided pattern(s)")
os.Exit(1)
}

if verbose {
fmt.Printf("\nProcessed %d file(s), %d file(s) with changes\n", totalFiles, totalChanges)
}

if hasErrors {
os.Exit(1)
}
}

// runLegacy handles the old command-line interface for backward compatibility
func runLegacy(cmd *cobra.Command, args []string) {
// Check for legacy flags
Expand Down Expand Up @@ -773,7 +654,7 @@ func main() {
// Check for legacy usage (no subcommand)
if len(os.Args) > 1 && !strings.HasPrefix(os.Args[1], "-") {
// If first arg is not a flag and not a known command, assume it's a file (legacy)
knownCommands := []string{"push", "pull", "schema", "migrate", "help", "completion"}
knownCommands := []string{"push", "pull", "schema", "help", "completion"}
isKnownCommand := false
for _, cmd := range knownCommands {
if os.Args[1] == cmd {
Expand Down
76 changes: 0 additions & 76 deletions cmd/ticketr/main_cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -135,75 +135,6 @@ func TestPullCmd_Flags(t *testing.T) {
}
}

// TestMigrateCmd_AcceptsMultipleFiles tests migrate command with multiple file arguments
func TestMigrateCmd_AcceptsMultipleFiles(t *testing.T) {
var capturedArgs []string
testMigrateCmd := &cobra.Command{
Use: "migrate [file]",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
capturedArgs = args
},
}

testMigrateCmd.SetArgs([]string{"file1.md", "file2.md", "file3.md"})
err := testMigrateCmd.Execute()

if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

if len(capturedArgs) != 3 {
t.Errorf("Expected 3 arguments, got %d", len(capturedArgs))
}

expectedFiles := []string{"file1.md", "file2.md", "file3.md"}
for i, expected := range expectedFiles {
if capturedArgs[i] != expected {
t.Errorf("Expected arg %d to be '%s', got '%s'", i, expected, capturedArgs[i])
}
}
}

// TestMigrateCmd_WriteFlag tests migrate command --write flag
func TestMigrateCmd_WriteFlag(t *testing.T) {
var testWrite bool
testMigrateCmd := &cobra.Command{
Use: "migrate [file]",
Args: cobra.MinimumNArgs(1),
Run: func(cmd *cobra.Command, args []string) {
testWrite, _ = cmd.Flags().GetBool("write")
},
}

testMigrateCmd.Flags().BoolVarP(&testWrite, "write", "w", false, "Write changes")

// Test with --write flag
testMigrateCmd.SetArgs([]string{"test.md", "--write"})
err := testMigrateCmd.Execute()

if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

if !testWrite {
t.Error("Expected write flag to be true")
}

// Test without --write flag
testWrite = false
testMigrateCmd.SetArgs([]string{"test.md"})
err = testMigrateCmd.Execute()

if err != nil {
t.Fatalf("Expected no error, got: %v", err)
}

if testWrite {
t.Error("Expected write flag to be false")
}
}

// TestConfigFile_Override tests that --config flag overrides default config location
func TestConfigFile_Override(t *testing.T) {
var testCfgFile string
Expand Down Expand Up @@ -378,13 +309,6 @@ func TestCommandHelp(t *testing.T) {
Short: "Pull tickets from JIRA",
},
},
{
name: "migrate",
command: &cobra.Command{
Use: "migrate [file]",
Short: "Migrate legacy format",
},
},
{
name: "schema",
command: &cobra.Command{
Expand Down
Loading
Loading