@voidlabs.gg/voidshift-agents 0.4.5 → 0.5.1
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 +15 -2
- package/dist/control.d.ts +8 -0
- package/dist/control.js +25 -14
- package/dist/index.js +1 -0
- package/dist/memory.d.ts +1 -0
- package/dist/memory.js +12 -0
- package/dist/runner.js +11 -6
- package/dist/types.d.ts +1 -0
- package/dist/workflow.d.ts +4 -0
- package/dist/workflow.js +15 -0
- package/pack/vs-plan/SKILL.md +4 -1
- package/pack/vs-plan-review/SKILL.md +3 -1
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -127,9 +127,18 @@ vsf memory status # the vault and where recall is on
|
|
|
127
127
|
vsf memory search WORDS # notes recall would bring up
|
|
128
128
|
vsf memory try "a prompt" # exactly what recall would add to that prompt
|
|
129
129
|
vsf memory check # notes missing from INDEX.md, broken links
|
|
130
|
+
vsf memory sync # pull and push the vault through its git remote
|
|
130
131
|
vsf memory on | off # turn recall on or off
|
|
131
132
|
```
|
|
132
133
|
|
|
134
|
+
To share the vault between machines, make it a git repository with a remote
|
|
135
|
+
named `origin` (any host; a private repository). `vsf memory sync` commits new
|
|
136
|
+
notes, rebases onto the remote and pushes. It also runs in the background from
|
|
137
|
+
the recall hook when the last sync is over ten minutes old, and the agents
|
|
138
|
+
runner syncs before and after every run. When a note changed on both sides,
|
|
139
|
+
the remote's version stays and this machine's copy is saved beside it as
|
|
140
|
+
`note.conflict-<machine>-<time>.md`, so nothing is lost.
|
|
141
|
+
|
|
133
142
|
Setup creates a vault at `~/memory` or uses one you already have. Codex asks you
|
|
134
143
|
to trust the recall hook once: run `/hooks` in Codex.
|
|
135
144
|
|
|
@@ -180,8 +189,12 @@ pnpm test
|
|
|
180
189
|
node packages/cli/src/main.ts
|
|
181
190
|
```
|
|
182
191
|
|
|
183
|
-
Node 22.18 or newer runs the TypeScript sources directly.
|
|
184
|
-
|
|
192
|
+
Node 22.18 or newer runs the TypeScript sources directly. Each package
|
|
193
|
+
releases on its own: bump its `package.json` and push `agents-v0.5.0` (or
|
|
194
|
+
`cli-v`, `memory-v`, `awake-v`), and CI publishes only that package. A plain
|
|
195
|
+
`v*` tag publishes all four at one version. Tools declare the CLI contract
|
|
196
|
+
they were built for; a CLI that is too old refuses to load them and says to
|
|
197
|
+
run `vsf update`.
|
|
185
198
|
|
|
186
199
|
## License
|
|
187
200
|
|
package/dist/control.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { type AgentsConfig, type Tracker as Kind } from './config.ts';
|
|
2
2
|
import { type Current, type RunEntry } from './state.ts';
|
|
3
|
+
import { type Trackers } from './tracker.ts';
|
|
3
4
|
import type { Check, ToolCommand, ToolContext } from './types.ts';
|
|
4
5
|
export { parsePlistArgs } from './launchd.ts';
|
|
5
6
|
export declare const trackersInUse: (config: Pick<AgentsConfig, "workflows">) => Kind[];
|
|
@@ -23,5 +24,12 @@ export interface CancelDeps {
|
|
|
23
24
|
cancelCard: (card: string, label: string, source: Kind) => Promise<boolean>;
|
|
24
25
|
}
|
|
25
26
|
export declare function cancelCurrent(ctx: ToolContext, c: Current, deps: CancelDeps): Promise<void>;
|
|
27
|
+
export declare function trackerWarnings(kinds: Kind[], trackers: Trackers): Promise<{
|
|
28
|
+
kind: Kind;
|
|
29
|
+
message: string;
|
|
30
|
+
}[]>;
|
|
31
|
+
export declare function workingLine(current: (Current & {
|
|
32
|
+
elapsedMinutes: number;
|
|
33
|
+
}) | null): string;
|
|
26
34
|
export declare const controlCommands: Record<string, ToolCommand>;
|
|
27
35
|
export declare function controlChecks(ctx: ToolContext): Promise<Check[]>;
|
package/dist/control.js
CHANGED
|
@@ -230,23 +230,30 @@ const realDeps = (ctx) => ({
|
|
|
230
230
|
}
|
|
231
231
|
},
|
|
232
232
|
});
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
const trackers = trackersFor(ctx.configDir);
|
|
239
|
-
for (const kind of trackersInUse(readAgentsConfig(ctx.configDir))) {
|
|
233
|
+
// One message per tracker whose key is missing or refused. The tick skips a
|
|
234
|
+
// tracker it cannot open, so these warn rather than stop the runner.
|
|
235
|
+
export async function trackerWarnings(kinds, trackers) {
|
|
236
|
+
const out = [];
|
|
237
|
+
for (const kind of kinds) {
|
|
240
238
|
try {
|
|
241
239
|
await trackers(kind).viewer();
|
|
242
240
|
}
|
|
243
241
|
catch (err) {
|
|
244
242
|
const msg = err.message;
|
|
245
243
|
const flag = kind === 'linear' ? '' : ` --${kind}`;
|
|
246
|
-
|
|
247
|
-
return 1;
|
|
244
|
+
out.push({ kind, message: /^No .* API key/.test(msg) ? msg : `${TRACKERS[kind].name} did not accept the key: ${msg}. Run vsf agents key${flag}.` });
|
|
248
245
|
}
|
|
249
246
|
}
|
|
247
|
+
return out;
|
|
248
|
+
}
|
|
249
|
+
async function startCmd(ctx) {
|
|
250
|
+
if (!Object.keys(readAgentsConfig(ctx.configDir).workflows).length) {
|
|
251
|
+
ctx.log('No workflows yet. Create one first: vsf agents workflow add.');
|
|
252
|
+
return 1;
|
|
253
|
+
}
|
|
254
|
+
for (const w of await trackerWarnings(trackersInUse(readAgentsConfig(ctx.configDir)), trackersFor(ctx.configDir))) {
|
|
255
|
+
ctx.log(`Warning: ${w.message.replace(/\.?$/, '.')} Workflows on ${TRACKERS[w.kind].name} are skipped until then.`);
|
|
256
|
+
}
|
|
250
257
|
setPaused(ctx.configDir, false);
|
|
251
258
|
if (!runnerService().start(ctx.home, program(ctx), logPath(ctx.configDir))) {
|
|
252
259
|
ctx.log('Could not start the runner. Check vsf agents logs.');
|
|
@@ -263,6 +270,14 @@ async function stopCmd(ctx) {
|
|
|
263
270
|
ctx.log('Runner stopped.');
|
|
264
271
|
return 0;
|
|
265
272
|
}
|
|
273
|
+
// The lock holds the round as the card's vs-vN label shows it; 0 before any round.
|
|
274
|
+
export function workingLine(current) {
|
|
275
|
+
if (!current)
|
|
276
|
+
return '';
|
|
277
|
+
if (!current.card)
|
|
278
|
+
return ' Checking now.';
|
|
279
|
+
return ` Working on ${current.card}${current.title ? ` "${current.title}"` : ''} (${[current.workflow, current.step, current.round ? `round ${current.round}` : '', `${current.elapsedMinutes}m`].filter(Boolean).join(', ')}).`;
|
|
280
|
+
}
|
|
266
281
|
const status = guard(async (args, ctx) => {
|
|
267
282
|
const p = parse('status', args, { json: { type: 'boolean' } }, ctx);
|
|
268
283
|
if (typeof p === 'number')
|
|
@@ -291,11 +306,7 @@ const status = guard(async (args, ctx) => {
|
|
|
291
306
|
ctx.log(JSON.stringify({ engine: config.engine, runner, lastTick, current, parked, interrupted, workflows, projects }, null, 2));
|
|
292
307
|
return 0;
|
|
293
308
|
}
|
|
294
|
-
const working = current
|
|
295
|
-
? ' Checking now.'
|
|
296
|
-
: current
|
|
297
|
-
? ` Working on ${current.card ?? 'a card'}${current.title ? ` "${current.title}"` : ''} (${[current.workflow, current.step, current.round !== undefined ? `round ${current.round + 1}` : '', `${current.elapsedMinutes}m`].filter(Boolean).join(', ')}).`
|
|
298
|
-
: '';
|
|
309
|
+
const working = workingLine(current);
|
|
299
310
|
if (runner === 'running')
|
|
300
311
|
ctx.log(`Runner: running.${working}`);
|
|
301
312
|
else if (runner === 'paused')
|
package/dist/index.js
CHANGED
|
@@ -1042,6 +1042,7 @@ const homeCommands = {
|
|
|
1042
1042
|
const sameModels = (a, b) => a.dev === b.dev && a.review === b.review;
|
|
1043
1043
|
const agents = {
|
|
1044
1044
|
name: 'agents',
|
|
1045
|
+
contract: 1,
|
|
1045
1046
|
summary: 'Agents for Claude Code and Codex that plan, build and QA Linear cards',
|
|
1046
1047
|
config: {
|
|
1047
1048
|
read: (dir) => readAgentsConfig(dir),
|
package/dist/memory.d.ts
CHANGED
package/dist/memory.js
CHANGED
|
@@ -35,3 +35,15 @@ export function recall(selfCommand, configDir, text) {
|
|
|
35
35
|
return '';
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
|
+
// Brings the vault up to date through its git remote, or shares the notes
|
|
39
|
+
// agents wrote. Never fails a run: returns a line worth logging, or ''.
|
|
40
|
+
export function syncMemory(selfCommand, configDir) {
|
|
41
|
+
if (!vaultPath(configDir) || !selfCommand.length)
|
|
42
|
+
return '';
|
|
43
|
+
const [cmd, ...args] = selfCommand;
|
|
44
|
+
const r = spawnSync(cmd, [...args, 'memory', 'sync', '--quiet'], { encoding: 'utf8', timeout: 90_000 });
|
|
45
|
+
const out = `${r.stdout ?? ''}${r.stderr ?? ''}`.trim();
|
|
46
|
+
if (r.error)
|
|
47
|
+
return `memory sync did not run (${r.error.message})`;
|
|
48
|
+
return r.status === 0 ? out : out || 'memory sync failed';
|
|
49
|
+
}
|
package/dist/runner.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { existsSync, mkdirSync } from 'node:fs';
|
|
2
2
|
import { basename, join } from 'node:path';
|
|
3
|
-
import { LABELS, PLAN_HEADING, buildPrompt, findingsFor, fixTarget, isStatusLabel, latestPlan, parseDevResult, parseVerdict, planBody, qaPassed, stageLabels, verdictBlock, } from "./workflow.js";
|
|
3
|
+
import { LABELS, PLAN_HEADING, buildPrompt, findingsFor, fixTarget, isStatusLabel, latestPlan, needsPlan, parseDevResult, parseVerdict, planBody, qaPassed, stageLabels, verdictBlock, } from "./workflow.js";
|
|
4
4
|
import { agentsDir, hasWork, isCardTrigger, resolveStep, stepId, stepIdLabel } from "./config.js";
|
|
5
5
|
import { lastDue, parseCron } from "./cron.js";
|
|
6
6
|
import { runAgent, stopAgents } from "./engine.js";
|
|
7
7
|
import { commitsAhead, enforceCommitPolicy, defaultBranch, ensureDraftPr, ensureWorktree, headSha, markReady, push, resetWorktree, waitForChecks, waitForPrHead } from "./git.js";
|
|
8
8
|
import { noTracker } from "./tracker.js";
|
|
9
|
-
import { recall, vaultPath } from "./memory.js";
|
|
9
|
+
import { recall, syncMemory, vaultPath } from "./memory.js";
|
|
10
10
|
import { claudeSettingsFile, readRules, rulesSection } from "./profile.js";
|
|
11
11
|
import { bootTime, clearCurrent, isPaused, ours, readCurrent, readPending, writeCurrent, writePending, writeRun, readCronState, writeCronState } from "./state.js";
|
|
12
12
|
const stamp = () => new Date().toISOString().replace(/[:.]/g, '-');
|
|
@@ -138,10 +138,7 @@ export async function runCard(card, trigger, workflowName, workflow, project, rc
|
|
|
138
138
|
throw new Park(`Workflow ${workflowName} has no ${stepIdLabel(opts.from)} step.`);
|
|
139
139
|
// A scheduled run has no card to plan on, so its task is the plan unless a Plan step writes one.
|
|
140
140
|
let plan = isCardTrigger(trigger) ? latestPlan(await linear.comments(card.identifier)) : card.description ?? null;
|
|
141
|
-
|
|
142
|
-
// A check before any commits is a check on the plan, like Plan review.
|
|
143
|
-
const needsPlan = steps.findIndex((d, n) => n >= from && (d.output === 'commits' || (d.output === 'verdict' && d.builtin !== 'vs-qa')));
|
|
144
|
-
if (!plan && needsPlan >= 0 && (firstPlan < 0 || firstPlan > needsPlan)) {
|
|
141
|
+
if (!plan && needsPlan(steps, from)) {
|
|
145
142
|
throw new Park('No plan comment (starting with "## Plan") was found. Publish a plan first, or start the workflow with Plan.');
|
|
146
143
|
}
|
|
147
144
|
await stage(stageOf(steps[from]));
|
|
@@ -156,6 +153,10 @@ export async function runCard(card, trigger, workflowName, workflow, project, rc
|
|
|
156
153
|
throw new Park(`Your agent profile's Claude settings are broken: ${err.message}`);
|
|
157
154
|
}
|
|
158
155
|
const rules = rulesSection(readRules(rc.configDir));
|
|
156
|
+
// Latest notes from every machine before recall reads them.
|
|
157
|
+
const pulled = syncMemory(rc.selfCommand, rc.configDir);
|
|
158
|
+
if (pulled)
|
|
159
|
+
say(pulled);
|
|
159
160
|
const known = [recall(rc.selfCommand, rc.configDir, `${workflow.project} ${card.title}\n${card.description ?? ''}`), vault ? memoryNote(vault) : '', rules].filter(Boolean).join('\n\n');
|
|
160
161
|
const worktree = join(agentsDir(rc.configDir), 'worktrees', card.identifier);
|
|
161
162
|
ensureWorktree(project.path, worktree, card.branchName, base);
|
|
@@ -336,6 +337,10 @@ export async function runCard(card, trigger, workflowName, workflow, project, rc
|
|
|
336
337
|
}
|
|
337
338
|
finally {
|
|
338
339
|
active = null;
|
|
340
|
+
// Share whatever notes the agents wrote, whatever the outcome.
|
|
341
|
+
const shared = syncMemory(rc.selfCommand, rc.configDir);
|
|
342
|
+
if (shared)
|
|
343
|
+
say(shared);
|
|
339
344
|
}
|
|
340
345
|
}
|
|
341
346
|
// A scheduled run's stand-in for a card: the task, on a branch of its own.
|
package/dist/types.d.ts
CHANGED
|
@@ -40,6 +40,7 @@ export type ToolCommand = (args: string[], ctx: ToolContext) => Promise<number>;
|
|
|
40
40
|
export interface Tool {
|
|
41
41
|
name: string;
|
|
42
42
|
summary: string;
|
|
43
|
+
contract?: number;
|
|
43
44
|
install(ctx: ToolContext): Promise<void>;
|
|
44
45
|
uninstall(ctx: ToolContext): Promise<void>;
|
|
45
46
|
checks(ctx: ToolContext): Promise<Check[]>;
|
package/dist/workflow.d.ts
CHANGED
|
@@ -49,4 +49,8 @@ export interface PromptInput {
|
|
|
49
49
|
export declare function buildPrompt(p: PromptInput): string;
|
|
50
50
|
export declare function verdictBlock<T>(steps: T[], i: number, isVerdict: (s: T) => boolean): [number, number];
|
|
51
51
|
export declare function fixTarget<T>(steps: T[], blockStart: number, isProducer: (s: T) => boolean): number;
|
|
52
|
+
export declare function needsPlan(steps: {
|
|
53
|
+
output: string;
|
|
54
|
+
builtin?: string;
|
|
55
|
+
}[], from: number): boolean;
|
|
52
56
|
export declare function planBody(output: string): string;
|
package/dist/workflow.js
CHANGED
|
@@ -105,6 +105,21 @@ export function fixTarget(steps, blockStart, isProducer) {
|
|
|
105
105
|
return j;
|
|
106
106
|
return -1;
|
|
107
107
|
}
|
|
108
|
+
// Whether a run starting at `from` needs a plan comment: some step before any
|
|
109
|
+
// Plan step writes commits or checks the plan. A check counts as a plan check
|
|
110
|
+
// only when a Plan step comes before it, so a review-only workflow runs bare.
|
|
111
|
+
export function needsPlan(steps, from) {
|
|
112
|
+
for (let n = from; n < steps.length; n++) {
|
|
113
|
+
const s = steps[n];
|
|
114
|
+
if (s.output === 'plan')
|
|
115
|
+
return false;
|
|
116
|
+
if (s.output === 'commits')
|
|
117
|
+
return true;
|
|
118
|
+
if (s.output === 'verdict' && s.builtin !== 'vs-qa' && (s.builtin === 'vs-plan-review' || steps.slice(0, n).some((d) => d.output === 'plan')))
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
return false;
|
|
122
|
+
}
|
|
108
123
|
// Plan output minus the trailing RESULT line, ready to post.
|
|
109
124
|
export function planBody(output) {
|
|
110
125
|
return output.replace(/^\s*RESULT:.*$/gim, '').trim();
|
package/pack/vs-plan/SKILL.md
CHANGED
|
@@ -94,7 +94,9 @@ The plan file holds the detail. In the chat, keep it short and plain:
|
|
|
94
94
|
Start straight at Understanding. Do not repeat the card id or title; the card
|
|
95
95
|
already shows them.
|
|
96
96
|
|
|
97
|
-
|
|
97
|
+
Outcome first. Under 350 words for a small change, never over 700 for any
|
|
98
|
+
change. File and line detail only in Surfaces; no code blocks over 5 lines;
|
|
99
|
+
Six angles lists only angles that apply.
|
|
98
100
|
|
|
99
101
|
**Understanding.** Two to four sentences: what the card asks, what it got
|
|
100
102
|
wrong, which reading you chose.
|
|
@@ -123,6 +125,7 @@ anything a human must click.
|
|
|
123
125
|
- No ticket ids in code comments.
|
|
124
126
|
- No em dashes or en dashes.
|
|
125
127
|
- No narration of process. State findings.
|
|
128
|
+
- Do not quote code the dev agent can read.
|
|
126
129
|
|
|
127
130
|
## When you run inside a workflow
|
|
128
131
|
|
|
@@ -41,7 +41,8 @@ main points.
|
|
|
41
41
|
2. The findings that change what gets built, most important first, at most 5,
|
|
42
42
|
one short plain line each: what is wrong and what to do about it.
|
|
43
43
|
3. Everything smaller in one line: `Minor: ...`.
|
|
44
|
-
4. A short `Details` section:
|
|
44
|
+
4. A short `Details` section: one line per finding, the file and line that
|
|
45
|
+
proves it.
|
|
45
46
|
5. Last line, exactly one of:
|
|
46
47
|
|
|
47
48
|
```
|
|
@@ -56,6 +57,7 @@ fixes to the plan file.
|
|
|
56
57
|
|
|
57
58
|
- No em dashes or en dashes.
|
|
58
59
|
- No narration of process. State findings.
|
|
60
|
+
- Whole review under 300 words.
|
|
59
61
|
|
|
60
62
|
## When you run inside a workflow
|
|
61
63
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@voidlabs.gg/voidshift-agents",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Agents for Claude Code and Codex that plan, build and QA Linear cards, chained and run in the background.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -40,7 +40,7 @@
|
|
|
40
40
|
"scripts": {
|
|
41
41
|
"build": "rm -rf dist && tsc -p tsconfig.json",
|
|
42
42
|
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
43
|
-
"test": "node --test test/*.test.ts",
|
|
43
|
+
"test": "node --test --test-force-exit --test-timeout=60000 test/*.test.ts",
|
|
44
44
|
"prepublishOnly": "rm -rf dist && tsc -p tsconfig.json"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|