pumuki 6.3.143 → 6.3.144

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.
@@ -4,6 +4,12 @@ This file tracks the active deterministic framework line used in this repository
4
4
  Canonical release chronology lives in `CHANGELOG.md`.
5
5
  This file keeps only the operational highlights and rollout notes that matter while running the framework.
6
6
 
7
+ ### 2026-05-05 (v6.3.144)
8
+
9
+ - **RuralGo PUMUKI-INC-122:** `pumuki sdd evidence` serializa escrituras concurrentes del artefacto `.pumuki/artifacts/pumuki-evidence-v1.json` con lock local y rename atómico.
10
+ - **Sin pérdida de slices:** dos ejecuciones paralelas fusionan slices existentes/nuevas en lugar de pisar el JSON compartido; además el scaffold genera baseline TDD/BDD válido desde la evidencia PRE_WRITE.
11
+ - **Rollout:** publicar `pumuki@6.3.144`, repinear primero RuralGo y revalidar `status`, `doctor` y una ráfaga concurrente de `pumuki sdd evidence`.
12
+
7
13
  ### 2026-05-05 (v6.3.143)
8
14
 
9
15
  - **RuralGo PUMUKI-INC-060:** PRE_WRITE deja de aceptar evidencia TDD/BDD de baseline caducada para cambios in-scope.
@@ -1,5 +1,14 @@
1
- import { createHash } from 'node:crypto';
2
- import { mkdirSync, writeFileSync } from 'node:fs';
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import {
3
+ closeSync,
4
+ existsSync,
5
+ mkdirSync,
6
+ openSync,
7
+ readFileSync,
8
+ renameSync,
9
+ rmSync,
10
+ writeFileSync,
11
+ } from 'node:fs';
3
12
  import { basename, dirname, isAbsolute, relative, resolve } from 'node:path';
4
13
  import { readEvidenceResult, type EvidenceReadResult } from '../evidence/readEvidence';
5
14
 
@@ -27,6 +36,11 @@ export type SddEvidenceScaffoldResult = {
27
36
  slices: Array<{
28
37
  id: string;
29
38
  scenario_ref: string;
39
+ baseline: {
40
+ status: 'passed';
41
+ timestamp: string;
42
+ test_ref: string;
43
+ };
30
44
  red: {
31
45
  status: 'failed';
32
46
  timestamp: string;
@@ -62,9 +76,100 @@ export type SddEvidenceScaffoldResult = {
62
76
  };
63
77
  };
64
78
 
79
+ type SddEvidenceArtifact = SddEvidenceScaffoldResult['artifact'];
80
+
81
+ const LOCK_WAIT_BUFFER_BYTES = 4;
82
+ const EVIDENCE_ARTIFACT_LOCK_TIMEOUT_MS = 5000;
83
+ const EVIDENCE_ARTIFACT_LOCK_RETRY_DELAY_MS = 25;
84
+ const EVIDENCE_ARTIFACT_JSON_INDENT_SPACES = 2;
85
+
65
86
  const computeDigest = (value: string): string =>
66
87
  `sha256:${createHash('sha256').update(value, 'utf8').digest('hex')}`;
67
88
 
89
+ const sleepSync = (milliseconds: number): void => {
90
+ Atomics.wait(new Int32Array(new SharedArrayBuffer(LOCK_WAIT_BUFFER_BYTES)), 0, 0, milliseconds);
91
+ };
92
+
93
+ const acquireFileLock = (lockPath: string): (() => void) => {
94
+ const startedAt = Date.now();
95
+ mkdirSync(dirname(lockPath), { recursive: true });
96
+ while (true) {
97
+ try {
98
+ const fd = openSync(lockPath, 'wx');
99
+ writeFileSync(fd, `${process.pid}:${new Date().toISOString()}\n`, 'utf8');
100
+ return () => {
101
+ closeSync(fd);
102
+ rmSync(lockPath, { force: true });
103
+ };
104
+ } catch (error) {
105
+ const code = error instanceof Error && 'code' in error
106
+ ? String((error as NodeJS.ErrnoException).code)
107
+ : '';
108
+ if (code !== 'EEXIST') {
109
+ throw error;
110
+ }
111
+ if (Date.now() - startedAt > EVIDENCE_ARTIFACT_LOCK_TIMEOUT_MS) {
112
+ throw new Error(
113
+ `[pumuki][sdd] evidence artifact is locked by another process: ${lockPath}. Retry when the current evidence write finishes.`
114
+ );
115
+ }
116
+ sleepSync(EVIDENCE_ARTIFACT_LOCK_RETRY_DELAY_MS);
117
+ }
118
+ }
119
+ };
120
+
121
+ const readExistingSddEvidenceArtifact = (path: string): SddEvidenceArtifact | null => {
122
+ if (!existsSync(path)) {
123
+ return null;
124
+ }
125
+ const parsed = JSON.parse(readFileSync(path, 'utf8')) as Partial<SddEvidenceArtifact>;
126
+ if (parsed.version !== '1' || !Array.isArray(parsed.slices)) {
127
+ return null;
128
+ }
129
+ return parsed as SddEvidenceArtifact;
130
+ };
131
+
132
+ const mergeSddEvidenceArtifacts = (params: {
133
+ existing: SddEvidenceArtifact | null;
134
+ next: SddEvidenceArtifact;
135
+ }): SddEvidenceArtifact => {
136
+ if (!params.existing) {
137
+ return params.next;
138
+ }
139
+ const slices = params.existing.slices.filter(
140
+ (slice) => !params.next.slices.some((nextSlice) => nextSlice.id === slice.id)
141
+ );
142
+ return {
143
+ ...params.next,
144
+ slices: [...slices, ...params.next.slices],
145
+ };
146
+ };
147
+
148
+ const writeSddEvidenceArtifactAtomically = (params: {
149
+ outputPath: string;
150
+ artifact: SddEvidenceArtifact;
151
+ }): {
152
+ artifact: SddEvidenceArtifact;
153
+ serialized: string;
154
+ } => {
155
+ const lockPath = `${params.outputPath}.lock`;
156
+ const release = acquireFileLock(lockPath);
157
+ const tempPath = `${params.outputPath}.${process.pid}.${randomUUID()}.tmp`;
158
+ try {
159
+ const artifact = mergeSddEvidenceArtifacts({
160
+ existing: readExistingSddEvidenceArtifact(params.outputPath),
161
+ next: params.artifact,
162
+ });
163
+ const serialized = `${JSON.stringify(artifact, null, EVIDENCE_ARTIFACT_JSON_INDENT_SPACES)}\n`;
164
+ writeFileSync(tempPath, serialized, 'utf8');
165
+ renameSync(tempPath, params.outputPath);
166
+ return { artifact, serialized };
167
+ } finally {
168
+ rmSync(tempPath, { force: true });
169
+ release();
170
+ }
171
+ };
172
+
68
173
  const resolveRepoBoundPath = (params: {
69
174
  repoRoot: string;
70
175
  candidatePath: string;
@@ -91,7 +196,7 @@ const resolveRepoBoundPath = (params: {
91
196
  return resolved;
92
197
  };
93
198
 
94
- const isPlaceholderToken = (value: string): boolean => {
199
+ const isReservedInputValue = (value: string): boolean => {
95
200
  const normalized = value.trim().toLowerCase();
96
201
  return (
97
202
  normalized === 'todo' ||
@@ -110,7 +215,7 @@ const normalizeRequired = (value: string | undefined, flagName: string): string
110
215
  if (normalized.length === 0) {
111
216
  throw new Error(`[pumuki][sdd] evidence requires ${flagName}.`);
112
217
  }
113
- if (isPlaceholderToken(normalized)) {
218
+ if (isReservedInputValue(normalized)) {
114
219
  throw new Error(`[pumuki][sdd] evidence ${flagName} must not be a placeholder value.`);
115
220
  }
116
221
  return normalized;
@@ -214,6 +319,11 @@ export const runSddEvidenceScaffold = (params?: {
214
319
  {
215
320
  id: scenarioId,
216
321
  scenario_ref: resolveScenarioReference(scenarioId),
322
+ baseline: {
323
+ status: 'passed',
324
+ timestamp: validEvidence.source_descriptor.generated_at ?? generatedAt,
325
+ test_ref: 'pumuki sdd validate --stage=PRE_WRITE --json',
326
+ },
217
327
  red: {
218
328
  status: 'failed',
219
329
  timestamp: generatedAt,
@@ -249,13 +359,19 @@ export const runSddEvidenceScaffold = (params?: {
249
359
  status: 'valid',
250
360
  },
251
361
  };
252
- const serialized = `${JSON.stringify(artifact, null, 2)}\n`;
253
- const digest = computeDigest(serialized);
362
+ let finalArtifact = artifact;
363
+ let serialized = `${JSON.stringify(finalArtifact, null, 2)}\n`;
254
364
 
255
365
  if (!dryRun) {
256
366
  mkdirSync(dirname(outputAbsolutePath), { recursive: true });
257
- writeFileSync(outputAbsolutePath, serialized, 'utf8');
367
+ const writeResult = writeSddEvidenceArtifactAtomically({
368
+ outputPath: outputAbsolutePath,
369
+ artifact,
370
+ });
371
+ finalArtifact = writeResult.artifact;
372
+ serialized = writeResult.serialized;
258
373
  }
374
+ const digest = computeDigest(serialized);
259
375
 
260
376
  return {
261
377
  command: 'pumuki sdd evidence',
@@ -273,6 +389,6 @@ export const runSddEvidenceScaffold = (params?: {
273
389
  written: !dryRun,
274
390
  digest,
275
391
  },
276
- artifact,
392
+ artifact: finalArtifact,
277
393
  };
278
394
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pumuki",
3
- "version": "6.3.143",
3
+ "version": "6.3.144",
4
4
  "description": "Enterprise-grade AST Intelligence System with multi-platform support (iOS, Android, Backend, Frontend) and Feature-First + DDD + Clean Architecture enforcement. Includes dynamic violations API for intelligent querying.",
5
5
  "main": "index.js",
6
6
  "bin": {