go/pkg/cli/daemon.go

447 lines
9.7 KiB
Go
Raw Normal View History

// Package cli provides the CLI runtime and utilities.
package cli
import (
"context"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"sync"
"syscall"
"time"
feat(cli): CLI enhancements (#182) * feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * feat(io): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * chore(io): migrate internal/cmd/docs and internal/cmd/dev to Medium - internal/cmd/docs: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.RemoveAll with io.Local equivalents - internal/cmd/dev: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.ReadDir with io.Local equivalents - Fix local.Medium to allow absolute paths when root is "/" for full filesystem access (io.Local use case) Refs #113, #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate internal/cmd/setup to Medium abstraction Migrated all direct os.* filesystem calls to use io.Local: - cmd_repo.go: os.MkdirAll -> io.Local.EnsureDir, os.WriteFile -> io.Local.Write, os.Stat -> io.Local.IsFile - cmd_bootstrap.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.IsDir/Exists, os.ReadDir -> io.Local.List - cmd_registry.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.Exists - cmd_ci.go: os.ReadFile -> io.Local.Read - github_config.go: os.ReadFile -> io.Local.Read, os.Stat -> io.Local.Exists Refs #116 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate pkg/cli/daemon.go to Medium abstraction Replaces direct os calls with io.Local: - os.ReadFile -> io.Local.Read - os.WriteFile -> io.Local.Write - os.Remove -> io.Local.Delete - os.MkdirAll -> io.Local.EnsureDir Closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): address Copilot review feedback - Fix MockMedium.Rename: collect keys before mutating maps during iteration - Fix .git checks to use Exists instead of List (handles worktrees/submodules) - Fix cmd_sync.go: use DeleteAll for recursive directory removal Files updated: - pkg/io/io.go: safe map iteration in Rename - internal/cmd/setup/cmd_bootstrap.go: Exists for .git checks - internal/cmd/setup/cmd_registry.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_install.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_manage.go: Exists for .git checks - internal/cmd/docs/cmd_sync.go: DeleteAll for recursive delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): remove duplicate method declarations Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(io): fix traversal test to match sanitization behavior The simplified path() sanitizes .. to . without returning errors. Update test to verify sanitization works correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:48:34 +00:00
"github.com/host-uk/core/pkg/io"
"golang.org/x/term"
)
// Mode represents the CLI execution mode.
type Mode int
const (
// ModeInteractive indicates TTY attached with coloured output.
ModeInteractive Mode = iota
// ModePipe indicates stdout is piped, colours disabled.
ModePipe
// ModeDaemon indicates headless execution, log-only output.
ModeDaemon
)
// String returns the string representation of the Mode.
func (m Mode) String() string {
switch m {
case ModeInteractive:
return "interactive"
case ModePipe:
return "pipe"
case ModeDaemon:
return "daemon"
default:
return "unknown"
}
}
// DetectMode determines the execution mode based on environment.
// Checks CORE_DAEMON env var first, then TTY status.
func DetectMode() Mode {
if os.Getenv("CORE_DAEMON") == "1" {
return ModeDaemon
}
if !IsTTY() {
return ModePipe
}
return ModeInteractive
}
// IsTTY returns true if stdout is a terminal.
func IsTTY() bool {
return term.IsTerminal(int(os.Stdout.Fd()))
}
// IsStdinTTY returns true if stdin is a terminal.
func IsStdinTTY() bool {
return term.IsTerminal(int(os.Stdin.Fd()))
}
// IsStderrTTY returns true if stderr is a terminal.
func IsStderrTTY() bool {
return term.IsTerminal(int(os.Stderr.Fd()))
}
// --- PID File Management ---
// PIDFile manages a process ID file for single-instance enforcement.
type PIDFile struct {
path string
mu sync.Mutex
}
// NewPIDFile creates a PID file manager.
func NewPIDFile(path string) *PIDFile {
return &PIDFile{path: path}
}
// Acquire writes the current PID to the file.
// Returns error if another instance is running.
func (p *PIDFile) Acquire() error {
p.mu.Lock()
defer p.mu.Unlock()
// Check if PID file exists
feat(cli): CLI enhancements (#182) * feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * feat(io): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * chore(io): migrate internal/cmd/docs and internal/cmd/dev to Medium - internal/cmd/docs: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.RemoveAll with io.Local equivalents - internal/cmd/dev: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.ReadDir with io.Local equivalents - Fix local.Medium to allow absolute paths when root is "/" for full filesystem access (io.Local use case) Refs #113, #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate internal/cmd/setup to Medium abstraction Migrated all direct os.* filesystem calls to use io.Local: - cmd_repo.go: os.MkdirAll -> io.Local.EnsureDir, os.WriteFile -> io.Local.Write, os.Stat -> io.Local.IsFile - cmd_bootstrap.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.IsDir/Exists, os.ReadDir -> io.Local.List - cmd_registry.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.Exists - cmd_ci.go: os.ReadFile -> io.Local.Read - github_config.go: os.ReadFile -> io.Local.Read, os.Stat -> io.Local.Exists Refs #116 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate pkg/cli/daemon.go to Medium abstraction Replaces direct os calls with io.Local: - os.ReadFile -> io.Local.Read - os.WriteFile -> io.Local.Write - os.Remove -> io.Local.Delete - os.MkdirAll -> io.Local.EnsureDir Closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): address Copilot review feedback - Fix MockMedium.Rename: collect keys before mutating maps during iteration - Fix .git checks to use Exists instead of List (handles worktrees/submodules) - Fix cmd_sync.go: use DeleteAll for recursive directory removal Files updated: - pkg/io/io.go: safe map iteration in Rename - internal/cmd/setup/cmd_bootstrap.go: Exists for .git checks - internal/cmd/setup/cmd_registry.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_install.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_manage.go: Exists for .git checks - internal/cmd/docs/cmd_sync.go: DeleteAll for recursive delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): remove duplicate method declarations Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(io): fix traversal test to match sanitization behavior The simplified path() sanitizes .. to . without returning errors. Update test to verify sanitization works correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:48:34 +00:00
if data, err := io.Local.Read(p.path); err == nil {
pid, err := strconv.Atoi(data)
if err == nil && pid > 0 {
// Check if process is still running
if process, err := os.FindProcess(pid); err == nil {
if err := process.Signal(syscall.Signal(0)); err == nil {
return fmt.Errorf("another instance is running (PID %d)", pid)
}
}
}
// Stale PID file, remove it
feat(cli): CLI enhancements (#182) * feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * feat(io): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * chore(io): migrate internal/cmd/docs and internal/cmd/dev to Medium - internal/cmd/docs: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.RemoveAll with io.Local equivalents - internal/cmd/dev: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.ReadDir with io.Local equivalents - Fix local.Medium to allow absolute paths when root is "/" for full filesystem access (io.Local use case) Refs #113, #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate internal/cmd/setup to Medium abstraction Migrated all direct os.* filesystem calls to use io.Local: - cmd_repo.go: os.MkdirAll -> io.Local.EnsureDir, os.WriteFile -> io.Local.Write, os.Stat -> io.Local.IsFile - cmd_bootstrap.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.IsDir/Exists, os.ReadDir -> io.Local.List - cmd_registry.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.Exists - cmd_ci.go: os.ReadFile -> io.Local.Read - github_config.go: os.ReadFile -> io.Local.Read, os.Stat -> io.Local.Exists Refs #116 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate pkg/cli/daemon.go to Medium abstraction Replaces direct os calls with io.Local: - os.ReadFile -> io.Local.Read - os.WriteFile -> io.Local.Write - os.Remove -> io.Local.Delete - os.MkdirAll -> io.Local.EnsureDir Closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): address Copilot review feedback - Fix MockMedium.Rename: collect keys before mutating maps during iteration - Fix .git checks to use Exists instead of List (handles worktrees/submodules) - Fix cmd_sync.go: use DeleteAll for recursive directory removal Files updated: - pkg/io/io.go: safe map iteration in Rename - internal/cmd/setup/cmd_bootstrap.go: Exists for .git checks - internal/cmd/setup/cmd_registry.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_install.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_manage.go: Exists for .git checks - internal/cmd/docs/cmd_sync.go: DeleteAll for recursive delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): remove duplicate method declarations Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(io): fix traversal test to match sanitization behavior The simplified path() sanitizes .. to . without returning errors. Update test to verify sanitization works correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:48:34 +00:00
_ = io.Local.Delete(p.path)
}
// Ensure directory exists
if dir := filepath.Dir(p.path); dir != "." {
feat(cli): CLI enhancements (#182) * feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * feat(io): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * chore(io): migrate internal/cmd/docs and internal/cmd/dev to Medium - internal/cmd/docs: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.RemoveAll with io.Local equivalents - internal/cmd/dev: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.ReadDir with io.Local equivalents - Fix local.Medium to allow absolute paths when root is "/" for full filesystem access (io.Local use case) Refs #113, #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate internal/cmd/setup to Medium abstraction Migrated all direct os.* filesystem calls to use io.Local: - cmd_repo.go: os.MkdirAll -> io.Local.EnsureDir, os.WriteFile -> io.Local.Write, os.Stat -> io.Local.IsFile - cmd_bootstrap.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.IsDir/Exists, os.ReadDir -> io.Local.List - cmd_registry.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.Exists - cmd_ci.go: os.ReadFile -> io.Local.Read - github_config.go: os.ReadFile -> io.Local.Read, os.Stat -> io.Local.Exists Refs #116 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate pkg/cli/daemon.go to Medium abstraction Replaces direct os calls with io.Local: - os.ReadFile -> io.Local.Read - os.WriteFile -> io.Local.Write - os.Remove -> io.Local.Delete - os.MkdirAll -> io.Local.EnsureDir Closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): address Copilot review feedback - Fix MockMedium.Rename: collect keys before mutating maps during iteration - Fix .git checks to use Exists instead of List (handles worktrees/submodules) - Fix cmd_sync.go: use DeleteAll for recursive directory removal Files updated: - pkg/io/io.go: safe map iteration in Rename - internal/cmd/setup/cmd_bootstrap.go: Exists for .git checks - internal/cmd/setup/cmd_registry.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_install.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_manage.go: Exists for .git checks - internal/cmd/docs/cmd_sync.go: DeleteAll for recursive delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): remove duplicate method declarations Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(io): fix traversal test to match sanitization behavior The simplified path() sanitizes .. to . without returning errors. Update test to verify sanitization works correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:48:34 +00:00
if err := io.Local.EnsureDir(dir); err != nil {
return fmt.Errorf("failed to create PID directory: %w", err)
}
}
// Write current PID
pid := os.Getpid()
feat(cli): CLI enhancements (#182) * feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * feat(io): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * chore(io): migrate internal/cmd/docs and internal/cmd/dev to Medium - internal/cmd/docs: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.RemoveAll with io.Local equivalents - internal/cmd/dev: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.ReadDir with io.Local equivalents - Fix local.Medium to allow absolute paths when root is "/" for full filesystem access (io.Local use case) Refs #113, #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate internal/cmd/setup to Medium abstraction Migrated all direct os.* filesystem calls to use io.Local: - cmd_repo.go: os.MkdirAll -> io.Local.EnsureDir, os.WriteFile -> io.Local.Write, os.Stat -> io.Local.IsFile - cmd_bootstrap.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.IsDir/Exists, os.ReadDir -> io.Local.List - cmd_registry.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.Exists - cmd_ci.go: os.ReadFile -> io.Local.Read - github_config.go: os.ReadFile -> io.Local.Read, os.Stat -> io.Local.Exists Refs #116 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate pkg/cli/daemon.go to Medium abstraction Replaces direct os calls with io.Local: - os.ReadFile -> io.Local.Read - os.WriteFile -> io.Local.Write - os.Remove -> io.Local.Delete - os.MkdirAll -> io.Local.EnsureDir Closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): address Copilot review feedback - Fix MockMedium.Rename: collect keys before mutating maps during iteration - Fix .git checks to use Exists instead of List (handles worktrees/submodules) - Fix cmd_sync.go: use DeleteAll for recursive directory removal Files updated: - pkg/io/io.go: safe map iteration in Rename - internal/cmd/setup/cmd_bootstrap.go: Exists for .git checks - internal/cmd/setup/cmd_registry.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_install.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_manage.go: Exists for .git checks - internal/cmd/docs/cmd_sync.go: DeleteAll for recursive delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): remove duplicate method declarations Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(io): fix traversal test to match sanitization behavior The simplified path() sanitizes .. to . without returning errors. Update test to verify sanitization works correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:48:34 +00:00
if err := io.Local.Write(p.path, strconv.Itoa(pid)); err != nil {
return fmt.Errorf("failed to write PID file: %w", err)
}
return nil
}
// Release removes the PID file.
func (p *PIDFile) Release() error {
p.mu.Lock()
defer p.mu.Unlock()
feat(cli): CLI enhancements (#182) * feat(help): Add CLI help command Fixes #136 * chore: remove binary * feat(mcp): Add TCP transport Fixes #126 * feat(io): Migrate pkg/mcp to use Medium abstraction Fixes #103 * feat(io): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(cli): batch implementation placeholder Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/docs/* to Medium abstraction Fixes #113 * chore(io): Migrate internal/cmd/dev/* to Medium abstraction Fixes #114 * chore(io): Migrate internal/cmd/setup/* to Medium abstraction * chore(io): Complete migration of internal/cmd/dev/* to Medium abstraction * feat(io): extend Medium interface with Delete, Rename, List, Stat operations Adds the following methods to the Medium interface: - Delete(path) - remove a file or empty directory - DeleteAll(path) - recursively remove a file or directory - Rename(old, new) - move/rename a file or directory - List(path) - list directory entries (returns []fs.DirEntry) - Stat(path) - get file information (returns fs.FileInfo) - Exists(path) - check if path exists - IsDir(path) - check if path is a directory Implements these methods in both local.Medium (using os package) and MockMedium (in-memory for testing). Includes FileInfo and DirEntry types for mock implementations. This enables migration of direct os.* calls to the Medium abstraction for consistent path validation and testability. Refs #101 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): Migrate internal/cmd/sdk, pkgcmd, and workspace to Medium abstraction * chore(io): migrate internal/cmd/docs and internal/cmd/dev to Medium - internal/cmd/docs: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.RemoveAll with io.Local equivalents - internal/cmd/dev: Replace os.Stat, os.ReadFile, os.WriteFile, os.MkdirAll, os.ReadDir with io.Local equivalents - Fix local.Medium to allow absolute paths when root is "/" for full filesystem access (io.Local use case) Refs #113, #114 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate internal/cmd/setup to Medium abstraction Migrated all direct os.* filesystem calls to use io.Local: - cmd_repo.go: os.MkdirAll -> io.Local.EnsureDir, os.WriteFile -> io.Local.Write, os.Stat -> io.Local.IsFile - cmd_bootstrap.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.IsDir/Exists, os.ReadDir -> io.Local.List - cmd_registry.go: os.MkdirAll -> io.Local.EnsureDir, os.Stat -> io.Local.Exists - cmd_ci.go: os.ReadFile -> io.Local.Read - github_config.go: os.ReadFile -> io.Local.Read, os.Stat -> io.Local.Exists Refs #116 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore(io): migrate pkg/cli/daemon.go to Medium abstraction Replaces direct os calls with io.Local: - os.ReadFile -> io.Local.Read - os.WriteFile -> io.Local.Write - os.Remove -> io.Local.Delete - os.MkdirAll -> io.Local.EnsureDir Closes #107 Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): address Copilot review feedback - Fix MockMedium.Rename: collect keys before mutating maps during iteration - Fix .git checks to use Exists instead of List (handles worktrees/submodules) - Fix cmd_sync.go: use DeleteAll for recursive directory removal Files updated: - pkg/io/io.go: safe map iteration in Rename - internal/cmd/setup/cmd_bootstrap.go: Exists for .git checks - internal/cmd/setup/cmd_registry.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_install.go: Exists for .git checks - internal/cmd/pkgcmd/cmd_manage.go: Exists for .git checks - internal/cmd/docs/cmd_sync.go: DeleteAll for recursive delete Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(updater): resolve PkgVersion duplicate declaration Remove var PkgVersion from updater.go since go generate creates const PkgVersion in version.go. Track version.go in git to ensure builds work without running go generate first. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * style: fix formatting in internal/variants Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * refactor(io): simplify local Medium implementation Rewrote to match the simpler TypeScript pattern: - path() sanitizes and returns string directly - Each method calls path() once - No complex symlink validation - Less code, less attack surface Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix(io): remove duplicate method declarations Clean up the client.go file that had duplicate method declarations from a bad cherry-pick merge. Now has 127 lines of simple, clean code. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(io): fix traversal test to match sanitization behavior The simplified path() sanitizes .. to . without returning errors. Update test to verify sanitization works correctly. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * test(mcp): update sandboxing tests for simplified Medium The simplified io/local.Medium implementation: - Sanitizes .. to . (no error, path is cleaned) - Allows absolute paths through (caller validates if needed) - Follows symlinks (no traversal blocking) Update tests to match this simplified behavior. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 07:48:34 +00:00
return io.Local.Delete(p.path)
}
// Path returns the PID file path.
func (p *PIDFile) Path() string {
return p.path
}
// --- Health Check Server ---
// HealthServer provides a minimal HTTP health check endpoint.
type HealthServer struct {
addr string
server *http.Server
listener net.Listener
mu sync.Mutex
ready bool
checks []HealthCheck
}
// HealthCheck is a function that returns nil if healthy.
type HealthCheck func() error
// NewHealthServer creates a health check server.
func NewHealthServer(addr string) *HealthServer {
return &HealthServer{
addr: addr,
ready: true,
}
}
// AddCheck registers a health check function.
func (h *HealthServer) AddCheck(check HealthCheck) {
h.mu.Lock()
h.checks = append(h.checks, check)
h.mu.Unlock()
}
// SetReady sets the readiness status.
func (h *HealthServer) SetReady(ready bool) {
h.mu.Lock()
h.ready = ready
h.mu.Unlock()
}
// Start begins serving health check endpoints.
// Endpoints:
// - /health - liveness probe (always 200 if server is up)
// - /ready - readiness probe (200 if ready, 503 if not)
func (h *HealthServer) Start() error {
mux := http.NewServeMux()
mux.HandleFunc("/health", func(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
checks := h.checks
h.mu.Unlock()
for _, check := range checks {
if err := check(); err != nil {
w.WriteHeader(http.StatusServiceUnavailable)
feat: git command, build improvements, and go fmt git-aware (#74) * feat(go): make go fmt git-aware by default - By default, only check changed Go files (modified, staged, untracked) - Add --all flag to check all files (previous behaviour) - Reduces noise when running fmt on large codebases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(build): minimal output by default, add missing i18n - Default output now shows single line: "Success Built N artifacts (dir)" - Add --verbose/-v flag to show full detailed output - Add all missing i18n translations for build commands - Errors still show failure reason in minimal mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add root-level `core git` command - Create pkg/gitcmd with git workflow commands as root menu - Export command builders from pkg/dev (AddCommitCommand, etc.) - Commands available under both `core git` and `core dev` for compatibility - Git commands: health, commit, push, pull, work, sync, apply - GitHub orchestration stays in dev: issues, reviews, ci, impact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(qa): add docblock coverage checking Implement docblock/docstring coverage analysis for Go code: - New `core qa docblock` command to check coverage - Shows compact file:line list when under threshold - Integrate with `core go qa` as a default check - Add --docblock-threshold flag (default 80%) The checker uses Go AST parsing to find exported symbols (functions, types, consts, vars) without documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - Fix doc comment: "status" → "health" in gitcmd package - Implement --check flag for `core go fmt` (exits non-zero if files need formatting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add docstrings for 100% coverage Add documentation comments to all exported symbols: - pkg/build: ProjectType constants - pkg/cli: LogLevel, RenderStyle, TableStyle - pkg/framework: ServiceFor, MustServiceFor, Core.Core - pkg/git: GitError.Error, GitError.Unwrap - pkg/i18n: Handler Match/Handle methods - pkg/log: Level constants - pkg/mcp: Tool input/output types - pkg/php: Service constants, QA types, service methods - pkg/process: ServiceError.Error - pkg/repos: RepoType constants - pkg/setup: ChangeType, ChangeCategory constants - pkg/workspace: AddWorkspaceCommands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: standardize line endings to LF Add .gitattributes to enforce LF line endings for all text files. Normalize all existing files to use Unix-style line endings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - cmd_format.go: validate --check/--fix mutual exclusivity, capture stderr - cmd_docblock.go: return error instead of os.Exit(1) for proper error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback (round 2) - linuxkit.go: propagate state update errors, handle cmd.Wait() errors in waitForExit - mcp.go: guard against empty old_string in editDiff to prevent runaway edits - cmd_docblock.go: log parse errors instead of silently skipping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 10:48:44 +00:00
_, _ = fmt.Fprintf(w, "unhealthy: %v\n", err)
return
}
}
w.WriteHeader(http.StatusOK)
feat: git command, build improvements, and go fmt git-aware (#74) * feat(go): make go fmt git-aware by default - By default, only check changed Go files (modified, staged, untracked) - Add --all flag to check all files (previous behaviour) - Reduces noise when running fmt on large codebases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(build): minimal output by default, add missing i18n - Default output now shows single line: "Success Built N artifacts (dir)" - Add --verbose/-v flag to show full detailed output - Add all missing i18n translations for build commands - Errors still show failure reason in minimal mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add root-level `core git` command - Create pkg/gitcmd with git workflow commands as root menu - Export command builders from pkg/dev (AddCommitCommand, etc.) - Commands available under both `core git` and `core dev` for compatibility - Git commands: health, commit, push, pull, work, sync, apply - GitHub orchestration stays in dev: issues, reviews, ci, impact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(qa): add docblock coverage checking Implement docblock/docstring coverage analysis for Go code: - New `core qa docblock` command to check coverage - Shows compact file:line list when under threshold - Integrate with `core go qa` as a default check - Add --docblock-threshold flag (default 80%) The checker uses Go AST parsing to find exported symbols (functions, types, consts, vars) without documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - Fix doc comment: "status" → "health" in gitcmd package - Implement --check flag for `core go fmt` (exits non-zero if files need formatting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add docstrings for 100% coverage Add documentation comments to all exported symbols: - pkg/build: ProjectType constants - pkg/cli: LogLevel, RenderStyle, TableStyle - pkg/framework: ServiceFor, MustServiceFor, Core.Core - pkg/git: GitError.Error, GitError.Unwrap - pkg/i18n: Handler Match/Handle methods - pkg/log: Level constants - pkg/mcp: Tool input/output types - pkg/php: Service constants, QA types, service methods - pkg/process: ServiceError.Error - pkg/repos: RepoType constants - pkg/setup: ChangeType, ChangeCategory constants - pkg/workspace: AddWorkspaceCommands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: standardize line endings to LF Add .gitattributes to enforce LF line endings for all text files. Normalize all existing files to use Unix-style line endings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - cmd_format.go: validate --check/--fix mutual exclusivity, capture stderr - cmd_docblock.go: return error instead of os.Exit(1) for proper error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback (round 2) - linuxkit.go: propagate state update errors, handle cmd.Wait() errors in waitForExit - mcp.go: guard against empty old_string in editDiff to prevent runaway edits - cmd_docblock.go: log parse errors instead of silently skipping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 10:48:44 +00:00
_, _ = fmt.Fprintln(w, "ok")
})
mux.HandleFunc("/ready", func(w http.ResponseWriter, r *http.Request) {
h.mu.Lock()
ready := h.ready
h.mu.Unlock()
if !ready {
w.WriteHeader(http.StatusServiceUnavailable)
feat: git command, build improvements, and go fmt git-aware (#74) * feat(go): make go fmt git-aware by default - By default, only check changed Go files (modified, staged, untracked) - Add --all flag to check all files (previous behaviour) - Reduces noise when running fmt on large codebases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(build): minimal output by default, add missing i18n - Default output now shows single line: "Success Built N artifacts (dir)" - Add --verbose/-v flag to show full detailed output - Add all missing i18n translations for build commands - Errors still show failure reason in minimal mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add root-level `core git` command - Create pkg/gitcmd with git workflow commands as root menu - Export command builders from pkg/dev (AddCommitCommand, etc.) - Commands available under both `core git` and `core dev` for compatibility - Git commands: health, commit, push, pull, work, sync, apply - GitHub orchestration stays in dev: issues, reviews, ci, impact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(qa): add docblock coverage checking Implement docblock/docstring coverage analysis for Go code: - New `core qa docblock` command to check coverage - Shows compact file:line list when under threshold - Integrate with `core go qa` as a default check - Add --docblock-threshold flag (default 80%) The checker uses Go AST parsing to find exported symbols (functions, types, consts, vars) without documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - Fix doc comment: "status" → "health" in gitcmd package - Implement --check flag for `core go fmt` (exits non-zero if files need formatting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add docstrings for 100% coverage Add documentation comments to all exported symbols: - pkg/build: ProjectType constants - pkg/cli: LogLevel, RenderStyle, TableStyle - pkg/framework: ServiceFor, MustServiceFor, Core.Core - pkg/git: GitError.Error, GitError.Unwrap - pkg/i18n: Handler Match/Handle methods - pkg/log: Level constants - pkg/mcp: Tool input/output types - pkg/php: Service constants, QA types, service methods - pkg/process: ServiceError.Error - pkg/repos: RepoType constants - pkg/setup: ChangeType, ChangeCategory constants - pkg/workspace: AddWorkspaceCommands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: standardize line endings to LF Add .gitattributes to enforce LF line endings for all text files. Normalize all existing files to use Unix-style line endings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - cmd_format.go: validate --check/--fix mutual exclusivity, capture stderr - cmd_docblock.go: return error instead of os.Exit(1) for proper error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback (round 2) - linuxkit.go: propagate state update errors, handle cmd.Wait() errors in waitForExit - mcp.go: guard against empty old_string in editDiff to prevent runaway edits - cmd_docblock.go: log parse errors instead of silently skipping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 10:48:44 +00:00
_, _ = fmt.Fprintln(w, "not ready")
return
}
w.WriteHeader(http.StatusOK)
feat: git command, build improvements, and go fmt git-aware (#74) * feat(go): make go fmt git-aware by default - By default, only check changed Go files (modified, staged, untracked) - Add --all flag to check all files (previous behaviour) - Reduces noise when running fmt on large codebases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(build): minimal output by default, add missing i18n - Default output now shows single line: "Success Built N artifacts (dir)" - Add --verbose/-v flag to show full detailed output - Add all missing i18n translations for build commands - Errors still show failure reason in minimal mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add root-level `core git` command - Create pkg/gitcmd with git workflow commands as root menu - Export command builders from pkg/dev (AddCommitCommand, etc.) - Commands available under both `core git` and `core dev` for compatibility - Git commands: health, commit, push, pull, work, sync, apply - GitHub orchestration stays in dev: issues, reviews, ci, impact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(qa): add docblock coverage checking Implement docblock/docstring coverage analysis for Go code: - New `core qa docblock` command to check coverage - Shows compact file:line list when under threshold - Integrate with `core go qa` as a default check - Add --docblock-threshold flag (default 80%) The checker uses Go AST parsing to find exported symbols (functions, types, consts, vars) without documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - Fix doc comment: "status" → "health" in gitcmd package - Implement --check flag for `core go fmt` (exits non-zero if files need formatting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add docstrings for 100% coverage Add documentation comments to all exported symbols: - pkg/build: ProjectType constants - pkg/cli: LogLevel, RenderStyle, TableStyle - pkg/framework: ServiceFor, MustServiceFor, Core.Core - pkg/git: GitError.Error, GitError.Unwrap - pkg/i18n: Handler Match/Handle methods - pkg/log: Level constants - pkg/mcp: Tool input/output types - pkg/php: Service constants, QA types, service methods - pkg/process: ServiceError.Error - pkg/repos: RepoType constants - pkg/setup: ChangeType, ChangeCategory constants - pkg/workspace: AddWorkspaceCommands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: standardize line endings to LF Add .gitattributes to enforce LF line endings for all text files. Normalize all existing files to use Unix-style line endings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - cmd_format.go: validate --check/--fix mutual exclusivity, capture stderr - cmd_docblock.go: return error instead of os.Exit(1) for proper error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback (round 2) - linuxkit.go: propagate state update errors, handle cmd.Wait() errors in waitForExit - mcp.go: guard against empty old_string in editDiff to prevent runaway edits - cmd_docblock.go: log parse errors instead of silently skipping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 10:48:44 +00:00
_, _ = fmt.Fprintln(w, "ready")
})
listener, err := net.Listen("tcp", h.addr)
if err != nil {
return fmt.Errorf("failed to listen on %s: %w", h.addr, err)
}
h.listener = listener
h.server = &http.Server{Handler: mux}
go func() {
if err := h.server.Serve(listener); err != http.ErrServerClosed {
LogError(fmt.Sprintf("health server error: %v", err))
}
}()
return nil
}
// Stop gracefully shuts down the health server.
func (h *HealthServer) Stop(ctx context.Context) error {
if h.server == nil {
return nil
}
return h.server.Shutdown(ctx)
}
// Addr returns the actual address the server is listening on.
// Useful when using port 0 for dynamic port assignment.
func (h *HealthServer) Addr() string {
if h.listener != nil {
return h.listener.Addr().String()
}
return h.addr
}
// --- Daemon Runner ---
// DaemonOptions configures daemon mode execution.
type DaemonOptions struct {
// PIDFile path for single-instance enforcement.
// Leave empty to skip PID file management.
PIDFile string
// ShutdownTimeout is the maximum time to wait for graceful shutdown.
// Default: 30 seconds.
ShutdownTimeout time.Duration
// HealthAddr is the address for health check endpoints.
// Example: ":8080", "127.0.0.1:9000"
// Leave empty to disable health checks.
HealthAddr string
// HealthChecks are additional health check functions.
HealthChecks []HealthCheck
// OnReload is called when SIGHUP is received.
// Use for config reloading. Leave nil to ignore SIGHUP.
OnReload func() error
}
// Daemon manages daemon lifecycle.
type Daemon struct {
opts DaemonOptions
pid *PIDFile
health *HealthServer
reload chan struct{}
running bool
mu sync.Mutex
}
// NewDaemon creates a daemon runner with the given options.
func NewDaemon(opts DaemonOptions) *Daemon {
if opts.ShutdownTimeout == 0 {
opts.ShutdownTimeout = 30 * time.Second
}
d := &Daemon{
opts: opts,
reload: make(chan struct{}, 1),
}
if opts.PIDFile != "" {
d.pid = NewPIDFile(opts.PIDFile)
}
if opts.HealthAddr != "" {
d.health = NewHealthServer(opts.HealthAddr)
for _, check := range opts.HealthChecks {
d.health.AddCheck(check)
}
}
return d
}
// Start initialises the daemon (PID file, health server).
// Call this after cli.Init().
func (d *Daemon) Start() error {
d.mu.Lock()
defer d.mu.Unlock()
if d.running {
return fmt.Errorf("daemon already running")
}
// Acquire PID file
if d.pid != nil {
if err := d.pid.Acquire(); err != nil {
return err
}
}
// Start health server
if d.health != nil {
if err := d.health.Start(); err != nil {
if d.pid != nil {
feat: git command, build improvements, and go fmt git-aware (#74) * feat(go): make go fmt git-aware by default - By default, only check changed Go files (modified, staged, untracked) - Add --all flag to check all files (previous behaviour) - Reduces noise when running fmt on large codebases Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(build): minimal output by default, add missing i18n - Default output now shows single line: "Success Built N artifacts (dir)" - Add --verbose/-v flag to show full detailed output - Add all missing i18n translations for build commands - Errors still show failure reason in minimal mode Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat: add root-level `core git` command - Create pkg/gitcmd with git workflow commands as root menu - Export command builders from pkg/dev (AddCommitCommand, etc.) - Commands available under both `core git` and `core dev` for compatibility - Git commands: health, commit, push, pull, work, sync, apply - GitHub orchestration stays in dev: issues, reviews, ci, impact Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * feat(qa): add docblock coverage checking Implement docblock/docstring coverage analysis for Go code: - New `core qa docblock` command to check coverage - Shows compact file:line list when under threshold - Integrate with `core go qa` as a default check - Add --docblock-threshold flag (default 80%) The checker uses Go AST parsing to find exported symbols (functions, types, consts, vars) without documentation. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - Fix doc comment: "status" → "health" in gitcmd package - Implement --check flag for `core go fmt` (exits non-zero if files need formatting) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * docs: add docstrings for 100% coverage Add documentation comments to all exported symbols: - pkg/build: ProjectType constants - pkg/cli: LogLevel, RenderStyle, TableStyle - pkg/framework: ServiceFor, MustServiceFor, Core.Core - pkg/git: GitError.Error, GitError.Unwrap - pkg/i18n: Handler Match/Handle methods - pkg/log: Level constants - pkg/mcp: Tool input/output types - pkg/php: Service constants, QA types, service methods - pkg/process: ServiceError.Error - pkg/repos: RepoType constants - pkg/setup: ChangeType, ChangeCategory constants - pkg/workspace: AddWorkspaceCommands Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * chore: standardize line endings to LF Add .gitattributes to enforce LF line endings for all text files. Normalize all existing files to use Unix-style line endings. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback - cmd_format.go: validate --check/--fix mutual exclusivity, capture stderr - cmd_docblock.go: return error instead of os.Exit(1) for proper error handling Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> * fix: address CodeRabbit review feedback (round 2) - linuxkit.go: propagate state update errors, handle cmd.Wait() errors in waitForExit - mcp.go: guard against empty old_string in editDiff to prevent runaway edits - cmd_docblock.go: log parse errors instead of silently skipping Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-01 10:48:44 +00:00
_ = d.pid.Release()
}
return err
}
}
d.running = true
return nil
}
// Run blocks until the context is cancelled or a signal is received.
// Handles graceful shutdown with the configured timeout.
func (d *Daemon) Run(ctx context.Context) error {
d.mu.Lock()
if !d.running {
d.mu.Unlock()
return fmt.Errorf("daemon not started - call Start() first")
}
d.mu.Unlock()
// Wait for context cancellation (from signal handler)
<-ctx.Done()
return d.Stop()
}
// Stop performs graceful shutdown.
func (d *Daemon) Stop() error {
d.mu.Lock()
defer d.mu.Unlock()
if !d.running {
return nil
}
var errs []error
// Create shutdown context with timeout
shutdownCtx, cancel := context.WithTimeout(context.Background(), d.opts.ShutdownTimeout)
defer cancel()
// Stop health server
if d.health != nil {
d.health.SetReady(false)
if err := d.health.Stop(shutdownCtx); err != nil {
errs = append(errs, fmt.Errorf("health server: %w", err))
}
}
// Release PID file
if d.pid != nil {
if err := d.pid.Release(); err != nil && !os.IsNotExist(err) {
errs = append(errs, fmt.Errorf("pid file: %w", err))
}
}
d.running = false
if len(errs) > 0 {
return fmt.Errorf("shutdown errors: %v", errs)
}
return nil
}
// SetReady sets the daemon readiness status for health checks.
func (d *Daemon) SetReady(ready bool) {
if d.health != nil {
d.health.SetReady(ready)
}
}
// HealthAddr returns the health server address, or empty if disabled.
func (d *Daemon) HealthAddr() string {
if d.health != nil {
return d.health.Addr()
}
return ""
}
// --- Convenience Functions ---
// Run blocks until context is cancelled or signal received.
// Simple helper for daemon mode without advanced features.
//
// cli.Init(cli.Options{AppName: "myapp"})
// defer cli.Shutdown()
// cli.Run(cli.Context())
func Run(ctx context.Context) error {
mustInit()
<-ctx.Done()
return ctx.Err()
}
// RunWithTimeout wraps Run with a graceful shutdown timeout.
// The returned function should be deferred to replace cli.Shutdown().
//
// cli.Init(cli.Options{AppName: "myapp"})
// shutdown := cli.RunWithTimeout(30 * time.Second)
// defer shutdown()
// cli.Run(cli.Context())
func RunWithTimeout(timeout time.Duration) func() {
return func() {
ctx, cancel := context.WithTimeout(context.Background(), timeout)
defer cancel()
// Create done channel for shutdown completion
done := make(chan struct{})
go func() {
Shutdown()
close(done)
}()
select {
case <-done:
// Clean shutdown
case <-ctx.Done():
// Timeout - force exit
LogWarn("shutdown timeout exceeded, forcing exit")
}
}
}