@forwardimpact/libuniverse 0.1.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/.prose-cache.json +442 -0
- package/LICENSE +201 -0
- package/bin/fit-universe.js +270 -0
- package/index.js +28 -0
- package/load.js +37 -0
- package/package.json +39 -0
- package/pipeline.js +240 -0
package/pipeline.js
ADDED
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pipeline orchestrator — parse → generate → prose → render → validate.
|
|
3
|
+
*
|
|
4
|
+
* @module libuniverse/pipeline
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFile } from "fs/promises";
|
|
8
|
+
import { join } from "path";
|
|
9
|
+
import { validateLinks, validateHTML } from "@forwardimpact/libsyntheticrender";
|
|
10
|
+
import { collectProseKeys } from "@forwardimpact/libsyntheticgen";
|
|
11
|
+
import { loadSchemas } from "@forwardimpact/libsyntheticprose/pathway";
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Pipeline class that orchestrates the full generation pipeline.
|
|
15
|
+
* All collaborators are injected via constructor.
|
|
16
|
+
*/
|
|
17
|
+
export class Pipeline {
|
|
18
|
+
/**
|
|
19
|
+
* @param {object} deps
|
|
20
|
+
* @param {import('@forwardimpact/libsyntheticgen').DslParser} deps.dslParser - DSL parser
|
|
21
|
+
* @param {import('@forwardimpact/libsyntheticgen').EntityGenerator} deps.entityGenerator - Entity generator
|
|
22
|
+
* @param {import('@forwardimpact/libsyntheticprose').ProseEngine} deps.proseEngine - Prose engine
|
|
23
|
+
* @param {import('@forwardimpact/libsyntheticprose').PathwayGenerator} deps.pathwayGenerator - Pathway generator
|
|
24
|
+
* @param {import('@forwardimpact/libsyntheticrender').Renderer} deps.renderer - Renderer
|
|
25
|
+
* @param {import('@forwardimpact/libsyntheticrender').ContentValidator} deps.validator - Content validator
|
|
26
|
+
* @param {import('@forwardimpact/libsyntheticrender').ContentFormatter} deps.formatter - Content formatter
|
|
27
|
+
* @param {object} deps.logger - Logger instance
|
|
28
|
+
*/
|
|
29
|
+
constructor({
|
|
30
|
+
dslParser,
|
|
31
|
+
entityGenerator,
|
|
32
|
+
proseEngine,
|
|
33
|
+
pathwayGenerator,
|
|
34
|
+
renderer,
|
|
35
|
+
validator,
|
|
36
|
+
formatter,
|
|
37
|
+
logger,
|
|
38
|
+
}) {
|
|
39
|
+
if (!dslParser) throw new Error("dslParser is required");
|
|
40
|
+
if (!entityGenerator) throw new Error("entityGenerator is required");
|
|
41
|
+
if (!proseEngine) throw new Error("proseEngine is required");
|
|
42
|
+
if (!pathwayGenerator) throw new Error("pathwayGenerator is required");
|
|
43
|
+
if (!renderer) throw new Error("renderer is required");
|
|
44
|
+
if (!validator) throw new Error("validator is required");
|
|
45
|
+
if (!formatter) throw new Error("formatter is required");
|
|
46
|
+
if (!logger) throw new Error("logger is required");
|
|
47
|
+
|
|
48
|
+
this.dslParser = dslParser;
|
|
49
|
+
this.entityGenerator = entityGenerator;
|
|
50
|
+
this.proseEngine = proseEngine;
|
|
51
|
+
this.pathwayGenerator = pathwayGenerator;
|
|
52
|
+
this.renderer = renderer;
|
|
53
|
+
this.validator = validator;
|
|
54
|
+
this.formatter = formatter;
|
|
55
|
+
this.logger = logger;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Run the full generation pipeline.
|
|
60
|
+
*
|
|
61
|
+
* @param {object} options
|
|
62
|
+
* @param {string} options.universePath - Path to the universe.dsl file
|
|
63
|
+
* @param {string} [options.only=null] - Render only a specific content type
|
|
64
|
+
* @param {string|null} [options.schemaDir=null] - Path to JSON schema directory
|
|
65
|
+
* @returns {Promise<{files: Map<string,string>, rawDocuments: Map<string,string>, entities: object, validation: object}>}
|
|
66
|
+
*/
|
|
67
|
+
async run(options) {
|
|
68
|
+
const { universePath, only = null, schemaDir = null } = options;
|
|
69
|
+
const log = this.logger;
|
|
70
|
+
|
|
71
|
+
// 1. Parse DSL
|
|
72
|
+
log.info("pipeline", "Parsing universe DSL");
|
|
73
|
+
const source = await readFile(universePath, "utf-8");
|
|
74
|
+
const ast = this.dslParser.parse(source);
|
|
75
|
+
|
|
76
|
+
// 2. Generate entity graph (Tier 0)
|
|
77
|
+
log.info("pipeline", "Generating entity graph");
|
|
78
|
+
const entities = this.entityGenerator.generate(ast);
|
|
79
|
+
|
|
80
|
+
// 3. Prose generation (Tier 1/2)
|
|
81
|
+
const proseKeys = collectProseKeys(entities);
|
|
82
|
+
const prose = new Map();
|
|
83
|
+
const totalKeys = proseKeys.size;
|
|
84
|
+
let keyIndex = 0;
|
|
85
|
+
if (this.proseEngine.mode !== "no-prose") {
|
|
86
|
+
log.info(
|
|
87
|
+
"pipeline",
|
|
88
|
+
`Generating prose (${this.proseEngine.mode} mode, ${totalKeys} keys)`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
for (const [key, context] of proseKeys) {
|
|
92
|
+
keyIndex++;
|
|
93
|
+
const result = await this.proseEngine.generateProse(key, context);
|
|
94
|
+
if (result) prose.set(key, result);
|
|
95
|
+
if (this.proseEngine.mode !== "no-prose") {
|
|
96
|
+
log.info("prose", `[${keyIndex}/${totalKeys}] ${key}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 4. Render outputs
|
|
101
|
+
const files = new Map();
|
|
102
|
+
const rawDocuments = new Map();
|
|
103
|
+
let htmlLinked = null;
|
|
104
|
+
|
|
105
|
+
const shouldRender = (type) => !only || only === type;
|
|
106
|
+
|
|
107
|
+
if (shouldRender("html")) {
|
|
108
|
+
log.info("render", "Rendering HTML (Pass 1: deterministic skeleton)");
|
|
109
|
+
const { files: htmlFiles, linked } = this.renderer.renderHtml(
|
|
110
|
+
entities,
|
|
111
|
+
prose,
|
|
112
|
+
);
|
|
113
|
+
htmlLinked = linked;
|
|
114
|
+
|
|
115
|
+
// Pass 2: LLM enrichment of prose blocks
|
|
116
|
+
if (this.proseEngine.mode !== "no-prose") {
|
|
117
|
+
log.info("render", "Enriching HTML (Pass 2: LLM prose enrichment)");
|
|
118
|
+
const enriched = await this.renderer.enrichHtml(
|
|
119
|
+
htmlFiles,
|
|
120
|
+
linked,
|
|
121
|
+
this.proseEngine,
|
|
122
|
+
entities.domain,
|
|
123
|
+
);
|
|
124
|
+
for (const [name, content] of enriched) {
|
|
125
|
+
files.set(join("examples/organizational", name), content);
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
for (const [name, content] of htmlFiles) {
|
|
129
|
+
files.set(join("examples/organizational", name), content);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
files.set(
|
|
134
|
+
"examples/organizational/README.md",
|
|
135
|
+
this.renderer.renderReadme(entities, prose),
|
|
136
|
+
);
|
|
137
|
+
files.set(
|
|
138
|
+
"examples/organizational/ONTOLOGY.md",
|
|
139
|
+
this.renderer.renderOntology(entities),
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (shouldRender("pathway")) {
|
|
144
|
+
log.info("render", "Rendering pathway");
|
|
145
|
+
const hasPathwayFramework =
|
|
146
|
+
entities.framework?.capabilities?.length > 0 &&
|
|
147
|
+
typeof entities.framework.capabilities[0] === "object";
|
|
148
|
+
|
|
149
|
+
if (hasPathwayFramework && schemaDir) {
|
|
150
|
+
const schemas = loadSchemas(schemaDir);
|
|
151
|
+
const pathwayData = await this.pathwayGenerator.generate({
|
|
152
|
+
framework: entities.framework,
|
|
153
|
+
domain: entities.domain,
|
|
154
|
+
industry: entities.industry,
|
|
155
|
+
schemas,
|
|
156
|
+
});
|
|
157
|
+
const pathwayFiles = this.renderer.renderPathway(pathwayData);
|
|
158
|
+
for (const [name, content] of pathwayFiles) {
|
|
159
|
+
files.set(`examples/pathway/${name}`, content);
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (shouldRender("raw")) {
|
|
165
|
+
log.info("render", "Rendering raw documents");
|
|
166
|
+
const raw = this.renderer.renderRaw(entities, prose);
|
|
167
|
+
for (const [path, content] of raw) {
|
|
168
|
+
rawDocuments.set(path, content);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const activityFiles = this.renderer.renderActivity(entities);
|
|
172
|
+
for (const [name, content] of activityFiles) {
|
|
173
|
+
files.set(join("examples/activity", name), content);
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (shouldRender("markdown")) {
|
|
178
|
+
log.info("render", "Rendering markdown");
|
|
179
|
+
const md = this.renderer.renderMarkdown(entities, prose);
|
|
180
|
+
for (const [name, content] of md) {
|
|
181
|
+
files.set(join("examples/personal", name), content);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Save prose cache after all generation
|
|
186
|
+
this.proseEngine.saveCache();
|
|
187
|
+
|
|
188
|
+
// 5. Format outputs with Prettier
|
|
189
|
+
log.info("format", "Formatting output files with Prettier");
|
|
190
|
+
const formattedFiles = await this.formatter.format(files);
|
|
191
|
+
const formattedRawDocuments = await this.formatter.format(rawDocuments);
|
|
192
|
+
|
|
193
|
+
// 6. Validate
|
|
194
|
+
const validation = this.validator.validate(entities);
|
|
195
|
+
|
|
196
|
+
if (htmlLinked) {
|
|
197
|
+
const linkValidation = validateLinks(htmlLinked, entities.domain);
|
|
198
|
+
validation.checks.push({
|
|
199
|
+
name: "link_density",
|
|
200
|
+
passed: linkValidation.passed,
|
|
201
|
+
});
|
|
202
|
+
if (!linkValidation.passed) {
|
|
203
|
+
validation.failures++;
|
|
204
|
+
validation.passed = false;
|
|
205
|
+
log.error(
|
|
206
|
+
"validate",
|
|
207
|
+
`Link validation: ${linkValidation.failures} failures`,
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const orgFiles = new Map();
|
|
212
|
+
for (const [path, content] of formattedFiles) {
|
|
213
|
+
if (
|
|
214
|
+
path.startsWith("examples/organizational/") &&
|
|
215
|
+
path.endsWith(".html")
|
|
216
|
+
) {
|
|
217
|
+
orgFiles.set(path, content);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
const htmlValidation = validateHTML(orgFiles, entities.domain);
|
|
221
|
+
for (const check of htmlValidation.checks) {
|
|
222
|
+
validation.checks.push(check);
|
|
223
|
+
}
|
|
224
|
+
if (!htmlValidation.passed) {
|
|
225
|
+
validation.failures += htmlValidation.failures;
|
|
226
|
+
validation.passed = false;
|
|
227
|
+
for (const c of htmlValidation.checks.filter((c) => !c.passed)) {
|
|
228
|
+
log.error("validate", c.message);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return {
|
|
234
|
+
files: formattedFiles,
|
|
235
|
+
rawDocuments: formattedRawDocuments,
|
|
236
|
+
entities,
|
|
237
|
+
validation,
|
|
238
|
+
};
|
|
239
|
+
}
|
|
240
|
+
}
|