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.
193 lines
4.8 KiB
Go
193 lines
4.8 KiB
Go
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,
|
|
)
|
|
}
|