feat(go): durable transaction engine and Kiro IDE 1.0 adapter
Split the monolithic transaction into focused modules: append-only crash-recoverable journal under ~/.elydora/transactions with owner tokens and per-transaction namespaces, staged atomic publication (stage .pending, fsync, rename, fsync), platform-specific atomic replacement (MoveFileEx, renameat2, renamex_np), private-directory hardening per OS, lock-guarded recovery, and workspace cleanup that removes only transaction-created empty directories. Add the Kiro IDE 1.0 plugin: strict v1 hook schema with one enabled PreToolUse guard and PostToolUse audit pair bound to the active workspace, agent-to-workspace identity binding in runtime config, and exact migration of the legacy elydora-audit.kiro.hook contract. Gates: go test ./... and -race pass, go vet clean, govulncheck reports zero reachable vulnerabilities, git diff --check clean.
This commit is contained in:
1 parent
dd72793cdb
commit
eb41b991bf
70 files changed
+8182
-358
No files matched your search
@@ -0,0 +1,192 @@
|
||||
package plugins
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
)
|
||||
|
||||
type kiroIdePaths struct {
|
||||
homeDirectory string
|
||||
workspaceRoot string
|
||||
kiroDirectory string
|
||||
hooksDirectory string
|
||||
configPath string
|
||||
legacyPath string
|
||||
}
|
||||
|
||||
type kiroIdeSources struct {
|
||||
paths *kiroIdePaths
|
||||
document *kiroIdeDocument
|
||||
legacy *kiroIdeLegacyDocument
|
||||
initialDirectories []kiroIdeDirectoryIdentity
|
||||
}
|
||||
|
||||
func resolveKiroIdePaths() (*kiroIdePaths, error) {
|
||||
workspace, err := os.Getwd()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve Kiro IDE workspace: %w", err)
|
||||
}
|
||||
workspace, err = filepath.Abs(workspace)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve Kiro IDE workspace path: %w", err)
|
||||
}
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("resolve home directory: %w", err)
|
||||
}
|
||||
kiroDirectory := filepath.Join(workspace, ".kiro")
|
||||
hooksDirectory := filepath.Join(kiroDirectory, "hooks")
|
||||
return &kiroIdePaths{
|
||||
homeDirectory: home, workspaceRoot: workspace,
|
||||
kiroDirectory: kiroDirectory, hooksDirectory: hooksDirectory,
|
||||
configPath: filepath.Join(hooksDirectory, kiroIdeConfigFile),
|
||||
legacyPath: filepath.Join(home, ".kiro", "hooks", kiroIdeLegacyFile),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func inspectKiroIdeWorkspace(paths *kiroIdePaths) error {
|
||||
exists, err := managedPhysicalDirectoryExists(
|
||||
paths.workspaceRoot,
|
||||
"Kiro IDE workspace",
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("Kiro IDE workspace is missing: %s", paths.workspaceRoot)
|
||||
}
|
||||
if _, err := managedPhysicalDirectoryExists(
|
||||
paths.kiroDirectory,
|
||||
"Kiro IDE configuration directory",
|
||||
); err != nil {
|
||||
return err
|
||||
}
|
||||
_, err = managedPhysicalDirectoryExists(
|
||||
paths.hooksDirectory,
|
||||
"Kiro IDE hooks directory",
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
func inspectKiroIdeLegacyDirectories(paths *kiroIdePaths) error {
|
||||
kiroDirectory := filepath.Dir(filepath.Dir(paths.legacyPath))
|
||||
hooksDirectory := filepath.Dir(paths.legacyPath)
|
||||
for _, item := range []struct{ path, label string }{
|
||||
{kiroDirectory, "legacy Kiro IDE configuration directory"},
|
||||
{hooksDirectory, "legacy Kiro IDE hooks directory"},
|
||||
} {
|
||||
exists, err := managedPhysicalDirectoryExists(item.path, item.label)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if !exists {
|
||||
return fmt.Errorf("%s is missing: %s", item.label, item.path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readKiroIdeSources() (*kiroIdeSources, error) {
|
||||
paths, err := resolveKiroIdePaths()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialDirectories := make([]kiroIdeDirectoryIdentity, 0, 5)
|
||||
for _, item := range []struct{ path, label string }{
|
||||
{paths.workspaceRoot, "Kiro IDE workspace"},
|
||||
{paths.kiroDirectory, "Kiro IDE configuration directory"},
|
||||
{paths.hooksDirectory, "Kiro IDE hooks directory"},
|
||||
} {
|
||||
identity, err := snapshotKiroIdeDirectory(item.path, item.label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialDirectories = append(initialDirectories, identity)
|
||||
}
|
||||
if err := inspectKiroIdeWorkspace(paths); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
workspaceSnapshot, err := readManagedFile(
|
||||
paths.configPath,
|
||||
"Kiro IDE hooks",
|
||||
maxManagedSourceBytes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
document, err := parseKiroIdeDocument(paths.configPath, workspaceSnapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
legacySnapshot, err := readManagedFile(
|
||||
paths.legacyPath,
|
||||
"legacy Kiro IDE hook",
|
||||
maxManagedSourceBytes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if legacySnapshot != nil {
|
||||
if err := inspectKiroIdeLegacyDirectories(paths); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, item := range []struct{ path, label string }{
|
||||
{
|
||||
filepath.Dir(filepath.Dir(paths.legacyPath)),
|
||||
"legacy Kiro IDE configuration directory",
|
||||
},
|
||||
{filepath.Dir(paths.legacyPath), "legacy Kiro IDE hooks directory"},
|
||||
} {
|
||||
identity, err := snapshotKiroIdeDirectory(item.path, item.label)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
initialDirectories = append(initialDirectories, identity)
|
||||
}
|
||||
current, err := readManagedFile(
|
||||
paths.legacyPath,
|
||||
"legacy Kiro IDE hook",
|
||||
maxManagedSourceBytes,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !sameManagedSnapshot(current, legacySnapshot) {
|
||||
return nil, fmt.Errorf(
|
||||
"legacy Kiro IDE hook changed while reading: %s",
|
||||
paths.legacyPath,
|
||||
)
|
||||
}
|
||||
}
|
||||
legacy, err := parseKiroIdeLegacyDocument(paths.legacyPath, legacySnapshot)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := assertKiroIdeDirectoryStates(
|
||||
initialDirectories,
|
||||
"Kiro IDE source read",
|
||||
); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &kiroIdeSources{
|
||||
paths: paths, document: document, legacy: legacy,
|
||||
initialDirectories: initialDirectories,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func prepareRenderedKiroIdeChange(
|
||||
rendered *kiroIdeRenderedDocument,
|
||||
) (*fileChange, error) {
|
||||
if rendered == nil || !rendered.changed {
|
||||
return nil, nil
|
||||
}
|
||||
return prepareSnapshotSourceChange(
|
||||
rendered.document.filePath,
|
||||
"Kiro IDE hooks",
|
||||
rendered.document.snapshot,
|
||||
rendered.next,
|
||||
0600,
|
||||
rendered.remove,
|
||||
)
|
||||
}
|
||||
Reference in new issue
Block a user