clawspec 1.0.19 → 1.0.21
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 +6 -0
- package/README.zh-CN.md +6 -0
- package/package.json +1 -2
- package/src/bootstrap/state.ts +128 -0
- package/src/dependencies/acpx.ts +6 -0
- package/src/dependencies/openspec.ts +5 -0
- package/src/index.ts +125 -43
- package/src/watchers/manager.ts +69 -1
- package/test/acp-client.test.ts +0 -309
- package/test/acpx-dependency.test.ts +0 -133
- package/test/assistant-journal.test.ts +0 -203
- package/test/command-surface.test.ts +0 -24
- package/test/config.test.ts +0 -77
- package/test/detach-attach.test.ts +0 -98
- package/test/doctor.test.ts +0 -142
- package/test/file-lock.test.ts +0 -88
- package/test/fs-utils.test.ts +0 -22
- package/test/helpers/harness.ts +0 -305
- package/test/helpers.test.ts +0 -108
- package/test/keywords.test.ts +0 -92
- package/test/notifier.test.ts +0 -29
- package/test/openspec-dependency.test.ts +0 -68
- package/test/paths-utils.test.ts +0 -30
- package/test/pause-cancel.test.ts +0 -55
- package/test/planning-journal.test.ts +0 -155
- package/test/plugin-registration.test.ts +0 -35
- package/test/project-memory.test.ts +0 -42
- package/test/proposal.test.ts +0 -24
- package/test/queue-planning.test.ts +0 -322
- package/test/queue-work.test.ts +0 -220
- package/test/recovery.test.ts +0 -603
- package/test/service-archive.test.ts +0 -87
- package/test/shell-command.test.ts +0 -48
- package/test/state-store.test.ts +0 -74
- package/test/tasks-and-checkpoint.test.ts +0 -60
- package/test/use-project.test.ts +0 -67
- package/test/watcher-planning.test.ts +0 -533
- package/test/watcher-work.test.ts +0 -1771
- package/test/worker-command.test.ts +0 -66
- package/test/worker-io-helper.test.ts +0 -97
- package/test/worker-skills.test.ts +0 -12
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { createServiceHarness, seedPlanningProject } from "./helpers/harness.ts";
|
|
4
|
-
|
|
5
|
-
test("worker command sets agent", async () => {
|
|
6
|
-
const harness = await createServiceHarness("clawspec-worker-command-");
|
|
7
|
-
const { service, stateStore } = harness;
|
|
8
|
-
const channelKey = "discord:worker-command:default:main";
|
|
9
|
-
|
|
10
|
-
await service.startProject(channelKey);
|
|
11
|
-
const before = await service.workerProject(channelKey, "");
|
|
12
|
-
const setResult = await service.workerProject(channelKey, "piper");
|
|
13
|
-
const project = await stateStore.getActiveProject(channelKey);
|
|
14
|
-
|
|
15
|
-
assert.match(before.text ?? "", /Current worker agent: `codex`/);
|
|
16
|
-
assert.match(setResult.text ?? "", /Worker Agent Updated/);
|
|
17
|
-
assert.equal(project?.workerAgentId, "piper");
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
test("worker status shows live state", async () => {
|
|
21
|
-
const harness = await createServiceHarness("clawspec-worker-status-");
|
|
22
|
-
const { service, stateStore, workspacePath, repoPath, changeDir } = harness;
|
|
23
|
-
const channelKey = "discord:worker-status:default:main";
|
|
24
|
-
|
|
25
|
-
await seedPlanningProject(stateStore, channelKey, {
|
|
26
|
-
workspacePath,
|
|
27
|
-
repoPath,
|
|
28
|
-
projectName: "demo-app",
|
|
29
|
-
changeName: "watch-status",
|
|
30
|
-
changeDir,
|
|
31
|
-
phase: "implementing",
|
|
32
|
-
status: "running",
|
|
33
|
-
planningDirty: false,
|
|
34
|
-
execution: { action: "work", state: "running", mode: "apply" },
|
|
35
|
-
});
|
|
36
|
-
|
|
37
|
-
await stateStore.updateProject(channelKey, (current) => ({
|
|
38
|
-
...current,
|
|
39
|
-
workerAgentId: "codex",
|
|
40
|
-
latestSummary: "Worker is currently applying task 1.2.",
|
|
41
|
-
taskCounts: { total: 4, complete: 1, remaining: 3 },
|
|
42
|
-
execution: current.execution
|
|
43
|
-
? {
|
|
44
|
-
...current.execution,
|
|
45
|
-
workerAgentId: "codex",
|
|
46
|
-
workerSlot: "primary",
|
|
47
|
-
startupPhase: "connected",
|
|
48
|
-
currentTaskId: "1.2",
|
|
49
|
-
currentArtifact: undefined,
|
|
50
|
-
sessionKey: "watcher:demo",
|
|
51
|
-
connectedAt: new Date(Date.now() - 6_000).toISOString(),
|
|
52
|
-
lastHeartbeatAt: "2026-03-22T10:00:00.000Z",
|
|
53
|
-
}
|
|
54
|
-
: current.execution,
|
|
55
|
-
}));
|
|
56
|
-
|
|
57
|
-
const result = await service.workerProject(channelKey, "status");
|
|
58
|
-
assert.match(result.text ?? "", /Worker Status/);
|
|
59
|
-
assert.match(result.text ?? "", /Change: `watch-status`/);
|
|
60
|
-
assert.match(result.text ?? "", /Execution state: `running`/);
|
|
61
|
-
assert.match(result.text ?? "", /Startup phase: `connected`/);
|
|
62
|
-
assert.match(result.text ?? "", /Startup wait: `\d+s`/);
|
|
63
|
-
assert.match(result.text ?? "", /Current task: `1.2`/);
|
|
64
|
-
assert.match(result.text ?? "", /Progress: 1\/4 complete, 3 remaining/);
|
|
65
|
-
assert.match(result.text ?? "", /Next: Wait for worker updates or use `\/clawspec pause`\./);
|
|
66
|
-
});
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import os from "node:os";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { mkdtemp, mkdir, writeFile } from "node:fs/promises";
|
|
6
|
-
import { spawnSync } from "node:child_process";
|
|
7
|
-
import { ensureWorkerIoHelper } from "../src/worker/io-helper.ts";
|
|
8
|
-
import { buildAcpImplementationTurnPrompt } from "../src/worker/prompts.ts";
|
|
9
|
-
import { getRepoStatePaths } from "../src/utils/paths.ts";
|
|
10
|
-
import { readUtf8 } from "../src/utils/fs.ts";
|
|
11
|
-
|
|
12
|
-
test("worker io helper appends progress events via node command", async () => {
|
|
13
|
-
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "clawspec-worker-io-helper-"));
|
|
14
|
-
const repoPath = path.join(tempRoot, "demo-app");
|
|
15
|
-
await mkdir(repoPath, { recursive: true });
|
|
16
|
-
const repoStatePaths = getRepoStatePaths(repoPath, "archives");
|
|
17
|
-
await ensureWorkerIoHelper(repoStatePaths);
|
|
18
|
-
|
|
19
|
-
const result = spawnSync(
|
|
20
|
-
process.execPath,
|
|
21
|
-
[
|
|
22
|
-
repoStatePaths.workerIoFile,
|
|
23
|
-
"event",
|
|
24
|
-
"--kind",
|
|
25
|
-
"status",
|
|
26
|
-
"--current",
|
|
27
|
-
"1",
|
|
28
|
-
"--total",
|
|
29
|
-
"3",
|
|
30
|
-
"--task-id",
|
|
31
|
-
"1.1",
|
|
32
|
-
"--message",
|
|
33
|
-
"Preparing 1.1: loading context. Next: read proposal.md.",
|
|
34
|
-
],
|
|
35
|
-
{ encoding: "utf8" },
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
assert.equal(result.status, 0, result.stderr);
|
|
39
|
-
const raw = await readUtf8(repoStatePaths.workerProgressFile);
|
|
40
|
-
const lines = raw.trim().split(/\r?\n/);
|
|
41
|
-
assert.equal(lines.length, 1);
|
|
42
|
-
const payload = JSON.parse(lines[0]!);
|
|
43
|
-
assert.equal(payload.kind, "status");
|
|
44
|
-
assert.equal(payload.current, 1);
|
|
45
|
-
assert.equal(payload.total, 3);
|
|
46
|
-
assert.equal(payload.taskId, "1.1");
|
|
47
|
-
assert.equal(payload.message, "Preparing 1.1: loading context. Next: read proposal.md.");
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
test("implementation prompt instructs the worker to use the helper", async () => {
|
|
51
|
-
const tempRoot = await mkdtemp(path.join(os.tmpdir(), "clawspec-worker-io-prompt-"));
|
|
52
|
-
const repoPath = path.join(tempRoot, "demo-app");
|
|
53
|
-
const changeDir = path.join(repoPath, "openspec", "changes", "demo-change");
|
|
54
|
-
await mkdir(changeDir, { recursive: true });
|
|
55
|
-
await writeFile(path.join(changeDir, "proposal.md"), "# Proposal\n", "utf8");
|
|
56
|
-
await writeFile(path.join(changeDir, "tasks.md"), "- [ ] 1.1 Demo task\n", "utf8");
|
|
57
|
-
|
|
58
|
-
const repoStatePaths = getRepoStatePaths(repoPath, "archives");
|
|
59
|
-
const prompt = buildAcpImplementationTurnPrompt({
|
|
60
|
-
project: {
|
|
61
|
-
version: 1,
|
|
62
|
-
projectId: "project-1",
|
|
63
|
-
channelKey: "discord:test:default:main",
|
|
64
|
-
storagePath: path.join(repoStatePaths.root, "state.json"),
|
|
65
|
-
status: "running",
|
|
66
|
-
phase: "implementing",
|
|
67
|
-
createdAt: new Date().toISOString(),
|
|
68
|
-
updatedAt: new Date().toISOString(),
|
|
69
|
-
repoPath,
|
|
70
|
-
workspacePath: tempRoot,
|
|
71
|
-
projectName: "demo-app",
|
|
72
|
-
changeName: "demo-change",
|
|
73
|
-
changeDir,
|
|
74
|
-
pauseRequested: false,
|
|
75
|
-
},
|
|
76
|
-
repoStatePaths,
|
|
77
|
-
apply: {
|
|
78
|
-
changeName: "demo-change",
|
|
79
|
-
changeDir,
|
|
80
|
-
schemaName: "spec-driven",
|
|
81
|
-
contextFiles: {
|
|
82
|
-
proposal: path.join(changeDir, "proposal.md"),
|
|
83
|
-
tasks: path.join(changeDir, "tasks.md"),
|
|
84
|
-
},
|
|
85
|
-
progress: { total: 1, complete: 0, remaining: 1 },
|
|
86
|
-
tasks: [{ id: "1.1", description: "Demo task", done: false }],
|
|
87
|
-
state: "ready",
|
|
88
|
-
instruction: "Implement the remaining task.",
|
|
89
|
-
},
|
|
90
|
-
task: { id: "1.1", description: "Demo task" },
|
|
91
|
-
tasks: [{ id: "1.1", description: "Demo task" }],
|
|
92
|
-
mode: "apply",
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
assert.match(prompt, /Use the worker IO helper instead of editing .*worker-progress\.jsonl directly\./);
|
|
96
|
-
assert.match(prompt, /worker_io\.mjs['"] event --kind <status\|task_start\|task_done\|blocked>/);
|
|
97
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
import test from "node:test";
|
|
2
|
-
import assert from "node:assert/strict";
|
|
3
|
-
import { loadClawSpecSkillBundle } from "../src/worker/skills.ts";
|
|
4
|
-
|
|
5
|
-
test("loadClawSpecSkillBundle reads bundled runtime skills", async () => {
|
|
6
|
-
const bundle = await loadClawSpecSkillBundle(["apply", "explore", "propose"]);
|
|
7
|
-
|
|
8
|
-
assert.match(bundle, /Imported Skill: openspec-apply-change/);
|
|
9
|
-
assert.match(bundle, /Imported Skill: openspec-explore/);
|
|
10
|
-
assert.match(bundle, /Imported Skill: openspec-propose/);
|
|
11
|
-
assert.doesNotMatch(bundle, /\.codex[\\/]/);
|
|
12
|
-
});
|