@dannylee1020/kkt 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/README.md +275 -0
- package/assets/kkt-readme-modern.png +0 -0
- package/bin/kkt-install.mjs +25 -0
- package/cmd/kkt/main.go +15 -0
- package/go.mod +3 -0
- package/internal/workflow/cli.go +349 -0
- package/internal/workflow/cli_test.go +1266 -0
- package/internal/workflow/guardrails.go +900 -0
- package/internal/workflow/init.go +164 -0
- package/internal/workflow/init_test.go +113 -0
- package/internal/workflow/operations.go +1855 -0
- package/internal/workflow/validation_commands.go +291 -0
- package/internal/workflow/workspace.go +802 -0
- package/internal/workflow/workspace_test.go +285 -0
- package/package.json +45 -0
- package/scripts/install-cli.sh +210 -0
- package/scripts/install.sh +644 -0
- package/skills/kkt/SKILL.md +74 -0
- package/skills/kkt/references/discovery-tooling.md +53 -0
- package/skills/kkt/references/feature-optimization-model.md +120 -0
- package/skills/kkt/references/kkt-kernel.md +58 -0
- package/skills/kkt/references/layered-modeling-methods.md +101 -0
- package/skills/kkt/references/plan-assimilation.md +46 -0
- package/skills/kkt/references/schemas.md +231 -0
- package/skills/kkt/references/state-contract.md +76 -0
- package/skills/kkt-loop/SKILL.md +99 -0
- package/skills/kkt-loop/references/discovery-tooling.md +53 -0
- package/skills/kkt-loop/references/feature-optimization-model.md +120 -0
- package/skills/kkt-loop/references/kkt-kernel.md +58 -0
- package/skills/kkt-loop/references/layered-modeling-methods.md +101 -0
- package/skills/kkt-loop/references/plan-assimilation.md +46 -0
- package/skills/kkt-loop/references/schemas.md +231 -0
- package/skills/kkt-loop/references/state-contract.md +76 -0
- package/skills/kkt-model/SKILL.md +76 -0
- package/skills/kkt-model/references/discovery-tooling.md +53 -0
- package/skills/kkt-model/references/feature-optimization-model.md +120 -0
- package/skills/kkt-model/references/kkt-kernel.md +58 -0
- package/skills/kkt-model/references/layered-modeling-methods.md +101 -0
- package/skills/kkt-model/references/plan-assimilation.md +46 -0
- package/skills/kkt-model/references/schemas.md +231 -0
- package/skills/kkt-model/references/state-contract.md +76 -0
- package/skills/kkt-run/SKILL.md +62 -0
- package/skills/kkt-run/references/discovery-tooling.md +53 -0
- package/skills/kkt-run/references/feature-optimization-model.md +120 -0
- package/skills/kkt-run/references/kkt-kernel.md +58 -0
- package/skills/kkt-run/references/layered-modeling-methods.md +101 -0
- package/skills/kkt-run/references/plan-assimilation.md +46 -0
- package/skills/kkt-run/references/schemas.md +231 -0
- package/skills/kkt-run/references/state-contract.md +76 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
package workflow
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"fmt"
|
|
5
|
+
"os"
|
|
6
|
+
"path/filepath"
|
|
7
|
+
"strings"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
const (
|
|
11
|
+
instructionStart = "<!-- kkt-workflow:start -->"
|
|
12
|
+
instructionEnd = "<!-- kkt-workflow:end -->"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type InitPlan struct {
|
|
16
|
+
Agent string
|
|
17
|
+
Path string
|
|
18
|
+
Remove bool
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
func UninstallPlans(agent string) ([]InitPlan, error) {
|
|
22
|
+
home, err := os.UserHomeDir()
|
|
23
|
+
if err != nil {
|
|
24
|
+
return nil, err
|
|
25
|
+
}
|
|
26
|
+
return UninstallPlansWithHome(agent, home)
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
func UninstallPlansWithHome(agent, home string) ([]InitPlan, error) {
|
|
30
|
+
targets, err := expandAgent(agent)
|
|
31
|
+
if err != nil {
|
|
32
|
+
return nil, err
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
plans := []InitPlan{}
|
|
36
|
+
for _, target := range targets {
|
|
37
|
+
plans = append(plans, uninstallPlansForTarget(target, home)...)
|
|
38
|
+
}
|
|
39
|
+
if needsLegacyCleanup(targets) {
|
|
40
|
+
plans = append(plans, legacyCleanupPlans(home)...)
|
|
41
|
+
}
|
|
42
|
+
return dedupePlans(plans), nil
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
func uninstallPlansForTarget(agent, home string) []InitPlan {
|
|
46
|
+
plans := []InitPlan{
|
|
47
|
+
{
|
|
48
|
+
Agent: agent,
|
|
49
|
+
Path: filepath.Join(home, instructionPath(agent)),
|
|
50
|
+
Remove: true,
|
|
51
|
+
},
|
|
52
|
+
}
|
|
53
|
+
if usesInstructionReference(agent) {
|
|
54
|
+
plans = append(plans, InitPlan{
|
|
55
|
+
Agent: agent,
|
|
56
|
+
Path: filepath.Join(home, kktInstructionPath(agent)),
|
|
57
|
+
Remove: true,
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
return plans
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
func expandAgent(agent string) ([]string, error) {
|
|
64
|
+
switch agent {
|
|
65
|
+
case "all":
|
|
66
|
+
return []string{"codex", "claude", "opencode", "pi"}, nil
|
|
67
|
+
case "codex", "claude", "opencode", "pi":
|
|
68
|
+
return []string{agent}, nil
|
|
69
|
+
default:
|
|
70
|
+
return nil, fmt.Errorf("unsupported agent %q", agent)
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
func instructionPath(agent string) string {
|
|
75
|
+
switch agent {
|
|
76
|
+
case "codex":
|
|
77
|
+
return filepath.Join(".codex", "AGENTS.md")
|
|
78
|
+
case "claude":
|
|
79
|
+
return filepath.Join(".claude", "CLAUDE.md")
|
|
80
|
+
case "opencode":
|
|
81
|
+
return filepath.Join(".config", "opencode", "AGENTS.md")
|
|
82
|
+
case "pi":
|
|
83
|
+
return filepath.Join(".pi", "agent", "AGENTS.md")
|
|
84
|
+
}
|
|
85
|
+
return ""
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
func kktInstructionPath(agent string) string {
|
|
89
|
+
switch agent {
|
|
90
|
+
case "codex":
|
|
91
|
+
return filepath.Join(".codex", "KKT.md")
|
|
92
|
+
case "claude":
|
|
93
|
+
return filepath.Join(".claude", "KKT.md")
|
|
94
|
+
}
|
|
95
|
+
return ""
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
func usesInstructionReference(agent string) bool {
|
|
99
|
+
return agent == "codex" || agent == "claude"
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
func needsLegacyCleanup(agents []string) bool {
|
|
103
|
+
for _, agent := range agents {
|
|
104
|
+
switch agent {
|
|
105
|
+
case "codex", "opencode", "pi":
|
|
106
|
+
return true
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
return false
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
func legacyCleanupPlans(home string) []InitPlan {
|
|
113
|
+
return []InitPlan{
|
|
114
|
+
{
|
|
115
|
+
Agent: "legacy",
|
|
116
|
+
Path: filepath.Join(home, ".agents", "AGENTS.md"),
|
|
117
|
+
Remove: true,
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
Agent: "legacy",
|
|
121
|
+
Path: filepath.Join(home, ".agents", "KKT.md"),
|
|
122
|
+
Remove: true,
|
|
123
|
+
},
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
func dedupePlans(plans []InitPlan) []InitPlan {
|
|
128
|
+
seen := map[string]bool{}
|
|
129
|
+
deduped := []InitPlan{}
|
|
130
|
+
for _, plan := range plans {
|
|
131
|
+
key := fmt.Sprintf("%t:%s", plan.Remove, plan.Path)
|
|
132
|
+
if seen[key] {
|
|
133
|
+
continue
|
|
134
|
+
}
|
|
135
|
+
seen[key] = true
|
|
136
|
+
deduped = append(deduped, plan)
|
|
137
|
+
}
|
|
138
|
+
return deduped
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
func RemoveInstruction(path string) (bool, error) {
|
|
142
|
+
existingBytes, err := os.ReadFile(path)
|
|
143
|
+
if os.IsNotExist(err) {
|
|
144
|
+
return false, nil
|
|
145
|
+
}
|
|
146
|
+
if err != nil {
|
|
147
|
+
return false, err
|
|
148
|
+
}
|
|
149
|
+
existing := string(existingBytes)
|
|
150
|
+
start := strings.Index(existing, instructionStart)
|
|
151
|
+
end := strings.Index(existing, instructionEnd)
|
|
152
|
+
if start < 0 || end < 0 || end < start {
|
|
153
|
+
return false, nil
|
|
154
|
+
}
|
|
155
|
+
end += len(instructionEnd)
|
|
156
|
+
next := strings.Trim(existing[:start]+existing[end:], "\n")
|
|
157
|
+
if next != "" {
|
|
158
|
+
next += "\n"
|
|
159
|
+
}
|
|
160
|
+
if next == existing {
|
|
161
|
+
return false, nil
|
|
162
|
+
}
|
|
163
|
+
return true, os.WriteFile(path, []byte(next), 0o644)
|
|
164
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
package workflow
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"os"
|
|
5
|
+
"path/filepath"
|
|
6
|
+
"strings"
|
|
7
|
+
"testing"
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
func TestRemoveInstructionPreservesExistingContent(t *testing.T) {
|
|
11
|
+
root := t.TempDir()
|
|
12
|
+
path := filepath.Join(root, "AGENTS.md")
|
|
13
|
+
existing := strings.Join([]string{
|
|
14
|
+
"# Existing",
|
|
15
|
+
"",
|
|
16
|
+
instructionStart,
|
|
17
|
+
"# KKT Workflow",
|
|
18
|
+
instructionEnd,
|
|
19
|
+
"",
|
|
20
|
+
"Keep this.",
|
|
21
|
+
"",
|
|
22
|
+
}, "\n")
|
|
23
|
+
if err := os.WriteFile(path, []byte(existing), 0o644); err != nil {
|
|
24
|
+
t.Fatal(err)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
changed, err := RemoveInstruction(path)
|
|
28
|
+
if err != nil {
|
|
29
|
+
t.Fatal(err)
|
|
30
|
+
}
|
|
31
|
+
if !changed {
|
|
32
|
+
t.Fatal("RemoveInstruction should report changed")
|
|
33
|
+
}
|
|
34
|
+
result, err := os.ReadFile(path)
|
|
35
|
+
if err != nil {
|
|
36
|
+
t.Fatal(err)
|
|
37
|
+
}
|
|
38
|
+
text := string(result)
|
|
39
|
+
if strings.Contains(text, instructionStart) || strings.Contains(text, instructionEnd) {
|
|
40
|
+
t.Fatal("KKT markers were not removed")
|
|
41
|
+
}
|
|
42
|
+
if !strings.Contains(text, "# Existing") || !strings.Contains(text, "Keep this.") {
|
|
43
|
+
t.Fatal("existing content was not preserved")
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
func TestRemoveInstructionIgnoresMissingOrUnmanagedFiles(t *testing.T) {
|
|
48
|
+
root := t.TempDir()
|
|
49
|
+
missingChanged, err := RemoveInstruction(filepath.Join(root, "missing.md"))
|
|
50
|
+
if err != nil {
|
|
51
|
+
t.Fatal(err)
|
|
52
|
+
}
|
|
53
|
+
if missingChanged {
|
|
54
|
+
t.Fatal("missing file should not report changed")
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
path := filepath.Join(root, "AGENTS.md")
|
|
58
|
+
if err := os.WriteFile(path, []byte("# Existing\n"), 0o644); err != nil {
|
|
59
|
+
t.Fatal(err)
|
|
60
|
+
}
|
|
61
|
+
unmanagedChanged, err := RemoveInstruction(path)
|
|
62
|
+
if err != nil {
|
|
63
|
+
t.Fatal(err)
|
|
64
|
+
}
|
|
65
|
+
if unmanagedChanged {
|
|
66
|
+
t.Fatal("unmanaged file should not report changed")
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
func TestUninstallPlansUsesGlobalAgentLocations(t *testing.T) {
|
|
71
|
+
home := t.TempDir()
|
|
72
|
+
plans, err := UninstallPlansWithHome("all", home)
|
|
73
|
+
if err != nil {
|
|
74
|
+
t.Fatal(err)
|
|
75
|
+
}
|
|
76
|
+
paths := map[string]bool{}
|
|
77
|
+
for _, plan := range plans {
|
|
78
|
+
if !plan.Remove {
|
|
79
|
+
t.Fatalf("uninstall plan should only contain remove actions: %s", plan.Path)
|
|
80
|
+
}
|
|
81
|
+
paths[plan.Path] = true
|
|
82
|
+
}
|
|
83
|
+
expectedPaths := []string{
|
|
84
|
+
filepath.Join(home, ".codex", "AGENTS.md"),
|
|
85
|
+
filepath.Join(home, ".codex", "KKT.md"),
|
|
86
|
+
filepath.Join(home, ".claude", "CLAUDE.md"),
|
|
87
|
+
filepath.Join(home, ".claude", "KKT.md"),
|
|
88
|
+
filepath.Join(home, ".pi", "agent", "AGENTS.md"),
|
|
89
|
+
filepath.Join(home, ".config", "opencode", "AGENTS.md"),
|
|
90
|
+
filepath.Join(home, ".agents", "AGENTS.md"),
|
|
91
|
+
filepath.Join(home, ".agents", "KKT.md"),
|
|
92
|
+
}
|
|
93
|
+
for _, path := range expectedPaths {
|
|
94
|
+
if !paths[path] {
|
|
95
|
+
t.Fatalf("missing uninstall path: %s", path)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
func TestUninstallPlansDedupesInstructionPaths(t *testing.T) {
|
|
101
|
+
home := t.TempDir()
|
|
102
|
+
plans, err := UninstallPlansWithHome("all", home)
|
|
103
|
+
if err != nil {
|
|
104
|
+
t.Fatal(err)
|
|
105
|
+
}
|
|
106
|
+
seen := map[string]bool{}
|
|
107
|
+
for _, plan := range plans {
|
|
108
|
+
if seen[plan.Path] {
|
|
109
|
+
t.Fatalf("duplicate instruction path: %s", plan.Path)
|
|
110
|
+
}
|
|
111
|
+
seen[plan.Path] = true
|
|
112
|
+
}
|
|
113
|
+
}
|