返回 DeepSeek-Reasonix
instruction.go
根目录 / internal / instruction / instruction.go
1 package instruction
2
3 import (
4 "context"
5 "strings"
6 )
7
8 // VerifyCheck is a host-observable project check extracted from structured
9 // project memory. It is runtime-only and is not serialized into prompts.
10 type VerifyCheck struct {
11 Command string
12 SourcePath string
13 Line int
14 }
15
16 type contextKey struct{}
17
18 func WithChecks(ctx context.Context, checks []VerifyCheck) context.Context {
19 if len(checks) == 0 {
20 return ctx
21 }
22 cp := append([]VerifyCheck(nil), checks...)
23 return context.WithValue(ctx, contextKey{}, cp)
24 }
25
26 func FromContext(ctx context.Context) []VerifyCheck {
27 checks, ok := ctx.Value(contextKey{}).([]VerifyCheck)
28 if !ok || len(checks) == 0 {
29 return nil
30 }
31 return append([]VerifyCheck(nil), checks...)
32 }
33
34 // ExtractHostChecks reads only the structured "Reasonix host checks" section.
35 // Ordinary project instructions remain guidance and do not become hard gates.
36 func ExtractHostChecks(docs []Document) []VerifyCheck {
37 seen := map[string]bool{}
38 var checks []VerifyCheck
39 for _, doc := range docs {
40 inSection := false
41 for i, raw := range strings.Split(doc.Body, "\n") {
42 line := strings.TrimRight(raw, "\r")
43 if heading, ok := markdownHeading(line); ok {
44 inSection = strings.EqualFold(heading, "Reasonix host checks")
45 continue
46 }
47 if !inSection {
48 continue
49 }
50 command, ok := verifyBullet(line)
51 if !ok || seen[command] {
52 continue
53 }
54 seen[command] = true
55 checks = append(checks, VerifyCheck{
56 Command: command,
57 SourcePath: doc.Path,
58 Line: i + 1,
59 })
60 }
61 }
62 return checks
63 }
64
65 func markdownHeading(line string) (string, bool) {
66 line = strings.TrimSpace(line)
67 if !strings.HasPrefix(line, "#") {
68 return "", false
69 }
70 i := 0
71 for i < len(line) && line[i] == '#' {
72 i++
73 }
74 if i == 0 || i >= len(line) || line[i] != ' ' {
75 return "", false
76 }
77 heading := strings.TrimSpace(line[i+1:])
78 heading = strings.TrimSpace(strings.TrimRight(heading, "#"))
79 return heading, heading != ""
80 }
81
82 func verifyBullet(line string) (string, bool) {
83 line = strings.TrimSpace(line)
84 if len(line) < 2 || (line[:2] != "- " && line[:2] != "* ") {
85 return "", false
86 }
87 body := strings.TrimSpace(line[2:])
88 const prefix = "verify:"
89 if len(body) < len(prefix) || !strings.EqualFold(body[:len(prefix)], prefix) {
90 return "", false
91 }
92 command := strings.TrimSpace(body[len(prefix):])
93 return command, command != ""
94 }
95
95 lines GO