@wanbicoi/next-steps 0.1.1 → 0.1.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 +63 -0
- package/dist/cli.js +129 -40
- package/docs/AGENTS.sample.md +120 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -92,6 +92,7 @@ next-steps next --key <key> [--json]
|
|
|
92
92
|
next-steps steps --key <key> [--limit <n>] [--after <seq>] [--json]
|
|
93
93
|
next-steps keys [--json]
|
|
94
94
|
next-steps reset --key <key> [--json]
|
|
95
|
+
next-steps agents [--json]
|
|
95
96
|
|
|
96
97
|
next-steps workflow new [--json]
|
|
97
98
|
next-steps workflow show [--json]
|
|
@@ -106,6 +107,68 @@ documented in `docs/AGENTS.sample.md`.
|
|
|
106
107
|
`create` only opens a session (the key is the task id; a UUID is generated when
|
|
107
108
|
omitted). It does not author the graph — use `workflow new` for that.
|
|
108
109
|
|
|
110
|
+
`agents` prints `docs/AGENTS.sample.md`, the playbook an agent reads to drive
|
|
111
|
+
this CLI from another repo. Copy the output into that repo's `AGENTS.md`:
|
|
112
|
+
|
|
113
|
+
```sh
|
|
114
|
+
next-steps agents > /tmp/agents.md
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
## Hooks
|
|
118
|
+
|
|
119
|
+
If `.next-steps/hook` exists and is executable (`chmod +x`), every successful
|
|
120
|
+
`log` runs it after the state is written. Use it to mirror progress into a
|
|
121
|
+
status bar, window title, or agent sidebar. Any language works; a Python hook:
|
|
122
|
+
|
|
123
|
+
```python
|
|
124
|
+
#!/usr/bin/env python3
|
|
125
|
+
# .next-steps/hook — mirror the state into a status file
|
|
126
|
+
import json
|
|
127
|
+
import sys
|
|
128
|
+
|
|
129
|
+
payload = json.load(sys.stdin)
|
|
130
|
+
|
|
131
|
+
if payload["status"] == "done":
|
|
132
|
+
label = "done"
|
|
133
|
+
elif payload["status"] == "review":
|
|
134
|
+
label = ", ".join(n["title"] for n in payload["next"] if n["reviewRequired"])
|
|
135
|
+
else:
|
|
136
|
+
label = " | ".join(n["title"] for n in payload["next"]) or payload["last"]["title"]
|
|
137
|
+
|
|
138
|
+
with open("/tmp/next-steps-status", "w") as out:
|
|
139
|
+
out.write(f'{payload["key"]} {payload["status"]} {label}\n')
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Replace the file write with a call to your status tool (a tmux status line, a
|
|
143
|
+
Herdr sidebar, a file watcher). The hook runs from the project root and inherits
|
|
144
|
+
the normal environment; its stdout and stderr are discarded, so report through
|
|
145
|
+
the tool it drives.
|
|
146
|
+
|
|
147
|
+
The payload on stdin is a status snapshot, not the full event:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
{
|
|
151
|
+
"event": "log",
|
|
152
|
+
"key": "4f1c...",
|
|
153
|
+
"status": "review",
|
|
154
|
+
"last": { "id": "read-issue", "title": "Read the issue" },
|
|
155
|
+
"next": [{ "id": "implement", "title": "Implement", "reviewRequired": true }]
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
`status` is derived: `done` when the run finished, `review` when any legal next
|
|
160
|
+
node has `reviewRequired`, otherwise `running`. `last` is the node just logged;
|
|
161
|
+
`next` is what the agent may do now. For anything richer (edges, descriptions,
|
|
162
|
+
conditions, history), call `next-steps next --key <key> --json` — the payload
|
|
163
|
+
carries the `key`.
|
|
164
|
+
|
|
165
|
+
Hooks run synchronously with a five-second timeout. A missing hook is silent; a
|
|
166
|
+
hook that fails, times out, or lacks the execute bit prints a warning to stderr
|
|
167
|
+
and never changes the log's exit code. Only `log` triggers the hook, so a fresh
|
|
168
|
+
session stays quiet until its first node is finished. Commit `.next-steps/hook`
|
|
169
|
+
when the whole team should share the behaviour; leave it untracked for a local
|
|
170
|
+
adapter.
|
|
171
|
+
|
|
109
172
|
## Workflow format
|
|
110
173
|
|
|
111
174
|
```json
|
package/dist/cli.js
CHANGED
|
@@ -2,11 +2,16 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// src/cli.ts
|
|
5
|
-
import { existsSync as
|
|
6
|
-
import { basename, join as
|
|
5
|
+
import { existsSync as existsSync5 } from "fs";
|
|
6
|
+
import { basename, dirname as dirname5, join as join3 } from "path";
|
|
7
7
|
import { pathToFileURL } from "url";
|
|
8
8
|
import { parseArgs } from "util";
|
|
9
9
|
|
|
10
|
+
// src/commands/agents.ts
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
12
|
+
import { dirname, join } from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
10
15
|
// src/types.ts
|
|
11
16
|
var END = "END";
|
|
12
17
|
|
|
@@ -23,6 +28,40 @@ class AppError extends Error {
|
|
|
23
28
|
}
|
|
24
29
|
}
|
|
25
30
|
|
|
31
|
+
// src/commands/shared.ts
|
|
32
|
+
function usageError(message) {
|
|
33
|
+
return new AppError("usage", message, 2);
|
|
34
|
+
}
|
|
35
|
+
function requireString(value, flag) {
|
|
36
|
+
if (typeof value !== "string" || value.length === 0) {
|
|
37
|
+
throw usageError(`--${flag} is required`);
|
|
38
|
+
}
|
|
39
|
+
return value;
|
|
40
|
+
}
|
|
41
|
+
function toJson(value) {
|
|
42
|
+
return JSON.stringify(value, null, 2);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// src/commands/agents.ts
|
|
46
|
+
var SAMPLE = join("docs", "AGENTS.sample.md");
|
|
47
|
+
function findSample() {
|
|
48
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
49
|
+
for (let depth = 0;depth < 6; depth++) {
|
|
50
|
+
const candidate = join(dir, SAMPLE);
|
|
51
|
+
if (existsSync(candidate))
|
|
52
|
+
return candidate;
|
|
53
|
+
const parent = dirname(dir);
|
|
54
|
+
if (parent === dir)
|
|
55
|
+
break;
|
|
56
|
+
dir = parent;
|
|
57
|
+
}
|
|
58
|
+
throw new AppError("agents_sample_missing", "could not find docs/AGENTS.sample.md next to the package", 3);
|
|
59
|
+
}
|
|
60
|
+
function cmdAgents(json) {
|
|
61
|
+
const markdown = readFileSync(findSample(), "utf8");
|
|
62
|
+
return json ? toJson({ markdown }) : markdown.trimEnd();
|
|
63
|
+
}
|
|
64
|
+
|
|
26
65
|
// src/format.ts
|
|
27
66
|
function table(headers, rows) {
|
|
28
67
|
const widths = headers.map((header, index) => Math.max(header.length, ...rows.map((row) => (row[index] ?? "").length)));
|
|
@@ -63,20 +102,6 @@ function formatCurrent(next) {
|
|
|
63
102
|
return `current: ${next.current}${title}`;
|
|
64
103
|
}
|
|
65
104
|
|
|
66
|
-
// src/commands/shared.ts
|
|
67
|
-
function usageError(message) {
|
|
68
|
-
return new AppError("usage", message, 2);
|
|
69
|
-
}
|
|
70
|
-
function requireString(value, flag) {
|
|
71
|
-
if (typeof value !== "string" || value.length === 0) {
|
|
72
|
-
throw usageError(`--${flag} is required`);
|
|
73
|
-
}
|
|
74
|
-
return value;
|
|
75
|
-
}
|
|
76
|
-
function toJson(value) {
|
|
77
|
-
return JSON.stringify(value, null, 2);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
105
|
// src/commands/session.ts
|
|
81
106
|
function cmdCreate(service, input) {
|
|
82
107
|
const { session, workflowTitle, next } = service.create({ key: input.key });
|
|
@@ -101,10 +126,12 @@ function cmdCreate(service, input) {
|
|
|
101
126
|
].join(`
|
|
102
127
|
`);
|
|
103
128
|
}
|
|
104
|
-
function cmdLog(service, input) {
|
|
129
|
+
function cmdLog(service, input, onLogged) {
|
|
105
130
|
const key = requireString(input.key, "key");
|
|
106
131
|
const node = requireString(input.node, "node");
|
|
107
|
-
const
|
|
132
|
+
const result = service.log(key, node);
|
|
133
|
+
const { session, step, next } = result;
|
|
134
|
+
onLogged?.(result);
|
|
108
135
|
if (input.json) {
|
|
109
136
|
return toJson({
|
|
110
137
|
step,
|
|
@@ -213,19 +240,19 @@ function toJson2(value) {
|
|
|
213
240
|
}
|
|
214
241
|
|
|
215
242
|
// src/commands/viz.ts
|
|
216
|
-
import { existsSync, readFileSync } from "node:fs";
|
|
243
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "node:fs";
|
|
217
244
|
import { createServer } from "node:http";
|
|
218
245
|
import { createRequire } from "node:module";
|
|
219
|
-
import { dirname, join } from "node:path";
|
|
220
|
-
import { fileURLToPath } from "node:url";
|
|
246
|
+
import { dirname as dirname2, join as join2 } from "node:path";
|
|
247
|
+
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
221
248
|
var require2 = createRequire(import.meta.url);
|
|
222
249
|
function findTemplate() {
|
|
223
|
-
let dir =
|
|
250
|
+
let dir = dirname2(fileURLToPath2(import.meta.url));
|
|
224
251
|
for (let depth = 0;depth < 6; depth++) {
|
|
225
|
-
const candidate =
|
|
226
|
-
if (
|
|
252
|
+
const candidate = join2(dir, "viz", "template.html");
|
|
253
|
+
if (existsSync2(candidate))
|
|
227
254
|
return candidate;
|
|
228
|
-
const parent =
|
|
255
|
+
const parent = dirname2(dir);
|
|
229
256
|
if (parent === dir)
|
|
230
257
|
break;
|
|
231
258
|
dir = parent;
|
|
@@ -237,9 +264,9 @@ function renderVizHtml(template, workflow) {
|
|
|
237
264
|
return template.replace("__WORKFLOW_DATA__", data);
|
|
238
265
|
}
|
|
239
266
|
function startVizServer(workflow, port) {
|
|
240
|
-
const template =
|
|
241
|
-
const cytoscapeJs =
|
|
242
|
-
const dagreJs =
|
|
267
|
+
const template = readFileSync2(findTemplate(), "utf8");
|
|
268
|
+
const cytoscapeJs = readFileSync2(require2.resolve("cytoscape/dist/cytoscape.min.js"), "utf8");
|
|
269
|
+
const dagreJs = readFileSync2(require2.resolve("cytoscape-dagre"), "utf8");
|
|
243
270
|
const html = renderVizHtml(template, workflow);
|
|
244
271
|
const server = createServer((request, response) => {
|
|
245
272
|
const path = (request.url ?? "/").split("?")[0];
|
|
@@ -270,8 +297,8 @@ function startVizServer(workflow, port) {
|
|
|
270
297
|
}
|
|
271
298
|
|
|
272
299
|
// src/commands/workflow.ts
|
|
273
|
-
import { existsSync as
|
|
274
|
-
import { dirname as
|
|
300
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync } from "node:fs";
|
|
301
|
+
import { dirname as dirname3 } from "node:path";
|
|
275
302
|
var ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/;
|
|
276
303
|
function skeleton(id) {
|
|
277
304
|
return {
|
|
@@ -289,11 +316,11 @@ function skeleton(id) {
|
|
|
289
316
|
};
|
|
290
317
|
}
|
|
291
318
|
function cmdWorkflowNew(graphPath, id, json) {
|
|
292
|
-
if (
|
|
319
|
+
if (existsSync3(graphPath)) {
|
|
293
320
|
throw usageError(`workflow file already exists: ${graphPath}`);
|
|
294
321
|
}
|
|
295
322
|
const workflowId = ID_PATTERN.test(id) ? id : "workflow";
|
|
296
|
-
mkdirSync(
|
|
323
|
+
mkdirSync(dirname3(graphPath), { recursive: true });
|
|
297
324
|
writeFileSync(graphPath, `${JSON.stringify(skeleton(workflowId), null, 2)}
|
|
298
325
|
`, "utf8");
|
|
299
326
|
if (json)
|
|
@@ -355,6 +382,10 @@ function cmdWorkflowCheck(load, json) {
|
|
|
355
382
|
`);
|
|
356
383
|
}
|
|
357
384
|
|
|
385
|
+
// src/runs/hook.ts
|
|
386
|
+
import { spawnSync } from "node:child_process";
|
|
387
|
+
import { existsSync as existsSync4, statSync } from "node:fs";
|
|
388
|
+
|
|
358
389
|
// src/workflow/validate.ts
|
|
359
390
|
import { z } from "zod";
|
|
360
391
|
var moveSchema = z.object({
|
|
@@ -560,6 +591,58 @@ function computeNext(wf, session) {
|
|
|
560
591
|
};
|
|
561
592
|
}
|
|
562
593
|
|
|
594
|
+
// src/runs/hook.ts
|
|
595
|
+
var TIMEOUT_MS = 5000;
|
|
596
|
+
function isExecutable(path) {
|
|
597
|
+
try {
|
|
598
|
+
return (statSync(path).mode & 73) !== 0;
|
|
599
|
+
} catch {
|
|
600
|
+
return false;
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
function warn(message) {
|
|
604
|
+
process.stderr.write(`hook: ${message}
|
|
605
|
+
`);
|
|
606
|
+
}
|
|
607
|
+
function runPostLogHook(config, result) {
|
|
608
|
+
if (!existsSync4(config.path))
|
|
609
|
+
return;
|
|
610
|
+
if (!isExecutable(config.path)) {
|
|
611
|
+
warn(`${config.path} is not executable (chmod +x)`);
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
const { done, next } = result.next;
|
|
615
|
+
const review = next.filter((option) => option.reviewRequired);
|
|
616
|
+
const payload = {
|
|
617
|
+
event: "log",
|
|
618
|
+
key: result.session.key,
|
|
619
|
+
status: done ? "done" : review.length > 0 ? "review" : "running",
|
|
620
|
+
last: {
|
|
621
|
+
id: result.step.node,
|
|
622
|
+
title: getNode(config.workflow, result.step.node)?.title ?? result.step.node
|
|
623
|
+
},
|
|
624
|
+
next: next.map((option) => ({
|
|
625
|
+
id: option.node,
|
|
626
|
+
title: option.title,
|
|
627
|
+
reviewRequired: option.reviewRequired
|
|
628
|
+
}))
|
|
629
|
+
};
|
|
630
|
+
const run = spawnSync(config.path, [], {
|
|
631
|
+
cwd: config.cwd,
|
|
632
|
+
input: JSON.stringify(payload),
|
|
633
|
+
encoding: "utf8",
|
|
634
|
+
timeout: TIMEOUT_MS
|
|
635
|
+
});
|
|
636
|
+
if (run.error) {
|
|
637
|
+
warn(`${config.path} failed: ${run.error.message}`);
|
|
638
|
+
return;
|
|
639
|
+
}
|
|
640
|
+
if (run.status !== 0) {
|
|
641
|
+
const detail = (run.stderr ?? "").trim();
|
|
642
|
+
warn(`${config.path} exited ${run.status}${detail ? `: ${detail}` : ""}`);
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
563
646
|
// src/runs/service.ts
|
|
564
647
|
var KEY_PATTERN = /^[A-Za-z0-9._-]{1,128}$/;
|
|
565
648
|
|
|
@@ -694,7 +777,7 @@ class SessionService {
|
|
|
694
777
|
|
|
695
778
|
// src/runs/store.ts
|
|
696
779
|
import { mkdirSync as mkdirSync2, unlinkSync } from "node:fs";
|
|
697
|
-
import { dirname as
|
|
780
|
+
import { dirname as dirname4 } from "node:path";
|
|
698
781
|
|
|
699
782
|
// src/runs/driver.ts
|
|
700
783
|
import { createRequire as createRequire2 } from "node:module";
|
|
@@ -819,7 +902,7 @@ class SqliteRunStore {
|
|
|
819
902
|
constructor(path) {
|
|
820
903
|
this.path = path;
|
|
821
904
|
if (path !== ":memory:") {
|
|
822
|
-
mkdirSync2(
|
|
905
|
+
mkdirSync2(dirname4(path), { recursive: true });
|
|
823
906
|
}
|
|
824
907
|
this.db = openDriver(path);
|
|
825
908
|
if (path !== ":memory:") {
|
|
@@ -916,11 +999,11 @@ class AppError2 extends Error {
|
|
|
916
999
|
}
|
|
917
1000
|
|
|
918
1001
|
// src/workflow/load.ts
|
|
919
|
-
import { readFileSync as
|
|
1002
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
920
1003
|
function loadWorkflowFile(path) {
|
|
921
1004
|
let raw;
|
|
922
1005
|
try {
|
|
923
|
-
raw = JSON.parse(
|
|
1006
|
+
raw = JSON.parse(readFileSync3(path, "utf8"));
|
|
924
1007
|
} catch (error) {
|
|
925
1008
|
const message = error instanceof Error ? error.message : String(error);
|
|
926
1009
|
const missing = error.code === "ENOENT";
|
|
@@ -947,6 +1030,7 @@ usage:
|
|
|
947
1030
|
next-steps steps --key <key> [--limit <n>] [--after <seq>] [--json]
|
|
948
1031
|
next-steps keys [--json]
|
|
949
1032
|
next-steps reset --key <key> [--json]
|
|
1033
|
+
next-steps agents [--json] (print the agent playbook)
|
|
950
1034
|
|
|
951
1035
|
next-steps workflow new [--json]
|
|
952
1036
|
next-steps workflow show [--json]
|
|
@@ -959,6 +1043,7 @@ usage:
|
|
|
959
1043
|
|
|
960
1044
|
state: ./.next-steps/state.db (created on first use, gitignored)
|
|
961
1045
|
graph: ./.next-steps/workflow.json (one graph per project, committed)
|
|
1046
|
+
hook: ./.next-steps/hook (optional, runs after each log)
|
|
962
1047
|
|
|
963
1048
|
exit codes:
|
|
964
1049
|
0 ok 2 usage 3 not found 4 invalid transition 5 invalid workflow`;
|
|
@@ -973,7 +1058,7 @@ function asString(value) {
|
|
|
973
1058
|
return typeof value === "string" ? value : undefined;
|
|
974
1059
|
}
|
|
975
1060
|
function requireGraphFile(graphPath) {
|
|
976
|
-
if (
|
|
1061
|
+
if (existsSync5(graphPath))
|
|
977
1062
|
return;
|
|
978
1063
|
throw new AppError2("workflow_not_found", `workflow file not found: ${graphPath} (run "next-steps workflow new")`, 3);
|
|
979
1064
|
}
|
|
@@ -989,8 +1074,8 @@ function requireWorkflow(graphPath) {
|
|
|
989
1074
|
}
|
|
990
1075
|
function execute(argv, options) {
|
|
991
1076
|
const cwd = options.cwd ?? process.cwd();
|
|
992
|
-
const graphPath = options.graphPath ??
|
|
993
|
-
const dbPath = options.dbPath ??
|
|
1077
|
+
const graphPath = options.graphPath ?? join3(cwd, ".next-steps", "workflow.json");
|
|
1078
|
+
const dbPath = options.dbPath ?? join3(cwd, ".next-steps", "state.db");
|
|
994
1079
|
const [command, ...rest] = argv;
|
|
995
1080
|
if (!command || command === "help" || command === "--help" || command === "-h") {
|
|
996
1081
|
return HELP;
|
|
@@ -998,6 +1083,10 @@ function execute(argv, options) {
|
|
|
998
1083
|
if (command === "version" || command === "--version" || command === "-v") {
|
|
999
1084
|
return `next-steps ${VERSION}`;
|
|
1000
1085
|
}
|
|
1086
|
+
if (command === "agents") {
|
|
1087
|
+
const { values } = parseOptions(rest, { json: { type: "boolean" } });
|
|
1088
|
+
return cmdAgents(Boolean(values.json));
|
|
1089
|
+
}
|
|
1001
1090
|
if (command === "workflow") {
|
|
1002
1091
|
const { positionals, values } = parseOptions(rest, { json: { type: "boolean" } }, true);
|
|
1003
1092
|
const [subcommand] = positionals;
|
|
@@ -1051,7 +1140,7 @@ press Ctrl+C to stop`;
|
|
|
1051
1140
|
key: asString(values.key),
|
|
1052
1141
|
node: asString(values.node),
|
|
1053
1142
|
json: Boolean(values.json)
|
|
1054
|
-
});
|
|
1143
|
+
}, (result) => runPostLogHook({ path: join3(dirname5(graphPath), "hook"), cwd, workflow }, result));
|
|
1055
1144
|
}
|
|
1056
1145
|
case "next": {
|
|
1057
1146
|
const { values } = parseOptions(rest, {
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
<!-- Template: copy this to AGENTS.md in a repo that uses next-steps on PATH. -->
|
|
2
|
+
|
|
3
|
+
# Work the graph with next-steps
|
|
4
|
+
|
|
5
|
+
This repo defines its work as one graph in `.next-steps/workflow.json`.
|
|
6
|
+
`next-steps` walks that graph one node at a time: it tells you which nodes are
|
|
7
|
+
legal now, and records the path you take.
|
|
8
|
+
|
|
9
|
+
Pass `--json` to every command.
|
|
10
|
+
|
|
11
|
+
## Walk
|
|
12
|
+
|
|
13
|
+
1. **Open a session.**
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
next-steps create --json
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
The response holds `key` (your task id) and the first `next` list.
|
|
20
|
+
|
|
21
|
+
**Done when:** you have `key` in hand and a non-empty `next` list.
|
|
22
|
+
|
|
23
|
+
2. **Choose one next node.**
|
|
24
|
+
|
|
25
|
+
Each entry has `node`, `edge`, `title`, `description`, `reviewRequired`, and
|
|
26
|
+
sometimes `condition`. The `condition` is prose telling you when that branch
|
|
27
|
+
applies; you judge it against the task, nothing checks it for you. `edge` is
|
|
28
|
+
derived from `from->to` and is what the log will record.
|
|
29
|
+
|
|
30
|
+
```json
|
|
31
|
+
{
|
|
32
|
+
"current": { "node": "verify", "title": "Verify" },
|
|
33
|
+
"next": [
|
|
34
|
+
{ "edge": "verify->implement", "node": "implement", "title": "Implement",
|
|
35
|
+
"description": "Loop back and fix the failure.",
|
|
36
|
+
"condition": "the build or the tests failed",
|
|
37
|
+
"reviewRequired": true },
|
|
38
|
+
{ "edge": "verify->review", "node": "review", "title": "Peer review",
|
|
39
|
+
"description": "Hand the change to review.",
|
|
40
|
+
"condition": "the build and the tests pass",
|
|
41
|
+
"reviewRequired": false }
|
|
42
|
+
],
|
|
43
|
+
"done": false
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
**Done when:** exactly one entry is chosen.
|
|
48
|
+
|
|
49
|
+
3. **If that entry has `reviewRequired: true`, stop and ask.**
|
|
50
|
+
|
|
51
|
+
Prepare everything the node needs — the plan, the diff, the command you are
|
|
52
|
+
about to run — show it to the user, and wait for an explicit yes. Then act.
|
|
53
|
+
|
|
54
|
+
**Done when:** the user said yes. The CLI only reports the flag — nothing
|
|
55
|
+
enforces it. A node without the flag: skip this step.
|
|
56
|
+
|
|
57
|
+
4. **Do that node's work.**
|
|
58
|
+
|
|
59
|
+
**Done when:** the node's `description` is satisfied — not when you started
|
|
60
|
+
it.
|
|
61
|
+
|
|
62
|
+
5. **Log the node.**
|
|
63
|
+
|
|
64
|
+
```sh
|
|
65
|
+
next-steps log --key <key> --node <node> --json
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
One log per visit, after the work. Log is the only call per node; the
|
|
69
|
+
response already carries the new `next` list, so continue straight from it.
|
|
70
|
+
|
|
71
|
+
**Done when:** exit `0` and the response contains the new `next` list.
|
|
72
|
+
|
|
73
|
+
6. **Repeat 2–5 until the response says `done: true`.**
|
|
74
|
+
|
|
75
|
+
**Done when:** `done` is true. Stop there; a written summary is its own node
|
|
76
|
+
if the graph wants one.
|
|
77
|
+
|
|
78
|
+
`next-steps next --key <key> --json` returns the same list on demand. Use it when
|
|
79
|
+
you resume rather than during the walk.
|
|
80
|
+
|
|
81
|
+
## Branches
|
|
82
|
+
|
|
83
|
+
- **`END` is a legal target.** Log it like any node; the session becomes
|
|
84
|
+
`done`. The `next` list also labels it `finish`.
|
|
85
|
+
- **A node whose only move is `END`** finishes the run as soon as you log it.
|
|
86
|
+
The response is already `done: true`; there is no extra step.
|
|
87
|
+
- **Exit `4`** means the move is not legal. The error's `details.legal` is the
|
|
88
|
+
complete list of nodes you can move to; choose from it, or correct the work and
|
|
89
|
+
log the node the graph expects.
|
|
90
|
+
- **Exit `3`** means the key is unknown or `.next-steps/workflow.json` is
|
|
91
|
+
missing — run `next-steps workflow new` if the graph has never been created. A
|
|
92
|
+
key belongs to one task: create a fresh session for a new task.
|
|
93
|
+
|
|
94
|
+
## Resume
|
|
95
|
+
|
|
96
|
+
A new agent, or the same one after a context reset, rebuilds the task from the
|
|
97
|
+
tool:
|
|
98
|
+
|
|
99
|
+
```sh
|
|
100
|
+
next-steps steps --key <key> --json # audit trail: seq, node, from, edge, at
|
|
101
|
+
next-steps next --key <key> --json # legal moves now
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
The `steps` record is the source of truth for what already happened.
|
|
105
|
+
|
|
106
|
+
## Housekeeping
|
|
107
|
+
|
|
108
|
+
```sh
|
|
109
|
+
next-steps keys --json # sessions on this machine
|
|
110
|
+
next-steps reset --key <key> --json # delete a finished or abandoned session
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`.next-steps/state.db` is local state and is gitignored. The graph,
|
|
114
|
+
`.next-steps/workflow.json`, is source code: commit it with the rest of the repo.
|
|
115
|
+
|
|
116
|
+
## Exit codes
|
|
117
|
+
|
|
118
|
+
`0` ok · `2` usage, duplicate key, or invalid key · `3` not found ·
|
|
119
|
+
`4` invalid move or unknown node · `5` invalid workflow file. Errors go to
|
|
120
|
+
stderr as `{ "error": { "code", "message", "details" } }`.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wanbicoi/next-steps",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Agent-facing workflow walker: log a step, list the next ones.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
12
|
"viz/template.html",
|
|
13
|
+
"docs/AGENTS.sample.md",
|
|
13
14
|
"README.md",
|
|
14
15
|
"LICENSE"
|
|
15
16
|
],
|