-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathsetup.go
More file actions
388 lines (331 loc) · 15.6 KB
/
setup.go
File metadata and controls
388 lines (331 loc) · 15.6 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
package main
import (
"errors"
"fmt"
"os"
"path/filepath"
"runtime"
"strings"
)
const (
deftRepoURL = "https://github.com/deftai/directive"
// agentsMDEntry is written into the project's AGENTS.md during install.
// It must contain agentsMDSentinel ("deft/main.md") for idempotency —
// WriteAgentsMD checks for that string before appending.
agentsMDEntry = `# Deft — AI Development Framework
Deft is installed in deft/. Full guidelines: deft/main.md
! If any deft/skills/ path referenced in this file cannot be read (missing file, stale path from a previous framework version, or a deprecation redirect stub), read deft/QUICK-START.md instead and follow it. QUICK-START refreshes this section idempotently for the current framework version.
## Pre-Cutover Check (run before First Session / Returning Sessions)
! Before the First Session / Returning Sessions checks below, detect whether this project pre-dates the v0.20 vBRIEF-centric model. If it does, migration MUST happen before any Phase 1, Phase 2, or Returning-Sessions routing fires.
**Pre-cutover detected** if ANY of the following are true:
- ./SPECIFICATION.md exists and its first 200 characters do NOT contain <!-- deft:deprecated-redirect -->
- ./PROJECT.md exists and its first 200 characters do NOT contain <!-- deft:deprecated-redirect -->
- ./vbrief/ exists but any of the five lifecycle subfolders (proposed/, pending/, active/, completed/, cancelled/) is missing
→ On detection: read deft/skills/deft-directive-setup/SKILL.md "Pre-Cutover Detection Guard" section and follow the migration path BEFORE any other action. The Migrating from pre-v0.20 section of the full guidelines has the canonical command, the "task -t ./deft/Taskfile.yml migrate:vbrief" fallback (for when "task migrate:vbrief" is not resolvable from the project root), what migration produces, and the available safety flags.
⊗ Start Phase 1, Phase 2, or a Returning-Sessions workflow while pre-cutover artifacts are present — run migration first.
## First Session
Check what exists before doing anything else:
**USER.md missing** (~/.config/deft/USER.md or %APPDATA%\deft\USER.md):
→ Read deft/skills/deft-directive-setup/SKILL.md and start Phase 1 (user preferences)
**USER.md exists, PROJECT-DEFINITION.vbrief.json missing** (./vbrief/):
→ Read deft/skills/deft-directive-setup/SKILL.md and start Phase 2 (project definition)
## Returning Sessions
When all config exists: read the guidelines, your USER.md preferences, and PROJECT-DEFINITION.vbrief.json, then continue with your task.
~ Run deft/skills/deft-directive-sync/SKILL.md to pull latest framework updates and validate project files.
## Commands
- /deft:change <name> — Propose a scoped change
- /deft:run:interview — Structured spec interview
- /deft:run:speckit — Five-phase spec workflow (large projects)
- /deft:run:discuss <topic> — Feynman-style alignment
- /deft:run:research <topic> — Research before planning
- /deft:run:map — Map an existing codebase
- deft/run bootstrap — CLI setup (terminal users)
- deft/run spec — CLI spec generation
`
// Sentinel used to detect an existing deft entry in AGENTS.md.
agentsMDSentinel = "deft/main.md"
// agentsSkillDeft is the thin pointer content for .agents/skills/deft/SKILL.md.
agentsSkillDeft = `---
name: deft
description: Apply deft framework standards for AI-assisted development. Use when starting projects, writing code, running tests, making commits, or when the user references deft, project standards, or coding guidelines.
---
Read and follow: deft/SKILL.md
`
// agentsSkillDeftDirectiveSetup is the thin pointer for .agents/skills/deft-directive-setup/SKILL.md.
agentsSkillDeftDirectiveSetup = `---
name: deft-directive-setup
description: >-
Set up a new project with Deft framework standards. Use when the user wants
to bootstrap user preferences, configure a project, or generate a project
specification. Walks through setup conversationally — no separate CLI needed.
---
Read and follow: deft/skills/deft-directive-setup/SKILL.md
`
// agentsSkillDeftDirectiveBuild is the thin pointer for .agents/skills/deft-directive-build/SKILL.md.
agentsSkillDeftDirectiveBuild = `---
name: deft-directive-build
description: >-
Build a project from scope vBRIEFs following Deft framework standards.
Use after deft-directive-setup has generated the project definition, or when
the user has scope vBRIEFs ready to implement. Handles scaffolding,
implementation, testing, and quality checks phase by phase.
---
Read and follow: deft/skills/deft-directive-build/SKILL.md
`
// agentsSkillDeftDirectiveReviewCycle is the thin pointer for .agents/skills/deft-directive-review-cycle/SKILL.md.
agentsSkillDeftDirectiveReviewCycle = `---
name: deft-directive-review-cycle
description: >-
Greptile bot reviewer response workflow. Use when running a review cycle
on a PR — to audit process prerequisites, fetch bot findings, fix all
issues in a single batch commit, and exit cleanly when no P0/P1 issues
remain. Enables cloud agents to run autonomous PR review cycles.
---
Read and follow: deft/skills/deft-directive-review-cycle/SKILL.md
`
// agentsSkillDeftDirectiveRefinement is the thin pointer for .agents/skills/deft-directive-refinement/SKILL.md.
agentsSkillDeftDirectiveRefinement = `---
name: deft-directive-refinement
description: >-
Structured refinement workflow. Compares open GitHub issues against
the roadmap, triages new issues one-at-a-time with human review, and updates
the roadmap with phase placement, analysis comments, and index entries.
---
Read and follow: deft/skills/deft-directive-refinement/SKILL.md
`
// agentsSkillDeftDirectiveSwarm is the thin pointer for .agents/skills/deft-directive-swarm/SKILL.md.
agentsSkillDeftDirectiveSwarm = `---
name: deft-directive-swarm
description: >-
Parallel local agent orchestration. Use when running multiple agents
on roadmap items simultaneously — to select non-overlapping tasks, set up
isolated worktrees, launch agents with proven prompts, monitor progress,
handle stalled review cycles, and close out PRs cleanly.
---
Read and follow: deft/skills/deft-directive-swarm/SKILL.md
`
// agentsSkillDeftDirectiveInterview is the thin pointer for .agents/skills/deft-directive-interview/SKILL.md.
agentsSkillDeftDirectiveInterview = `---
name: deft-directive-interview
description: >-
Deterministic structured Q&A interview skill. Use when a skill or workflow
needs to collect structured answers from the user — one question per turn,
numbered options, default acceptance, and a confirmation gate.
---
Read and follow: deft/skills/deft-directive-interview/SKILL.md
`
// agentsSkillDeftDirectivePrePr is the thin pointer for .agents/skills/deft-directive-pre-pr/SKILL.md.
agentsSkillDeftDirectivePrePr = `---
name: deft-directive-pre-pr
description: >-
Iterative pre-PR quality loop (Read-Write-Lint-Diff-Loop). Use before
pushing a branch for PR creation — structured self-review that agents run
to catch issues before they reach the bot reviewer.
---
Read and follow: deft/skills/deft-directive-pre-pr/SKILL.md
`
// agentsSkillDeftDirectiveSync is the thin pointer for .agents/skills/deft-directive-sync/SKILL.md.
agentsSkillDeftDirectiveSync = `---
name: deft-directive-sync
description: >-
Session-start framework sync skill. Use at the beginning of a session to
pull latest framework updates, validate project files, and confirm alignment
before starting work.
---
Read and follow: deft/skills/deft-directive-sync/SKILL.md
`
)
// ---------------------------------------------------------------------------
// 4.1 Clone deft
// ---------------------------------------------------------------------------
// CloneDeft clones the deft repository into deftDir.
// The parent directory (projectDir) is created if it does not exist.
// If branch is non-empty the clone checks out that branch.
func CloneDeft(w *Wizard, result *WizardResult, branch string) error {
// Ensure the project directory exists.
if err := os.MkdirAll(result.ProjectDir, 0o755); err != nil {
return fmt.Errorf("could not create project directory: %w", err)
}
args := []string{"clone"}
if branch != "" {
args = append(args, "--branch", branch)
w.printf("Cloning deft (branch %s) into %s ...\n", branch, result.DeftDir)
} else {
w.printf("Cloning deft into %s ...\n", result.DeftDir)
}
args = append(args, deftRepoURL, result.DeftDir)
if err := runCmdFunc(w.out, "git", args...); err != nil {
w.printf("\nClone failed. Please check your internet connection and try again.\n")
return fmt.Errorf("git clone failed: %w", err)
}
return nil
}
// UpdateDeft fetches the latest changes and optionally switches branch.
// Used when deft/ already exists and the user chose to update.
func UpdateDeft(w *Wizard, result *WizardResult, branch string) error {
w.printf("Updating deft at %s ...\n", result.DeftDir)
// Fetch latest from origin.
if err := runCmdFunc(w.out, "git", "-C", result.DeftDir, "fetch", "origin"); err != nil {
return fmt.Errorf("git fetch failed: %w", err)
}
// Switch branch if requested.
if branch != "" {
w.printf("Switching to branch %s ...\n", branch)
if err := runCmdFunc(w.out, "git", "-C", result.DeftDir, "checkout", branch); err != nil {
return fmt.Errorf("git checkout %s failed: %w", branch, err)
}
}
// Pull latest changes.
if err := runCmdFunc(w.out, "git", "-C", result.DeftDir, "pull"); err != nil {
return fmt.Errorf("git pull failed: %w", err)
}
w.printf("Deft updated successfully.\n")
return nil
}
// ---------------------------------------------------------------------------
// 4.2 Write AGENTS.md
// ---------------------------------------------------------------------------
// WriteAgentsMD creates or appends deft entries to AGENTS.md in the project
// folder. If the entries already exist the file is left unchanged (idempotent).
func WriteAgentsMD(w *Wizard, projectDir string) error {
path := filepath.Join(projectDir, "AGENTS.md")
existing, err := os.ReadFile(path)
if err == nil {
// File exists — check for existing deft entry.
if strings.Contains(string(existing), agentsMDSentinel) {
w.printf("AGENTS.md already contains deft entries — skipping.\n")
return nil
}
// Append to existing file.
content := string(existing)
if !strings.HasSuffix(content, "\n") {
content += "\n"
}
content += "\n" + agentsMDEntry
if err := os.WriteFile(path, []byte(content), 0o644); err != nil {
return fmt.Errorf("could not update AGENTS.md: %w", err)
}
w.printf("AGENTS.md updated with deft entries.\n")
return nil
}
// File does not exist — create it.
if err := os.WriteFile(path, []byte(agentsMDEntry), 0o644); err != nil {
return fmt.Errorf("could not create AGENTS.md: %w", err)
}
w.printf("AGENTS.md created.\n")
return nil
}
// ---------------------------------------------------------------------------
// 4.3 Write .agents/skills/ thin pointer files
// ---------------------------------------------------------------------------
// WriteAgentsSkills creates the .agents/skills/ discovery structure in the
// project root so AI agents auto-discover deft skills without user prompting.
// Each skill gets its own subdirectory with a thin SKILL.md pointer that
// redirects agents to the canonical skill files inside deft/.
// Idempotent — skips only when all skill files are present.
// Returns true if files were created, false if skipped.
func WriteAgentsSkills(w *Wizard, projectDir string) (bool, error) {
// All skills that the installer creates thin pointers for.
allSkillNames := []string{
"deft", "deft-directive-setup", "deft-directive-build",
"deft-directive-review-cycle", "deft-directive-refinement", "deft-directive-swarm",
"deft-directive-interview", "deft-directive-pre-pr", "deft-directive-sync",
}
// Check all skill files before deciding to skip.
allExist := true
for _, skill := range allSkillNames {
p := filepath.Join(projectDir, ".agents", "skills", skill, "SKILL.md")
if _, err := os.Stat(p); err != nil {
if !errors.Is(err, os.ErrNotExist) {
return false, fmt.Errorf("could not check %s: %w", p, err)
}
allExist = false
break
}
}
if allExist {
w.printf(".agents/skills/ already present — skipping.\n")
return false, nil
}
skills := []struct {
dir string
content string
}{
{"deft", agentsSkillDeft},
{"deft-directive-setup", agentsSkillDeftDirectiveSetup},
{"deft-directive-build", agentsSkillDeftDirectiveBuild},
{"deft-directive-review-cycle", agentsSkillDeftDirectiveReviewCycle},
{"deft-directive-refinement", agentsSkillDeftDirectiveRefinement},
{"deft-directive-swarm", agentsSkillDeftDirectiveSwarm},
{"deft-directive-interview", agentsSkillDeftDirectiveInterview},
{"deft-directive-pre-pr", agentsSkillDeftDirectivePrePr},
{"deft-directive-sync", agentsSkillDeftDirectiveSync},
}
for _, skill := range skills {
dir := filepath.Join(projectDir, ".agents", "skills", skill.dir)
if err := os.MkdirAll(dir, 0o755); err != nil {
return false, fmt.Errorf("could not create %s: %w", dir, err)
}
path := filepath.Join(dir, "SKILL.md")
if _, err := os.Stat(path); err == nil {
continue // already present — leave as-is
}
if err := os.WriteFile(path, []byte(skill.content), 0o644); err != nil {
return false, fmt.Errorf("could not write %s: %w", path, err)
}
}
w.printf(".agents/skills/ created — deft skills will be auto-discovered.\n")
return true, nil
}
// ---------------------------------------------------------------------------
// 4.4 Create USER.md config directory
// ---------------------------------------------------------------------------
// UserConfigDir returns the platform-appropriate deft config directory.
//
// Windows: %APPDATA%\deft\
// macOS/Linux: ~/.config/deft/
// Override: DEFT_USER_PATH env var
func UserConfigDir() string {
if p := os.Getenv("DEFT_USER_PATH"); p != "" {
return p
}
if runtime.GOOS == "windows" {
return filepath.Join(os.Getenv("APPDATA"), "deft")
}
home, _ := os.UserHomeDir()
return filepath.Join(home, ".config", "deft")
}
// CreateUserConfigDir ensures the user config directory exists.
// If USER.md already exists inside it, a note is printed but no error is returned.
func CreateUserConfigDir(w *Wizard) (string, error) {
dir := UserConfigDir()
if err := os.MkdirAll(dir, 0o755); err != nil {
return "", fmt.Errorf("could not create config directory %s: %w", dir, err)
}
userMD := filepath.Join(dir, "USER.md")
if _, err := os.Stat(userMD); err == nil {
w.printf("USER.md already exists at %s — keeping existing file.\n", userMD)
}
return dir, nil
}
// ---------------------------------------------------------------------------
// 4.5 Print next steps
// ---------------------------------------------------------------------------
// PrintNextSteps displays the success banner and post-install instructions.
func PrintNextSteps(w *Wizard, result *WizardResult, configDir string, skillsCreated bool) {
skillsStatus := "already present"
if skillsCreated {
skillsStatus = "created"
}
w.printf("\n✓ Deft installed successfully!\n\n")
w.printf(" Location : %s%c\n", result.DeftDir, os.PathSeparator)
w.printf(" AGENTS.md : updated\n")
w.printf(" Skills : .agents/skills/ %s (auto-discovered by AI agents)\n", skillsStatus)
w.printf(" User config : %s%c\n", configDir, os.PathSeparator)
w.printf("\nNext steps:\n")
w.printf(" 1. Open your AI coding assistant in %s%c\n", result.ProjectDir, os.PathSeparator)
w.printf(" 2. Deft skill auto-discovery is partially implemented — if your agent doesn't\n")
w.printf(" start setup automatically, tell it: \"Use AGENTS.md\"\n")
w.printf(" 3. On first session, the agent will guide you through creating USER.md and PROJECT-DEFINITION.vbrief.json\n")
w.printf("\n")
}