directive.go raw
1 package oracle
2
3 import (
4 "fmt"
5 "strings"
6 )
7
8 // GenerateDirectives builds strategy directives from a reading's hexagram
9 // state, source identifier, and engagement style.
10 //
11 // The primary directive comes from the inner+outer trigram decomposition.
12 // Additional directives come from each changing line, identifying which
13 // specific dimension is in flux.
14 func GenerateDirectives(r *Reading, source string, style EngagementStyle) []Directive {
15 var directives []Directive
16
17 inner := r.InnerTrigram()
18 outer := r.OuterTrigram()
19 domain := TrigramToDomain(outer)
20 intention := TrigramToIntention(inner)
21
22 // Primary directive: intention + domain + source + style.
23 primary := Directive{
24 Type: directiveTypeForSource(source),
25 Query: buildQuery(intention, domain, source, style),
26 Domain: domain,
27 Intention: intention,
28 Style: style,
29 ChangingBit: -1, // primary, not from a changing line
30 }
31 directives = append(directives, primary)
32
33 // Changing line directives: each changing line adds a dimension-specific
34 // follow-up identifying what is in flux.
35 for _, lineIdx := range r.ChangingLines() {
36 bitWithinTrigram := lineIdx % 3
37 isInner := lineIdx < 3
38
39 d := Directive{
40 Type: directiveTypeForSource(source),
41 Query: buildChangingLineQuery(bitWithinTrigram, isInner, source, style),
42 Domain: domain,
43 Intention: intention,
44 Style: style,
45 ChangingBit: lineIdx,
46 }
47 directives = append(directives, d)
48 }
49
50 return directives
51 }
52
53 // directiveTypeForSource maps a data source identifier to the appropriate
54 // directive type.
55 func directiveTypeForSource(source string) DirectiveType {
56 switch source {
57 case "self", "walk", "local":
58 return DirectiveWalkWeight
59 case "forage", "web", "search":
60 return DirectiveSearch
61 case "nostr", "relay":
62 return DirectiveSubscribe
63 default:
64 return DirectiveFocus
65 }
66 }
67
68 // buildQuery constructs a strategy string from intention, domain, source, and style.
69 // For walk/self sources this produces a concise focus descriptor; for search
70 // sources it produces a natural language query.
71 func buildQuery(intention IntentionType, domain DomainType, source string, style EngagementStyle) string {
72 keywords := DomainKeywords(domain)
73 dtype := directiveTypeForSource(source)
74
75 // Walk/self: concise focus tags, not a search sentence.
76 if dtype == DirectiveWalkWeight {
77 parts := []string{domainLabel(domain), intentionLabel(intention)}
78 if len(keywords) > 1 {
79 parts = append(parts, keywords[1]) // second keyword for variety
80 }
81 parts = append(parts, styleLabel(style))
82 return strings.Join(parts, "→")
83 }
84
85 // Search/subscribe: natural language query.
86 prefix := IntentionPrefix(intention)
87 var b strings.Builder
88 b.WriteString(prefix)
89 b.WriteString(" ")
90 b.WriteString(source)
91 if len(keywords) > 0 {
92 b.WriteString(" ")
93 b.WriteString(keywords[0])
94 }
95 switch style {
96 case StyleBroad:
97 b.WriteString(" overview")
98 case StyleTargeted:
99 b.WriteString(" detail")
100 case StyleReference:
101 b.WriteString(" reference")
102 case StyleSynthesis:
103 b.WriteString(" synthesis")
104 }
105 return b.String()
106 }
107
108 func domainLabel(d DomainType) string {
109 switch d {
110 case DomainEmpirical:
111 return "empirical"
112 case DomainEmerging:
113 return "emerging"
114 case DomainCritiques:
115 return "critiques"
116 case DomainApplications:
117 return "applied"
118 case DomainVisualization:
119 return "visual"
120 case DomainFoundations:
121 return "foundations"
122 case DomainMethodology:
123 return "method"
124 case DomainCanon:
125 return "canon"
126 default:
127 return "general"
128 }
129 }
130
131 func intentionLabel(i IntentionType) string {
132 switch i {
133 case IntentEmpirical:
134 return "evidence"
135 case IntentEmerging:
136 return "novelty"
137 case IntentCritiques:
138 return "failures"
139 case IntentApplications:
140 return "practice"
141 case IntentVisualization:
142 return "intuition"
143 case IntentFoundations:
144 return "axioms"
145 case IntentMethodology:
146 return "technique"
147 case IntentCanon:
148 return "standard"
149 default:
150 return "focus"
151 }
152 }
153
154 func styleLabel(s EngagementStyle) string {
155 switch s {
156 case StyleBroad:
157 return "broad"
158 case StyleTargeted:
159 return "targeted"
160 case StyleReference:
161 return "reference"
162 case StyleSynthesis:
163 return "synthesis"
164 default:
165 return "explore"
166 }
167 }
168
169 // buildChangingLineQuery constructs a follow-up query for a changing line.
170 func buildChangingLineQuery(bit int, isInner bool, source string, style EngagementStyle) string {
171 dtype := directiveTypeForSource(source)
172
173 // Walk/self: short dimension tag.
174 if dtype == DirectiveWalkWeight {
175 dim := changingBitLabel(bit, isInner)
176 return dim + "→" + styleLabel(style)
177 }
178
179 // Search: natural language.
180 focus := ChangingBitFocus(bit, isInner)
181 return fmt.Sprintf("%s %s", focus, source)
182 }
183
184 func changingBitLabel(bit int, isInner bool) string {
185 scope := "source"
186 if isInner {
187 scope = "understanding"
188 }
189 switch bit {
190 case 0:
191 return "bonding:" + scope
192 case 1:
193 return "constraint:" + scope
194 case 2:
195 return "energy:" + scope
196 default:
197 return "flux:" + scope
198 }
199 }
200