@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.
Files changed (50) hide show
  1. package/LICENSE +201 -0
  2. package/README.md +275 -0
  3. package/assets/kkt-readme-modern.png +0 -0
  4. package/bin/kkt-install.mjs +25 -0
  5. package/cmd/kkt/main.go +15 -0
  6. package/go.mod +3 -0
  7. package/internal/workflow/cli.go +349 -0
  8. package/internal/workflow/cli_test.go +1266 -0
  9. package/internal/workflow/guardrails.go +900 -0
  10. package/internal/workflow/init.go +164 -0
  11. package/internal/workflow/init_test.go +113 -0
  12. package/internal/workflow/operations.go +1855 -0
  13. package/internal/workflow/validation_commands.go +291 -0
  14. package/internal/workflow/workspace.go +802 -0
  15. package/internal/workflow/workspace_test.go +285 -0
  16. package/package.json +45 -0
  17. package/scripts/install-cli.sh +210 -0
  18. package/scripts/install.sh +644 -0
  19. package/skills/kkt/SKILL.md +74 -0
  20. package/skills/kkt/references/discovery-tooling.md +53 -0
  21. package/skills/kkt/references/feature-optimization-model.md +120 -0
  22. package/skills/kkt/references/kkt-kernel.md +58 -0
  23. package/skills/kkt/references/layered-modeling-methods.md +101 -0
  24. package/skills/kkt/references/plan-assimilation.md +46 -0
  25. package/skills/kkt/references/schemas.md +231 -0
  26. package/skills/kkt/references/state-contract.md +76 -0
  27. package/skills/kkt-loop/SKILL.md +99 -0
  28. package/skills/kkt-loop/references/discovery-tooling.md +53 -0
  29. package/skills/kkt-loop/references/feature-optimization-model.md +120 -0
  30. package/skills/kkt-loop/references/kkt-kernel.md +58 -0
  31. package/skills/kkt-loop/references/layered-modeling-methods.md +101 -0
  32. package/skills/kkt-loop/references/plan-assimilation.md +46 -0
  33. package/skills/kkt-loop/references/schemas.md +231 -0
  34. package/skills/kkt-loop/references/state-contract.md +76 -0
  35. package/skills/kkt-model/SKILL.md +76 -0
  36. package/skills/kkt-model/references/discovery-tooling.md +53 -0
  37. package/skills/kkt-model/references/feature-optimization-model.md +120 -0
  38. package/skills/kkt-model/references/kkt-kernel.md +58 -0
  39. package/skills/kkt-model/references/layered-modeling-methods.md +101 -0
  40. package/skills/kkt-model/references/plan-assimilation.md +46 -0
  41. package/skills/kkt-model/references/schemas.md +231 -0
  42. package/skills/kkt-model/references/state-contract.md +76 -0
  43. package/skills/kkt-run/SKILL.md +62 -0
  44. package/skills/kkt-run/references/discovery-tooling.md +53 -0
  45. package/skills/kkt-run/references/feature-optimization-model.md +120 -0
  46. package/skills/kkt-run/references/kkt-kernel.md +58 -0
  47. package/skills/kkt-run/references/layered-modeling-methods.md +101 -0
  48. package/skills/kkt-run/references/plan-assimilation.md +46 -0
  49. package/skills/kkt-run/references/schemas.md +231 -0
  50. 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
+ }