@llm-dev-ops/agentics-cli 2.1.4 → 2.3.0

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.
Files changed (76) hide show
  1. package/dist/pipeline/auto-chain.d.ts +73 -0
  2. package/dist/pipeline/auto-chain.d.ts.map +1 -1
  3. package/dist/pipeline/auto-chain.js +525 -38
  4. package/dist/pipeline/auto-chain.js.map +1 -1
  5. package/dist/pipeline/phase2/phases/prompt-generator.d.ts.map +1 -1
  6. package/dist/pipeline/phase2/phases/prompt-generator.js +53 -6
  7. package/dist/pipeline/phase2/phases/prompt-generator.js.map +1 -1
  8. package/dist/pipeline/phase2/schemas.d.ts +10 -10
  9. package/dist/pipeline/phase4/phases/http-server-generator.d.ts +12 -0
  10. package/dist/pipeline/phase4/phases/http-server-generator.d.ts.map +1 -1
  11. package/dist/pipeline/phase4/phases/http-server-generator.js +92 -25
  12. package/dist/pipeline/phase4/phases/http-server-generator.js.map +1 -1
  13. package/dist/pipeline/phase5-build/phase5-build-coordinator.d.ts.map +1 -1
  14. package/dist/pipeline/phase5-build/phase5-build-coordinator.js +44 -0
  15. package/dist/pipeline/phase5-build/phase5-build-coordinator.js.map +1 -1
  16. package/dist/pipeline/phase5-build/phases/post-generation-validator.d.ts +75 -0
  17. package/dist/pipeline/phase5-build/phases/post-generation-validator.d.ts.map +1 -0
  18. package/dist/pipeline/phase5-build/phases/post-generation-validator.js +728 -0
  19. package/dist/pipeline/phase5-build/phases/post-generation-validator.js.map +1 -0
  20. package/dist/pipeline/phase5-build/types.d.ts +1 -1
  21. package/dist/pipeline/phase5-build/types.d.ts.map +1 -1
  22. package/dist/pipeline/types.d.ts +84 -0
  23. package/dist/pipeline/types.d.ts.map +1 -1
  24. package/dist/pipeline/types.js +43 -1
  25. package/dist/pipeline/types.js.map +1 -1
  26. package/dist/synthesis/consensus-svg.d.ts +19 -0
  27. package/dist/synthesis/consensus-svg.d.ts.map +1 -0
  28. package/dist/synthesis/consensus-svg.js +95 -0
  29. package/dist/synthesis/consensus-svg.js.map +1 -0
  30. package/dist/synthesis/consensus-tiers.d.ts +99 -0
  31. package/dist/synthesis/consensus-tiers.d.ts.map +1 -0
  32. package/dist/synthesis/consensus-tiers.js +285 -0
  33. package/dist/synthesis/consensus-tiers.js.map +1 -0
  34. package/dist/synthesis/domain-labor-classifier.d.ts +101 -0
  35. package/dist/synthesis/domain-labor-classifier.d.ts.map +1 -0
  36. package/dist/synthesis/domain-labor-classifier.js +312 -0
  37. package/dist/synthesis/domain-labor-classifier.js.map +1 -0
  38. package/dist/synthesis/domain-unit-registry.d.ts +59 -0
  39. package/dist/synthesis/domain-unit-registry.d.ts.map +1 -0
  40. package/dist/synthesis/domain-unit-registry.js +294 -0
  41. package/dist/synthesis/domain-unit-registry.js.map +1 -0
  42. package/dist/synthesis/financial-claim-extractor.d.ts +52 -0
  43. package/dist/synthesis/financial-claim-extractor.d.ts.map +1 -0
  44. package/dist/synthesis/financial-claim-extractor.js +351 -0
  45. package/dist/synthesis/financial-claim-extractor.js.map +1 -0
  46. package/dist/synthesis/financial-consistency-rules.d.ts +66 -0
  47. package/dist/synthesis/financial-consistency-rules.d.ts.map +1 -0
  48. package/dist/synthesis/financial-consistency-rules.js +432 -0
  49. package/dist/synthesis/financial-consistency-rules.js.map +1 -0
  50. package/dist/synthesis/financial-consistency-runner.d.ts +73 -0
  51. package/dist/synthesis/financial-consistency-runner.d.ts.map +1 -0
  52. package/dist/synthesis/financial-consistency-runner.js +131 -0
  53. package/dist/synthesis/financial-consistency-runner.js.map +1 -0
  54. package/dist/synthesis/forbidden-spin-phrases.d.ts +32 -0
  55. package/dist/synthesis/forbidden-spin-phrases.d.ts.map +1 -0
  56. package/dist/synthesis/forbidden-spin-phrases.js +84 -0
  57. package/dist/synthesis/forbidden-spin-phrases.js.map +1 -0
  58. package/dist/synthesis/phase-gate-thresholds.d.ts +30 -0
  59. package/dist/synthesis/phase-gate-thresholds.d.ts.map +1 -0
  60. package/dist/synthesis/phase-gate-thresholds.js +34 -0
  61. package/dist/synthesis/phase-gate-thresholds.js.map +1 -0
  62. package/dist/synthesis/prompts/index.d.ts.map +1 -1
  63. package/dist/synthesis/prompts/index.js +22 -0
  64. package/dist/synthesis/prompts/index.js.map +1 -1
  65. package/dist/synthesis/simulation-artifact-generator.d.ts.map +1 -1
  66. package/dist/synthesis/simulation-artifact-generator.js +89 -1
  67. package/dist/synthesis/simulation-artifact-generator.js.map +1 -1
  68. package/dist/synthesis/simulation-renderers.d.ts +105 -2
  69. package/dist/synthesis/simulation-renderers.d.ts.map +1 -1
  70. package/dist/synthesis/simulation-renderers.js +1056 -92
  71. package/dist/synthesis/simulation-renderers.js.map +1 -1
  72. package/dist/synthesis/unit-economics-loader.d.ts +71 -0
  73. package/dist/synthesis/unit-economics-loader.d.ts.map +1 -0
  74. package/dist/synthesis/unit-economics-loader.js +200 -0
  75. package/dist/synthesis/unit-economics-loader.js.map +1 -0
  76. package/package.json +1 -1
@@ -87,6 +87,343 @@ function copyDirRecursive(src, dest) {
87
87
  * Called after every phase so even if later phases fail, earlier artifacts
88
88
  * are still available.
89
89
  */
90
+ /**
91
+ * ADR-PIPELINE-068: scaffold body for `src/simulation-lineage.ts`.
92
+ *
93
+ * Exported so unit tests can write the file to a temp directory and
94
+ * exercise the runtime behavior (env override, manifest walk, fallback)
95
+ * without having to spin up the full pipeline.
96
+ *
97
+ * Invariants enforced by tests:
98
+ * - Uses `readFileSync` + `fileURLToPath` (NEVER `require(`)
99
+ * - Exports `loadSimulationLineage`, `requireSimulationLineage`,
100
+ * `formatLineageBanner`, and the legacy `loadSimulationId` alias
101
+ * - Walks up at most 6 directories from the module location and from cwd
102
+ */
103
+ export const SIMULATION_LINEAGE_SCAFFOLD = `// Auto-generated by Agentics pipeline (ADR-PIPELINE-068)
104
+ // ESM-safe simulation lineage loader. Reads .agentics/plans/manifest.json
105
+ // (or .agentics/runs/latest/manifest.json) using readFileSync — never
106
+ // CommonJS require(). Do NOT reimplement this helper; import from it.
107
+ import { readFileSync, existsSync } from 'node:fs';
108
+ import { resolve, dirname, join } from 'node:path';
109
+ import { fileURLToPath } from 'node:url';
110
+
111
+ export type SimulationLineageSource = 'env' | 'manifest' | 'fallback';
112
+
113
+ export interface SimulationLineage {
114
+ readonly simulationId: string;
115
+ readonly traceId: string;
116
+ readonly runId: string;
117
+ readonly source: SimulationLineageSource;
118
+ readonly manifestPath?: string;
119
+ }
120
+
121
+ const MANIFEST_CANDIDATES: readonly string[] = [
122
+ '.agentics/plans/manifest.json',
123
+ '.agentics/runs/latest/manifest.json',
124
+ ];
125
+
126
+ const WALK_DEPTH = 6;
127
+
128
+ /** Walk up from startDir looking for any manifest candidate path. */
129
+ function findManifest(startDir: string): string | null {
130
+ let dir = startDir;
131
+ for (let i = 0; i < WALK_DEPTH; i++) {
132
+ for (const rel of MANIFEST_CANDIDATES) {
133
+ const candidate = resolve(dir, rel);
134
+ if (existsSync(candidate)) return candidate;
135
+ }
136
+ const parent = dirname(dir);
137
+ if (parent === dir) break;
138
+ dir = parent;
139
+ }
140
+ return null;
141
+ }
142
+
143
+ function moduleDir(): string {
144
+ try {
145
+ return dirname(fileURLToPath(import.meta.url));
146
+ } catch {
147
+ return process.cwd();
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Load the simulation lineage. Permissive — returns a fallback record when
153
+ * nothing is available. Callers who need strict behavior should use
154
+ * requireSimulationLineage() instead.
155
+ *
156
+ * Resolution order:
157
+ * 1. AGENTICS_SIMULATION_ID / AGENTICS_TRACE_ID environment variables
158
+ * 2. First manifest.json found walking up from this module (then cwd)
159
+ * 3. Fallback record with source='fallback'
160
+ */
161
+ export function loadSimulationLineage(): SimulationLineage {
162
+ const envSim = process.env['AGENTICS_SIMULATION_ID'];
163
+ const envTrace = process.env['AGENTICS_TRACE_ID'];
164
+ if (envSim) {
165
+ return {
166
+ simulationId: envSim,
167
+ traceId: envTrace ?? envSim,
168
+ runId: envSim,
169
+ source: 'env',
170
+ };
171
+ }
172
+
173
+ const startDirs = [moduleDir(), process.cwd()];
174
+ for (const start of startDirs) {
175
+ try {
176
+ const manifestPath = findManifest(start);
177
+ if (!manifestPath) continue;
178
+ const raw = JSON.parse(readFileSync(manifestPath, 'utf-8')) as Record<string, unknown>;
179
+ const runId = String(raw['run_id'] ?? raw['runId'] ?? '');
180
+ const simulationId = String(
181
+ raw['simulation_id'] ?? raw['simulationId'] ?? raw['execution_id'] ?? runId,
182
+ );
183
+ const traceId = String(raw['trace_id'] ?? raw['traceId'] ?? simulationId);
184
+ if (simulationId) {
185
+ return { simulationId, traceId, runId: runId || simulationId, source: 'manifest', manifestPath };
186
+ }
187
+ } catch {
188
+ // Try the next start dir. Never throw from the permissive loader.
189
+ }
190
+ }
191
+
192
+ return {
193
+ simulationId: 'sim-unknown',
194
+ traceId: 'trace-unknown',
195
+ runId: 'run-unknown',
196
+ source: 'fallback',
197
+ };
198
+ }
199
+
200
+ /**
201
+ * Strict variant — throws ECLI-LIN-068 when no simulation lineage is
202
+ * available. Use this at startup when the service MUST carry a real
203
+ * simulation id (audit logs, ERP posts, etc.).
204
+ */
205
+ export function requireSimulationLineage(): SimulationLineage {
206
+ const lineage = loadSimulationLineage();
207
+ if (lineage.source === 'fallback') {
208
+ throw new Error(
209
+ 'ECLI-LIN-068: simulation lineage unavailable — set AGENTICS_SIMULATION_ID or place .agentics/plans/manifest.json in the project tree',
210
+ );
211
+ }
212
+ return lineage;
213
+ }
214
+
215
+ /**
216
+ * Human-readable banner printed by the demo script when the loader falls
217
+ * back. Makes the break visible instead of letting sim-unknown leak into
218
+ * downstream audit logs and ERP posts.
219
+ */
220
+ export function formatLineageBanner(lineage: SimulationLineage): string {
221
+ if (lineage.source !== 'fallback') {
222
+ return \`simulation lineage: \${lineage.simulationId} (source: \${lineage.source})\`;
223
+ }
224
+ return [
225
+ '',
226
+ '⚠️ Simulation lineage unavailable (source: fallback)',
227
+ ' All audit entries and ERP posts will carry sim-unknown.',
228
+ ' Fix: place .agentics/plans/manifest.json in the project tree,',
229
+ ' or set AGENTICS_SIMULATION_ID=<run-id> before running the demo.',
230
+ '',
231
+ ].join('\\n');
232
+ }
233
+
234
+ /** Also exported under the legacy alias some older prompts referenced. */
235
+ export const loadSimulationId = (): string => loadSimulationLineage().simulationId;
236
+ `;
237
+ /**
238
+ * ADR-PIPELINE-069: scaffold body for `src/circuit-breaker.ts`.
239
+ *
240
+ * Extracted from the legacy middleware.ts scaffold so generators can
241
+ * `import { CircuitBreaker } from './circuit-breaker.js'` without pulling
242
+ * the entire middleware module. middleware.ts now re-exports this symbol
243
+ * so existing imports keep working.
244
+ */
245
+ export const CIRCUIT_BREAKER_SCAFFOLD = `// Auto-generated by Agentics pipeline (ADR-PIPELINE-069)
246
+ // Owned by the scaffold — do NOT redeclare CircuitBreaker in generated code.
247
+ import { createLogger } from './logger.js';
248
+
249
+ /** Simple circuit breaker for external service calls. */
250
+ export class CircuitBreaker {
251
+ private failures = 0;
252
+ private state: 'closed' | 'open' | 'half-open' = 'closed';
253
+ private nextAttempt = 0;
254
+
255
+ constructor(
256
+ private readonly name: string,
257
+ private readonly threshold = 5,
258
+ private readonly cooldownMs = 30000,
259
+ private readonly logger = createLogger('circuit-breaker'),
260
+ ) {}
261
+
262
+ async call<T>(fn: () => Promise<T>): Promise<T> {
263
+ if (this.state === 'open') {
264
+ if (Date.now() < this.nextAttempt) {
265
+ throw new Error(\`Circuit breaker '\${this.name}' is OPEN — retry after \${new Date(this.nextAttempt).toISOString()}\`);
266
+ }
267
+ this.state = 'half-open';
268
+ this.logger.info('circuit.half-open', { name: this.name });
269
+ }
270
+ try {
271
+ const result = await fn();
272
+ if (this.state === 'half-open') {
273
+ this.state = 'closed';
274
+ this.failures = 0;
275
+ this.logger.info('circuit.closed', { name: this.name });
276
+ }
277
+ return result;
278
+ } catch (err) {
279
+ this.failures++;
280
+ if (this.failures >= this.threshold) {
281
+ this.state = 'open';
282
+ this.nextAttempt = Date.now() + this.cooldownMs;
283
+ this.logger.warn('circuit.open', { name: this.name, failures: this.failures, retryAt: new Date(this.nextAttempt).toISOString() });
284
+ }
285
+ throw err;
286
+ }
287
+ }
288
+
289
+ getState(): string { return this.state; }
290
+ }
291
+ `;
292
+ export const OWNED_SCAFFOLD_MODULES = [
293
+ {
294
+ path: 'src/logger.ts',
295
+ exports: ['Logger', 'createLogger', 'setCorrelationId', 'getCorrelationId'],
296
+ },
297
+ {
298
+ path: 'src/config.ts',
299
+ exports: ['AppConfig', 'config'],
300
+ },
301
+ {
302
+ path: 'src/errors.ts',
303
+ exports: ['AppError', 'ValidationError', 'NotFoundError', 'ERPError'],
304
+ },
305
+ {
306
+ path: 'src/middleware.ts',
307
+ exports: [
308
+ 'correlationId',
309
+ 'requestLogger',
310
+ 'incrementCounter',
311
+ 'recordHistogram',
312
+ 'metricsHandler',
313
+ ],
314
+ },
315
+ {
316
+ path: 'src/circuit-breaker.ts',
317
+ exports: ['CircuitBreaker'],
318
+ },
319
+ {
320
+ path: 'src/unit-economics.ts',
321
+ exports: [
322
+ 'DomainUnit',
323
+ 'UnitEconomicsScope',
324
+ 'UnitEconomics',
325
+ 'writeUnitEconomics',
326
+ 'readUnitEconomics',
327
+ ],
328
+ },
329
+ {
330
+ path: 'src/simulation-lineage.ts',
331
+ exports: [
332
+ 'SimulationLineage',
333
+ 'SimulationLineageSource',
334
+ 'loadSimulationLineage',
335
+ 'requireSimulationLineage',
336
+ 'formatLineageBanner',
337
+ 'loadSimulationId',
338
+ ],
339
+ },
340
+ ];
341
+ export function buildOwnedModulesManifest(now = new Date()) {
342
+ return {
343
+ version: '1.0',
344
+ generated_at: now.toISOString(),
345
+ owned: OWNED_SCAFFOLD_MODULES,
346
+ };
347
+ }
348
+ const SCAFFOLD_SCAN_SKIP_DIRS = new Set([
349
+ 'node_modules', 'dist', 'build', '.git', 'coverage', '.next', '.agentics',
350
+ ]);
351
+ /**
352
+ * ADR-PIPELINE-069: Walk a project tree looking for generator-emitted files
353
+ * that redeclare an export listed in OWNED_SCAFFOLD_MODULES. Skips:
354
+ * - the scaffold-owned files themselves (matched by basename)
355
+ * - test files
356
+ * - node_modules / dist / .agentics
357
+ *
358
+ * Returns one finding per (file, export) pair.
359
+ */
360
+ export function detectScaffoldDuplicates(projectRoot, owned = OWNED_SCAFFOLD_MODULES) {
361
+ if (!fs.existsSync(projectRoot))
362
+ return [];
363
+ // Build (exportName -> ownedPath) lookup once.
364
+ const exportToOwned = new Map();
365
+ const ownedBasenames = new Set();
366
+ for (const mod of owned) {
367
+ ownedBasenames.add(path.basename(mod.path));
368
+ for (const ex of mod.exports) {
369
+ if (!exportToOwned.has(ex))
370
+ exportToOwned.set(ex, mod.path);
371
+ }
372
+ }
373
+ const findings = [];
374
+ const walk = (currentDir) => {
375
+ let entries;
376
+ try {
377
+ entries = fs.readdirSync(currentDir, { withFileTypes: true });
378
+ }
379
+ catch {
380
+ return;
381
+ }
382
+ for (const entry of entries) {
383
+ if (entry.isDirectory()) {
384
+ if (SCAFFOLD_SCAN_SKIP_DIRS.has(entry.name))
385
+ continue;
386
+ walk(path.join(currentDir, entry.name));
387
+ continue;
388
+ }
389
+ if (!entry.name.endsWith('.ts'))
390
+ continue;
391
+ // Skip the scaffolded files themselves — they're allowed to declare
392
+ // their own exports. Match by basename so any project layout works.
393
+ if (ownedBasenames.has(entry.name))
394
+ continue;
395
+ // Skip tests + scripts + demos
396
+ const lower = entry.name.toLowerCase();
397
+ if (lower.endsWith('.test.ts') || lower.endsWith('.spec.ts') || lower.endsWith('.d.ts'))
398
+ continue;
399
+ const fullPath = path.join(currentDir, entry.name);
400
+ const lowerFull = fullPath.toLowerCase();
401
+ if (lowerFull.includes('/tests/') || lowerFull.includes('/__tests__/') || lowerFull.includes('/scripts/'))
402
+ continue;
403
+ let content;
404
+ try {
405
+ const stat = fs.statSync(fullPath);
406
+ if (stat.size > 1_000_000)
407
+ continue;
408
+ content = fs.readFileSync(fullPath, 'utf-8');
409
+ }
410
+ catch {
411
+ continue;
412
+ }
413
+ for (const [exportName, ownedPath] of exportToOwned) {
414
+ // Match `export class Foo`, `export function Foo`, `export const Foo`,
415
+ // `export interface Foo`, `export type Foo`, or `export { Foo }`.
416
+ const declRe = new RegExp(`\\bexport\\s+(?:class|function|const|let|interface|type|enum)\\s+${exportName}\\b`);
417
+ const reExportRe = new RegExp(`\\bexport\\s*\\{[^}]*\\b${exportName}\\b[^}]*\\}`);
418
+ if (declRe.test(content) || reExportRe.test(content)) {
419
+ findings.push({ path: fullPath, exportName, ownedPath });
420
+ }
421
+ }
422
+ }
423
+ };
424
+ walk(projectRoot);
425
+ return findings;
426
+ }
90
427
  function copyPlanningArtifacts(runDir, targetRoot) {
91
428
  try {
92
429
  // ADR-051: Use git repo root (or explicit target) instead of process.cwd()
@@ -293,51 +630,161 @@ export function metricsHandler(_req: any, res: any): void {
293
630
  res.end(lines.join('\\n'));
294
631
  }
295
632
 
296
- /** Simple circuit breaker for external service calls */
297
- export class CircuitBreaker {
298
- private failures = 0;
299
- private state: 'closed' | 'open' | 'half-open' = 'closed';
300
- private nextAttempt = 0;
633
+ // ADR-PIPELINE-069: CircuitBreaker now lives in its own scaffold module.
634
+ // This re-export keeps existing imports (from './middleware.js') working
635
+ // while making the canonical class importable from a dedicated file.
636
+ export { CircuitBreaker } from './circuit-breaker.js';
637
+ `;
638
+ fs.writeFileSync(path.join(scaffoldDir, 'middleware.ts'), middlewareCode, 'utf-8');
639
+ totalCopied += 1;
640
+ // ADR-PIPELINE-069: standalone scaffolded circuit-breaker module so
641
+ // generators can import CircuitBreaker without pulling all of
642
+ // middleware.ts. Owned by the scaffold (see OWNED_SCAFFOLD_MODULES).
643
+ fs.writeFileSync(path.join(scaffoldDir, 'circuit-breaker.ts'), CIRCUIT_BREAKER_SCAFFOLD, 'utf-8');
644
+ totalCopied += 1;
645
+ // ADR-PIPELINE-066: unit-economics helper. The demo script calls
646
+ // writeUnitEconomics() to emit a machine-readable manifest that the
647
+ // executive renderer prefers over per-employee heuristics.
648
+ const unitEconomicsCode = `// Auto-generated by Agentics pipeline (ADR-PIPELINE-066)
649
+ // Writes .agentics/runs/<run-id>/unit-economics.json so the executive
650
+ // renderer can use bottom-up unit economics instead of heuristics.
651
+ import { mkdirSync, writeFileSync, existsSync, readFileSync } from 'node:fs';
652
+ import { dirname, join, resolve } from 'node:path';
653
+ import { fileURLToPath } from 'node:url';
654
+
655
+ export interface DomainUnit {
656
+ label: string; // e.g. "occupied room night"
657
+ abbrev: string; // e.g. "orn"
658
+ }
301
659
 
302
- constructor(
303
- private readonly name: string,
304
- private readonly threshold = 5,
305
- private readonly cooldownMs = 30000,
306
- private readonly logger = createLogger('circuit-breaker'),
307
- ) {}
660
+ export interface UnitEconomicsScope {
661
+ properties?: number;
662
+ rooms?: number;
663
+ sqft?: number;
664
+ vehicles?: number;
665
+ beds?: number;
666
+ agents?: number;
667
+ units?: number;
668
+ employees?: number;
669
+ region_mix?: Array<'NA' | 'EMEA' | 'APAC' | 'LATAM' | 'MEA'>;
670
+ }
308
671
 
309
- async call<T>(fn: () => Promise<T>): Promise<T> {
310
- if (this.state === 'open') {
311
- if (Date.now() < this.nextAttempt) {
312
- throw new Error(\`Circuit breaker '\${this.name}' is OPEN — retry after \${new Date(this.nextAttempt).toISOString()}\`);
313
- }
314
- this.state = 'half-open';
315
- this.logger.info('circuit.half-open', { name: this.name });
316
- }
317
- try {
318
- const result = await fn();
319
- if (this.state === 'half-open') {
320
- this.state = 'closed';
321
- this.failures = 0;
322
- this.logger.info('circuit.closed', { name: this.name });
323
- }
324
- return result;
325
- } catch (err) {
326
- this.failures++;
327
- if (this.failures >= this.threshold) {
328
- this.state = 'open';
329
- this.nextAttempt = Date.now() + this.cooldownMs;
330
- this.logger.warn('circuit.open', { name: this.name, failures: this.failures, retryAt: new Date(this.nextAttempt).toISOString() });
331
- }
332
- throw err;
333
- }
672
+ export interface UnitEconomics {
673
+ run_id: string;
674
+ sector: string;
675
+ domain_unit: DomainUnit;
676
+ measured_scope: UnitEconomicsScope;
677
+ enterprise_scope: UnitEconomicsScope;
678
+ unit_savings: Record<string, number>;
679
+ annual_measured_savings_usd: number;
680
+ annual_extrapolated_savings_usd: number;
681
+ extrapolation_method: string;
682
+ confidence: { directional: number; precision: number };
683
+ generated_at?: string;
684
+ source?: 'prototype';
685
+ }
686
+
687
+ function findRunDir(): string | null {
688
+ // 1. Explicit override
689
+ const override = process.env['AGENTICS_RUN_DIR'];
690
+ if (override && existsSync(override)) return override;
691
+
692
+ // 2. Walk upward looking for .agentics/runs/latest
693
+ let dir: string;
694
+ try {
695
+ dir = dirname(fileURLToPath(import.meta.url));
696
+ } catch {
697
+ dir = process.cwd();
698
+ }
699
+ for (let i = 0; i < 6; i++) {
700
+ const candidate = join(dir, '.agentics', 'runs', 'latest');
701
+ if (existsSync(candidate)) return resolve(candidate);
702
+ const parent = dirname(dir);
703
+ if (parent === dir) break;
704
+ dir = parent;
334
705
  }
335
706
 
336
- getState(): string { return this.state; }
707
+ // 3. Build from run id if we can find one
708
+ const simId = process.env['AGENTICS_SIMULATION_ID'];
709
+ if (simId) {
710
+ const dest = join(process.cwd(), '.agentics', 'runs', simId);
711
+ return dest;
712
+ }
713
+
714
+ return join(process.cwd(), '.agentics', 'runs', 'local-run');
715
+ }
716
+
717
+ export function writeUnitEconomics(manifest: UnitEconomics): string {
718
+ const runDir = findRunDir();
719
+ if (!runDir) throw new Error('ECLI-UE-066: unable to resolve run directory');
720
+ mkdirSync(runDir, { recursive: true });
721
+ const out = {
722
+ source: 'prototype' as const,
723
+ generated_at: new Date().toISOString(),
724
+ ...manifest,
725
+ };
726
+ const filePath = join(runDir, 'unit-economics.json');
727
+ writeFileSync(filePath, JSON.stringify(out, null, 2) + '\\n', 'utf-8');
728
+ return filePath;
729
+ }
730
+
731
+ export function readUnitEconomics(runDir: string): UnitEconomics | null {
732
+ const filePath = join(runDir, 'unit-economics.json');
733
+ if (!existsSync(filePath)) return null;
734
+ try {
735
+ return JSON.parse(readFileSync(filePath, 'utf-8')) as UnitEconomics;
736
+ } catch {
737
+ return null;
738
+ }
337
739
  }
338
740
  `;
339
- fs.writeFileSync(path.join(scaffoldDir, 'middleware.ts'), middlewareCode, 'utf-8');
741
+ fs.writeFileSync(path.join(scaffoldDir, 'unit-economics.ts'), unitEconomicsCode, 'utf-8');
742
+ totalCopied += 1;
743
+ // ADR-PIPELINE-068: ESM-safe simulation-lineage helper. Every generated
744
+ // project gets a loader that reads .agentics/plans/manifest.json using
745
+ // readFileSync + fileURLToPath. NEVER use CommonJS require() in the
746
+ // generated project — it is "type": "module" and require() throws at
747
+ // runtime, producing a silent sim-unknown fallback that severs lineage.
748
+ // The string body lives at module scope (SIMULATION_LINEAGE_SCAFFOLD)
749
+ // so it can be unit-tested without invoking copyPlanningArtifacts.
750
+ fs.writeFileSync(path.join(scaffoldDir, 'simulation-lineage.ts'), SIMULATION_LINEAGE_SCAFFOLD, 'utf-8');
340
751
  totalCopied += 1;
752
+ // ADR-PIPELINE-069: Sidecar manifest listing every scaffold-owned
753
+ // module + its public exports. Consumed by:
754
+ // - prompt-generator.ts (injects "do not reimplement" block)
755
+ // - post-generation-validator PGV-012 (bans duplicate declarations)
756
+ // The manifest is the single source of truth — generators MUST NOT
757
+ // re-emit any export listed here.
758
+ const ownedManifestPath = path.join(plansDir, 'scaffold', 'OWNED_MODULES.json');
759
+ fs.writeFileSync(ownedManifestPath, JSON.stringify(buildOwnedModulesManifest(), null, 2) + '\n', 'utf-8');
760
+ totalCopied += 1;
761
+ // ADR-PIPELINE-069: Cleanup pass — scan the project tree for files
762
+ // that redeclare an export listed in OWNED_MODULES.json. Logs the
763
+ // count; under AGENTICS_AUTO_DEDUPE=true, deletes the duplicates.
764
+ try {
765
+ const projectRootForScan = projectRoot;
766
+ const dupes = detectScaffoldDuplicates(projectRootForScan, OWNED_SCAFFOLD_MODULES);
767
+ const autoDedupe = process.env['AGENTICS_AUTO_DEDUPE'] === 'true';
768
+ if (dupes.length > 0) {
769
+ console.error(` [SCAFFOLD] scaffold.duplicate.detected count=${dupes.length}` +
770
+ (autoDedupe ? ' (auto-deduping)' : ' (set AGENTICS_AUTO_DEDUPE=true to delete)'));
771
+ for (const d of dupes) {
772
+ console.error(` - ${d.path} redeclares ${d.exportName} (owned by ${d.ownedPath})`);
773
+ if (autoDedupe) {
774
+ try {
775
+ fs.unlinkSync(d.path);
776
+ }
777
+ catch { /* best-effort */ }
778
+ }
779
+ }
780
+ }
781
+ else {
782
+ console.error(' [SCAFFOLD] scaffold.duplicate.detected: 0');
783
+ }
784
+ }
785
+ catch {
786
+ // Cleanup is best-effort — never block scaffold emission on a scan failure.
787
+ }
341
788
  // Python scaffold (if SPARC specifies Python or query mentions it)
342
789
  const pyDir = path.join(plansDir, 'scaffold', 'python');
343
790
  fs.mkdirSync(pyDir, { recursive: true });
@@ -1562,6 +2009,46 @@ These numbers MUST come from the actual scoring/analysis results on seed data,
1562
2009
  not from generic heuristics. The evaluator checks that the business case
1563
2010
  references specific prototype findings.
1564
2011
 
2012
+ ## 1c. Unit Economics Manifest (ADR-PIPELINE-066) — REQUIRED
2013
+
2014
+ Copy \`src/unit-economics.ts\` from scaffold and call \`writeUnitEconomics(...)\`
2015
+ at the end of the demo run. This emits \`.agentics/runs/<run-id>/unit-economics.json\`,
2016
+ the machine-readable contract the executive renderer prefers over per-employee
2017
+ heuristics.
2018
+
2019
+ Required fields (derive every number from the prototype's actual computations,
2020
+ NOT from employee headcount):
2021
+
2022
+ \`\`\`typescript
2023
+ import { writeUnitEconomics } from './unit-economics.js';
2024
+
2025
+ writeUnitEconomics({
2026
+ run_id: process.env.AGENTICS_SIMULATION_ID ?? 'local-run',
2027
+ sector: 'hospitality', // or the detected sector
2028
+ domain_unit: { label: 'occupied room night', abbrev: 'orn' },
2029
+ measured_scope: { properties: 4, rooms: 1450, region_mix: ['NA','EMEA','APAC'] },
2030
+ enterprise_scope: { properties: 100, rooms: 36250, employees: 91000 },
2031
+ unit_savings: {
2032
+ usd_per_orn: 0.82, // or usd_per_sqft_year / usd_per_vehicle_mile / ...
2033
+ water_liters_per_orn: 14.5,
2034
+ co2_kg_per_orn: 0.62,
2035
+ },
2036
+ annual_measured_savings_usd: measuredSavings, // from the pilot scenario
2037
+ annual_extrapolated_savings_usd: enterpriseSavings,
2038
+ extrapolation_method: 'linear_by_rooms_weighted_by_regional_cost',
2039
+ confidence: { directional: 0.92, precision: 0.42 },
2040
+ });
2041
+ \`\`\`
2042
+
2043
+ IMPORTANT:
2044
+ - **Do NOT multiply employees × $/employee anywhere** to derive the savings figures.
2045
+ - \`measured_scope\` must reflect the real pilot data the prototype analyzed.
2046
+ - \`enterprise_scope\` and \`annual_extrapolated_savings_usd\` must use a clearly
2047
+ named extrapolation method (linear by rooms, weighted by region, etc.).
2048
+ - The executive renderer will refuse to emit a financial analysis if the printed
2049
+ figures drift more than ±1% from the manifest or the scope ratio deviates by
2050
+ more than ±5% (ECLI-SYN-066).
2051
+
1565
2052
  Also print an "Analysis Confidence" section:
1566
2053
  \`\`\`
1567
2054
  ═══ ANALYSIS CONFIDENCE ═══