feat: migrate npm registry from Verdaccio to Gitea Packages
Some checks failed
Monorepo Pipeline / ⚡ Prioritize Release (push) Successful in 1s
Monorepo Pipeline / 🧹 Lint (push) Failing after 35s
Monorepo Pipeline / 🧪 Test (push) Failing after 35s
Monorepo Pipeline / 🏗️ Build (push) Failing after 12s
Monorepo Pipeline / 🚀 Release (push) Has been skipped
Monorepo Pipeline / 🐳 Build Image Processor (push) Has been skipped
Monorepo Pipeline / 🐳 Build Directus (Base) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Gatekeeper (Product) (push) Has been skipped
Monorepo Pipeline / 🐳 Build Build-Base (push) Has been skipped
Monorepo Pipeline / 🐳 Build Production Runtime (push) Has been skipped

This commit is contained in:
2026-02-27 00:12:00 +01:00
parent efd1341762
commit 5da88356a8
69 changed files with 5397 additions and 114 deletions

View File

@@ -0,0 +1,95 @@
// ============================================================================
// Step 05: Synthesize — Position Descriptions (Gemini Pro)
// ============================================================================
import { llmJsonRequest } from "../llm-client.js";
import type { EstimationState, StepResult, PipelineConfig } from "../types.js";
import { DEFAULT_MODELS } from "../types.js";
export async function executeSynthesize(
state: EstimationState,
config: PipelineConfig,
): Promise<StepResult> {
const models = { ...DEFAULT_MODELS, ...config.modelsOverride };
const startTime = Date.now();
if (!state.concept?.auditedFacts || !state.concept?.architecture?.sitemap) {
return { success: false, error: "Missing audited facts or sitemap." };
}
const facts = state.concept.auditedFacts;
// Determine which positions are required
const requiredPositions = [
"Das technische Fundament",
(facts.selectedPages?.length || 0) + (facts.otherPages?.length || 0) > 0
? "Individuelle Seiten"
: null,
facts.features?.length > 0 ? "System-Module (Features)" : null,
facts.functions?.length > 0 ? "Logik-Funktionen" : null,
facts.apiSystems?.length > 0 ? "Schnittstellen (API)" : null,
facts.cmsSetup ? "Inhalts-Verwaltung" : null,
facts.multilang ? "Mehrsprachigkeit" : null,
"Inhaltliche Initial-Pflege",
"Sorglos Betrieb",
].filter(Boolean);
const systemPrompt = `
You are a Senior Solution Architect. Write position descriptions for a professional B2B quote.
### REQUIRED POSITIONS (STRICT — ONLY DESCRIBE THESE):
${requiredPositions.map((p) => `"${p}"`).join(", ")}
### RULES (STRICT):
1. NO FIRST PERSON: NEVER "Ich", "Mein", "Wir", "Unser". Lead with nouns or passive verbs.
2. QUANTITY PARITY: Description MUST list EXACTLY the number of items matching 'qty'.
3. CMS GUARD: If cmsSetup=false, do NOT mention "CMS", "Inhaltsverwaltung". Use "Plattform-Struktur".
4. TONE: "Erstellung von...", "Anbindung der...", "Bereitstellung von...". Technical, high-density.
5. PAGES: List actual page names. NO implementation notes in parentheses.
6. HARD SPECIFICS: Use industry terms from the briefing (e.g. "Kabeltiefbau", "110 kV").
7. KEYS: Return EXACTLY the keys from REQUIRED POSITIONS.
8. NO AGB: NEVER mention "AGB" or "Geschäftsbedingungen".
9. Sorglos Betrieb: "Inklusive 1 Jahr technischer Betrieb, Hosting, SSL, Sicherheits-Updates, Monitoring und techn. Support."
10. Inhaltliche Initial-Pflege: Refers to DATENSÄTZE (datasets like products, references), NOT Seiten.
Use "Datensätze" in the description, not "Seiten".
11. Mehrsprachigkeit: This is a +20% markup on the subtotal. NOT an API. NOT a Schnittstelle.
### EXAMPLES:
- GOOD: "Erstellung der Seiten: Startseite, Über uns, Leistungen, Kontakt."
- GOOD: "Native API-Anbindung an Google Maps mit individueller Standort-Visualisierung."
- BAD: "Ich richte dir das CMS ein."
- BAD: "Verschiedene Funktionen" (too generic — name the things!)
### DATA CONTEXT:
${JSON.stringify({ facts, sitemap: state.concept.architecture.sitemap, strategy: { briefingSummary: state.concept.strategy.briefingSummary } }, null, 2)}
### OUTPUT FORMAT:
{
"positionDescriptions": { "Das technische Fundament": string, ... }
}
`;
try {
const { data, usage } = await llmJsonRequest({
model: models.pro,
systemPrompt,
userPrompt: state.concept.briefing,
apiKey: config.openrouterKey,
});
return {
success: true,
data: data.positionDescriptions || data,
usage: {
step: "05-synthesize",
model: models.pro,
promptTokens: usage.promptTokens,
completionTokens: usage.completionTokens,
cost: usage.cost,
durationMs: Date.now() - startTime,
},
};
} catch (err) {
return { success: false, error: `Synthesize step failed: ${(err as Error).message}` };
}
}

View File

@@ -0,0 +1,99 @@
// ============================================================================
// Step 06: Critique — Industrial Critic Quality Gate (Claude Opus)
// ============================================================================
import { llmJsonRequest } from "../llm-client.js";
import type { EstimationState, StepResult, PipelineConfig } from "../types.js";
import { DEFAULT_MODELS } from "../types.js";
export async function executeCritique(
state: EstimationState,
config: PipelineConfig,
): Promise<StepResult> {
const models = { ...DEFAULT_MODELS, ...config.modelsOverride };
const startTime = Date.now();
const currentState = {
facts: state.concept?.auditedFacts,
briefingSummary: state.concept?.strategy?.briefingSummary,
designVision: state.concept?.strategy?.designVision,
sitemap: state.concept?.architecture?.sitemap,
positionDescriptions: state.positionDescriptions,
siteProfile: state.concept?.siteProfile
? {
existingFeatures: state.concept.siteProfile.existingFeatures,
services: state.concept.siteProfile.services,
externalDomains: state.concept.siteProfile.externalDomains,
navigation: state.concept.siteProfile.navigation,
totalPages: state.concept.siteProfile.totalPages,
}
: null,
};
const systemPrompt = `
You are the "Industrial Critic" — the final quality gate for a professional B2B estimation.
Your job is to find EVERY error, hallucination, and inconsistency before this goes to the client.
### CRITICAL ERROR CHECKLIST (FAIL IF ANY FOUND):
1. HALLUCINATION: FAIL if names, software versions, or details not in the BRIEFING are used.
- "Sie", "Ansprechpartner" for personName when an actual name exists = FAIL.
2. LOGIC CONFLICT: FAIL if isRelaunch=true but text claims "no website exists".
3. IMPLEMENTATION FLUFF: FAIL if "React", "Next.js", "TypeScript", "Tailwind" are mentioned.
4. GENERICISM: FAIL if text could apply to ANY company. Must use specific industry terms.
5. NAMEN-VERBOT: FAIL if personal names in briefingSummary or designVision.
6. CMS-LEAKAGE: FAIL if cmsSetup=false but descriptions mention "CMS", "Inhaltsverwaltung".
7. AGB BAN: FAIL if "AGB" or "Geschäftsbedingungen" appear anywhere.
8. LENGTH: briefingSummary ~6 sentences, designVision ~4 sentences. Shorten if too wordy.
9. LEGAL SAFETY: FAIL if "rechtssicher" is used. Use "Standard-konform" instead.
10. BULLSHIT DETECTOR: FAIL if jargon like "SEO-Standards zur Fachkräftesicherung",
"B2B-Nutzerströme", "Digitale Konvergenzstrategie" or similar meaningless buzzwords are used.
The text must make SENSE to a construction industry CEO.
11. PAGE STRUCTURE: FAIL if the sitemap contains:
- Videos as pages (Messefilm, Imagefilm)
- Internal functions as pages (Verwaltung)
- Entities with their own domains as sub-pages (check externalDomains!)
12. SORGLOS-BETRIEB: FAIL if not mentioned in the summary or position descriptions.
13. TONE: FAIL if "wir/unser" or "Ich/Mein" in position descriptions. FAIL if marketing fluff.
14. MULTILANG: FAIL if Mehrsprachigkeit is described as an API or Schnittstelle.
15. INITIAL-PFLEGE: FAIL if described in terms of "Seiten" instead of "Datensätze".
### MISSION:
Return corrected fields ONLY for fields with issues. If everything passes, return empty corrections.
### OUTPUT FORMAT:
{
"passed": boolean,
"errors": [{ "field": string, "issue": string, "severity": "critical" | "warning" }],
"corrections": {
"briefingSummary"?: string,
"designVision"?: string,
"positionDescriptions"?: Record<string, string>,
"sitemap"?: array
}
}
`;
try {
const { data, usage } = await llmJsonRequest({
model: models.opus,
systemPrompt,
userPrompt: `BRIEFING_TRUTH:\n${state.concept?.briefing}\n\nCURRENT_STATE:\n${JSON.stringify(currentState, null, 2)}`,
apiKey: config.openrouterKey,
});
return {
success: true,
data,
usage: {
step: "06-critique",
model: models.opus,
promptTokens: usage.promptTokens,
completionTokens: usage.completionTokens,
cost: usage.cost,
durationMs: Date.now() - startTime,
},
};
} catch (err) {
return { success: false, error: `Critique step failed: ${(err as Error).message}` };
}
}