sinapse-ai 1.19.1 → 1.19.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.
@@ -231,6 +231,11 @@ class BuildOrchestrator extends EventEmitter {
231
231
  // quality gate can inspect its honesty (degraded/stub/subtasks). Without this
232
232
  // the epic4_to_epic6 gate has nothing real to bite on.
233
233
  plan: ctx.plan,
234
+ // epic: empty-build-honesty — surface the REAL files the build wrote/modified
235
+ // (collected from the build loop's per-subtask results). Without this the
236
+ // downstream `implementation_exists` gate cannot tell a real build apart from
237
+ // an empty "success" that touched zero code files.
238
+ filesModified: this._collectModifiedFiles(ctx),
234
239
  };
235
240
  } catch (error) {
236
241
  ctx.errors.push(error);
@@ -263,6 +268,32 @@ class BuildOrchestrator extends EventEmitter {
263
268
  }
264
269
  }
265
270
 
271
+ /**
272
+ * Collect the real files the build wrote/modified from the build-loop result.
273
+ *
274
+ * epic: empty-build-honesty — the per-subtask results carry `filesModified`
275
+ * (extracted from the agent output). This flattens + de-duplicates them into a
276
+ * single honest list. Returns [] when nothing was touched (an empty build), which
277
+ * is precisely the signal the `implementation_exists` gate needs to block.
278
+ *
279
+ * @param {Object} ctx - Build context ({ result: { results: [...] } })
280
+ * @returns {string[]} De-duplicated list of modified files
281
+ * @private
282
+ */
283
+ _collectModifiedFiles(ctx) {
284
+ const results = ctx && ctx.result && Array.isArray(ctx.result.results) ? ctx.result.results : [];
285
+ const files = [];
286
+ for (const r of results) {
287
+ const list = r && Array.isArray(r.filesModified) ? r.filesModified : [];
288
+ for (const f of list) {
289
+ if (typeof f === 'string' && f.trim() !== '' && !files.includes(f)) {
290
+ files.push(f);
291
+ }
292
+ }
293
+ }
294
+ return files;
295
+ }
296
+
266
297
  /**
267
298
  * Run a phase with event emission and error handling
268
299
  */
@@ -113,13 +113,23 @@ class Epic4Executor extends EpicExecutor {
113
113
  if (this._realExecutionAllowed()) {
114
114
  const buildResult = await this._executeViaBuildOrchestrator(storyId, context);
115
115
  if (buildResult && buildResult.success) {
116
+ // Honesty invariant (epic: empty-build-honesty): do NOT set
117
+ // `implementationPath = planPath`. A plan path ALWAYS exists and is NOT
118
+ // proof of implementation — the multi-story measurement caught the gate
119
+ // approving an empty build because of exactly this. Surface the REAL files
120
+ // the build touched so the epic4_to_epic6 gate can tell an implementation
121
+ // apart from an empty "success".
122
+ const codeChanges = Array.isArray(buildResult.filesModified)
123
+ ? buildResult.filesModified
124
+ : [];
116
125
  return this._completeExecution({
117
- implementationPath: planPath,
118
126
  planPath,
119
127
  // F5 (epic: orchestration-consolidation): propagate the real plan object
120
128
  // so the downstream epic4_to_epic6 gate can verify it is not degraded/stub.
121
129
  plan: buildResult.plan,
122
130
  build: buildResult,
131
+ codeChanges,
132
+ filesModified: codeChanges,
123
133
  reportPath: buildResult.reportPath,
124
134
  phases: buildResult.phases,
125
135
  });
@@ -144,7 +154,10 @@ class Epic4Executor extends EpicExecutor {
144
154
  return this._stubExecution(
145
155
  'Epic 4 stub mode — real build is delegated to BuildOrchestrator outside the test runner',
146
156
  {
147
- implementationPath: planPath,
157
+ // Honesty invariant (epic: empty-build-honesty): no `implementationPath`
158
+ // here — the plan path is not an implementation. The stub already reports
159
+ // success:false, and `codeChanges` carries whatever real files (if any)
160
+ // the subtasks touched.
148
161
  planPath,
149
162
  progress,
150
163
  subtaskResults,
@@ -300,6 +300,69 @@ class GateEvaluator {
300
300
  return null;
301
301
  }
302
302
 
303
+ /**
304
+ * Collect the REAL code files an epic actually wrote/modified.
305
+ *
306
+ * Honesty invariant (epic: empty-build-honesty): a plan file is NOT an
307
+ * implementation. This gathers the concrete files touched from `codeChanges` /
308
+ * `filesModified` (and `build.filesModified` when the build result is nested),
309
+ * then EXCLUDES the plan artifact (`planPath` / `implementationPath`) so a build
310
+ * that only produced a plan yields an empty list — and the caller can block it.
311
+ *
312
+ * Entries may be strings or `{ path | file }` objects; blanks and duplicates are
313
+ * dropped. Returns a de-duplicated array of file paths (never the plan itself).
314
+ *
315
+ * @param {Object} epicResult - Result from the source epic
316
+ * @returns {string[]} Real implementation files (plan artifact excluded)
317
+ * @private
318
+ */
319
+ _collectImplementationFiles(epicResult) {
320
+ if (!epicResult || typeof epicResult !== 'object') {
321
+ return [];
322
+ }
323
+
324
+ const resolveSafe = (p) => {
325
+ try {
326
+ return path.resolve(p);
327
+ } catch {
328
+ return null;
329
+ }
330
+ };
331
+
332
+ // The plan artifact is explicitly NOT an implementation — exclude it.
333
+ const planPaths = new Set(
334
+ [epicResult.planPath, epicResult.implementationPath]
335
+ .filter((p) => typeof p === 'string' && p.trim() !== '')
336
+ .map(resolveSafe)
337
+ .filter(Boolean),
338
+ );
339
+
340
+ const raw = []
341
+ .concat(Array.isArray(epicResult.codeChanges) ? epicResult.codeChanges : [])
342
+ .concat(Array.isArray(epicResult.filesModified) ? epicResult.filesModified : [])
343
+ .concat(
344
+ epicResult.build && Array.isArray(epicResult.build.filesModified)
345
+ ? epicResult.build.filesModified
346
+ : [],
347
+ );
348
+
349
+ const files = [];
350
+ for (const entry of raw) {
351
+ const file = typeof entry === 'string' ? entry : entry && (entry.path || entry.file);
352
+ if (typeof file !== 'string' || file.trim() === '') {
353
+ continue;
354
+ }
355
+ const resolved = resolveSafe(file);
356
+ if (resolved && planPaths.has(resolved)) {
357
+ continue; // a plan path masquerading as a code change — not implementation
358
+ }
359
+ if (!files.includes(file)) {
360
+ files.push(file);
361
+ }
362
+ }
363
+ return files;
364
+ }
365
+
303
366
  /**
304
367
  * Run a single check
305
368
  * @private
@@ -380,11 +443,23 @@ class GateEvaluator {
380
443
  break;
381
444
  }
382
445
 
383
- case 'implementation_exists':
384
- result.passed = !!epicResult.implementationPath || epicResult.codeChanges?.length > 0;
385
- result.message = result.passed ? 'Implementation exists' : 'No implementation found';
446
+ case 'implementation_exists': {
447
+ // Honesty invariant (epic: empty-build-honesty): "implementation exists"
448
+ // MUST mean real code was written not merely that a PLAN file exists. The
449
+ // multi-story measurement (2026-06-30) caught this gate APPROVING (score 5.0)
450
+ // a build that wrote ZERO code files: the old check trusted
451
+ // `implementationPath`, but epic-4 set that to the PLAN path (which ALWAYS
452
+ // exists), so an empty build slipped through. Base the check on the real list
453
+ // of files touched by the build, excluding the plan artifact itself. Empty
454
+ // build → zero real files → critical fail → gate BLOCKS (empty ≠ success).
455
+ const files = this._collectImplementationFiles(epicResult);
456
+ result.passed = files.length > 0;
457
+ result.message = result.passed
458
+ ? `Implementation exists (${files.length} code file(s) changed)`
459
+ : 'No implementation found — build wrote zero code files (plan path is not implementation)';
386
460
  result.severity = 'critical';
387
461
  break;
462
+ }
388
463
 
389
464
  case 'no_critical_errors': {
390
465
  const criticalErrors = (epicResult.errors || []).filter((e) => e.severity === 'critical');
@@ -7,7 +7,7 @@
7
7
  # - SHA256 hashes for change detection
8
8
  # - File types for categorization
9
9
  #
10
- version: 1.19.1
10
+ version: 1.19.2
11
11
  generator: scripts/generate-install-manifest.js
12
12
  file_count: 1161
13
13
  files:
@@ -496,9 +496,9 @@ files:
496
496
  type: core
497
497
  size: 34059
498
498
  - path: core/execution/build-orchestrator.js
499
- hash: sha256:ac8adfb8d5ef580641430735a7f351cf96d6bfe8608e8e45b92106ee0d412cb7
499
+ hash: sha256:6b1a213652493a7f453267d2f4f7937f5dac625cdfb95ca558747468922cb12f
500
500
  type: core
501
- size: 39805
501
+ size: 41204
502
502
  - path: core/execution/build-state-manager.js
503
503
  hash: sha256:3b2fe56bf0e1a480f663a02afbb83c6d11cf89fb56815381c7ccafab948a63ae
504
504
  type: core
@@ -992,9 +992,9 @@ files:
992
992
  type: core
993
993
  size: 9463
994
994
  - path: core/orchestration/executors/epic-4-executor.js
995
- hash: sha256:dbf091d97a91fa601268a9f61ba2b8d7f4ed9a42349123e02fc0898218069ead
995
+ hash: sha256:9aa50bd546d00d79e9ed2642377a66cdecef4f91c36440fde5ef617b18296226
996
996
  type: core
997
- size: 10174
997
+ size: 11029
998
998
  - path: core/orchestration/executors/epic-5-executor.js
999
999
  hash: sha256:865224cd6cb1f80c1228e149349aea5c335440201c9047f849d2ec85b73d3f03
1000
1000
  type: core
@@ -1016,9 +1016,9 @@ files:
1016
1016
  type: core
1017
1017
  size: 10211
1018
1018
  - path: core/orchestration/gate-evaluator.js
1019
- hash: sha256:22ac74902559eed2d3a85733b84b6a5794998255a892d54319a5106e878aec46
1019
+ hash: sha256:514c80ec7b305bf85d9816bca4ecdfc972faa84796a84794fef71d302a93f72b
1020
1020
  type: core
1021
- size: 20388
1021
+ size: 23437
1022
1022
  - path: core/orchestration/greenfield-handler.js
1023
1023
  hash: sha256:330be2c76c3eb5a9a1f1c975837308802b8fc6fb871f9b584787ae4e2aa8fcb1
1024
1024
  type: core
package/CHANGELOG.md CHANGED
@@ -7,6 +7,18 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [1.19.2] — 2026-07-01 — 🔒 Honestidade do gate (build vazio não passa) + aposta medida
11
+
12
+ > Patch. Atualização segura via `npx sinapse-ai update`. Fecha o último furo de honestidade do motor achado ao medir a aposta de orquestração multi-story.
13
+
14
+ ### Bug Fixes
15
+
16
+ - **orchestration** — o gate `epic4_to_epic6` aprovava (score 5.0) um build que não escreveu **nenhum arquivo**: o check `implementation_exists` aceitava `implementationPath`, que o epic-4 setava para o caminho do **plano** (sempre existe). Agora o check exige arquivos de código **reais** (`codeChanges` + `filesModified`, excluindo o plano) e bloqueia build vazio; o epic-4 não usa mais o `implementationPath` enganoso e o `BuildOrchestrator` expõe `filesModified` de verdade. Estende a invariante de honestidade — build "sucedido" sem arquivos ≠ sucesso (#317).
17
+
18
+ ### Documentation
19
+
20
+ - **epic** — fecha o épico `orchestration-consolidation` com o **veredito medido do checkpoint "matar ou dobrar"**: HÍBRIDO. Na medição multi-story, o modo nativo entregou 3/3 stories em 64s e 1 chamada, enquanto o motor entregou 1/3 em ~13,5min (a coordenação multi-story corrompeu o estado entre stories). O motor é assumido como **assistente de story isolada** (spec + plano reais); a orquestração autônoma multi-story vira **limitação documentada** (`KNOWN-LIMITATIONS.md`), não promessa. Docs públicos já eram honestos (#318).
21
+
10
22
  ## [1.19.1] — 2026-06-30 — 🔧 Motor honesto ponta-a-ponta + Windows-safe
11
23
 
12
24
  > Patch. Atualização segura via `npx sinapse-ai update`. Fecha 2 defeitos do motor expostos pelo checkpoint e2e do épico orchestration-consolidation.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sinapse-ai",
3
- "version": "1.19.1",
3
+ "version": "1.19.2",
4
4
  "description": "SINAPSE AI: Framework de orquestracao de IA — 17 squads, 172 agentes especializados",
5
5
  "bin": {
6
6
  "sinapse": "bin/sinapse.js",