gentle-pi 2.1.1 → 2.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.
@@ -480,6 +480,7 @@ export interface NativeReviewAuthorityEntry {
480
480
  status: NativeReviewAuthorityEntryStatus;
481
481
  state?: string;
482
482
  revision?: string;
483
+ snapshotIdentity?: string;
483
484
  chainIdentity?: string;
484
485
  recovery?: NativeReviewRecovery;
485
486
  problems: readonly string[];
@@ -748,6 +749,7 @@ function exactObject(value: unknown, required: readonly string[], optional: read
748
749
  }
749
750
  function requiredString(value: unknown): string { if (typeof value !== "string" || value.length === 0) throw new Error("expected string"); return value; }
750
751
  function stringValue(value: unknown): string { if (typeof value !== "string") throw new Error("expected string"); return value; }
752
+ function sha256Identity(value: unknown): string { const parsed = requiredString(value); if (!/^sha256:[0-9a-f]{64}$/.test(parsed)) throw new Error("expected canonical SHA-256 identity"); return parsed; }
751
753
  function booleanValue(value: unknown): boolean { if (typeof value !== "boolean") throw new Error("expected boolean"); return value; }
752
754
  function nonNegativeInteger(value: unknown): number { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error("expected safe non-negative integer"); return value; }
753
755
  function positiveInteger(value: unknown): number { if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new Error("expected safe positive integer"); return value; }
@@ -957,7 +959,7 @@ function decodeNativeReviewRecovery(value: unknown): NativeReviewRecovery {
957
959
  };
958
960
  }
959
961
  function decodeNativeReviewStatusEntry(value: unknown): NativeReviewAuthorityEntry {
960
- const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "chain_identity", "recovery"]);
962
+ const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery"]);
961
963
  return {
962
964
  version: enumString(entry.version, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION)) as NativeReviewAuthorityEntryVersion,
963
965
  ...(entry.lineage_id === undefined ? {} : { lineageId: requiredString(entry.lineage_id) }),
@@ -965,6 +967,7 @@ function decodeNativeReviewStatusEntry(value: unknown): NativeReviewAuthorityEnt
965
967
  status: enumString(entry.status, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS)) as NativeReviewAuthorityEntryStatus,
966
968
  ...(entry.state === undefined ? {} : { state: requiredString(entry.state) }),
967
969
  ...(entry.revision === undefined ? {} : { revision: requiredString(entry.revision) }),
970
+ ...(entry.snapshot_identity === undefined ? {} : { snapshotIdentity: sha256Identity(entry.snapshot_identity) }),
968
971
  ...(entry.chain_identity === undefined ? {} : { chainIdentity: requiredString(entry.chain_identity) }),
969
972
  ...(entry.recovery === undefined ? {} : { recovery: decodeNativeReviewRecovery(entry.recovery) }),
970
973
  problems: stringArray(entry.problems),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "gentle-pi",
3
- "version": "2.1.1",
3
+ "version": "2.1.2",
4
4
  "description": "Turn Pi into el Gentleman: a senior-architect development harness with SDD/OpenSpec, subagents, strict TDD evidence, review guardrails, and skill discovery.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -505,6 +505,7 @@ export const NATIVE_REVIEW_RECOVERY_DISPOSITION = {
505
505
 
506
506
 
507
507
 
508
+
508
509
 
509
510
 
510
511
  export const NATIVE_START_ACTION = { CREATED: "created", RESUMED: "resumed", REUSE_RECEIPT: "reuse-receipt", BLOCKED_SCOPE_ACTION: "blocked-scope-action" } ;
@@ -749,6 +750,7 @@ function exactObject(value , required , optional
749
750
  }
750
751
  function requiredString(value ) { if (typeof value !== "string" || value.length === 0) throw new Error("expected string"); return value; }
751
752
  function stringValue(value ) { if (typeof value !== "string") throw new Error("expected string"); return value; }
753
+ function sha256Identity(value ) { const parsed = requiredString(value); if (!/^sha256:[0-9a-f]{64}$/.test(parsed)) throw new Error("expected canonical SHA-256 identity"); return parsed; }
752
754
  function booleanValue(value ) { if (typeof value !== "boolean") throw new Error("expected boolean"); return value; }
753
755
  function nonNegativeInteger(value ) { if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) throw new Error("expected safe non-negative integer"); return value; }
754
756
  function positiveInteger(value ) { if (typeof value !== "number" || !Number.isSafeInteger(value) || value <= 0) throw new Error("expected safe positive integer"); return value; }
@@ -958,7 +960,7 @@ function decodeNativeReviewRecovery(value ) {
958
960
  };
959
961
  }
960
962
  function decodeNativeReviewStatusEntry(value ) {
961
- const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "chain_identity", "recovery"]);
963
+ const entry = exactObject(value, ["version", "path", "status", "problems"], ["lineage_id", "state", "revision", "snapshot_identity", "chain_identity", "recovery"]);
962
964
  return {
963
965
  version: enumString(entry.version, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_VERSION)) ,
964
966
  ...(entry.lineage_id === undefined ? {} : { lineageId: requiredString(entry.lineage_id) }),
@@ -966,6 +968,7 @@ function decodeNativeReviewStatusEntry(value )
966
968
  status: enumString(entry.status, Object.values(NATIVE_REVIEW_AUTHORITY_ENTRY_STATUS)) ,
967
969
  ...(entry.state === undefined ? {} : { state: requiredString(entry.state) }),
968
970
  ...(entry.revision === undefined ? {} : { revision: requiredString(entry.revision) }),
971
+ ...(entry.snapshot_identity === undefined ? {} : { snapshotIdentity: sha256Identity(entry.snapshot_identity) }),
969
972
  ...(entry.chain_identity === undefined ? {} : { chainIdentity: requiredString(entry.chain_identity) }),
970
973
  ...(entry.recovery === undefined ? {} : { recovery: decodeNativeReviewRecovery(entry.recovery) }),
971
974
  problems: stringArray(entry.problems),
@@ -668,6 +668,33 @@ test("native review status uses the anticipated v2.1.5 contract, preserves Windo
668
668
  }
669
669
  });
670
670
 
671
+ test("native review status decodes and retains the v2.2.2 compact snapshot identity", async () => {
672
+ const snapshotIdentity = "sha256:0586c3d40c3acf3db92214ddaf07afe44ad862518b48d608ae4157377ab2f3cc";
673
+ const entry = {
674
+ version: "compact-v2",
675
+ lineage_id: "issue-136-v2-1-2-final",
676
+ path: "C:\\repo with spaces\\.git\\gentle-ai\\review-transactions\\v2\\issue-136-v2-1-2-final",
677
+ status: "active",
678
+ state: "reviewing",
679
+ revision: "sha256:8103efb360d10d1717a4d22e38c8b75074f99680ed6a5eb3ab40e0ed9fb69931",
680
+ snapshot_identity: snapshotIdentity,
681
+ problems: [],
682
+ };
683
+ const status = { ...JSON.parse(REVIEW_STATUS.stdout), status: "active", entries: [entry] };
684
+ const queue = queuedAdapter([{ stdout: "gentle-ai 2.2.2\n" }, { stdout: JSON.stringify(status) }]);
685
+ const decoded = await new NativeReviewCliV213(queue.adapter).reviewStatus({ cwd: "C:\\repo with spaces" });
686
+ assert.equal(decoded.entries[0]?.snapshotIdentity, snapshotIdentity);
687
+
688
+ const malformed = queuedAdapter([{ stdout: "gentle-ai 2.2.2\n" }, { stdout: JSON.stringify({ ...status, entries: [{ ...entry, snapshot_identity: snapshotIdentity.slice("sha256:".length) }] }) }]);
689
+ await assert.rejects(
690
+ () => new NativeReviewCliV213(malformed.adapter).reviewStatus({ cwd: "C:\\repo with spaces" }),
691
+ (error: unknown) => error instanceof NativeReviewCliError
692
+ && error.code === NATIVE_REVIEW_ERROR_CODE.SCHEMA_INCOMPATIBLE
693
+ && error.operation === "review/status"
694
+ && error.mutationOutcome === "none",
695
+ );
696
+ });
697
+
671
698
  test("native review status decodes 2.1.8 released lock residue and keeps unknown lock statuses fail-closed", async () => {
672
699
  // gentle-ai 2.1.8 leaves review-transactions/v2/LOCK behind after ORDINARY
673
700
  // successful operations and reports it as {"status":"released"} without
@@ -1137,9 +1137,9 @@ test("pi-pretty wrapper uses real package path resolution for pnpm symlink insta
1137
1137
  assert.match(wrapper, /quietToolsEnabled/);
1138
1138
  });
1139
1139
 
1140
- test("v2.1.1 release package and runtime stop before publication", () => {
1140
+ test("v2.1.2 release package and runtime stop before publication", () => {
1141
1141
  const packageJson = readPackageJson();
1142
- assert.equal(packageJson.version, "2.1.1", "the release manifest must remain explicitly pinned to v2.1.1");
1142
+ assert.equal(packageJson.version, "2.1.2", "the release manifest must remain explicitly pinned to v2.1.2");
1143
1143
  assert.equal(
1144
1144
  packageJson.scripts?.test,
1145
1145
  "node --experimental-strip-types --test tests/*.test.ts && pnpm run test:harness",