@mestreyoda/fabrica 0.1.7 → 0.1.9

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/index.js CHANGED
@@ -111329,8 +111329,8 @@ import fsSync from "node:fs";
111329
111329
  import path5 from "node:path";
111330
111330
  import { fileURLToPath as fileURLToPath3 } from "node:url";
111331
111331
  function getCurrentVersion() {
111332
- if ("0.1.7") {
111333
- return "0.1.7";
111332
+ if ("0.1.9") {
111333
+ return "0.1.9";
111334
111334
  }
111335
111335
  try {
111336
111336
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -138808,8 +138808,8 @@ async function ensurePythonEnvironment(repoPath, stack, runCommand) {
138808
138808
  skipped: false,
138809
138809
  stack,
138810
138810
  family: "python",
138811
- toolchain: "python",
138812
- packageManager: "pip",
138811
+ toolchain: "uv",
138812
+ packageManager: "uv",
138813
138813
  lockfile: null,
138814
138814
  environmentPath: null,
138815
138815
  commandsRun,
@@ -139086,6 +139086,7 @@ fi
139086
139086
  }
139087
139087
 
139088
139088
  // lib/intake/steps/scaffold.ts
139089
+ var PYTHON_STACKS2 = /* @__PURE__ */ new Set(["python-cli", "fastapi", "flask", "django"]);
139089
139090
  var scaffoldStep = {
139090
139091
  name: "scaffold",
139091
139092
  shouldRun: (payload) => payload.impact?.is_greenfield === true && !payload.dry_run,
@@ -139122,7 +139123,7 @@ var scaffoldStep = {
139122
139123
  ctx.log(
139123
139124
  bootstrap.skipped ? `Scaffold bootstrap already current (${bootstrap.packageManager})` : `Scaffold bootstrap completed (${bootstrap.packageManager})`
139124
139125
  );
139125
- if (payload.spec) {
139126
+ if (scaffold.stack && PYTHON_STACKS2.has(scaffold.stack) && payload.spec) {
139126
139127
  try {
139127
139128
  const contract = generateQaContract({
139128
139129
  spec: payload.spec,
@@ -142031,6 +142032,9 @@ function registerAttachmentHook(api, ctx) {
142031
142032
 
142032
142033
  // lib/dispatch/telegram-bootstrap-hook.ts
142033
142034
  import { homedir as homedir3 } from "node:os";
142035
+ init_runtime_paths();
142036
+ init_extract_json();
142037
+ init_zod();
142034
142038
 
142035
142039
  // lib/dispatch/telegram-bootstrap-session.ts
142036
142040
  init_migrate_layout();
@@ -142069,7 +142073,7 @@ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
142069
142073
  try {
142070
142074
  const raw = await fs38.readFile(sessionPath(workspaceDir, conversationId), "utf-8");
142071
142075
  const session = JSON.parse(raw);
142072
- if (session.status === "clarifying" && Date.parse(session.suppressUntil) < Date.now()) {
142076
+ if ((session.status === "clarifying" || session.status === "classifying") && Date.parse(session.suppressUntil) < Date.now()) {
142073
142077
  await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
142074
142078
  });
142075
142079
  return null;
@@ -142080,6 +142084,10 @@ async function readTelegramBootstrapSession(workspaceDir, conversationId) {
142080
142084
  throw error48;
142081
142085
  }
142082
142086
  }
142087
+ async function deleteTelegramBootstrapSession(workspaceDir, conversationId) {
142088
+ await fs38.unlink(sessionPath(workspaceDir, conversationId)).catch(() => {
142089
+ });
142090
+ }
142083
142091
  async function writeTelegramBootstrapSession(workspaceDir, session) {
142084
142092
  const dir = sessionsDir(workspaceDir);
142085
142093
  await fs38.mkdir(dir, { recursive: true });
@@ -142183,10 +142191,60 @@ function parseBootstrapRequest(text) {
142183
142191
  stackHint
142184
142192
  };
142185
142193
  }
142194
+ var MAX_CLASSIFY_LENGTH = 500;
142195
+ function isAmbiguousCandidate(text) {
142196
+ const lower2 = text.toLowerCase();
142197
+ if (lower2.length <= 20 || lower2.length > MAX_CLASSIFY_LENGTH) return false;
142198
+ const softwareCue = /\b(projeto|project|cli|api|app|aplicativo|servi[cç]o|library|biblioteca|repo|reposit[oó]rio|tool|ferramenta|sistema|system|bot|script|programa|program)\b/.test(lower2);
142199
+ return softwareCue;
142200
+ }
142201
+ var DmIntentSchema = external_exports.object({
142202
+ intent: external_exports.enum(["create_project", "other"]),
142203
+ confidence: external_exports.number().min(0).max(1),
142204
+ stackHint: external_exports.string().nullable().optional(),
142205
+ projectSlug: external_exports.string().nullable().optional()
142206
+ });
142207
+ var CLASSIFY_PROMPT_TEMPLATE = `Classify this Telegram DM. Is the user asking to create/build a new software project, or is it something else (question, greeting, status check)?
142208
+
142209
+ Message: "$CONTENT"
142210
+
142211
+ Return ONLY valid JSON:
142212
+ {"intent": "create_project" | "other", "confidence": 0.0-1.0, "stackHint": "<detected stack or null>", "projectSlug": "<suggested slug or null>"}
142213
+
142214
+ Examples:
142215
+ - "Cria uma CLI Python que valida CPF" \u2192 {"intent":"create_project","confidence":0.95,"stackHint":"python-cli","projectSlug":"validador-cpf-cli"}
142216
+ - "Build me a REST API for tasks" \u2192 {"intent":"create_project","confidence":0.9,"stackHint":"fastapi","projectSlug":"task-api"}
142217
+ - "How's the project going?" \u2192 {"intent":"other","confidence":0.95,"stackHint":null,"projectSlug":null}
142218
+ - "Oi, tudo bem?" \u2192 {"intent":"other","confidence":0.99,"stackHint":null,"projectSlug":null}
142219
+ - "Me faz um app que converte temperaturas" \u2192 {"intent":"create_project","confidence":0.9,"stackHint":null,"projectSlug":"conversor-temperaturas"}`;
142220
+ async function classifyDmIntent(ctx, content, workspaceDir) {
142221
+ try {
142222
+ const truncated = content.slice(0, MAX_CLASSIFY_LENGTH);
142223
+ const prompt = CLASSIFY_PROMPT_TEMPLATE.replace("$CONTENT", truncated.replace(/"/g, '\\"'));
142224
+ const cliPath = resolveOpenClawCli({ homeDir: homedir3(), workspaceDir });
142225
+ const sessionId = `dm-classify-${Date.now()}`;
142226
+ const result = await ctx.runCommand(
142227
+ [cliPath, "agent", "--local", "-m", prompt, "--session-id", sessionId, "--json"],
142228
+ { timeoutMs: 15e3 }
142229
+ );
142230
+ const stdout = result.stdout ?? "";
142231
+ if (!stdout.trim()) return null;
142232
+ const parsed = extractJsonFromStdout(stdout);
142233
+ if (!parsed) return null;
142234
+ const text = parsed?.payloads?.[0]?.text;
142235
+ const jsonStr = text ? text.replace(/^```(json)?/gm, "").replace(/```$/gm, "").trim() : JSON.stringify(parsed);
142236
+ const intentData = JSON.parse(jsonStr);
142237
+ const validated = DmIntentSchema.safeParse(intentData);
142238
+ if (!validated.success) return null;
142239
+ return validated.data;
142240
+ } catch {
142241
+ return null;
142242
+ }
142243
+ }
142186
142244
  function isBootstrapCandidate(text) {
142187
142245
  const lower2 = text.toLowerCase();
142188
142246
  if (/^\s*(project name|nome do projeto|repository url|repo url|stack)\s*:/im.test(text)) return true;
142189
- const createCue = /\b(crie|criar|create|register|registre|novo projeto|new project)\b/.test(lower2);
142247
+ const createCue = /\b(crie|cria|criar|create|register|registre|construa|desenvolva|novo projeto|new project)\b/.test(lower2);
142190
142248
  const softwareCue = /\b(projeto|project|cli|api|app|aplicativo|servi[cç]o|library|biblioteca|repo|reposit[oó]rio)\b/.test(lower2);
142191
142249
  return createCue && softwareCue;
142192
142250
  }
@@ -142273,6 +142331,67 @@ function logBootstrapWarning(ctx, message) {
142273
142331
  ctx.logger.info(message);
142274
142332
  }
142275
142333
  }
142334
+ async function classifyAndBootstrap(ctx, workspaceDir, conversationId, content) {
142335
+ const classification = await classifyDmIntent(ctx, content, workspaceDir);
142336
+ if (!classification || classification.intent !== "create_project" || classification.confidence < 0.7) {
142337
+ if (!classification) {
142338
+ logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify failed, falling back (conversation: ${conversationId})`);
142339
+ }
142340
+ await deleteTelegramBootstrapSession(workspaceDir, conversationId);
142341
+ return;
142342
+ }
142343
+ await sendTelegramText(ctx, conversationId, "Recebi! Vou analisar e come\xE7ar a montar o projeto...");
142344
+ const parsed = parseBootstrapRequest(content);
142345
+ if (classification.stackHint && !parsed.stackHint) {
142346
+ parsed.stackHint = classification.stackHint;
142347
+ }
142348
+ if (classification.projectSlug && !parsed.projectName) {
142349
+ parsed.projectName = classification.projectSlug;
142350
+ }
142351
+ const incomingRequest = {
142352
+ rawIdea: parsed.rawIdea,
142353
+ projectName: parsed.projectName ?? null,
142354
+ stackHint: parsed.stackHint ?? null,
142355
+ repoUrl: parsed.repoUrl ?? null,
142356
+ repoPath: parsed.repoPath ?? null
142357
+ };
142358
+ const incomingRequestHash = buildBootstrapRequestHash(incomingRequest);
142359
+ const sessionForHash = await readTelegramBootstrapSession(workspaceDir, conversationId);
142360
+ if (sessionForHash?.requestHash === incomingRequestHash) {
142361
+ if (sessionForHash.status === "completed") {
142362
+ ctx.logger.info(`[telegram-bootstrap] duplicate completed DM ignored (LLM path) for conversation ${conversationId}`);
142363
+ return;
142364
+ }
142365
+ const isExpiredReceived = sessionForHash.status === "received" && Date.parse(sessionForHash.suppressUntil) < Date.now();
142366
+ if (sessionForHash.status !== "failed" && sessionForHash.status !== "classifying" && !isExpiredReceived) {
142367
+ ctx.logger.info(`[telegram-bootstrap] duplicate in-flight DM ignored (LLM path) for conversation ${conversationId}`);
142368
+ return;
142369
+ }
142370
+ }
142371
+ const sourceRoute = { channel: "telegram", channelId: conversationId };
142372
+ const session = await upsertTelegramBootstrapSession(workspaceDir, {
142373
+ conversationId,
142374
+ ...incomingRequest,
142375
+ sourceRoute,
142376
+ sourceChannel: "telegram",
142377
+ status: "received"
142378
+ });
142379
+ if (!parsed.stackHint) {
142380
+ const pendingClarification = !parsed.projectName ? "stack_and_name" : "stack";
142381
+ await upsertTelegramBootstrapSession(workspaceDir, {
142382
+ conversationId,
142383
+ ...incomingRequest,
142384
+ sourceRoute: session.sourceRoute,
142385
+ status: "clarifying",
142386
+ pendingClarification
142387
+ });
142388
+ await sendTelegramText(ctx, conversationId, buildClarificationMessage(parsed, pendingClarification));
142389
+ return;
142390
+ }
142391
+ continueBootstrap(ctx, conversationId, workspaceDir, incomingRequest, sourceRoute).catch((err) => {
142392
+ logBootstrapWarning(ctx, `[telegram-bootstrap] unhandled pipeline error (LLM path): ${err instanceof Error ? err.message : String(err)}`);
142393
+ });
142394
+ }
142276
142395
  async function continueBootstrap(ctx, conversationId, workspaceDir, request2, sourceRoute) {
142277
142396
  const telegramConfig = readFabricaTelegramConfig(ctx.pluginConfig);
142278
142397
  if (!telegramConfig.projectsForumChatId) {
@@ -142517,7 +142636,20 @@ function registerTelegramBootstrapHook(api, ctx) {
142517
142636
  });
142518
142637
  return;
142519
142638
  }
142520
- if (!isBootstrapCandidate(content)) return;
142639
+ if (!isBootstrapCandidate(content)) {
142640
+ if (isAmbiguousCandidate(content)) {
142641
+ await upsertTelegramBootstrapSession(workspaceDir, {
142642
+ conversationId,
142643
+ rawIdea: content,
142644
+ sourceRoute: { channel: "telegram", channelId: conversationId },
142645
+ status: "classifying"
142646
+ });
142647
+ classifyAndBootstrap(ctx, workspaceDir, conversationId, content).catch((err) => {
142648
+ logBootstrapWarning(ctx, `[telegram-bootstrap] LLM classify error: ${err instanceof Error ? err.message : String(err)}`);
142649
+ });
142650
+ }
142651
+ return;
142652
+ }
142521
142653
  const parsed = parseBootstrapRequest(content);
142522
142654
  const incomingRequest = {
142523
142655
  rawIdea: parsed.rawIdea,
@@ -142542,6 +142674,7 @@ function registerTelegramBootstrapHook(api, ctx) {
142542
142674
  ctx.logger.info(`[telegram-bootstrap] stale received session (expired) \u2014 restarting pipeline for conversation ${conversationId}`);
142543
142675
  }
142544
142676
  }
142677
+ await sendTelegramText(ctx, conversationId, "Recebi! Vou analisar e come\xE7ar a montar o projeto...");
142545
142678
  const session = await upsertTelegramBootstrapSession(workspaceDir, {
142546
142679
  conversationId,
142547
142680
  ...incomingRequest,