uni-harness 0.2.0 → 0.2.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/bin/cli.js CHANGED
@@ -180,6 +180,36 @@ function mergeSettings(target) {
180
180
  }
181
181
  }
182
182
 
183
+ // pre-v0.2 checkpoints lived at the project root. Migrate them into
184
+ // .harness/state/, but ONLY files whose content is recognizably the
185
+ // harness's own checkpoint format — a root plan.md may well be the
186
+ // project's own document, and those are never touched.
187
+ function migrateLegacyState(target) {
188
+ const isOurs = {
189
+ 'progress.json': (s) => {
190
+ try { const j = JSON.parse(s); return !!j && typeof j === 'object' && 'status' in j && ('task_id' in j || 'next_step' in j); }
191
+ catch { return false; }
192
+ },
193
+ 'decisions.jsonl': (s) => {
194
+ const lines = s.trim().split('\n');
195
+ return lines.length > 0 && lines.every((l) => {
196
+ try { return 'decision' in JSON.parse(l); } catch { return false; }
197
+ });
198
+ },
199
+ 'plan.md': (s) => /^# Plan:/m.test(s),
200
+ };
201
+ for (const [name, test] of Object.entries(isOurs)) {
202
+ const src = path.join(target, name);
203
+ const dst = path.join(target, '.harness/state', name);
204
+ if (!fs.existsSync(src) || fs.existsSync(dst)) continue;
205
+ let body;
206
+ try { body = fs.readFileSync(src, 'utf8'); } catch { continue; }
207
+ if (!test(body)) { log(' ! ' + name + ' left at the root (not in harness checkpoint format)'); continue; }
208
+ fs.renameSync(src, dst);
209
+ log(' > ' + name + ' -> .harness/state/' + name + ' (migrated)');
210
+ }
211
+ }
212
+
183
213
  // ── init ─────────────────────────────────────────────────────────
184
214
  function init(target, force) {
185
215
  const files = machineryFiles();
@@ -273,6 +303,7 @@ function update(target, force) {
273
303
 
274
304
  writeManifest(target, files);
275
305
  ensureRuntimeDirs(target);
306
+ migrateLegacyState(target);
276
307
  log(`\n${changed} file(s) updated (v${manifest.kitVersion} -> v${KIT_VERSION}). ` +
277
308
  'CLAUDE.md / commands.env / settings.json were not touched.');
278
309
 
@@ -282,11 +313,11 @@ function update(target, force) {
282
313
  const cm = fs.readFileSync(path.join(target, 'CLAUDE.md'), 'utf8');
283
314
  if (cm.includes('progress.json') && !cm.includes('.harness/state')) {
284
315
  log('\nNote: checkpoint files (plan.md / progress.json / decisions.jsonl)');
285
- log('now live in .harness/state/ instead of the project root. Your');
316
+ log('now live in .harness/state/ instead of the project root. Recognized');
317
+ log('checkpoint files were migrated automatically (see above). Your');
286
318
  log('CLAUDE.md still references the old locations — update its Work Loop');
287
- log('and Checkpoints sections, and move any existing checkpoint files');
288
- log('into .harness/state/. Until then the SessionStart hook still reads');
289
- log('a root progress.json as a fallback.');
319
+ log('and Checkpoints sections. Until then the SessionStart hook still');
320
+ log('reads a root progress.json as a fallback.');
290
321
  }
291
322
  } catch { /* no CLAUDE.md — nothing to migrate */ }
292
323
  if (skipped.length) {
@@ -111,8 +111,16 @@ check "update re-adds state gitignore entry" ".harness/state/" "$(cat "$TGT/.git
111
111
  # pre-v0.2 CLAUDE.md pointing checkpoints at the project root -> migration
112
112
  # note (told, never touched); the current template must NOT trigger it
113
113
  printf '# old project\n- read progress.json first\n' > "$TGT/CLAUDE.md"
114
+ # legacy root checkpoints: harness-format files are migrated, foreign ones kept
115
+ echo '{"task_id":"t1","status":"in_progress","next_step":"do X"}' > "$TGT/progress.json"
116
+ printf '{"ts":"2026-01-01","decision":"use X","why":"y"}\n' > "$TGT/decisions.jsonl"
117
+ printf '# Roadmap\nnot a harness plan\n' > "$TGT/plan.md"
114
118
  OUT=$(node "$CLI" update "$TGT" 2>&1)
115
119
  check "prints checkpoint migration note" "now live in .harness/state/" "$OUT"
120
+ check "migrates harness-format progress.json" "yes" "$([ -f "$TGT/.harness/state/progress.json" ] && [ ! -f "$TGT/progress.json" ] && echo yes)"
121
+ check "migrates harness-format decisions.jsonl" "yes" "$([ -f "$TGT/.harness/state/decisions.jsonl" ] && echo yes)"
122
+ check "keeps foreign plan.md at the root" "yes" "$([ -f "$TGT/plan.md" ] && [ ! -f "$TGT/.harness/state/plan.md" ] && echo yes)"
123
+ rm -f "$TGT/plan.md" "$TGT/.harness/state/progress.json" "$TGT/.harness/state/decisions.jsonl"
116
124
  check "migration note does not edit CLAUDE.md" "# old project" "$(cat "$TGT/CLAUDE.md")"
117
125
  cp "$ROOT/CLAUDE.md" "$TGT/CLAUDE.md" # restore for the doctor section
118
126
  OUT=$(node "$CLI" update "$TGT" 2>&1)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uni-harness",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Agent harness kit for Claude Code — installs verification sensors, destructive-command guards, checkpoint recovery, observability logs, and ratchet skills into your project",
5
5
  "bin": {
6
6
  "uni-harness": "bin/cli.js"