iterate-plugin 3.9.1 → 3.9.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.
@@ -789,19 +789,28 @@ export async function markFixRolledBackInTranscript(projectRoot, id) {
789
789
  const file = transcriptPath(projectRoot);
790
790
  if (!existsSync(file))
791
791
  return false;
792
- try {
793
- const parsed = JSON.parse(await readFile(file, 'utf-8'));
794
- if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.fixes))
792
+ // Cross-process read-modify-write: serialize on the shared transcript lock
793
+ // (the same lock `nudge` takes), otherwise two concurrent rollbacks each read
794
+ // the same base and the second write silently discards the first's flag. The
795
+ // lock is held across the awaits because `withProjectLock` awaits a
796
+ // promise-returning callback. Kept fail-safe: any error still returns false.
797
+ return withProjectLock(projectRoot, 'transcript', async () => {
798
+ if (!existsSync(file))
795
799
  return false;
796
- const { config } = loadEffectiveConfig(projectRoot);
797
- const approval = config.observatory?.approval ?? 'ask';
798
- const builder = rehydrateBuilder(parsed, approval);
799
- builder.markFixRolledBack(id);
800
- await persist(file, builder.serialize());
801
- return true;
802
- }
803
- catch {
804
- // Never let the secondary transcript write break the rollback flow.
805
- return false;
806
- }
800
+ try {
801
+ const parsed = JSON.parse(await readFile(file, 'utf-8'));
802
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.fixes))
803
+ return false;
804
+ const { config } = loadEffectiveConfig(projectRoot);
805
+ const approval = config.observatory?.approval ?? 'ask';
806
+ const builder = rehydrateBuilder(parsed, approval);
807
+ builder.markFixRolledBack(id);
808
+ await persist(file, builder.serialize());
809
+ return true;
810
+ }
811
+ catch {
812
+ // Never let the secondary transcript write break the rollback flow.
813
+ return false;
814
+ }
815
+ });
807
816
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "iterate-plugin",
3
- "version": "3.9.1",
3
+ "version": "3.9.2",
4
4
  "description": "dsh plugin that turns the iterate skill into an autonomous closed-loop harness with quality command center and experience bank (v3.3). Features: plan -> parallel review xN -> atomic fixes -> validate -> loop -> auto-stop, plus dry-run pure-review mode, quality gate compute/persist, writable experience bank, defense events stream (record + bilingual labels), and native command buttons — with a live quality command center (F8/F9/F10 render real session data), §8 assign-fix instruction, and task_mode indicator wired end to end.",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -815,17 +815,25 @@ export async function markFixRolledBackInTranscript(
815
815
  ): Promise<boolean> {
816
816
  const file = transcriptPath(projectRoot)
817
817
  if (!existsSync(file)) return false
818
- try {
819
- const parsed = JSON.parse(await readFile(file, 'utf-8')) as unknown as TranscriptManifest
820
- if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.fixes)) return false
821
- const { config } = loadEffectiveConfig(projectRoot)
822
- const approval = config.observatory?.approval ?? 'ask'
823
- const builder = rehydrateBuilder(parsed, approval)
824
- builder.markFixRolledBack(id)
825
- await persist(file, builder.serialize())
826
- return true
827
- } catch {
828
- // Never let the secondary transcript write break the rollback flow.
829
- return false
830
- }
818
+ // Cross-process read-modify-write: serialize on the shared transcript lock
819
+ // (the same lock `nudge` takes), otherwise two concurrent rollbacks each read
820
+ // the same base and the second write silently discards the first's flag. The
821
+ // lock is held across the awaits because `withProjectLock` awaits a
822
+ // promise-returning callback. Kept fail-safe: any error still returns false.
823
+ return withProjectLock(projectRoot, 'transcript', async () => {
824
+ if (!existsSync(file)) return false
825
+ try {
826
+ const parsed = JSON.parse(await readFile(file, 'utf-8')) as unknown as TranscriptManifest
827
+ if (!parsed || typeof parsed !== 'object' || !Array.isArray(parsed.fixes)) return false
828
+ const { config } = loadEffectiveConfig(projectRoot)
829
+ const approval = config.observatory?.approval ?? 'ask'
830
+ const builder = rehydrateBuilder(parsed, approval)
831
+ builder.markFixRolledBack(id)
832
+ await persist(file, builder.serialize())
833
+ return true
834
+ } catch {
835
+ // Never let the secondary transcript write break the rollback flow.
836
+ return false
837
+ }
838
+ })
831
839
  }