clawspec 1.0.9 → 1.0.11

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawspec",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
4
4
  "type": "module",
5
5
  "description": "OpenClaw plugin that orchestrates OpenSpec project workflows with visible main-agent execution.",
6
6
  "keywords": [
@@ -675,6 +675,13 @@ export class ClawSpecService {
675
675
  repoStatePaths.planningJournalSnapshotFile,
676
676
  project.planningJournal?.lastSyncedAt,
677
677
  );
678
+ const snapshot = await journalStore.readSnapshot(repoStatePaths.planningJournalSnapshotFile);
679
+ const digest = await journalStore.digest(project.changeName);
680
+ this.logger.info(`[clawspec] cs-work check for ${project.changeName}:`);
681
+ this.logger.info(` - hasUnsyncedChanges: ${hasUnsyncedChanges}`);
682
+ this.logger.info(` - Snapshot: entryCount=${snapshot?.entryCount}, lastEntryAt=${snapshot?.lastEntryAt}, hash=${snapshot?.contentHash?.slice(0, 8)}`);
683
+ this.logger.info(` - Current digest: entryCount=${digest.entryCount}, lastEntryAt=${digest.lastEntryAt}, hash=${digest.contentHash.slice(0, 8)}`);
684
+ this.logger.info(` - fallbackLastSyncedAt: ${project.planningJournal?.lastSyncedAt}`);
678
685
  if (!hasUnsyncedChanges) {
679
686
  const isDetached = !isProjectContextAttached(project);
680
687
  if (isDetached) {
@@ -2460,9 +2467,12 @@ export class ClawSpecService {
2460
2467
  }
2461
2468
  }
2462
2469
 
2463
- if (!journalDirty) {
2464
- await journalStore.writeSnapshot(repoStatePaths.planningJournalSnapshotFile, project.changeName, timestamp);
2465
- }
2470
+ await journalStore.writeSnapshot(repoStatePaths.planningJournalSnapshotFile, project.changeName, timestamp);
2471
+ const writtenSnapshot = await journalStore.readSnapshot(repoStatePaths.planningJournalSnapshotFile);
2472
+ const currentDigest = await journalStore.digest(project.changeName);
2473
+ this.logger.info(`[clawspec] Planning snapshot written for ${project.changeName}:`);
2474
+ this.logger.info(` - Snapshot: entryCount=${writtenSnapshot?.entryCount}, lastEntryAt=${writtenSnapshot?.lastEntryAt}, hash=${writtenSnapshot?.contentHash?.slice(0, 8)}`);
2475
+ this.logger.info(` - Current digest: entryCount=${currentDigest.entryCount}, lastEntryAt=${currentDigest.lastEntryAt}, hash=${currentDigest.contentHash.slice(0, 8)}`);
2466
2476
  await this.writeLatestSummary(repoStatePaths, latestSummary);
2467
2477
 
2468
2478
  const finalized = await this.stateStore.updateProject(project.channelKey, (current) => ({
@@ -188,13 +188,19 @@ export function buildPlanningPrependContext(params: {
188
188
  "Required workflow for this turn:",
189
189
  "0. The active change directory shown above is the only OpenSpec change directory you may inspect or modify in this turn.",
190
190
  "1. Read planning-journal.jsonl, .openspec.yaml, and any planning artifacts that already exist.",
191
- "2. Treat the prefetched OpenSpec instruction files in this context as the authoritative source for artifact structure, output paths, and writing guidance.",
192
- "3. Use the current visible chat context plus the planning journal to decide whether there are substantive new requirements, constraints, or design changes since the last planning sync.",
193
- "4. The allowed planning scope is limited to what is explicitly supported by the current user message, the planning journal, existing dependency artifacts, and the prefetched OpenSpec instructions.",
194
- "5. Do not invent endpoints, features, constraints, files, acceptance criteria, test scenarios, or architecture details that are not grounded in those sources.",
195
- "6. If there is no substantive planning change, say so clearly in chat and do not rewrite artifacts unnecessarily.",
196
- "7. If artifacts are missing or stale, update `proposal`, `specs`, `design`, and `tasks` in dependency order using those prefetched OpenSpec instruction files.",
197
- "8. Do not generate or rewrite planning artifacts from ad-hoc structure guesses; follow OpenSpec instruction/template constraints only.",
191
+ "2. **CRITICAL**: The prefetched OpenSpec instruction blocks below contain the EXACT prompts you must follow to generate each artifact.",
192
+ "3. Each instruction block shows:",
193
+ " - Instruction: The EXACT prompt/guidance for generating that artifact",
194
+ " - Template: The structure/format to follow",
195
+ "4. **YOU MUST**:",
196
+ " - Treat each instruction block's 'Instruction' section as if it were given to you directly by the user",
197
+ " - Follow the instruction's requirements, structure, and sections EXACTLY as specified",
198
+ " - Use the planning journal and user requirements to fill in the CONTENT, but follow the instruction for STRUCTURE",
199
+ " - Do NOT create your own format - the instruction tells you exactly what to write and how",
200
+ "5. The allowed planning scope is limited to what is explicitly supported by the current user message, the planning journal, existing dependency artifacts, and the prefetched OpenSpec instructions.",
201
+ "6. Do not invent endpoints, features, constraints, files, acceptance criteria, test scenarios, or architecture details that are not grounded in those sources.",
202
+ "7. If there is no substantive planning change, say so clearly in chat and do not rewrite artifacts unnecessarily.",
203
+ "8. Update artifacts in dependency order: proposal → specs → design → tasks, using the prefetched instruction prompts for each.",
198
204
  "9. Before updating each artifact, post a short chat update naming the artifact you are about to refresh.",
199
205
  "10. After updating each artifact, post a short chat update describing what changed and what artifact comes next.",
200
206
  "11. For any implementation-oriented task item, ensure tasks.md contains an explicit testing task (new tests or updated tests) and a validation command.",
@@ -67,3 +67,58 @@ test("planning journal falls back to lastSyncedAt when no snapshot exists yet",
67
67
  true,
68
68
  );
69
69
  });
70
+
71
+ test("snapshot is always written after planning sync regardless of journal dirty state", async () => {
72
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), "clawspec-snapshot-always-"));
73
+ const journalPath = path.join(tempRoot, "planning-journal.jsonl");
74
+ const snapshotPath = path.join(tempRoot, "planning-journal.snapshot.json");
75
+ const store = new PlanningJournalStore(journalPath);
76
+
77
+ await store.append({
78
+ timestamp: "2026-03-27T03:00:00.000Z",
79
+ changeName: "test",
80
+ role: "user",
81
+ text: "initial requirement",
82
+ });
83
+
84
+ const snapshot1 = await store.writeSnapshot(snapshotPath, "test", "2026-03-27T03:05:00.000Z");
85
+ assert.equal(snapshot1.entryCount, 1);
86
+
87
+ await store.append({
88
+ timestamp: "2026-03-27T03:10:00.000Z",
89
+ changeName: "test",
90
+ role: "assistant",
91
+ text: "planning sync response",
92
+ });
93
+
94
+ const snapshot2 = await store.writeSnapshot(snapshotPath, "test", "2026-03-27T03:15:00.000Z");
95
+ assert.equal(snapshot2.entryCount, 2);
96
+ assert.equal(await store.hasUnsyncedChanges("test", snapshotPath), false);
97
+ });
98
+
99
+ test("snapshot correctly captures all journal entries including assistant messages", async () => {
100
+ const tempRoot = await mkdtemp(path.join(os.tmpdir(), "clawspec-snapshot-complete-"));
101
+ const journalPath = path.join(tempRoot, "planning-journal.jsonl");
102
+ const snapshotPath = path.join(tempRoot, "planning-journal.snapshot.json");
103
+ const store = new PlanningJournalStore(journalPath);
104
+
105
+ await store.append({
106
+ timestamp: "2026-03-27T03:00:00.000Z",
107
+ changeName: "test",
108
+ role: "user",
109
+ text: "user requirement",
110
+ });
111
+ await store.append({
112
+ timestamp: "2026-03-27T03:05:00.000Z",
113
+ changeName: "test",
114
+ role: "assistant",
115
+ text: "assistant response",
116
+ });
117
+
118
+ const snapshot = await store.writeSnapshot(snapshotPath, "test");
119
+ const digest = await store.digest("test");
120
+
121
+ assert.equal(snapshot.entryCount, digest.entryCount);
122
+ assert.equal(snapshot.lastEntryAt, digest.lastEntryAt);
123
+ assert.equal(snapshot.contentHash, digest.contentHash);
124
+ });