Add native Windows support (#91)

- Native Windows daemon using TCP loopback endpoints
- Direct-mode fallback for CLI/daemon compatibility
- Comment operations over RPC
- PowerShell installer script
- Go 1.24 requirement
- Cross-OS testing documented

Co-authored-by: danshapiro <danshapiro@users.noreply.github.com>
Amp-Thread-ID: https://ampcode.com/threads/T-c6230265-055f-4af1-9712-4481061886db
Co-authored-by: Amp <amp@ampcode.com>
This commit is contained in:
Steve Yegge
2025-10-20 21:08:49 -07:00
parent 94a23cae39
commit a86f3e139e
58 changed files with 1707 additions and 729 deletions
+20 -20
View File
@@ -161,9 +161,9 @@ Risks:
if jsonOutput {
result := map[string]interface{}{
"total_issues": len(issues),
"changed": changed,
"unchanged": len(issues) - changed,
"total_issues": len(issues),
"changed": changed,
"unchanged": len(issues) - changed,
}
enc := json.NewEncoder(os.Stdout)
enc.SetIndent("", " ")
@@ -178,27 +178,27 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
if err != nil {
return fmt.Errorf("failed to get dependency records: %w", err)
}
// Step 1: Rename all issues to temporary UUIDs to avoid collisions
tempMapping := make(map[string]string)
for _, issue := range issues {
oldID := issue.ID
// Use UUID to guarantee uniqueness (no collision possible)
tempID := fmt.Sprintf("temp-%s", uuid.New().String())
tempMapping[oldID] = tempID
// Rename to temp ID (don't update text yet)
issue.ID = tempID
if err := store.UpdateIssueID(ctx, oldID, tempID, issue, actor); err != nil {
return fmt.Errorf("failed to rename %s to temp ID: %w", oldID, err)
}
}
// Step 2: Rename from temp IDs to final IDs (still don't update text)
for _, issue := range issues {
tempID := issue.ID // Currently has temp ID
// Find original ID
var oldOriginalID string
for origID, tID := range tempMapping {
@@ -208,14 +208,14 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
}
}
finalID := idMapping[oldOriginalID]
// Just update the ID, not text yet
issue.ID = finalID
if err := store.UpdateIssueID(ctx, tempID, finalID, issue, actor); err != nil {
return fmt.Errorf("failed to update issue %s: %w", tempID, err)
}
}
// Step 3: Now update all text references using the original old->new mapping
// Build regex to match any OLD issue ID (before renumbering)
oldIDs := make([]string, 0, len(idMapping))
@@ -223,7 +223,7 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
oldIDs = append(oldIDs, regexp.QuoteMeta(oldID))
}
oldPattern := regexp.MustCompile(`\b(` + strings.Join(oldIDs, "|") + `)\b`)
replaceFunc := func(match string) string {
if newID, ok := idMapping[match]; ok {
return newID
@@ -234,19 +234,19 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
// Update text references in all issues
for _, issue := range issues {
changed := false
newTitle := oldPattern.ReplaceAllStringFunc(issue.Title, replaceFunc)
if newTitle != issue.Title {
issue.Title = newTitle
changed = true
}
newDesc := oldPattern.ReplaceAllStringFunc(issue.Description, replaceFunc)
if newDesc != issue.Description {
issue.Description = newDesc
changed = true
}
if issue.Design != "" {
newDesign := oldPattern.ReplaceAllStringFunc(issue.Design, replaceFunc)
if newDesign != issue.Design {
@@ -254,7 +254,7 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
changed = true
}
}
if issue.AcceptanceCriteria != "" {
newAC := oldPattern.ReplaceAllStringFunc(issue.AcceptanceCriteria, replaceFunc)
if newAC != issue.AcceptanceCriteria {
@@ -262,7 +262,7 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
changed = true
}
}
if issue.Notes != "" {
newNotes := oldPattern.ReplaceAllStringFunc(issue.Notes, replaceFunc)
if newNotes != issue.Notes {
@@ -270,7 +270,7 @@ func renumberIssuesInDB(ctx context.Context, prefix string, idMapping map[string
changed = true
}
}
// Only update if text changed
if changed {
if err := store.UpdateIssue(ctx, issue.ID, map[string]interface{}{
@@ -341,15 +341,15 @@ func renumberDependencies(ctx context.Context, idMapping map[string]string, allD
// Remove old dependency (may not exist if IDs already updated)
_ = store.RemoveDependency(ctx, oldDep.IssueID, oldDep.DependsOnID, "renumber")
}
// Then add all new dependencies
for _, newDep := range newDeps {
// Add new dependency
if err := store.AddDependency(ctx, newDep, "renumber"); err != nil {
// Ignore duplicate and validation errors (parent-child direction might be swapped)
if !strings.Contains(err.Error(), "UNIQUE constraint failed") &&
!strings.Contains(err.Error(), "duplicate") &&
!strings.Contains(err.Error(), "invalid parent-child") {
!strings.Contains(err.Error(), "duplicate") &&
!strings.Contains(err.Error(), "invalid parent-child") {
return fmt.Errorf("failed to add dependency %s -> %s: %w", newDep.IssueID, newDep.DependsOnID, err)
}
}