@opvs-ai/cli 0.27.0 → 0.27.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -0
- package/dist/.build-stamp.json +4 -4
- package/dist/commands/calendar.d.ts +3 -0
- package/dist/commands/calendar.js +294 -0
- package/dist/commands/calendar.js.map +1 -0
- package/dist/commands/code.js +229 -3
- package/dist/commands/code.js.map +1 -1
- package/dist/commands/tasks.js +117 -10
- package/dist/commands/tasks.js.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/lib/calendar-spec.d.ts +62 -0
- package/dist/lib/calendar-spec.js +199 -0
- package/dist/lib/calendar-spec.js.map +1 -0
- package/dist/lib/card-anchors.d.ts +12 -0
- package/dist/lib/card-anchors.js +67 -0
- package/dist/lib/card-anchors.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -195,6 +195,31 @@ opvs scheduled-runs cancel <id> --yes # only a run that has not fired
|
|
|
195
195
|
> admin only (403). `--run-at` must carry a zone (`Z` or `+02:00`); ids are full UUIDs. Cancel never disarms
|
|
196
196
|
> a run that already fired — it returns the row unchanged, and the CLI says so.
|
|
197
197
|
|
|
198
|
+
### Calendar
|
|
199
|
+
|
|
200
|
+
Write a wake-up for the current agent, inspect what is open, or report that a wake finished:
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
opvs calendar wait --pr opvs-ai/OPVS#3470 --merged --title "pull request 3470 is merged"
|
|
204
|
+
opvs calendar wait --card HEA-39 --status done --title "calendar CLI card is done"
|
|
205
|
+
opvs calendar wait --mail --title "direct mail arrives"
|
|
206
|
+
opvs calendar wait --mail-reply <message-id> --title "the owner replies"
|
|
207
|
+
opvs calendar wait --box <environment-id> --version 0.2.140 --title "the box is upgraded"
|
|
208
|
+
opvs calendar at 2026-10-20T18:00+02:00 --title "send the report"
|
|
209
|
+
opvs calendar every 2h --title "check the queue" --ends 3x
|
|
210
|
+
opvs calendar preview --every 1d --title "daily review"
|
|
211
|
+
opvs calendar list [--owner me] [--status open]
|
|
212
|
+
opvs calendar cancel <entry-id> --yes
|
|
213
|
+
opvs calendar done --token <run-id>.<secret> [--no-work]
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
Times must include `Z` or an offset such as `+02:00`. Add `--deadline <zoned-time>`, repeatable
|
|
217
|
+
`--remind who:how:anchor:minutes`, and `--note <words>` when registering an entry. For example,
|
|
218
|
+
`--remind owner:notify:deadline:+1440` tells the owner one day after the deadline. Repeating entries
|
|
219
|
+
are previewed before they are saved. Every calendar command accepts `--json`; `cancel` reads the
|
|
220
|
+
entry's current version before writing and needs `--yes` when no terminal is attached. A direct-mail
|
|
221
|
+
wait uses `OPVS_AGENT_ADDRESS`, then the address configured for `opvs mail`.
|
|
222
|
+
|
|
198
223
|
### Members
|
|
199
224
|
|
|
200
225
|
```bash
|
|
@@ -245,6 +270,30 @@ opvs tasks search -q OSB-78 # By ref, PR number (#1256), title, d
|
|
|
245
270
|
opvs tasks search -q deploy --board OSB,ASP --status pending --column Review --offset 20
|
|
246
271
|
```
|
|
247
272
|
|
|
273
|
+
#### Task schedules
|
|
274
|
+
|
|
275
|
+
Tasks can carry a start time, repeat rule, and reminders alongside their due date:
|
|
276
|
+
|
|
277
|
+
```bash
|
|
278
|
+
opvs tasks create --board OPS -t "Send the weekly report" \
|
|
279
|
+
--start 2026-10-20T09:00+02:00 --timezone Europe/Berlin \
|
|
280
|
+
--repeat 1w --ends 8x \
|
|
281
|
+
--remind assignee:wake:fire:-15 \
|
|
282
|
+
--remind owner:notify:deadline:+1440 \
|
|
283
|
+
--due 2026-10-20T17:00+02:00
|
|
284
|
+
|
|
285
|
+
opvs tasks update OPS-142 --no-schedule # Clear the whole schedule
|
|
286
|
+
opvs tasks get OPS-142 # Schedule shown in words
|
|
287
|
+
opvs tasks get OPS-142 --json # Raw API schedule object
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
`--start` and a timed `--due` must be ISO 8601 times with an explicit offset or
|
|
291
|
+
`Z`. A bare due date such as `--due 2026-10-20` remains valid. `--timezone`
|
|
292
|
+
takes an IANA name and is used for repeats across clock changes; when it is
|
|
293
|
+
omitted, the CLI uses and prints the machine's timezone. Repeat intervals are
|
|
294
|
+
`<N>d`, `<N>w`, or `<N>m`; repeat ends are `never`, a date, or `<N>x`.
|
|
295
|
+
Each repeatable reminder is `who:how:anchor:minutes`.
|
|
296
|
+
|
|
248
297
|
### Comments
|
|
249
298
|
|
|
250
299
|
```bash
|
package/dist/.build-stamp.json
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
{
|
|
2
2
|
"stamp_version": 1,
|
|
3
3
|
"package": "@opvs-ai/cli",
|
|
4
|
-
"version": "0.27.
|
|
5
|
-
"source_digest": "
|
|
6
|
-
"source_files":
|
|
7
|
-
"built_at": "2026-10-
|
|
4
|
+
"version": "0.27.2",
|
|
5
|
+
"source_digest": "b93b5a62130f3048bbfc2a17c8ecbaa8fa5ef352a1ab6859cb2fff42957fd7a0",
|
|
6
|
+
"source_files": 59,
|
|
7
|
+
"built_at": "2026-10-15T09:58:32.629Z",
|
|
8
8
|
"node": "v22.23.3"
|
|
9
9
|
}
|
|
@@ -0,0 +1,294 @@
|
|
|
1
|
+
/** opvs calendar — write and inspect the current agent's wake-up calendar. */
|
|
2
|
+
import { createClient, getEffectiveConfig, resolveAgentIdentity, } from "@opvs-ai/core";
|
|
3
|
+
import { confirmOrExit, YES_HELP } from "../lib/confirm.js";
|
|
4
|
+
import { CalendarSpecError, conditionFromFlags, describeReminder, describeRepeat, parseEnds, parseEvery, parseRemind, parseZonedTime, } from "../lib/calendar-spec.js";
|
|
5
|
+
import { printJson } from "../lib/format.js";
|
|
6
|
+
const BASE = "/api/v1/calendar";
|
|
7
|
+
function collect(value, previous) {
|
|
8
|
+
return [...previous, value];
|
|
9
|
+
}
|
|
10
|
+
function addEntryOptions(command) {
|
|
11
|
+
return command
|
|
12
|
+
.requiredOption("--title <words>", "What should wake the agent, in words")
|
|
13
|
+
.option("--deadline <time>", "Deadline as ISO 8601 with a zone")
|
|
14
|
+
.option("--ends <end>", "never, YYYY-MM-DD, or a count such as 3x")
|
|
15
|
+
.option("--remind <who:how:anchor:minutes>", "Reminder to add; repeat this flag for more than one", collect, [])
|
|
16
|
+
.option("--note <words>", "A note delivered with the wake")
|
|
17
|
+
.option("--json", "Print one JSON document");
|
|
18
|
+
}
|
|
19
|
+
function commonEntry(opts) {
|
|
20
|
+
const body = {
|
|
21
|
+
title: opts.title,
|
|
22
|
+
reminders: opts.remind.map(parseRemind),
|
|
23
|
+
};
|
|
24
|
+
if (opts.deadline)
|
|
25
|
+
body.deadline_at = parseZonedTime(opts.deadline);
|
|
26
|
+
if (opts.ends)
|
|
27
|
+
Object.assign(body, parseEnds(opts.ends));
|
|
28
|
+
if (opts.note)
|
|
29
|
+
body.registered_note = opts.note;
|
|
30
|
+
return body;
|
|
31
|
+
}
|
|
32
|
+
function errorPayload(error) {
|
|
33
|
+
if (!error || typeof error !== "object")
|
|
34
|
+
return undefined;
|
|
35
|
+
const candidate = error;
|
|
36
|
+
for (const value of [candidate.detail, candidate.data, candidate.body]) {
|
|
37
|
+
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
38
|
+
return value;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return candidate;
|
|
42
|
+
}
|
|
43
|
+
function fail(error, fallback) {
|
|
44
|
+
if (error instanceof CalendarSpecError) {
|
|
45
|
+
console.error(error.message);
|
|
46
|
+
process.exit(1);
|
|
47
|
+
}
|
|
48
|
+
const payload = errorPayload(error);
|
|
49
|
+
if (payload?.error === "calendar_bounds") {
|
|
50
|
+
console.error(`${String(payload.field)} has a limit of ${String(payload.limit)}; got ${String(payload.got)}.`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
if (payload?.error === "stale_version") {
|
|
54
|
+
console.error("someone changed it; run the command again");
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
const candidate = error;
|
|
58
|
+
const detail = typeof candidate?.detail === "string" ? candidate.detail : candidate?.message;
|
|
59
|
+
console.error(`Error: ${detail || fallback}`);
|
|
60
|
+
process.exit(1);
|
|
61
|
+
}
|
|
62
|
+
async function responseJson(response) {
|
|
63
|
+
return response.json();
|
|
64
|
+
}
|
|
65
|
+
function printTimes(times) {
|
|
66
|
+
if (!Array.isArray(times))
|
|
67
|
+
return;
|
|
68
|
+
console.log("Next 5 times:");
|
|
69
|
+
for (const time of times)
|
|
70
|
+
console.log(`- ${String(time)}`);
|
|
71
|
+
}
|
|
72
|
+
function printCreated(entry, title, reminders, repeat) {
|
|
73
|
+
const id = entry && typeof entry === "object" && "id" in entry
|
|
74
|
+
? String(entry.id)
|
|
75
|
+
: "(unknown id)";
|
|
76
|
+
console.log(`Registered ${id}: ${title}`);
|
|
77
|
+
if (repeat)
|
|
78
|
+
console.log(describeRepeat(repeat));
|
|
79
|
+
for (const reminder of reminders)
|
|
80
|
+
console.log(describeReminder(reminder));
|
|
81
|
+
}
|
|
82
|
+
function directMailAddress(workspace) {
|
|
83
|
+
const fromEnvironment = process.env.OPVS_AGENT_ADDRESS?.trim();
|
|
84
|
+
const configured = resolveAgentIdentity(workspace)?.address?.trim();
|
|
85
|
+
const address = fromEnvironment || configured;
|
|
86
|
+
if (!address) {
|
|
87
|
+
throw new CalendarSpecError("mail conditions need an OPVS mail address (opvs mail whoami)");
|
|
88
|
+
}
|
|
89
|
+
return address;
|
|
90
|
+
}
|
|
91
|
+
async function createEntry(program, body) {
|
|
92
|
+
const client = createClient(program.opts().workspace);
|
|
93
|
+
return responseJson(await client.post(`${BASE}/commitments`, body));
|
|
94
|
+
}
|
|
95
|
+
export function registerCalendarCommands(program) {
|
|
96
|
+
const calendar = program
|
|
97
|
+
.command("calendar")
|
|
98
|
+
.description("Write and inspect this agent's wake-up calendar");
|
|
99
|
+
const wait = addEntryOptions(calendar
|
|
100
|
+
.command("wait")
|
|
101
|
+
.description("Wake when one registered condition becomes true")
|
|
102
|
+
.option("--pr <owner/repo#N>", "Pull request to watch")
|
|
103
|
+
.option("--merged", "Wake when --pr is merged")
|
|
104
|
+
.option("--closed", "Wake when --pr is closed")
|
|
105
|
+
.option("--green", "Wake when every required check on --pr is green")
|
|
106
|
+
.option("--card <REF>", "Card to watch")
|
|
107
|
+
.option("--status <word>", "Wake when --card reaches this status")
|
|
108
|
+
.option("--mail-reply <id>", "Wake when this message receives a reply")
|
|
109
|
+
.option("--mail", "Wake when direct mail arrives for this agent")
|
|
110
|
+
.option("--box <environment>", "Environment to watch")
|
|
111
|
+
.option("--version <version>", "Wake when --box reaches this version"));
|
|
112
|
+
wait.action(async (opts) => {
|
|
113
|
+
try {
|
|
114
|
+
const cfg = getEffectiveConfig(program.opts().workspace);
|
|
115
|
+
const condition = conditionFromFlags(opts);
|
|
116
|
+
if (condition.source === "mail" && condition.key === "direct") {
|
|
117
|
+
condition.value = {
|
|
118
|
+
exists: true,
|
|
119
|
+
owner_address: directMailAddress(cfg.workspace_slug),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
const body = {
|
|
123
|
+
...commonEntry(opts),
|
|
124
|
+
schedule_kind: "trigger",
|
|
125
|
+
condition,
|
|
126
|
+
};
|
|
127
|
+
const entry = await createEntry(program, body);
|
|
128
|
+
if (opts.json)
|
|
129
|
+
printJson(entry);
|
|
130
|
+
else
|
|
131
|
+
printCreated(entry, opts.title, opts.remind.map(parseRemind));
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
fail(error, "Failed to register calendar wait");
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
const at = addEntryOptions(calendar
|
|
138
|
+
.command("at <time>")
|
|
139
|
+
.description("Wake once at an ISO 8601 time with a zone"));
|
|
140
|
+
at.action(async (time, opts) => {
|
|
141
|
+
try {
|
|
142
|
+
const body = {
|
|
143
|
+
...commonEntry(opts),
|
|
144
|
+
schedule_kind: "once",
|
|
145
|
+
fire_at: parseZonedTime(time),
|
|
146
|
+
};
|
|
147
|
+
const entry = await createEntry(program, body);
|
|
148
|
+
if (opts.json)
|
|
149
|
+
printJson(entry);
|
|
150
|
+
else
|
|
151
|
+
printCreated(entry, opts.title, opts.remind.map(parseRemind));
|
|
152
|
+
}
|
|
153
|
+
catch (error) {
|
|
154
|
+
fail(error, "Failed to register calendar time");
|
|
155
|
+
}
|
|
156
|
+
});
|
|
157
|
+
const every = addEntryOptions(calendar
|
|
158
|
+
.command("every <repeat>")
|
|
159
|
+
.description("Wake every N minutes, hours or days, starting now"));
|
|
160
|
+
every.action(async (repeat, opts) => {
|
|
161
|
+
try {
|
|
162
|
+
const repeatFields = {
|
|
163
|
+
...parseEvery(repeat),
|
|
164
|
+
...(opts.ends ? parseEnds(opts.ends) : { repeat_ends: "never" }),
|
|
165
|
+
fire_at: new Date().toISOString(),
|
|
166
|
+
};
|
|
167
|
+
const body = {
|
|
168
|
+
...commonEntry({ ...opts, ends: undefined }),
|
|
169
|
+
schedule_kind: "interval",
|
|
170
|
+
...repeatFields,
|
|
171
|
+
};
|
|
172
|
+
const client = createClient(program.opts().workspace);
|
|
173
|
+
const times = await responseJson(await client.post(`${BASE}/commitments/preview`, body));
|
|
174
|
+
if (!opts.json)
|
|
175
|
+
printTimes(times);
|
|
176
|
+
const entry = await responseJson(await client.post(`${BASE}/commitments`, body));
|
|
177
|
+
if (opts.json)
|
|
178
|
+
printJson({ next_five: times, entry });
|
|
179
|
+
else {
|
|
180
|
+
printCreated(entry, opts.title, opts.remind.map(parseRemind), repeatFields);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
catch (error) {
|
|
184
|
+
fail(error, "Failed to register repeating calendar entry");
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
calendar
|
|
188
|
+
.command("list")
|
|
189
|
+
.description("List calendar entries")
|
|
190
|
+
.option("--owner <owner>", "Owner to list; defaults to me")
|
|
191
|
+
.option("--status <status>", "Entry status; defaults to open")
|
|
192
|
+
.option("--json", "Print one JSON document")
|
|
193
|
+
.action(async (opts) => {
|
|
194
|
+
try {
|
|
195
|
+
const params = new URLSearchParams();
|
|
196
|
+
if (opts.owner)
|
|
197
|
+
params.set("owner", opts.owner);
|
|
198
|
+
if (opts.status)
|
|
199
|
+
params.set("status", opts.status);
|
|
200
|
+
const suffix = params.size ? `?${params.toString()}` : "";
|
|
201
|
+
const client = createClient(program.opts().workspace);
|
|
202
|
+
const entries = await responseJson(await client.get(`${BASE}/commitments${suffix}`));
|
|
203
|
+
printJson(entries);
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
fail(error, "Failed to list calendar entries");
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
const preview = addEntryOptions(calendar
|
|
210
|
+
.command("preview")
|
|
211
|
+
.description("Show the next five times for an unsaved entry")
|
|
212
|
+
.option("--at <time>", "Preview a one-time entry")
|
|
213
|
+
.option("--every <repeat>", "Preview an interval entry"));
|
|
214
|
+
preview.action(async (opts) => {
|
|
215
|
+
try {
|
|
216
|
+
if (Boolean(opts.at) === Boolean(opts.every)) {
|
|
217
|
+
throw new CalendarSpecError("a preview needs exactly one time, for example --at 2026-10-20T18:00+02:00 or --every 2h.");
|
|
218
|
+
}
|
|
219
|
+
const body = {
|
|
220
|
+
...commonEntry(opts),
|
|
221
|
+
schedule_kind: opts.at ? "once" : "interval",
|
|
222
|
+
};
|
|
223
|
+
if (opts.at)
|
|
224
|
+
body.fire_at = parseZonedTime(opts.at);
|
|
225
|
+
if (opts.every) {
|
|
226
|
+
Object.assign(body, parseEvery(opts.every));
|
|
227
|
+
body.fire_at = new Date().toISOString();
|
|
228
|
+
}
|
|
229
|
+
const client = createClient(program.opts().workspace);
|
|
230
|
+
const times = await responseJson(await client.post(`${BASE}/commitments/preview`, body));
|
|
231
|
+
if (opts.json)
|
|
232
|
+
printJson(times);
|
|
233
|
+
else
|
|
234
|
+
printTimes(times);
|
|
235
|
+
}
|
|
236
|
+
catch (error) {
|
|
237
|
+
fail(error, "Failed to preview calendar entry");
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
calendar
|
|
241
|
+
.command("cancel <id>")
|
|
242
|
+
.description("Cancel an open entry. Asks first; needs --yes off a terminal")
|
|
243
|
+
.option("-y, --yes", YES_HELP)
|
|
244
|
+
.option("--json", "Print one JSON document")
|
|
245
|
+
.action(async (id, opts) => {
|
|
246
|
+
await confirmOrExit(`Cancel calendar entry ${id}?`, opts.yes);
|
|
247
|
+
try {
|
|
248
|
+
const client = createClient(program.opts().workspace);
|
|
249
|
+
const current = (await responseJson(await client.get(`${BASE}/commitments/${id}`)));
|
|
250
|
+
if (!Number.isInteger(current.version)) {
|
|
251
|
+
throw new Error("calendar entry did not include its version");
|
|
252
|
+
}
|
|
253
|
+
const entry = await responseJson(await client.post(`${BASE}/commitments/${id}/cancel`, {
|
|
254
|
+
version: current.version,
|
|
255
|
+
}));
|
|
256
|
+
if (opts.json)
|
|
257
|
+
printJson(entry);
|
|
258
|
+
else
|
|
259
|
+
console.log(`Cancelled ${id}.`);
|
|
260
|
+
}
|
|
261
|
+
catch (error) {
|
|
262
|
+
fail(error, "Failed to cancel calendar entry");
|
|
263
|
+
}
|
|
264
|
+
});
|
|
265
|
+
calendar
|
|
266
|
+
.command("done")
|
|
267
|
+
.description("Report the outcome of a finished wake")
|
|
268
|
+
.requiredOption("--token <run.secret>", "One-use dispatch token")
|
|
269
|
+
.option("--no-work", "Report that the wake found no work")
|
|
270
|
+
.option("--json", "Print one JSON document")
|
|
271
|
+
.action(async (opts) => {
|
|
272
|
+
try {
|
|
273
|
+
const dot = opts.token.indexOf(".");
|
|
274
|
+
if (dot <= 0 || dot === opts.token.length - 1) {
|
|
275
|
+
throw new CalendarSpecError("a dispatch token needs its run id and secret, for example R.S.");
|
|
276
|
+
}
|
|
277
|
+
const runId = opts.token.slice(0, dot);
|
|
278
|
+
const client = createClient(program.opts().workspace);
|
|
279
|
+
const result = await responseJson(await client.post(`${BASE}/runs/${runId}/complete`, {
|
|
280
|
+
dispatch_token: opts.token,
|
|
281
|
+
outcome: opts.work === false ? "no_work" : "work_done",
|
|
282
|
+
}));
|
|
283
|
+
if (opts.json)
|
|
284
|
+
printJson(result);
|
|
285
|
+
else {
|
|
286
|
+
console.log(`Completed wake ${runId}: ${opts.work === false ? "no work" : "work done"}.`);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
catch (error) {
|
|
290
|
+
fail(error, "Failed to complete calendar wake");
|
|
291
|
+
}
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
//# sourceMappingURL=calendar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"calendar.js","sourceRoot":"","sources":["../../src/commands/calendar.ts"],"names":[],"mappings":"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"}
|
package/dist/commands/code.js
CHANGED
|
@@ -29,7 +29,9 @@
|
|
|
29
29
|
* `format=undefined` is a literal string on the URL. Each `if (opts.x) ...`
|
|
30
30
|
* gate exists so the request the server sees is the request the user asked for.
|
|
31
31
|
*/
|
|
32
|
+
import { readFile } from "node:fs/promises";
|
|
32
33
|
import { codeCallers, createClient, getEffectiveConfig, } from "@opvs-ai/core";
|
|
34
|
+
import { extractCardAnchors, } from "../lib/card-anchors.js";
|
|
33
35
|
import { buildUrl, printResponse } from "../lib/format.js";
|
|
34
36
|
export function registerCodeCommands(program) {
|
|
35
37
|
const code = program
|
|
@@ -131,6 +133,80 @@ export function registerCodeCommands(program) {
|
|
|
131
133
|
process.exit(1);
|
|
132
134
|
}
|
|
133
135
|
});
|
|
136
|
+
// ── check-card: bounded index facts for a planner's draft brief ──
|
|
137
|
+
code
|
|
138
|
+
.command("check-card <brief.md>")
|
|
139
|
+
.description("Check a draft card's paths and names against the code index")
|
|
140
|
+
.option("--project <id>", "Project id (or OPVS_CODE_PROJECT)")
|
|
141
|
+
.option("--similar <words>", "Append existing code similar to these words (repeatable, max 5)", (value, previous) => [...previous, value], [])
|
|
142
|
+
.option("--strict", "Exit 1 when an anchor is absent and not declared new")
|
|
143
|
+
.option("--json", "Print the same facts as JSON")
|
|
144
|
+
.action(async (briefPath, opts) => {
|
|
145
|
+
const project = opts.project || process.env.OPVS_CODE_PROJECT;
|
|
146
|
+
if (!project) {
|
|
147
|
+
reportCannotCheckCard("project required: pass --project <id> or set OPVS_CODE_PROJECT");
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
const similarQueries = Array.isArray(opts.similar) ? opts.similar : [];
|
|
151
|
+
if (similarQueries.length > 5) {
|
|
152
|
+
reportCannotCheckCard("--similar may be repeated at most 5 times");
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
let markdown;
|
|
156
|
+
try {
|
|
157
|
+
markdown = await readFile(briefPath, "utf8");
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
reportCannotCheckCard(`cannot read brief: ${briefPath}`);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
const extracted = extractCardAnchors(markdown);
|
|
164
|
+
const anchorNames = extracted.anchors.map((item) => item.anchor);
|
|
165
|
+
const wsSlug = program.opts().workspace;
|
|
166
|
+
try {
|
|
167
|
+
const client = createClient(wsSlug);
|
|
168
|
+
const resolvedResponse = await client.post(`/api/v1/code/${project}/nodes/resolve`, { anchors: anchorNames });
|
|
169
|
+
const resolutions = await resolvedResponse.json();
|
|
170
|
+
const resolutionByAnchor = new Map(resolutions.map((item) => [item.anchor, item]));
|
|
171
|
+
const live = resolutions.filter((item) => item.state === "live");
|
|
172
|
+
const nodeIds = live.map((item) => item.node_id);
|
|
173
|
+
const callersOptions = { callersLimit: 20, testsLimit: 10 };
|
|
174
|
+
const callersResponse = wsSlug
|
|
175
|
+
? await codeCallers(project, nodeIds, callersOptions, wsSlug)
|
|
176
|
+
: await codeCallers(project, nodeIds, callersOptions);
|
|
177
|
+
if (callersResponse.degraded || !callersResponse.index) {
|
|
178
|
+
reportCannotCheckCard("code index unavailable");
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const callerByNode = new Map(callersResponse.results.map((item) => [item.node_id, item]));
|
|
182
|
+
const facts = extracted.anchors.map((item) => cardAnchorFact(item, resolutionByAnchor.get(item.anchor), callersResponse, callerByNode));
|
|
183
|
+
const similar = await loadSimilarFacts(client, project, similarQueries);
|
|
184
|
+
const checkedAt = new Date().toISOString();
|
|
185
|
+
const output = {
|
|
186
|
+
version: "index-facts-v1",
|
|
187
|
+
project_id: project,
|
|
188
|
+
checked_at: checkedAt,
|
|
189
|
+
index: callersResponse.index,
|
|
190
|
+
paths_checked: facts.length,
|
|
191
|
+
paths_left_out: extracted.leftOut,
|
|
192
|
+
anchors: facts,
|
|
193
|
+
similar,
|
|
194
|
+
};
|
|
195
|
+
if (opts.json) {
|
|
196
|
+
console.log(JSON.stringify(output, null, 2));
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
console.log(boundIndexFactsMarkdown(indexFactsMarkdown(project, callersResponse, checkedAt, facts, extracted.leftOut, similar)));
|
|
200
|
+
}
|
|
201
|
+
if (opts.strict && facts.some((fact) => fact.in_index === "NO — not in the index and not declared new")) {
|
|
202
|
+
process.exitCode = 1;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
const e = err;
|
|
207
|
+
reportCannotCheckCard(e.detail || e.message || "code index unavailable");
|
|
208
|
+
}
|
|
209
|
+
});
|
|
134
210
|
// ── summary: the orientation document as data ───────────────────
|
|
135
211
|
code
|
|
136
212
|
.command("summary")
|
|
@@ -368,15 +444,19 @@ function printCallersText(anchors, resolutions, response) {
|
|
|
368
444
|
console.log(response.index
|
|
369
445
|
? `index: ${response.index.commit.slice(0, 7)}, ${ageInWords(response.index.age_seconds)}`
|
|
370
446
|
: "index: unavailable");
|
|
371
|
-
|
|
447
|
+
const callerByNode = new Map(response.results.map((item) => [item.node_id, item]));
|
|
372
448
|
for (let anchorIndex = 0; anchorIndex < anchors.length; anchorIndex += 1) {
|
|
373
449
|
const resolution = resolutions[anchorIndex];
|
|
374
450
|
if (!resolution || resolution.state !== "live") {
|
|
375
451
|
printUnresolvedAnchor(resolution?.anchor ?? anchors[anchorIndex], resolution);
|
|
376
452
|
continue;
|
|
377
453
|
}
|
|
378
|
-
const result =
|
|
379
|
-
|
|
454
|
+
const result = callerByNode.get(resolution.node_id);
|
|
455
|
+
if (!result || result.state !== "found") {
|
|
456
|
+
console.log(`\n${resolution.anchor}\n` +
|
|
457
|
+
"UNKNOWN — the node was not found when callers were asked (index changed?)");
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
380
460
|
console.log(`\n${nodeLabel(result, resolution.anchor)}`);
|
|
381
461
|
if (!response.calls_indexed) {
|
|
382
462
|
console.log("calls are not indexed for this repo: callers are UNKNOWN, not zero");
|
|
@@ -415,4 +495,150 @@ function printUnresolvedAnchor(anchor, resolution) {
|
|
|
415
495
|
}
|
|
416
496
|
console.log(`\n${anchor}\n${status}`);
|
|
417
497
|
}
|
|
498
|
+
function reportCannotCheckCard(reason) {
|
|
499
|
+
console.error(`CANNOT: ${reason}`);
|
|
500
|
+
process.exitCode = 2;
|
|
501
|
+
}
|
|
502
|
+
function cardAnchorFact(cardAnchor, resolution, response, callerByNode) {
|
|
503
|
+
const state = resolution?.state ?? "unknown";
|
|
504
|
+
if (state === "moved") {
|
|
505
|
+
return {
|
|
506
|
+
anchor: cardAnchor.anchor,
|
|
507
|
+
declared_new: cardAnchor.declaredNew,
|
|
508
|
+
state,
|
|
509
|
+
moved_to_anchor: resolution?.moved_to_anchor ?? null,
|
|
510
|
+
in_index: resolution?.moved_to_anchor
|
|
511
|
+
? `MOVED → ${resolution.moved_to_anchor}`
|
|
512
|
+
: "MOVED",
|
|
513
|
+
callers: "n/a",
|
|
514
|
+
test_hints: "n/a",
|
|
515
|
+
callers_truncated: false,
|
|
516
|
+
test_hints_truncated: false,
|
|
517
|
+
};
|
|
518
|
+
}
|
|
519
|
+
if (state !== "live" || !resolution) {
|
|
520
|
+
return {
|
|
521
|
+
anchor: cardAnchor.anchor,
|
|
522
|
+
declared_new: cardAnchor.declaredNew,
|
|
523
|
+
state,
|
|
524
|
+
moved_to_anchor: null,
|
|
525
|
+
in_index: cardAnchor.declaredNew
|
|
526
|
+
? "no (declared new)"
|
|
527
|
+
: "NO — not in the index and not declared new",
|
|
528
|
+
callers: "n/a",
|
|
529
|
+
test_hints: "n/a",
|
|
530
|
+
callers_truncated: false,
|
|
531
|
+
test_hints_truncated: false,
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const result = callerByNode.get(resolution.node_id);
|
|
535
|
+
if (!result || result.state !== "found") {
|
|
536
|
+
return {
|
|
537
|
+
anchor: cardAnchor.anchor,
|
|
538
|
+
declared_new: cardAnchor.declaredNew,
|
|
539
|
+
state: result?.state ?? "unknown",
|
|
540
|
+
moved_to_anchor: null,
|
|
541
|
+
in_index: "UNKNOWN — gone between resolve and callers",
|
|
542
|
+
callers: "n/a",
|
|
543
|
+
test_hints: "n/a",
|
|
544
|
+
callers_truncated: result?.callers_truncated ?? false,
|
|
545
|
+
test_hints_truncated: result?.nearby_tests_truncated ?? false,
|
|
546
|
+
};
|
|
547
|
+
}
|
|
548
|
+
let callers;
|
|
549
|
+
if (!response.calls_indexed) {
|
|
550
|
+
callers = "unknown (calls not indexed)";
|
|
551
|
+
}
|
|
552
|
+
else if (!result.callers_known) {
|
|
553
|
+
callers = "unknown (calls not indexed for this file type)";
|
|
554
|
+
}
|
|
555
|
+
else if (result.callers_truncated && result.callers_total === 0) {
|
|
556
|
+
callers = "unknown — answer cut (file has more than 500 symbols)";
|
|
557
|
+
}
|
|
558
|
+
else {
|
|
559
|
+
const shown = result.callers.slice(0, 5);
|
|
560
|
+
const exact = result.callers.filter((caller) => caller.resolution === "exact").length;
|
|
561
|
+
const heuristic = result.callers.filter((caller) => caller.resolution === "heuristic").length;
|
|
562
|
+
const labels = shown.map((caller) => caller.symbol ? `${caller.path}#${caller.symbol}` : caller.path);
|
|
563
|
+
const more = result.callers_total > shown.length ? ", …" : "";
|
|
564
|
+
const count = result.callers_truncated
|
|
565
|
+
? `≥ ${result.callers_total} (page of ${result.callers.length}: ${exact} exact, ${heuristic} heuristic) — truncated`
|
|
566
|
+
: `${result.callers_total} (${exact} exact, ${heuristic} heuristic)`;
|
|
567
|
+
callers = count +
|
|
568
|
+
(labels.length > 0 ? `: ${labels.map(inlineCode).join(", ")}${more}` : "");
|
|
569
|
+
}
|
|
570
|
+
const testHints = (result.nearby_tests.length
|
|
571
|
+
? result.nearby_tests.map((test) => inlineCode(test.path)).join(", ")
|
|
572
|
+
: "none") + (result.nearby_tests_truncated ? " — truncated" : "");
|
|
573
|
+
return {
|
|
574
|
+
anchor: cardAnchor.anchor,
|
|
575
|
+
declared_new: cardAnchor.declaredNew,
|
|
576
|
+
state,
|
|
577
|
+
moved_to_anchor: null,
|
|
578
|
+
in_index: "yes",
|
|
579
|
+
callers,
|
|
580
|
+
test_hints: testHints,
|
|
581
|
+
callers_truncated: result.callers_truncated,
|
|
582
|
+
test_hints_truncated: result.nearby_tests_truncated,
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
async function loadSimilarFacts(client, project, queries) {
|
|
586
|
+
const facts = [];
|
|
587
|
+
for (const query of queries) {
|
|
588
|
+
const response = await client.get(buildUrl(`/api/v1/code/${project}/context`, { query, limit: "5" }, "json"));
|
|
589
|
+
const body = await response.json();
|
|
590
|
+
for (const match of (body.matches ?? []).slice(0, 5)) {
|
|
591
|
+
const path = match.file_path;
|
|
592
|
+
const symbol = match.name;
|
|
593
|
+
const detail = firstLine(match.doc_first_line) || firstLine(match.signature) || "matching code";
|
|
594
|
+
facts.push({ query, anchor: symbol ? `${path}#${symbol}` : path, detail });
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
return facts;
|
|
598
|
+
}
|
|
599
|
+
function firstLine(value) {
|
|
600
|
+
return value?.split(/\r?\n/, 1)[0].trim() ?? "";
|
|
601
|
+
}
|
|
602
|
+
function indexFactsMarkdown(project, response, checkedAt, facts, leftOut, similar) {
|
|
603
|
+
const index = response.index;
|
|
604
|
+
const lines = [
|
|
605
|
+
"## Index facts",
|
|
606
|
+
`<!-- index-facts v1 commit=${index.commit} indexed_at=${index.indexed_at} checked_at=${checkedAt} -->`,
|
|
607
|
+
`index: ${index.commit.slice(0, 7)}, ${ageInWords(index.age_seconds)} · project ${project} · ${facts.length} paths checked`,
|
|
608
|
+
];
|
|
609
|
+
if (leftOut > 0) {
|
|
610
|
+
lines.push(`_${leftOut} ${leftOut === 1 ? "path" : "paths"} left out after the 50-anchor limit._`);
|
|
611
|
+
}
|
|
612
|
+
lines.push("| Anchor | In the index | Callers | Test hints (not coverage) |", "| --- | --- | --- | --- |");
|
|
613
|
+
for (const fact of facts) {
|
|
614
|
+
lines.push(`| ${inlineCode(fact.anchor)} | ${markdownCell(fact.in_index)} | ${markdownCell(fact.callers)} | ${markdownCell(fact.test_hints)} |`);
|
|
615
|
+
}
|
|
616
|
+
for (const fact of similar) {
|
|
617
|
+
lines.push(`Already exists for "${markdownText(fact.query)}": ${inlineCode(fact.anchor)} — ${markdownText(fact.detail)}`);
|
|
618
|
+
}
|
|
619
|
+
return lines.join("\n");
|
|
620
|
+
}
|
|
621
|
+
const INDEX_FACTS_MAX_BYTES = 6_000;
|
|
622
|
+
const INDEX_FACTS_TRUNCATED = "_Truncated: output exceeded 6,000 bytes._";
|
|
623
|
+
function boundIndexFactsMarkdown(markdown) {
|
|
624
|
+
if (Buffer.byteLength(`${markdown}\n`, "utf8") <= INDEX_FACTS_MAX_BYTES)
|
|
625
|
+
return markdown;
|
|
626
|
+
const kept = [];
|
|
627
|
+
for (const line of markdown.split("\n")) {
|
|
628
|
+
const candidate = [...kept, line, INDEX_FACTS_TRUNCATED].join("\n");
|
|
629
|
+
if (Buffer.byteLength(`${candidate}\n`, "utf8") > INDEX_FACTS_MAX_BYTES)
|
|
630
|
+
break;
|
|
631
|
+
kept.push(line);
|
|
632
|
+
}
|
|
633
|
+
return [...kept, INDEX_FACTS_TRUNCATED].join("\n");
|
|
634
|
+
}
|
|
635
|
+
function inlineCode(value) {
|
|
636
|
+
return `\`${markdownText(value).replaceAll("`", "\\`")}\``;
|
|
637
|
+
}
|
|
638
|
+
function markdownCell(value) {
|
|
639
|
+
return value.replaceAll("|", "\\|").replaceAll("\n", " ");
|
|
640
|
+
}
|
|
641
|
+
function markdownText(value) {
|
|
642
|
+
return value.replaceAll("|", "\\|").replaceAll("\r", " ").replaceAll("\n", " ");
|
|
643
|
+
}
|
|
418
644
|
//# sourceMappingURL=code.js.map
|