@llm-dev-ops/agentics-cli 2.1.5 → 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.
- package/dist/pipeline/auto-chain.d.ts +73 -0
- package/dist/pipeline/auto-chain.d.ts.map +1 -1
- package/dist/pipeline/auto-chain.js +525 -38
- package/dist/pipeline/auto-chain.js.map +1 -1
- package/dist/pipeline/phase2/phases/prompt-generator.d.ts.map +1 -1
- package/dist/pipeline/phase2/phases/prompt-generator.js +53 -6
- package/dist/pipeline/phase2/phases/prompt-generator.js.map +1 -1
- package/dist/pipeline/phase2/schemas.d.ts +10 -10
- package/dist/pipeline/phase4/phases/http-server-generator.d.ts +12 -0
- package/dist/pipeline/phase4/phases/http-server-generator.d.ts.map +1 -1
- package/dist/pipeline/phase4/phases/http-server-generator.js +92 -25
- package/dist/pipeline/phase4/phases/http-server-generator.js.map +1 -1
- package/dist/pipeline/phase5-build/phase5-build-coordinator.d.ts.map +1 -1
- package/dist/pipeline/phase5-build/phase5-build-coordinator.js +44 -0
- package/dist/pipeline/phase5-build/phase5-build-coordinator.js.map +1 -1
- package/dist/pipeline/phase5-build/phases/post-generation-validator.d.ts +75 -0
- package/dist/pipeline/phase5-build/phases/post-generation-validator.d.ts.map +1 -0
- package/dist/pipeline/phase5-build/phases/post-generation-validator.js +728 -0
- package/dist/pipeline/phase5-build/phases/post-generation-validator.js.map +1 -0
- package/dist/pipeline/phase5-build/types.d.ts +1 -1
- package/dist/pipeline/phase5-build/types.d.ts.map +1 -1
- package/dist/pipeline/types.d.ts +84 -0
- package/dist/pipeline/types.d.ts.map +1 -1
- package/dist/pipeline/types.js +43 -1
- package/dist/pipeline/types.js.map +1 -1
- package/dist/synthesis/consensus-svg.d.ts +19 -0
- package/dist/synthesis/consensus-svg.d.ts.map +1 -0
- package/dist/synthesis/consensus-svg.js +95 -0
- package/dist/synthesis/consensus-svg.js.map +1 -0
- package/dist/synthesis/consensus-tiers.d.ts +99 -0
- package/dist/synthesis/consensus-tiers.d.ts.map +1 -0
- package/dist/synthesis/consensus-tiers.js +285 -0
- package/dist/synthesis/consensus-tiers.js.map +1 -0
- package/dist/synthesis/domain-labor-classifier.d.ts +101 -0
- package/dist/synthesis/domain-labor-classifier.d.ts.map +1 -0
- package/dist/synthesis/domain-labor-classifier.js +312 -0
- package/dist/synthesis/domain-labor-classifier.js.map +1 -0
- package/dist/synthesis/domain-unit-registry.d.ts +59 -0
- package/dist/synthesis/domain-unit-registry.d.ts.map +1 -0
- package/dist/synthesis/domain-unit-registry.js +294 -0
- package/dist/synthesis/domain-unit-registry.js.map +1 -0
- package/dist/synthesis/financial-claim-extractor.d.ts +52 -0
- package/dist/synthesis/financial-claim-extractor.d.ts.map +1 -0
- package/dist/synthesis/financial-claim-extractor.js +351 -0
- package/dist/synthesis/financial-claim-extractor.js.map +1 -0
- package/dist/synthesis/financial-consistency-rules.d.ts +66 -0
- package/dist/synthesis/financial-consistency-rules.d.ts.map +1 -0
- package/dist/synthesis/financial-consistency-rules.js +432 -0
- package/dist/synthesis/financial-consistency-rules.js.map +1 -0
- package/dist/synthesis/financial-consistency-runner.d.ts +73 -0
- package/dist/synthesis/financial-consistency-runner.d.ts.map +1 -0
- package/dist/synthesis/financial-consistency-runner.js +131 -0
- package/dist/synthesis/financial-consistency-runner.js.map +1 -0
- package/dist/synthesis/forbidden-spin-phrases.d.ts +32 -0
- package/dist/synthesis/forbidden-spin-phrases.d.ts.map +1 -0
- package/dist/synthesis/forbidden-spin-phrases.js +84 -0
- package/dist/synthesis/forbidden-spin-phrases.js.map +1 -0
- package/dist/synthesis/phase-gate-thresholds.d.ts +30 -0
- package/dist/synthesis/phase-gate-thresholds.d.ts.map +1 -0
- package/dist/synthesis/phase-gate-thresholds.js +34 -0
- package/dist/synthesis/phase-gate-thresholds.js.map +1 -0
- package/dist/synthesis/prompts/index.d.ts.map +1 -1
- package/dist/synthesis/prompts/index.js +22 -0
- package/dist/synthesis/prompts/index.js.map +1 -1
- package/dist/synthesis/simulation-artifact-generator.d.ts.map +1 -1
- package/dist/synthesis/simulation-artifact-generator.js +89 -1
- package/dist/synthesis/simulation-artifact-generator.js.map +1 -1
- package/dist/synthesis/simulation-renderers.d.ts +105 -2
- package/dist/synthesis/simulation-renderers.d.ts.map +1 -1
- package/dist/synthesis/simulation-renderers.js +1056 -92
- package/dist/synthesis/simulation-renderers.js.map +1 -1
- package/dist/synthesis/unit-economics-loader.d.ts +71 -0
- package/dist/synthesis/unit-economics-loader.d.ts.map +1 -0
- package/dist/synthesis/unit-economics-loader.js +200 -0
- package/dist/synthesis/unit-economics-loader.js.map +1 -0
- 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
|
-
|
|
297
|
-
export
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
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
|
-
|
|
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, '
|
|
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 ═══
|