返回 DeepSeek-Reasonix
source.go
根目录 / internal / extension / source.go
1 package extension
2
3 // Scope records which tier contributed a capability. The wire names are
4 // stable: they appear in manifests, diagnostics, and conflict reports, so a
5 // rename is a protocol break.
6 type Scope string
7
8 const (
9 // ScopeBuiltin is compiled into the binary (built-in tools, shipped
10 // skills). It is the lowest tier: anything user-provided shadows it.
11 ScopeBuiltin Scope = "builtin"
12 // ScopeGlobal is user-level configuration under the Reasonix home dir.
13 ScopeGlobal Scope = "global"
14 // ScopeProject is configuration rooted at the current project. It is the
15 // highest tier, matching the existing skill and command discovery where
16 // project roots always win.
17 ScopeProject Scope = "project"
18 // ScopePlugin is contributed by an installed plugin package. Plugins sit
19 // below project config but above global user config: a project may
20 // override a plugin, a plugin may override a user default.
21 ScopePlugin Scope = "plugin"
22 // ScopeClaudeCompat marks contributions imported from Claude conventions
23 // (.claude dirs, Claude-style settings). Compat tiers rank just below
24 // their native project counterpart so imported config never silently
25 // overrides native Reasonix config.
26 ScopeClaudeCompat Scope = "claude_compat"
27 // ScopeCodexCompat marks contributions imported from Codex conventions.
28 ScopeCodexCompat Scope = "codex_compat"
29 )
30
31 // tierRank orders the scopes for shadowing: a higher rank wins the same
32 // canonical ID. Compat scopes sit between project and plugin because imported
33 // project-adjacent config (.claude under a project root) is closer to project
34 // intent than to an installed package, but must never outrank native config.
35 // Unknown scopes rank below builtin so they lose every shadow race instead of
36 // accidentally winning one.
37 func tierRank(s Scope) int {
38 switch s {
39 case ScopeProject:
40 return 60
41 case ScopeClaudeCompat:
42 return 50
43 case ScopeCodexCompat:
44 return 40
45 case ScopePlugin:
46 return 30
47 case ScopeGlobal:
48 return 20
49 case ScopeBuiltin:
50 return 10
51 default:
52 return 0
53 }
54 }
55
56 // knownScope reports whether s is one of the declared wire values. The builder
57 // rejects unknown scopes at validation time; silently accepting a typo'd scope
58 // would rank it 0 and change shadowing outcomes invisibly.
59 func knownScope(s Scope) bool {
60 switch s {
61 case ScopeBuiltin, ScopeGlobal, ScopeProject, ScopePlugin, ScopeClaudeCompat, ScopeCodexCompat:
62 return true
63 default:
64 return false
65 }
66 }
67
68 // ContributionSource is the provenance of one contribution. It answers "who
69 // put this here" for diagnostics and conflict reports, and its Scope drives
70 // shadow resolution.
71 type ContributionSource struct {
72 // PluginID is the installed plugin package name when the contribution came
73 // from one; empty for non-plugin sources.
74 PluginID string
75 // Version is the contributor's version (plugin package version, contract
76 // version). Optional; empty means unspecified.
77 Version string
78 // Origin is a short stable label for the discovery path: "builtin",
79 // "user", "project", "plugin", a manifest path, and so on.
80 Origin string
81 // Scope is the shadowing tier.
82 Scope Scope
83 // Priority is provenance-level priority metadata carried for display and
84 // tie-breaking. Per-contribution priority lives on Contribution.Priority;
85 // the two are deliberately separate so a source descriptor can be shared
86 // by contributions with different priorities.
87 Priority int
88 // Path is the file or directory the contribution was loaded from, when one
89 // exists. Diagnostic only; it never influences winner rules.
90 Path string
91 }
92
93 // key identifies a source for conflict detection. Two contributions share a
94 // source only when scope, plugin, and origin all agree — path is excluded so
95 // several files discovered by the same root count as one source (mirroring
96 // today's first-root-wins behavior inside a single discovery pass).
97 func (s ContributionSource) key() string {
98 return string(s.Scope) + "|" + s.PluginID + "|" + s.Origin
99 }
100
101 // label renders the source for error messages, preferring the plugin identity
102 // because cross-plugin conflicts are the ones users must act on.
103 func (s ContributionSource) label() string {
104 if s.PluginID != "" {
105 return "plugin " + s.PluginID
106 }
107 if s.Origin != "" {
108 return string(s.Scope) + " (" + s.Origin + ")"
109 }
110 return string(s.Scope)
111 }
112
112 lines GO