-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathcontext.lua
More file actions
173 lines (153 loc) · 6.37 KB
/
Copy pathcontext.lua
File metadata and controls
173 lines (153 loc) · 6.37 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
local util = require("openspec.util")
local M = {}
local function read_lines(path)
if not path or not util.is_file(path) then
return {}
end
local ok, lines = pcall(vim.fn.readfile, path)
if not ok then
return {}
end
return lines
end
local function excerpt(path, limit)
local source = read_lines(path)
local lines = {}
for index, line in ipairs(source) do
if index > limit then
table.insert(lines, "...")
break
end
table.insert(lines, line)
end
return lines
end
local function push_section(lines, title, body)
table.insert(lines, "")
table.insert(lines, "## " .. title)
table.insert(lines, "")
if #body == 0 then
table.insert(lines, "_No content available._")
else
vim.list_extend(lines, body)
end
end
local function artifact_path(change, name)
return util.join_path(change.path, name)
end
local function spec_paths(change)
return vim.fn.glob(util.join_path(change.path, "specs", "*", "spec.md"), false, true)
end
function M.lines(change, parsed, state)
local task = state.selected_task
local git_info = state.git or {}
local validation = state.cli and state.cli.validation or {}
local validation_state = validation.ok and "passed" or (validation.enabled == false and "disabled" or "failed")
local recommendation = state.recommendations or {}
local primary = recommendation[1]
local recommendation_text = "none"
if primary then
if primary.skill and primary.skill ~= "" and primary.skill ~= primary.command then
recommendation_text = primary.command .. " / " .. primary.skill
else
recommendation_text = primary.command
end
end
local lines = {
"# OpenSpec Upstream Context",
"",
"This context helps prepare a downstream OpenSpec action.",
"openspec.nvim does not own apply/verify/archive lifecycles; it only prepares context and shows local findings.",
"",
"- Change: `" .. change.name .. "`",
"- Task line: " .. (task and tostring(task.lnum) or "(none)"),
"- Task status: `" .. (task and task.status or "(none)") .. "`",
"- Task text: " .. (task and task.text or "(none)"),
"- Branch: `" .. (git_info.branch or "(unknown)") .. "`",
"- Dirty entries: " .. tostring(#(git_info.dirty or {})),
"- Validation: `" .. validation_state .. "`",
"- Recommended upstream action: `" .. recommendation_text .. "`",
}
local worktree_resolution = state.worktree_resolution
if worktree_resolution and worktree_resolution.spec_snapshot then
table.insert(lines, "- Spec source: `origin dirty snapshot`")
table.insert(lines, "- Spec snapshot target: `" .. (worktree_resolution.path or "(unknown)") .. "`")
end
table.insert(lines, "")
table.insert(lines, "## Local Health Findings")
if #state.findings == 0 then
table.insert(lines, "- No findings.")
else
for _, finding in ipairs(state.findings or {}) do
table.insert(lines, "- [" .. (finding.severity or "info") .. "/" .. finding.category .. "] " .. finding.message)
end
end
if worktree_resolution and worktree_resolution.spec_snapshot then
push_section(lines, "Spec Snapshot", {
"- Source: origin dirty snapshot",
"- Target worktree: `" .. (worktree_resolution.path or "(unknown)") .. "`",
"- Origin checkout is not committed, stashed, cleaned, or modified by this launch.",
})
for _, entry in ipairs(worktree_resolution.spec_snapshot.entries or {}) do
table.insert(lines, "- `" .. entry .. "`")
end
end
if task then
push_section(lines, "Allowed Scope", {
"- Implement only the selected task.",
"- Update OpenSpec artifacts only when implementation proves the task/spec is stale or incomplete.",
"- Touch code, tests, and docs that directly support the selected task.",
})
else
push_section(lines, "Allowed Scope", {
"- Implement all tasks in this change in order.",
"- Update OpenSpec artifacts only when implementation proves a task or spec is stale or incomplete.",
"- Touch code, tests, and docs that directly support each task.",
})
end
push_section(lines, "Forbidden Scope", {
"- Do not treat this buffer as approval to run lifecycle actions.",
"- Do not archive or sync this change from this context alone.",
})
push_section(lines, "Verification Commands", {
"- `openspec validate --all --strict`",
"- `make check` when available, otherwise the closest documented project check.",
})
push_section(lines, "Model Control Guidance", {
"- Model guidance in this context is advisory for copied sessions.",
"- Existing Codex/Claude sessions started separately will not auto-switch model from pasted text.",
"- To enforce model selection, run `:OpenSpecImplement` (for example `:OpenSpecImplement codex profile=implementation`).",
})
if task then
push_section(lines, "Stop Conditions", {
"- Stop if implementation requires expanding the selected task scope.",
"- Stop if a referenced file is missing and the correct replacement is unclear.",
"- Stop if local changes cannot be mapped back to the selected task.",
"- Stop if validation or project checks fail after a focused fix attempt.",
})
else
push_section(lines, "Stop Conditions", {
"- Stop if a referenced file is missing and the correct replacement is unclear.",
"- Stop if validation or project checks fail after a focused fix attempt.",
"- Stop and request human input if scope needs expansion beyond the tasks listed in this change.",
})
end
push_section(lines, "Proposal", excerpt(artifact_path(change, "proposal.md"), 80))
push_section(lines, "Design", excerpt(artifact_path(change, "design.md"), 120))
push_section(lines, "Tasks", excerpt(change.tasks_path, 120))
for _, path in ipairs(spec_paths(change)) do
push_section(lines, "Spec Delta: " .. util.basename(util.dirname(path)), excerpt(path, 120))
end
return lines
end
function M.open(change, parsed, state)
local buf = vim.api.nvim_create_buf(false, true)
vim.api.nvim_buf_set_name(buf, "OpenSpec Upstream Context")
vim.api.nvim_buf_set_lines(buf, 0, -1, false, M.lines(change, parsed, state))
vim.api.nvim_set_option_value("buftype", "nofile", { buf = buf })
vim.api.nvim_set_option_value("bufhidden", "wipe", { buf = buf })
vim.api.nvim_set_option_value("filetype", "markdown", { buf = buf })
vim.api.nvim_set_current_buf(buf)
util.notify("Opened OpenSpec agent context buffer.")
end
return M