@dombaras/agent-harness 0.1.5 → 0.1.6

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 CHANGED
@@ -123,6 +123,15 @@ npx @dombaras/agent-harness update --target /path/to/project
123
123
  modified harness files. Your unrelated uncommitted work is never staged.
124
124
  - `--no-commit` to skip commit+push, `--no-push` to commit but not push.
125
125
  - `init` never commits.
126
+ - **`[Nothing-to-Update]` confirmation**: if the target is already at this CLI's version and no
127
+ harness-owned file changed, `update` says so explicitly, writes nothing, and makes no commit —
128
+ a no-op is never mistaken for a sync and never churns `.harness.json`.
129
+ - **Downgrade guard**: `update` refuses to run on a target whose deployed harness is NEWER than
130
+ this CLI (e.g. deployed from a local checkout that was never published) and tells you to publish
131
+ that version first.
132
+ - **Published-version check**: if this CLI's version is not the latest `dist-tags.latest` on npm,
133
+ `update` warns you — catches the "bumped the version and updated a project from a local
134
+ checkout, but never published" gap before it silently forks the projects.
126
135
  - **Non-destructive**: if a harness-owned file was modified locally since the
127
136
  last deploy (tracked by checksum in `.harness.json`), it is backed up to
128
137
  `.harness-backup/<timestamp>/` before overwrite.
@@ -274,6 +274,17 @@ async function deploy(target, opts) {
274
274
  }
275
275
  const VARIABLES = { PROJECT_NAME: projectName, PROJECT_DOMAIN: projectDomain };
276
276
 
277
+ // Refuse to downgrade: the target's deployed harness is newer than this CLI.
278
+ // This is how the "I updated from a local checkout that I never published"
279
+ // mistake surfaces instead of silently writing older templates over a newer
280
+ // deployment (and auto-committing that regression).
281
+ if (isUpdate && existing.version && compareVersions(existing.version, PKG.version) > 0) {
282
+ throw new Error(
283
+ `"${projectName}" has harness v${existing.version} — NEWER than this CLI (v${PKG.version}). ` +
284
+ `Not downgrading. Publish v${existing.version} (or this CLI) so \`update\` can re-sync from the registry.`
285
+ );
286
+ }
287
+
277
288
  const previousFiles = existing.files || {};
278
289
  const actions = [];
279
290
  const manifest = {};
@@ -349,6 +360,16 @@ async function deploy(target, opts) {
349
360
  }
350
361
  }
351
362
 
363
+ // Nothing-to-update detection: an update that made no effective change to any
364
+ // harness-owned file and was already running this exact version is a no-op —
365
+ // report it clearly instead of printing a zero-delta "Updated", and leave the
366
+ // target byte-for-byte untouched (no .harness.json timestamp churn, no commit).
367
+ const changedRelsAll = actions
368
+ .filter((a) => ["create", "overwrite", "overwrite (backup)", "merge"].includes(a.kind))
369
+ .map((a) => a.rel);
370
+ const nothingToUpdate =
371
+ !!isUpdate && changedRelsAll.length === 0 && compareVersions(existing.version || "0", PKG.version) === 0;
372
+
352
373
  const config = {
353
374
  version: PKG.version,
354
375
  projectName,
@@ -357,22 +378,13 @@ async function deploy(target, opts) {
357
378
  updatedAt: new Date().toISOString(),
358
379
  files: manifest,
359
380
  };
360
- if (!dryRun) {
381
+ if (!dryRun && !nothingToUpdate) {
361
382
  fs.writeFileSync(path.join(target, CONFIG_FILE), JSON.stringify(config, null, 2) + "\n", "utf8");
362
383
  }
363
384
 
364
- // Nothing-to-update detection: an update that made no effective change to any
365
- // harness-owned file and was already running this exact version is a no-op —
366
- // still report it clearly instead of printing a zero-delta "Updated".
367
- const changedRelsAll = actions
368
- .filter((a) => ["create", "overwrite", "overwrite (backup)", "merge"].includes(a.kind))
369
- .map((a) => a.rel);
370
- const nothingToUpdate =
371
- !!isUpdate && changedRelsAll.length === 0 && compareVersions(existing.version || "0", PKG.version) === 0;
372
-
373
385
  // Auto-commit only this run's changed harness-owned files (update only).
374
386
  let commitResult = null;
375
- if (isUpdate && !dryRun) {
387
+ if (isUpdate && !dryRun && !nothingToUpdate) {
376
388
  const changedRels = changedRelsAll.filter(
377
389
  (r) => !r.startsWith(".agents/memory/") && !r.startsWith(".harness-backup/")
378
390
  );
@@ -404,10 +416,22 @@ function summarize(actions) {
404
416
 
405
417
  function printSummary(result, isUpdate) {
406
418
  const { projectName, actions, dryRun } = result;
419
+
420
+ if (isUpdate && result.nothingToUpdate) {
421
+ console.log(
422
+ `\n[Nothing-to-Update] agent-harness v${PKG.version}: "${projectName}" is already at ` +
423
+ `v${result.priorVersion || PKG.version} and no harness-owned file changed.`
424
+ );
425
+ return;
426
+ }
427
+
407
428
  const verb = isUpdate
408
429
  ? (dryRun ? "Would update" : "Updated")
409
430
  : (dryRun ? "Would initialize" : "Initialized");
410
431
  console.log(`\nagent-harness v${PKG.version} ${verb.toLowerCase()} "${projectName}"` + (dryRun ? " (dry run)" : ""));
432
+ if (isUpdate && result.priorVersion && result.priorVersion !== PKG.version) {
433
+ console.log(` harness: v${result.priorVersion} -> v${PKG.version}`);
434
+ }
411
435
  for (const a of actions) console.log(` [${a.kind}] ${a.rel}`);
412
436
  const counts = summarize(actions);
413
437
  console.log(
@@ -417,6 +441,22 @@ function printSummary(result, isUpdate) {
417
441
  );
418
442
  }
419
443
 
444
+ /* Best-effort warning: when this CLI's version is not the latest published on
445
+ * the registry, flag it. This catches the "bumped + ran update from a local
446
+ * checkout, but never published" silent gap. */
447
+ function printUnpublishedWarning() {
448
+ const published = publishedLatestVersion();
449
+ if (published == null || published === "" || published === PKG.version) return;
450
+ const cmp = compareVersions(PKG.version, published);
451
+ if (cmp <= 0) return;
452
+ console.log(
453
+ `\n WARNING: this CLI is v${PKG.version}, but the latest version published on ` +
454
+ `npm is v${published}. If you ran this from a local checkout, the changes were ` +
455
+ `NOT published — Publish first, then \`update\` other projects, or they will ` +
456
+ `branch off an older harness.`
457
+ );
458
+ }
459
+
420
460
  function printNextSteps() {
421
461
  console.log(" Next steps:");
422
462
  console.log(" 1. Fill in `.agents/memory/domain-map.md`, `.agents/memory/stack-versions.md`,");
@@ -487,6 +527,7 @@ async function update(target, flags) {
487
527
  push: !flags["--no-commit"] && !flags["--no-push"],
488
528
  });
489
529
  printSummary(result, true);
530
+ printUnpublishedWarning();
490
531
  printCommitResult(result);
491
532
  }
492
533
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dombaras/agent-harness",
3
- "version": "0.1.5",
3
+ "version": "0.1.6",
4
4
  "description": "Reusable multi-agent harness for AI-assisted development: personas, skills, operating rules, model routing, and QA gates. Deploy into any project with `npx @dombaras/agent-harness init`.",
5
5
  "bin": {
6
6
  "agent-harness": "bin/agent-harness.js"