@mestreyoda/fabrica 0.2.19 → 0.2.20

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 (2) hide show
  1. package/dist/index.js +120 -9
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -113905,8 +113905,8 @@ import fsSync from "node:fs";
113905
113905
  import path5 from "node:path";
113906
113906
  import { fileURLToPath as fileURLToPath3 } from "node:url";
113907
113907
  function getCurrentVersion() {
113908
- if ("0.2.19") {
113909
- return "0.2.19";
113908
+ if ("0.2.20") {
113909
+ return "0.2.20";
113910
113910
  }
113911
113911
  try {
113912
113912
  const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
@@ -126282,6 +126282,25 @@ async function validatePrExistsForDeveloper(issueId, repoPath, provider, runComm
126282
126282
  const branchPrIsReviewable = !!branchPr?.url && branchPr.state !== PrState.MERGED && branchPr.state !== PrState.CLOSED;
126283
126283
  const prStatus = preferIssuePr ? issuePrIsReviewable ? issuePr : branchPr ?? issuePr : branchPrIsReviewable ? branchPr : issuePr;
126284
126284
  if (!prStatus.url || prStatus.state === PrState.MERGED || prStatus.state === PrState.CLOSED) {
126285
+ if (preferIssuePr && isCurrentProjectBaseBranch(branchName, baseBranch)) {
126286
+ const currentBase = branchName || baseBranch || "main";
126287
+ const suggestedBranch = `feature/${issueId}-${projectSlug.replace(/[^a-z0-9]+/g, "-").slice(0, 40)}`;
126288
+ throw new Error(
126289
+ `Cannot mark work_finish(done) while on the base branch ("${currentBase}") without an open PR.
126290
+
126291
+ You must implement changes on a feature branch and open a PR before calling work_finish.
126292
+
126293
+ Steps to fix:
126294
+ 1. git worktree add ../${projectSlug}.worktrees/${suggestedBranch} -b ${suggestedBranch}
126295
+ 2. cd ../${projectSlug}.worktrees/${suggestedBranch}
126296
+ 3. Implement the changes there, commit, push, and create a PR:
126297
+ git push -u origin ${suggestedBranch}
126298
+ gh pr create --base ${baseBranch ?? "main"} --head ${suggestedBranch} --title "feat: ..." --body "Closes #${issueId}"
126299
+ 4. Then call work_finish again.
126300
+
126301
+ If the worktree already exists, cd into it and continue from there.`
126302
+ );
126303
+ }
126285
126304
  const currentBranch = branchName || "current-branch";
126286
126305
  const reason = !prStatus.url ? `\u2717 No PR found for branch: ${currentBranch}` : prStatus.state === PrState.MERGED ? `\u2717 Last linked PR is already merged: ${prStatus.url}` : `\u2717 Last linked PR is closed and not reviewable: ${prStatus.url}`;
126287
126306
  throw new Error(
@@ -126290,7 +126309,7 @@ async function validatePrExistsForDeveloper(issueId, repoPath, provider, runComm
126290
126309
  ${reason}
126291
126310
 
126292
126311
  Please create a PR first:
126293
- gh pr create --base main --head ${currentBranch} --title "..." --body "..."
126312
+ gh pr create --base ${baseBranch ?? "main"} --head ${currentBranch} --title "..." --body "Closes #${issueId}"
126294
126313
 
126295
126314
  Then call work_finish again.`
126296
126315
  );
@@ -139671,11 +139690,44 @@ var createTaskStep = {
139671
139690
  };
139672
139691
 
139673
139692
  // lib/intake/lib/triage-logic.ts
139674
- function calculateEffort(filesChanged, acCount) {
139675
- if (filesChanged <= 3 && acCount <= 3) return "small";
139676
- if (filesChanged <= 10 && acCount <= 7) return "medium";
139677
- if (filesChanged <= 25 && acCount <= 15) return "large";
139678
- return "xlarge";
139693
+ function detectRawIdeaComplexity(rawIdea) {
139694
+ const text = rawIdea.toLowerCase();
139695
+ const signals = [];
139696
+ const subsystemPatterns = [
139697
+ [/\b(worker|background.?job|queue|celery|bull|sidekiq|task.?runner)\b/i, "background-worker"],
139698
+ [/\b(websocket|server.?sent|sse|real.?time|realtime|socket\.io|push.?notif)\b/i, "realtime"],
139699
+ [/\b(auth|oauth|jwt|login|register|session|user.?account|signup)\b/i, "auth"],
139700
+ [/\b(notif|alert|email|sms|webhook|subscription|subscribe)\b/i, "notifications"],
139701
+ [/\b(database|banco|db|postgres|mysql|mongodb|redis|sqlite|orm)\b/i, "database"],
139702
+ [/\b(api\s+rest|rest\s+api|endpoint|rota|route|graphql|grpc)\b/i, "api-layer"],
139703
+ [/\b(docker|kubernetes|k8s|deploy|ci|cd|pipeline)\b/i, "infra"],
139704
+ [/\b(dashboard|frontend|interface|ui|tela|p[aá]gina)\b/i, "frontend"]
139705
+ ];
139706
+ for (const [regex, label] of subsystemPatterns) {
139707
+ if (regex.test(text)) signals.push(label);
139708
+ }
139709
+ let floor = null;
139710
+ if (signals.length >= 4) floor = "large";
139711
+ else if (signals.length >= 3) floor = "medium";
139712
+ else if (signals.length >= 2) floor = "medium";
139713
+ return { floor, signals };
139714
+ }
139715
+ function calculateEffort(filesChanged, acCount, rawIdea) {
139716
+ let effort;
139717
+ if (filesChanged <= 3 && acCount <= 3) effort = "small";
139718
+ else if (filesChanged <= 10 && acCount <= 7) effort = "medium";
139719
+ else if (filesChanged <= 25 && acCount <= 15) effort = "large";
139720
+ else effort = "xlarge";
139721
+ if (rawIdea) {
139722
+ const { floor } = detectRawIdeaComplexity(rawIdea);
139723
+ if (floor) {
139724
+ const ORDER = ["small", "medium", "large", "xlarge"];
139725
+ if (ORDER.indexOf(floor) > ORDER.indexOf(effort)) {
139726
+ effort = floor;
139727
+ }
139728
+ }
139729
+ }
139730
+ return effort;
139679
139731
  }
139680
139732
  function calculatePriority(type, effort, totalRisks, matrix) {
139681
139733
  for (const rule of matrix.priority_rules_v2) {
@@ -139742,7 +139794,7 @@ function determineLevel(effort, targetState) {
139742
139794
  return level;
139743
139795
  }
139744
139796
  function runTriageLogic(input, matrix) {
139745
- const effort = calculateEffort(input.filesChanged, input.acCount);
139797
+ const effort = calculateEffort(input.filesChanged, input.acCount, input.rawIdea);
139746
139798
  const { priority, label: priorityLabel } = calculatePriority(input.type, effort, input.totalRisks, matrix);
139747
139799
  const effortLabel = matrix.effort_rules[effort]?.label ?? `effort:${effort}`;
139748
139800
  const typeLabel = matrix.auto_labels[input.type] ?? "";
@@ -140996,6 +141048,22 @@ var BOOTSTRAP_MESSAGES = {
140996
141048
  pt: "Como voc\xEA quer chamar o projeto? Se preferir, posso escolher um nome.",
140997
141049
  en: "What do you want to name the project? If you prefer, I can pick one."
140998
141050
  },
141051
+ clarifyScope: {
141052
+ pt: (idea) => `Recebi! Seu pedido envolve v\xE1rios subsistemas (autentica\xE7\xE3o, notifica\xE7\xF5es, worker, banco de dados...). Para montar uma spec de qualidade, preciso de algumas defini\xE7\xF5es:
141053
+
141054
+ 1. **Stack/linguagem**: qual prefere? (Python/FastAPI, Node.js/Express, Go...)
141055
+ 2. **Banco de dados**: PostgreSQL, MongoDB, Redis, outro?
141056
+ 3. **Autentica\xE7\xE3o**: JWT, OAuth2, sess\xE3o?
141057
+
141058
+ Se preferir deixar a escolha comigo, responda "livre" e eu decido.`,
141059
+ en: (idea) => `Got it! Your request involves multiple subsystems (auth, notifications, background worker, DB...). To produce a quality spec, I need a few decisions:
141060
+
141061
+ 1. **Stack/language**: which do you prefer? (Python/FastAPI, Node.js/Express, Go...)
141062
+ 2. **Database**: PostgreSQL, MongoDB, Redis, other?
141063
+ 3. **Auth**: JWT, OAuth2, session?
141064
+
141065
+ If you want me to choose, reply "your call" and I'll decide.`
141066
+ },
140999
141067
  registered: {
141000
141068
  pt: (name, link) => `Projeto "${name}" registrado.
141001
141069
  Vou continuar o fluxo em ${link}`,
@@ -141003,6 +141071,28 @@ Vou continuar o fluxo em ${link}`,
141003
141071
  I'll continue the flow at ${link}`
141004
141072
  }
141005
141073
  };
141074
+ function detectScopeAmbiguity(rawIdea, stackHint) {
141075
+ const text = rawIdea.toLowerCase();
141076
+ if (/\b(livre|free.?choice|your.?call|pode.?escolher|voc[eê].?decide|qualquer)\b/i.test(text)) {
141077
+ return false;
141078
+ }
141079
+ const subsystems = [
141080
+ /\b(worker|background.?job|queue|task.?runner|celery|bull)\b/i,
141081
+ /\b(websocket|sse|real.?time|realtime|push.?notif)\b/i,
141082
+ /\b(auth|oauth|jwt|login|register|signup|autenticac)\b/i,
141083
+ /\b(notif|alert|assinatura|subscription|subscribe|email|sms)\b/i,
141084
+ /\b(banco|database|db|postgres|mysql|mongodb|redis|sqlite)\b/i,
141085
+ /\b(api\s+rest|rest\s+api|endpoint|graphql|grpc)\b/i,
141086
+ /\b(dashboard|frontend|interface|ui|tela)\b/i
141087
+ ];
141088
+ const matchedSubsystems = subsystems.filter((r2) => r2.test(text)).length;
141089
+ if (matchedSubsystems < 3) return false;
141090
+ const hasExplicitDB = /\b(postgres(ql)?|mysql|mongodb|mongo|redis|sqlite|supabase|dynamodb|cockroach)\b/i.test(text);
141091
+ const hasExplicitAuth = /\b(jwt|oauth2?|basic.?auth|api.?key|session.?based|cookie.?auth)\b/i.test(text);
141092
+ const hasExplicitStack = !!stackHint && !["api", "rest-api", "backend"].includes(stackHint);
141093
+ const unspecifiedDimensions = [!hasExplicitDB, !hasExplicitAuth, !hasExplicitStack].filter(Boolean).length;
141094
+ return unspecifiedDimensions >= 2;
141095
+ }
141006
141096
  function inferProjectSlug(text) {
141007
141097
  let cleaned = text.replace(/^(create|build|crie|cria|criar|fazer?|quero|i need|i want)\s+(uma|um|me\s+a?|an|a)?\s*/i, "").replace(/\s+(that|which|que|para|for|pra)\s+.*/i, "").trim();
141008
141098
  if (!cleaned) cleaned = text;
@@ -141471,6 +141561,27 @@ async function handleTelegramBootstrapDmMessage(ctx, rawConversationId, content)
141471
141561
  ));
141472
141562
  return;
141473
141563
  }
141564
+ if (detectScopeAmbiguity(content, parsed.stackHint)) {
141565
+ const existingSession2 = await readTelegramBootstrapSession(workspaceDir, conversationId);
141566
+ const alreadyAskedScope = existingSession2?.pendingClarification === "scope";
141567
+ if (!alreadyAskedScope) {
141568
+ await upsertTelegramBootstrapSession(workspaceDir, {
141569
+ conversationId,
141570
+ rawIdea: content,
141571
+ stackHint: parsed.stackHint ?? void 0,
141572
+ projectName: parsed.projectSlug ?? void 0,
141573
+ status: "clarifying",
141574
+ pendingClarification: "scope",
141575
+ language
141576
+ });
141577
+ const clarifyMsg = BOOTSTRAP_MESSAGES.clarifyScope[language](content);
141578
+ await sendTelegramText(ctx, rawConversationId, clarifyMsg);
141579
+ return;
141580
+ }
141581
+ if (existingSession2?.stackHint) {
141582
+ incomingRequest.stackHint = existingSession2.stackHint;
141583
+ }
141584
+ }
141474
141585
  const handled = await runBootstrapPreflightOrFail(
141475
141586
  ctx,
141476
141587
  conversationId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mestreyoda/fabrica",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "description": "Autonomous software engineering pipeline for OpenClaw. Turns ideas into deployed code via intake, dispatch, review, test, and merge.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",