@mestreyoda/fabrica 0.2.19 → 0.2.21
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 +423 -33
- 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.
|
|
113909
|
-
return "0.2.
|
|
113908
|
+
if ("0.2.21") {
|
|
113909
|
+
return "0.2.21";
|
|
113910
113910
|
}
|
|
113911
113911
|
try {
|
|
113912
113912
|
const pkgPath = path5.join(THIS_DIR, "..", "..", "package.json");
|
|
@@ -114883,7 +114883,7 @@ function resolve2(config2, sourceLayers, trace2) {
|
|
|
114883
114883
|
dispatchMs: config2.timeouts?.dispatchMs ?? 6e5,
|
|
114884
114884
|
staleWorkerHours: config2.timeouts?.staleWorkerHours ?? 2,
|
|
114885
114885
|
sessionContextBudget: config2.timeouts?.sessionContextBudget ?? 0.6,
|
|
114886
|
-
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ??
|
|
114886
|
+
stallTimeoutMinutes: config2.timeouts?.stallTimeoutMinutes ?? 25,
|
|
114887
114887
|
sessionConfirmAttempts: config2.timeouts?.sessionConfirmAttempts ?? 5,
|
|
114888
114888
|
sessionConfirmDelayMs: config2.timeouts?.sessionConfirmDelayMs ?? 250,
|
|
114889
114889
|
sessionLabelMaxLength: config2.timeouts?.sessionLabelMaxLength ?? 64,
|
|
@@ -118561,6 +118561,34 @@ var conduct_interview_exports = {};
|
|
|
118561
118561
|
__export(conduct_interview_exports, {
|
|
118562
118562
|
conductInterviewStep: () => conductInterviewStep
|
|
118563
118563
|
});
|
|
118564
|
+
function deriveFeatureScopeFromRawIdea(rawIdea) {
|
|
118565
|
+
const text = rawIdea.toLowerCase();
|
|
118566
|
+
const scope = [];
|
|
118567
|
+
const add = (item) => {
|
|
118568
|
+
if (!scope.includes(item)) scope.push(item);
|
|
118569
|
+
};
|
|
118570
|
+
if (/\b(auth|oauth|jwt|login|register|session|role-based access|rbac|permissions?)\b/i.test(text)) {
|
|
118571
|
+
add("Implement authentication, authorization, and role-aware access rules");
|
|
118572
|
+
}
|
|
118573
|
+
if (/\b(incident|project|task|owner|assign|timeline|status|update)s?\b/i.test(text)) {
|
|
118574
|
+
add("Implement the core domain workflows and CRUD endpoints for the main entities");
|
|
118575
|
+
}
|
|
118576
|
+
if (/\b(alert|alerts|notif|notification|notifications|reminder|reminders|escalation|escalations)\b/i.test(text)) {
|
|
118577
|
+
add("Implement notifications, reminders, and escalation flows for key events");
|
|
118578
|
+
}
|
|
118579
|
+
if (/\b(background process|background worker|worker|queue|job|celery|bull|sidekiq|scheduler)\b/i.test(text)) {
|
|
118580
|
+
add("Implement the background processing pipeline required for asynchronous work");
|
|
118581
|
+
}
|
|
118582
|
+
if (/\b(admin view|admin panel|admin console|dashboard|audit history|audit log|activity history)\b/i.test(text)) {
|
|
118583
|
+
add("Implement administrative visibility and audit/history capabilities for operators");
|
|
118584
|
+
}
|
|
118585
|
+
if (scope.length < 3) {
|
|
118586
|
+
add("Implement the primary user workflow described in the request");
|
|
118587
|
+
add("Expose the key interactions needed to operate the system end to end");
|
|
118588
|
+
add("Add validation and persistence rules for the main business flow");
|
|
118589
|
+
}
|
|
118590
|
+
return scope.slice(0, 5);
|
|
118591
|
+
}
|
|
118564
118592
|
function fallbackSpecData(type, rawIdea) {
|
|
118565
118593
|
const title = rawIdea.slice(0, 120);
|
|
118566
118594
|
const base = {
|
|
@@ -118588,7 +118616,12 @@ function fallbackSpecData(type, rawIdea) {
|
|
|
118588
118616
|
base.acceptance_criteria = ["Infrastructure change applied and verified"];
|
|
118589
118617
|
break;
|
|
118590
118618
|
default:
|
|
118591
|
-
base.
|
|
118619
|
+
base.scope_v1 = deriveFeatureScopeFromRawIdea(rawIdea);
|
|
118620
|
+
base.acceptance_criteria = [
|
|
118621
|
+
"The primary workflow works end to end as requested",
|
|
118622
|
+
"Role, validation, or delivery constraints from the request are enforced",
|
|
118623
|
+
"The asynchronous/background behavior works for the main operational path"
|
|
118624
|
+
];
|
|
118592
118625
|
}
|
|
118593
118626
|
return base;
|
|
118594
118627
|
}
|
|
@@ -124987,6 +125020,20 @@ function buildMessage(event) {
|
|
|
124987
125020
|
}
|
|
124988
125021
|
return msg;
|
|
124989
125022
|
}
|
|
125023
|
+
case "workerProgress": {
|
|
125024
|
+
const worker = formatWorkerString(event.role, {
|
|
125025
|
+
name: event.name,
|
|
125026
|
+
level: event.level
|
|
125027
|
+
});
|
|
125028
|
+
let msg = `\u23F3 ${worker} still working on #${event.issueId}: ${event.issueTitle}`;
|
|
125029
|
+
if (event.summary) msg += `
|
|
125030
|
+
${event.summary}`;
|
|
125031
|
+
if (event.minutesActive != null) msg += `
|
|
125032
|
+
\u{1F552} Active for ${event.minutesActive} min`;
|
|
125033
|
+
msg += `
|
|
125034
|
+
\u{1F4CB} [Issue #${event.issueId}](${event.issueUrl})`;
|
|
125035
|
+
return msg;
|
|
125036
|
+
}
|
|
124990
125037
|
case "workerComplete": {
|
|
124991
125038
|
const icons = {
|
|
124992
125039
|
done: "\u2705",
|
|
@@ -126282,6 +126329,25 @@ async function validatePrExistsForDeveloper(issueId, repoPath, provider, runComm
|
|
|
126282
126329
|
const branchPrIsReviewable = !!branchPr?.url && branchPr.state !== PrState.MERGED && branchPr.state !== PrState.CLOSED;
|
|
126283
126330
|
const prStatus = preferIssuePr ? issuePrIsReviewable ? issuePr : branchPr ?? issuePr : branchPrIsReviewable ? branchPr : issuePr;
|
|
126284
126331
|
if (!prStatus.url || prStatus.state === PrState.MERGED || prStatus.state === PrState.CLOSED) {
|
|
126332
|
+
if (preferIssuePr && isCurrentProjectBaseBranch(branchName, baseBranch)) {
|
|
126333
|
+
const currentBase = branchName || baseBranch || "main";
|
|
126334
|
+
const suggestedBranch = `feature/${issueId}-${projectSlug.replace(/[^a-z0-9]+/g, "-").slice(0, 40)}`;
|
|
126335
|
+
throw new Error(
|
|
126336
|
+
`Cannot mark work_finish(done) while on the base branch ("${currentBase}") without an open PR.
|
|
126337
|
+
|
|
126338
|
+
You must implement changes on a feature branch and open a PR before calling work_finish.
|
|
126339
|
+
|
|
126340
|
+
Steps to fix:
|
|
126341
|
+
1. git worktree add ../${projectSlug}.worktrees/${suggestedBranch} -b ${suggestedBranch}
|
|
126342
|
+
2. cd ../${projectSlug}.worktrees/${suggestedBranch}
|
|
126343
|
+
3. Implement the changes there, commit, push, and create a PR:
|
|
126344
|
+
git push -u origin ${suggestedBranch}
|
|
126345
|
+
gh pr create --base ${baseBranch ?? "main"} --head ${suggestedBranch} --title "feat: ..." --body "Closes #${issueId}"
|
|
126346
|
+
4. Then call work_finish again.
|
|
126347
|
+
|
|
126348
|
+
If the worktree already exists, cd into it and continue from there.`
|
|
126349
|
+
);
|
|
126350
|
+
}
|
|
126285
126351
|
const currentBranch = branchName || "current-branch";
|
|
126286
126352
|
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
126353
|
throw new Error(
|
|
@@ -126290,7 +126356,7 @@ async function validatePrExistsForDeveloper(issueId, repoPath, provider, runComm
|
|
|
126290
126356
|
${reason}
|
|
126291
126357
|
|
|
126292
126358
|
Please create a PR first:
|
|
126293
|
-
gh pr create --base main --head ${currentBranch} --title "..." --body "
|
|
126359
|
+
gh pr create --base ${baseBranch ?? "main"} --head ${currentBranch} --title "..." --body "Closes #${issueId}"
|
|
126294
126360
|
|
|
126295
126361
|
Then call work_finish again.`
|
|
126296
126362
|
);
|
|
@@ -126722,6 +126788,10 @@ function createTaskCreateTool(ctx) {
|
|
|
126722
126788
|
pickup: {
|
|
126723
126789
|
type: "boolean",
|
|
126724
126790
|
description: "If true, immediately pick up this issue for DEV after creation. Defaults to false."
|
|
126791
|
+
},
|
|
126792
|
+
parentIssueId: {
|
|
126793
|
+
type: "number",
|
|
126794
|
+
description: "Optional parent/epic issue ID when creating a child task as part of a decomposition plan."
|
|
126725
126795
|
}
|
|
126726
126796
|
}
|
|
126727
126797
|
},
|
|
@@ -126730,12 +126800,17 @@ function createTaskCreateTool(ctx) {
|
|
|
126730
126800
|
const description = params.description ?? "";
|
|
126731
126801
|
const assignees = params.assignees ?? [];
|
|
126732
126802
|
const pickup = params.pickup ?? false;
|
|
126803
|
+
const parentIssueId = Number.isFinite(params.parentIssueId) ? Number(params.parentIssueId) : null;
|
|
126733
126804
|
const workspaceDir = requireWorkspaceDir(toolCtx);
|
|
126734
126805
|
const { project, route } = await resolveProjectFromContext(workspaceDir, toolCtx, params.channelId);
|
|
126735
126806
|
const resolvedConfig = await loadConfig(workspaceDir, project.slug);
|
|
126736
126807
|
const label = resolvedConfig.workflow.states[resolvedConfig.workflow.initial]?.label ?? "Planning";
|
|
126737
126808
|
const { provider, type: providerType } = await resolveProvider(project, ctx.runCommand);
|
|
126738
|
-
|
|
126809
|
+
let body = description;
|
|
126810
|
+
if (parentIssueId) {
|
|
126811
|
+
body = `${description}${description.trim().length > 0 ? "\n\n" : ""}Parent issue: #${parentIssueId}`;
|
|
126812
|
+
}
|
|
126813
|
+
const issue2 = await provider.createIssue(title, body, label, assignees);
|
|
126739
126814
|
provider.reactToIssue(issue2.iid, "eyes").catch(() => {
|
|
126740
126815
|
});
|
|
126741
126816
|
const primaryChannel = findPrimaryChannel(project);
|
|
@@ -126749,13 +126824,31 @@ function createTaskCreateTool(ctx) {
|
|
|
126749
126824
|
);
|
|
126750
126825
|
autoAssignOwnerLabel(workspaceDir, provider, issue2.iid, project).catch(() => {
|
|
126751
126826
|
});
|
|
126827
|
+
if (parentIssueId) {
|
|
126828
|
+
await updateIssueRuntime(workspaceDir, project.slug, issue2.iid, {
|
|
126829
|
+
parentIssueId
|
|
126830
|
+
}).catch(() => {
|
|
126831
|
+
});
|
|
126832
|
+
const parentRuntime = project.issueRuntime?.[String(parentIssueId)] ?? {};
|
|
126833
|
+
const previousChildren = Array.isArray(parentRuntime.childIssueIds) ? parentRuntime.childIssueIds : [];
|
|
126834
|
+
await updateIssueRuntime(workspaceDir, project.slug, parentIssueId, {
|
|
126835
|
+
childIssueIds: [.../* @__PURE__ */ new Set([...previousChildren, issue2.iid])]
|
|
126836
|
+
}).catch(() => {
|
|
126837
|
+
});
|
|
126838
|
+
provider.addComment(
|
|
126839
|
+
parentIssueId,
|
|
126840
|
+
`\u{1F4CC} Child task created: [#${issue2.iid}: ${issue2.title}](${issue2.web_url})`
|
|
126841
|
+
).catch(() => {
|
|
126842
|
+
});
|
|
126843
|
+
}
|
|
126752
126844
|
await log(workspaceDir, "task_create", {
|
|
126753
126845
|
project: project.name,
|
|
126754
126846
|
issueId: issue2.iid,
|
|
126755
126847
|
title,
|
|
126756
126848
|
label,
|
|
126757
126849
|
provider: providerType,
|
|
126758
|
-
pickup
|
|
126850
|
+
pickup,
|
|
126851
|
+
parentIssueId
|
|
126759
126852
|
});
|
|
126760
126853
|
const hasBody = description && description.trim().length > 0;
|
|
126761
126854
|
let announcement = `\u{1F4CB} Created #${issue2.iid}: "${title}" (${label})`;
|
|
@@ -128816,6 +128909,7 @@ async function dispatchTask(opts) {
|
|
|
128816
128909
|
inconclusiveCompletionAt: null,
|
|
128817
128910
|
inconclusiveCompletionReason: null,
|
|
128818
128911
|
sessionCompletedAt: null,
|
|
128912
|
+
progressNotifiedAt: null,
|
|
128819
128913
|
lastSessionKey: sessionKey
|
|
128820
128914
|
}).catch((err) => {
|
|
128821
128915
|
log(workspaceDir, "dispatch_warning", { step: "record_dispatch_requested", issue: issueId, err: String(err) }).catch(() => {
|
|
@@ -131313,6 +131407,7 @@ async function checkWorkerHealth(opts) {
|
|
|
131313
131407
|
completionRecoveryWindowMs = COMPLETION_RECOVERY_WINDOW_MS,
|
|
131314
131408
|
executionContractRecoveryWindowMs = EXECUTION_CONTRACT_RECOVERY_WINDOW_MS,
|
|
131315
131409
|
runCommand,
|
|
131410
|
+
stallTimeoutMinutes = 25,
|
|
131316
131411
|
notificationConfig
|
|
131317
131412
|
} = opts;
|
|
131318
131413
|
const fixes = [];
|
|
@@ -131812,7 +131907,120 @@ async function checkWorkerHealth(opts) {
|
|
|
131812
131907
|
continue;
|
|
131813
131908
|
}
|
|
131814
131909
|
if (slot.active && slot.startTime && sessionKey && sessions && sessionAlive) {
|
|
131815
|
-
const
|
|
131910
|
+
const startedAtMs = new Date(slot.startTime).getTime();
|
|
131911
|
+
const minutesActive = (Date.now() - startedAtMs) / 6e4;
|
|
131912
|
+
const hours = minutesActive / 60;
|
|
131913
|
+
let hasReviewableArtifact = Boolean(
|
|
131914
|
+
issueRuntime?.currentPrNumber || issueRuntime?.currentPrUrl || issueRuntime?.artifactOfRecord?.prNumber
|
|
131915
|
+
);
|
|
131916
|
+
if (!hasReviewableArtifact && issueIdNum) {
|
|
131917
|
+
try {
|
|
131918
|
+
const prStatus = await provider.getPrStatus(issueIdNum);
|
|
131919
|
+
hasReviewableArtifact = Boolean(
|
|
131920
|
+
prStatus.url && prStatus.state !== PrState.MERGED && prStatus.state !== PrState.CLOSED && prStatus.currentIssueMatch !== false
|
|
131921
|
+
);
|
|
131922
|
+
} catch {
|
|
131923
|
+
}
|
|
131924
|
+
}
|
|
131925
|
+
if (role === "developer" && issue2 && issueIdNum && !hasReviewableArtifact && minutesActive >= Math.max(5, Math.floor(stallTimeoutMinutes / 2)) && !issueRuntime?.progressNotifiedAt) {
|
|
131926
|
+
const channel = project.channels?.[0];
|
|
131927
|
+
await notify(
|
|
131928
|
+
{
|
|
131929
|
+
type: "workerProgress",
|
|
131930
|
+
project: project.name,
|
|
131931
|
+
issueId: issueIdNum,
|
|
131932
|
+
issueUrl: issue2.web_url,
|
|
131933
|
+
issueTitle: issue2.title,
|
|
131934
|
+
role,
|
|
131935
|
+
level,
|
|
131936
|
+
minutesActive: Math.round(minutesActive),
|
|
131937
|
+
summary: "Still iterating on implementation/QA without a PR yet.",
|
|
131938
|
+
dispatchCycleId: slot.dispatchCycleId ?? issueRuntime?.lastDispatchCycleId ?? null,
|
|
131939
|
+
dispatchRunId: slot.dispatchRunId ?? issueRuntime?.dispatchRunId ?? null
|
|
131940
|
+
},
|
|
131941
|
+
{
|
|
131942
|
+
workspaceDir,
|
|
131943
|
+
config: notificationConfig,
|
|
131944
|
+
target: channel ? {
|
|
131945
|
+
channelId: channel.channelId,
|
|
131946
|
+
channel: channel.channel,
|
|
131947
|
+
accountId: channel.accountId,
|
|
131948
|
+
messageThreadId: channel.messageThreadId
|
|
131949
|
+
} : void 0,
|
|
131950
|
+
runCommand
|
|
131951
|
+
}
|
|
131952
|
+
).catch(() => {
|
|
131953
|
+
});
|
|
131954
|
+
await updateIssueRuntime(workspaceDir, projectSlug, issueIdNum, {
|
|
131955
|
+
progressNotifiedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
131956
|
+
lastSessionKey: sessionKey
|
|
131957
|
+
}).catch(() => {
|
|
131958
|
+
});
|
|
131959
|
+
}
|
|
131960
|
+
if (role === "developer" && issue2 && issueIdNum && !hasReviewableArtifact && minutesActive >= stallTimeoutMinutes) {
|
|
131961
|
+
const fix = {
|
|
131962
|
+
issue: {
|
|
131963
|
+
type: "completion_recovery_exhausted",
|
|
131964
|
+
severity: "critical",
|
|
131965
|
+
project: project.name,
|
|
131966
|
+
projectSlug,
|
|
131967
|
+
role,
|
|
131968
|
+
level,
|
|
131969
|
+
sessionKey,
|
|
131970
|
+
issueId: slot.issueId,
|
|
131971
|
+
slotIndex,
|
|
131972
|
+
message: `${role.toUpperCase()} ${level}[${slotIndex}] active for ${Math.round(minutesActive)}m without a PR or terminal result`
|
|
131973
|
+
},
|
|
131974
|
+
fixed: false
|
|
131975
|
+
};
|
|
131976
|
+
if (autoFix) {
|
|
131977
|
+
const channel = project.channels?.[0];
|
|
131978
|
+
await notify(
|
|
131979
|
+
{
|
|
131980
|
+
type: "workerRecoveryExhausted",
|
|
131981
|
+
project: project.name,
|
|
131982
|
+
issueId: issueIdNum,
|
|
131983
|
+
issueUrl: issue2.web_url,
|
|
131984
|
+
issueTitle: issue2.title,
|
|
131985
|
+
role,
|
|
131986
|
+
detail: `No PR or canonical completion after ${Math.round(minutesActive)} minutes of active work. Re-queueing for a fresh attempt.`,
|
|
131987
|
+
nextState: slotQueueLabel,
|
|
131988
|
+
dispatchCycleId: slot.dispatchCycleId ?? issueRuntime?.lastDispatchCycleId ?? null,
|
|
131989
|
+
dispatchRunId: slot.dispatchRunId ?? issueRuntime?.dispatchRunId ?? null
|
|
131990
|
+
},
|
|
131991
|
+
{
|
|
131992
|
+
workspaceDir,
|
|
131993
|
+
config: notificationConfig,
|
|
131994
|
+
target: channel ? {
|
|
131995
|
+
channelId: channel.channelId,
|
|
131996
|
+
channel: channel.channel,
|
|
131997
|
+
accountId: channel.accountId,
|
|
131998
|
+
messageThreadId: channel.messageThreadId
|
|
131999
|
+
} : void 0,
|
|
132000
|
+
runCommand
|
|
132001
|
+
}
|
|
132002
|
+
).catch(() => {
|
|
132003
|
+
});
|
|
132004
|
+
await revertLabel(fix, expectedLabel, slotQueueLabel);
|
|
132005
|
+
if (!fix.labelRevertFailed) {
|
|
132006
|
+
await deactivateSlot();
|
|
132007
|
+
await updateIssueRuntime(workspaceDir, projectSlug, issueIdNum, {
|
|
132008
|
+
inconclusiveCompletionAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
132009
|
+
inconclusiveCompletionReason: "stalled_without_artifact",
|
|
132010
|
+
progressNotifiedAt: null
|
|
132011
|
+
}).catch(() => {
|
|
132012
|
+
});
|
|
132013
|
+
fix.fixed = true;
|
|
132014
|
+
await auditHealthFixApplied(workspaceDir, fix, {
|
|
132015
|
+
action: "requeue_issue",
|
|
132016
|
+
fromLabel: expectedLabel,
|
|
132017
|
+
toLabel: slotQueueLabel
|
|
132018
|
+
});
|
|
132019
|
+
}
|
|
132020
|
+
}
|
|
132021
|
+
fixes.push(fix);
|
|
132022
|
+
continue;
|
|
132023
|
+
}
|
|
131816
132024
|
if (hours > staleWorkerHours) {
|
|
131817
132025
|
const fix = {
|
|
131818
132026
|
issue: {
|
|
@@ -139671,11 +139879,44 @@ var createTaskStep = {
|
|
|
139671
139879
|
};
|
|
139672
139880
|
|
|
139673
139881
|
// lib/intake/lib/triage-logic.ts
|
|
139674
|
-
function
|
|
139675
|
-
|
|
139676
|
-
|
|
139677
|
-
|
|
139678
|
-
|
|
139882
|
+
function detectRawIdeaComplexity(rawIdea) {
|
|
139883
|
+
const text = rawIdea.toLowerCase();
|
|
139884
|
+
const signals = [];
|
|
139885
|
+
const subsystemPatterns = [
|
|
139886
|
+
[/\b(worker|background.?job|background process|background service|background worker|queue|celery|bull|sidekiq|task.?runner|scheduler|job processor)\b/i, "background-worker"],
|
|
139887
|
+
[/\b(websocket|server.?sent|sse|real.?time|realtime|socket\.io|push.?notif)\b/i, "realtime"],
|
|
139888
|
+
[/\b(auth|oauth|jwt|login|register|session|user.?account|signup|role-based access|rbac|permission[s]?)\b/i, "auth"],
|
|
139889
|
+
[/\b(notif|notification[s]?|alert[s]?|email|sms|webhook|subscription|subscribe|reminder[s]?|escalation[s]?)\b/i, "notifications"],
|
|
139890
|
+
[/\b(database|banco|db|postgres|mysql|mongodb|redis|sqlite|orm|audit history|audit log|activity history)\b/i, "database"],
|
|
139891
|
+
[/\b(api\s+rest|rest\s+api|endpoint|rota|route|graphql|grpc)\b/i, "api-layer"],
|
|
139892
|
+
[/\b(docker|kubernetes|k8s|deploy|ci|cd|pipeline)\b/i, "infra"],
|
|
139893
|
+
[/\b(dashboard|frontend|interface|ui|tela|p[aá]gina|admin view|admin panel|admin console)\b/i, "frontend"]
|
|
139894
|
+
];
|
|
139895
|
+
for (const [regex, label] of subsystemPatterns) {
|
|
139896
|
+
if (regex.test(text)) signals.push(label);
|
|
139897
|
+
}
|
|
139898
|
+
let floor = null;
|
|
139899
|
+
if (signals.length >= 4) floor = "large";
|
|
139900
|
+
else if (signals.length >= 3) floor = "medium";
|
|
139901
|
+
else if (signals.length >= 2) floor = "medium";
|
|
139902
|
+
return { floor, signals };
|
|
139903
|
+
}
|
|
139904
|
+
function calculateEffort(filesChanged, acCount, rawIdea) {
|
|
139905
|
+
let effort;
|
|
139906
|
+
if (filesChanged <= 3 && acCount <= 3) effort = "small";
|
|
139907
|
+
else if (filesChanged <= 10 && acCount <= 7) effort = "medium";
|
|
139908
|
+
else if (filesChanged <= 25 && acCount <= 15) effort = "large";
|
|
139909
|
+
else effort = "xlarge";
|
|
139910
|
+
if (rawIdea) {
|
|
139911
|
+
const { floor } = detectRawIdeaComplexity(rawIdea);
|
|
139912
|
+
if (floor) {
|
|
139913
|
+
const ORDER = ["small", "medium", "large", "xlarge"];
|
|
139914
|
+
if (ORDER.indexOf(floor) > ORDER.indexOf(effort)) {
|
|
139915
|
+
effort = floor;
|
|
139916
|
+
}
|
|
139917
|
+
}
|
|
139918
|
+
}
|
|
139919
|
+
return effort;
|
|
139679
139920
|
}
|
|
139680
139921
|
function calculatePriority(type, effort, totalRisks, matrix) {
|
|
139681
139922
|
for (const rule of matrix.priority_rules_v2) {
|
|
@@ -139742,7 +139983,7 @@ function determineLevel(effort, targetState) {
|
|
|
139742
139983
|
return level;
|
|
139743
139984
|
}
|
|
139744
139985
|
function runTriageLogic(input, matrix) {
|
|
139745
|
-
const effort = calculateEffort(input.filesChanged, input.acCount);
|
|
139986
|
+
const effort = calculateEffort(input.filesChanged, input.acCount, input.rawIdea);
|
|
139746
139987
|
const { priority, label: priorityLabel } = calculatePriority(input.type, effort, input.totalRisks, matrix);
|
|
139747
139988
|
const effortLabel = matrix.effort_rules[effort]?.label ?? `effort:${effort}`;
|
|
139748
139989
|
const typeLabel = matrix.auto_labels[input.type] ?? "";
|
|
@@ -139806,6 +140047,38 @@ function loadMatrix() {
|
|
|
139806
140047
|
cachedMatrix = _require3("../configs/triage-matrix.json");
|
|
139807
140048
|
return cachedMatrix;
|
|
139808
140049
|
}
|
|
140050
|
+
function buildChildDrafts(payload, issueNumber, effort) {
|
|
140051
|
+
const spec = payload.spec;
|
|
140052
|
+
const scopeItems = spec.scope_v1.filter((item) => item.trim().length > 0);
|
|
140053
|
+
const maxChildren = effort === "xlarge" ? 4 : 3;
|
|
140054
|
+
const chunkSize = 2;
|
|
140055
|
+
const drafts = [];
|
|
140056
|
+
for (let i2 = 0; i2 < scopeItems.length && drafts.length < maxChildren; i2 += chunkSize) {
|
|
140057
|
+
const slice = scopeItems.slice(i2, i2 + chunkSize);
|
|
140058
|
+
if (slice.length === 0) continue;
|
|
140059
|
+
const childIndex = drafts.length + 1;
|
|
140060
|
+
drafts.push({
|
|
140061
|
+
title: `${spec.title} \u2014 Part ${childIndex}`,
|
|
140062
|
+
description: [
|
|
140063
|
+
`## Objective`,
|
|
140064
|
+
spec.objective,
|
|
140065
|
+
"",
|
|
140066
|
+
`## Parent Issue`,
|
|
140067
|
+
`Parent issue: #${issueNumber}`,
|
|
140068
|
+
"",
|
|
140069
|
+
`## This Part`,
|
|
140070
|
+
...slice.map((item) => `- ${item}`),
|
|
140071
|
+
"",
|
|
140072
|
+
`## Acceptance Criteria`,
|
|
140073
|
+
...spec.acceptance_criteria.map((item) => `- ${item}`),
|
|
140074
|
+
"",
|
|
140075
|
+
`## Definition of Done`,
|
|
140076
|
+
...spec.definition_of_done.map((item) => `- ${item}`)
|
|
140077
|
+
].join("\n")
|
|
140078
|
+
});
|
|
140079
|
+
}
|
|
140080
|
+
return drafts;
|
|
140081
|
+
}
|
|
139809
140082
|
var triageStep = {
|
|
139810
140083
|
name: "triage",
|
|
139811
140084
|
shouldRun: (payload) => !!payload.issues?.length && !payload.dry_run,
|
|
@@ -139825,9 +140098,9 @@ var triageStep = {
|
|
|
139825
140098
|
totalRisks: (impact?.risk_areas?.length ?? 0) + (payload.security?.spec_security_notes?.length ?? 0),
|
|
139826
140099
|
objective: spec.objective,
|
|
139827
140100
|
rawIdea: payload.raw_idea,
|
|
139828
|
-
acText: spec.acceptance_criteria.join("
|
|
139829
|
-
scopeText: spec.scope_v1.join("
|
|
139830
|
-
oosText: spec.out_of_scope.join("
|
|
140101
|
+
acText: spec.acceptance_criteria.join("\n"),
|
|
140102
|
+
scopeText: spec.scope_v1.join("\n"),
|
|
140103
|
+
oosText: spec.out_of_scope.join("\n"),
|
|
139831
140104
|
authSignal: payload.metadata?.auth_gate?.signal ?? false
|
|
139832
140105
|
}, matrix);
|
|
139833
140106
|
const repoUrl = payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? "";
|
|
@@ -139837,8 +140110,12 @@ var triageStep = {
|
|
|
139837
140110
|
const initialLabel = resolvedConfig?.workflow.states[resolvedConfig.workflow.initial]?.label ?? "Planning";
|
|
139838
140111
|
const allLabels = [decision.priorityLabel, decision.effortLabel];
|
|
139839
140112
|
if (decision.typeLabel) allLabels.push(decision.typeLabel);
|
|
139840
|
-
if (!decision.readyForDispatch) allLabels.push("needs-human");
|
|
140113
|
+
if (!decision.readyForDispatch || decision.specQualityBlock) allLabels.push("needs-human");
|
|
139841
140114
|
const uniqueLabels = Array.from(new Set(allLabels.filter(Boolean)));
|
|
140115
|
+
const shouldDecompose = decision.readyForDispatch && !decision.specQualityBlock && ["large", "xlarge"].includes(decision.effort);
|
|
140116
|
+
const decompositionDrafts = shouldDecompose ? buildChildDrafts(payload, issue2.number, decision.effort) : [];
|
|
140117
|
+
const canDecompose = decompositionDrafts.length >= 2;
|
|
140118
|
+
let createdChildIssueNumbers = [];
|
|
139842
140119
|
if (ctx.createIssueProvider && repoPath) {
|
|
139843
140120
|
try {
|
|
139844
140121
|
const { provider } = await ctx.createIssueProvider({
|
|
@@ -139848,7 +140125,36 @@ var triageStep = {
|
|
|
139848
140125
|
for (const label of uniqueLabels) {
|
|
139849
140126
|
await provider.addLabel(issue2.number, label);
|
|
139850
140127
|
}
|
|
139851
|
-
if (decision.
|
|
140128
|
+
if (decision.specQualityBlock) {
|
|
140129
|
+
await provider.addComment(issue2.number, [
|
|
140130
|
+
"\u{1F6AB} Spec quality gate blocked automatic dispatch.",
|
|
140131
|
+
"",
|
|
140132
|
+
"The request needs a stronger objective, more concrete scope items, and verifiable acceptance criteria before creating execution tasks."
|
|
140133
|
+
].join("\n"));
|
|
140134
|
+
} else if (canDecompose) {
|
|
140135
|
+
await provider.addLabel(issue2.number, "decomposition:parent");
|
|
140136
|
+
const childIssues = [];
|
|
140137
|
+
for (const draft of decompositionDrafts) {
|
|
140138
|
+
const child = await provider.createIssue(draft.title, draft.description, initialLabel);
|
|
140139
|
+
childIssues.push({ iid: child.iid, title: child.title, web_url: child.web_url });
|
|
140140
|
+
createdChildIssueNumbers.push(child.iid);
|
|
140141
|
+
for (const label of uniqueLabels) {
|
|
140142
|
+
await provider.addLabel(child.iid, label);
|
|
140143
|
+
}
|
|
140144
|
+
await provider.addLabel(child.iid, "decomposition:child");
|
|
140145
|
+
}
|
|
140146
|
+
await provider.addComment(issue2.number, [
|
|
140147
|
+
"## Decomposition Plan",
|
|
140148
|
+
...childIssues.map((child) => `- [ ] #${child.iid} ${child.title}`)
|
|
140149
|
+
].join("\n"));
|
|
140150
|
+
} else if (shouldDecompose) {
|
|
140151
|
+
await provider.addLabel(issue2.number, "needs-human");
|
|
140152
|
+
await provider.addComment(issue2.number, [
|
|
140153
|
+
"\u26A0\uFE0F Automatic decomposition was requested, but the generated spec did not yield at least two independently scoped child tasks.",
|
|
140154
|
+
"",
|
|
140155
|
+
"Refine the scope/acceptance criteria or split the plan manually before dispatch."
|
|
140156
|
+
].join("\n"));
|
|
140157
|
+
} else if (decision.readyForDispatch) {
|
|
139852
140158
|
await provider.transitionLabel(issue2.number, initialLabel, decision.targetState);
|
|
139853
140159
|
const dispatchLabels = [];
|
|
139854
140160
|
if (decision.targetState === "To Do" && decision.dispatchLabel) {
|
|
@@ -139913,8 +140219,14 @@ var triageStep = {
|
|
|
139913
140219
|
project_channel_id: null,
|
|
139914
140220
|
labels_applied: uniqueLabels,
|
|
139915
140221
|
issue_number: issue2.number,
|
|
139916
|
-
ready_for_dispatch: decision.readyForDispatch && decision.errors.length === 0,
|
|
139917
|
-
errors:
|
|
140222
|
+
ready_for_dispatch: decision.readyForDispatch && decision.errors.length === 0 && !decision.specQualityBlock && !canDecompose,
|
|
140223
|
+
errors: [
|
|
140224
|
+
...decision.errors,
|
|
140225
|
+
...decision.specQualityBlock ? ["spec_quality_block"] : [],
|
|
140226
|
+
...shouldDecompose && !canDecompose ? ["decomposition_needs_human"] : []
|
|
140227
|
+
],
|
|
140228
|
+
decomposition_mode: canDecompose ? "parent_child" : "none",
|
|
140229
|
+
child_issue_numbers: createdChildIssueNumbers
|
|
139918
140230
|
};
|
|
139919
140231
|
ctx.log(`Triage: ${triage.priority}, effort=${triage.effort}, ready=${triage.ready_for_dispatch}`);
|
|
139920
140232
|
return {
|
|
@@ -140996,6 +141308,22 @@ var BOOTSTRAP_MESSAGES = {
|
|
|
140996
141308
|
pt: "Como voc\xEA quer chamar o projeto? Se preferir, posso escolher um nome.",
|
|
140997
141309
|
en: "What do you want to name the project? If you prefer, I can pick one."
|
|
140998
141310
|
},
|
|
141311
|
+
clarifyScope: {
|
|
141312
|
+
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:
|
|
141313
|
+
|
|
141314
|
+
1. **Stack/linguagem**: qual prefere? (Python/FastAPI, Node.js/Express, Go...)
|
|
141315
|
+
2. **Banco de dados**: PostgreSQL, MongoDB, Redis, outro?
|
|
141316
|
+
3. **Autentica\xE7\xE3o**: JWT, OAuth2, sess\xE3o?
|
|
141317
|
+
|
|
141318
|
+
Se preferir deixar a escolha comigo, responda "livre" e eu decido.`,
|
|
141319
|
+
en: (idea) => `Got it! Your request involves multiple subsystems (auth, notifications, background worker, DB...). To produce a quality spec, I need a few decisions:
|
|
141320
|
+
|
|
141321
|
+
1. **Stack/language**: which do you prefer? (Python/FastAPI, Node.js/Express, Go...)
|
|
141322
|
+
2. **Database**: PostgreSQL, MongoDB, Redis, other?
|
|
141323
|
+
3. **Auth**: JWT, OAuth2, session?
|
|
141324
|
+
|
|
141325
|
+
If you want me to choose, reply "your call" and I'll decide.`
|
|
141326
|
+
},
|
|
140999
141327
|
registered: {
|
|
141000
141328
|
pt: (name, link) => `Projeto "${name}" registrado.
|
|
141001
141329
|
Vou continuar o fluxo em ${link}`,
|
|
@@ -141003,12 +141331,39 @@ Vou continuar o fluxo em ${link}`,
|
|
|
141003
141331
|
I'll continue the flow at ${link}`
|
|
141004
141332
|
}
|
|
141005
141333
|
};
|
|
141334
|
+
function detectScopeAmbiguity(rawIdea, stackHint) {
|
|
141335
|
+
const text = rawIdea.toLowerCase();
|
|
141336
|
+
if (/\b(livre|free.?choice|your.?call|pode.?escolher|voc[eê].?decide|qualquer)\b/i.test(text)) {
|
|
141337
|
+
return false;
|
|
141338
|
+
}
|
|
141339
|
+
const subsystems = [
|
|
141340
|
+
/\b(worker|background.?job|queue|task.?runner|celery|bull)\b/i,
|
|
141341
|
+
/\b(websocket|sse|real.?time|realtime|push.?notif)\b/i,
|
|
141342
|
+
/\b(auth|oauth|jwt|login|register|signup|autenticac)\b/i,
|
|
141343
|
+
/\b(notif|alert|assinatura|subscription|subscribe|email|sms)\b/i,
|
|
141344
|
+
/\b(banco|database|db|postgres|mysql|mongodb|redis|sqlite)\b/i,
|
|
141345
|
+
/\b(api\s+rest|rest\s+api|endpoint|graphql|grpc)\b/i,
|
|
141346
|
+
/\b(dashboard|frontend|interface|ui|tela)\b/i
|
|
141347
|
+
];
|
|
141348
|
+
const matchedSubsystems = subsystems.filter((r2) => r2.test(text)).length;
|
|
141349
|
+
if (matchedSubsystems < 3) return false;
|
|
141350
|
+
const hasExplicitDB = /\b(postgres(ql)?|mysql|mongodb|mongo|redis|sqlite|supabase|dynamodb|cockroach)\b/i.test(text);
|
|
141351
|
+
const hasExplicitAuth = /\b(jwt|oauth2?|basic.?auth|api.?key|session.?based|cookie.?auth)\b/i.test(text);
|
|
141352
|
+
const hasExplicitStack = !!stackHint && !["api", "rest-api", "backend"].includes(stackHint);
|
|
141353
|
+
const unspecifiedDimensions = [!hasExplicitDB, !hasExplicitAuth, !hasExplicitStack].filter(Boolean).length;
|
|
141354
|
+
return unspecifiedDimensions >= 2;
|
|
141355
|
+
}
|
|
141006
141356
|
function inferProjectSlug(text) {
|
|
141007
141357
|
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
141358
|
if (!cleaned) cleaned = text;
|
|
141009
141359
|
const slug = cleaned.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 64);
|
|
141010
141360
|
return slug || void 0;
|
|
141011
141361
|
}
|
|
141362
|
+
function normalizeProjectNameCandidate(value) {
|
|
141363
|
+
const normalized = normalizeText3(value);
|
|
141364
|
+
if (!normalized) return void 0;
|
|
141365
|
+
return inferProjectSlug(normalized) ?? void 0;
|
|
141366
|
+
}
|
|
141012
141367
|
function normalizeText3(value) {
|
|
141013
141368
|
const trimmed = value?.trim();
|
|
141014
141369
|
return trimmed ? trimmed : void 0;
|
|
@@ -141032,7 +141387,7 @@ function detectStackHint(text) {
|
|
|
141032
141387
|
}
|
|
141033
141388
|
function parseField(text, labels) {
|
|
141034
141389
|
for (const label of labels) {
|
|
141035
|
-
const regex = new RegExp(
|
|
141390
|
+
const regex = new RegExp(`(?:^|[\\n.,;!?]\\s*)${label}\\s*:\\s*(.+)$`, "im");
|
|
141036
141391
|
const match = text.match(regex);
|
|
141037
141392
|
if (match?.[1]) return normalizeText3(match[1]);
|
|
141038
141393
|
}
|
|
@@ -141044,12 +141399,12 @@ function parseIdeaBlock(text) {
|
|
|
141044
141399
|
}
|
|
141045
141400
|
function parseExplicitProjectName(text) {
|
|
141046
141401
|
const match = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
|
|
141047
|
-
return match?.[1]?.toLowerCase();
|
|
141402
|
+
return normalizeProjectNameCandidate(match?.[1]?.toLowerCase());
|
|
141048
141403
|
}
|
|
141049
141404
|
function parseBootstrapRequest(text) {
|
|
141050
141405
|
const repoUrl = parseField(text, ["repository url", "repo url", "reposit[o\xF3]rio url", "github repo"]);
|
|
141051
141406
|
const rawIdea = parseIdeaBlock(text) ?? text.trim();
|
|
141052
|
-
const projectName = parseField(text, ["project name", "nome do projeto", "repo name", "repository name"]) ?? parseExplicitProjectName(text);
|
|
141407
|
+
const projectName = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "repo name", "repository name"])) ?? parseExplicitProjectName(text);
|
|
141053
141408
|
const repoPath = parseField(text, ["local repository path", "repo path", "caminho local", "local path"]);
|
|
141054
141409
|
const explicitStack = parseField(text, ["stack", "framework", "linguagem"]);
|
|
141055
141410
|
const stackHint = (explicitStack ? normalizeStackHint(explicitStack) : "") || detectStackHint(text);
|
|
@@ -141228,25 +141583,26 @@ function parseClarificationResponse(text, session) {
|
|
|
141228
141583
|
if (autoPatterns.test(normalizeUserResponse(text))) {
|
|
141229
141584
|
return { recognized: true, projectName: inferProjectSlug(session.rawIdea) ?? `project-${Date.now()}`, stackHint: session.stackHint ?? void 0 };
|
|
141230
141585
|
}
|
|
141231
|
-
const nameField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
141586
|
+
const nameField = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "nome", "name"]));
|
|
141232
141587
|
if (nameField) {
|
|
141233
141588
|
return { recognized: true, projectName: nameField, stackHint: session.stackHint ?? void 0 };
|
|
141234
141589
|
}
|
|
141235
|
-
|
|
141236
|
-
|
|
141590
|
+
const normalizedInlineName = trimmed.length <= 64 ? normalizeProjectNameCandidate(trimmed) : void 0;
|
|
141591
|
+
if (normalizedInlineName) {
|
|
141592
|
+
return { recognized: true, projectName: normalizedInlineName, stackHint: session.stackHint ?? void 0 };
|
|
141237
141593
|
}
|
|
141238
141594
|
return { recognized: false };
|
|
141239
141595
|
}
|
|
141596
|
+
const projectNameFromField = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "nome", "name"]));
|
|
141597
|
+
const nameItMatch = !projectNameFromField ? text.match(/(?:name|call|chamar?)\s+(?:it\s+)?([\w-]{2,64})/i) : null;
|
|
141598
|
+
const inlineName = projectNameFromField ?? normalizeProjectNameCandidate(nameItMatch?.[1]);
|
|
141240
141599
|
const stackField = parseField(text, ["stack", "framework", "linguagem", "language"]);
|
|
141241
141600
|
if (stackField) {
|
|
141242
141601
|
const normalizedStack = normalizeStackHint(stackField) || detectStackHint(stackField) || stackField;
|
|
141243
|
-
return { recognized: true, stackHint: normalizedStack };
|
|
141602
|
+
return { recognized: true, stackHint: normalizedStack, projectName: inlineName };
|
|
141244
141603
|
}
|
|
141245
141604
|
const detectedStack = detectStackHint(text);
|
|
141246
141605
|
if (detectedStack) {
|
|
141247
|
-
const projectNameFromField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
141248
|
-
const nameItMatch = !projectNameFromField ? text.match(/(?:name|call|chamar?)\s+(?:it\s+)?([\w-]{2,64})/i) : null;
|
|
141249
|
-
const inlineName = projectNameFromField ?? (nameItMatch ? nameItMatch[1].toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-") || void 0 : void 0);
|
|
141250
141606
|
return { recognized: true, stackHint: detectedStack, projectName: inlineName };
|
|
141251
141607
|
}
|
|
141252
141608
|
const lower2 = normalizeUserResponse(text);
|
|
@@ -141471,6 +141827,27 @@ async function handleTelegramBootstrapDmMessage(ctx, rawConversationId, content)
|
|
|
141471
141827
|
));
|
|
141472
141828
|
return;
|
|
141473
141829
|
}
|
|
141830
|
+
if (detectScopeAmbiguity(content, parsed.stackHint)) {
|
|
141831
|
+
const existingSession2 = await readTelegramBootstrapSession(workspaceDir, conversationId);
|
|
141832
|
+
const alreadyAskedScope = existingSession2?.pendingClarification === "scope";
|
|
141833
|
+
if (!alreadyAskedScope) {
|
|
141834
|
+
await upsertTelegramBootstrapSession(workspaceDir, {
|
|
141835
|
+
conversationId,
|
|
141836
|
+
rawIdea: content,
|
|
141837
|
+
stackHint: parsed.stackHint ?? void 0,
|
|
141838
|
+
projectName: parsed.projectName ?? parsed.projectSlug ?? void 0,
|
|
141839
|
+
status: "clarifying",
|
|
141840
|
+
pendingClarification: "scope",
|
|
141841
|
+
language
|
|
141842
|
+
});
|
|
141843
|
+
const clarifyMsg = BOOTSTRAP_MESSAGES.clarifyScope[language](content);
|
|
141844
|
+
await sendTelegramText(ctx, rawConversationId, clarifyMsg);
|
|
141845
|
+
return;
|
|
141846
|
+
}
|
|
141847
|
+
if (existingSession2?.stackHint) {
|
|
141848
|
+
incomingRequest.stackHint = existingSession2.stackHint;
|
|
141849
|
+
}
|
|
141850
|
+
}
|
|
141474
141851
|
const handled = await runBootstrapPreflightOrFail(
|
|
141475
141852
|
ctx,
|
|
141476
141853
|
conversationId,
|
|
@@ -143844,6 +144221,7 @@ async function performHealthPass(workspaceDir, projectSlug, project, sessions, p
|
|
|
143844
144221
|
workflow: resolvedConfig?.workflow,
|
|
143845
144222
|
dispatchConfirmTimeoutMs: resolvedConfig?.timeouts?.dispatchConfirmTimeoutMs,
|
|
143846
144223
|
healthGracePeriodMs: resolvedConfig?.timeouts?.healthGracePeriodMs,
|
|
144224
|
+
stallTimeoutMinutes,
|
|
143847
144225
|
runCommand,
|
|
143848
144226
|
notificationConfig: notifyConfig
|
|
143849
144227
|
});
|
|
@@ -146221,8 +146599,18 @@ function stringifyAnswerRecord(record2) {
|
|
|
146221
146599
|
}
|
|
146222
146600
|
return normalized;
|
|
146223
146601
|
}
|
|
146224
|
-
function
|
|
146602
|
+
function extractExplicitProjectName(text) {
|
|
146603
|
+
if (!text) return null;
|
|
146604
|
+
const fieldMatch = text.match(/(?:^|[\n.,;!?]\s*)(?:project name|repo name|repository name|nome do projeto)\s*:\s*([a-z0-9][a-z0-9-]{1,63})\b/i);
|
|
146605
|
+
if (fieldMatch?.[1]) return fieldMatch[1].trim().toLowerCase();
|
|
146606
|
+
const inlineMatch = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
|
|
146607
|
+
if (inlineMatch?.[1]) return inlineMatch[1].trim().toLowerCase();
|
|
146608
|
+
return null;
|
|
146609
|
+
}
|
|
146610
|
+
function deriveProjectName(repoUrl, projectName, freeText) {
|
|
146225
146611
|
if (projectName) return projectName;
|
|
146612
|
+
const parsedFromText = extractExplicitProjectName(freeText ?? null);
|
|
146613
|
+
if (parsedFromText) return parsedFromText;
|
|
146226
146614
|
if (!repoUrl) return null;
|
|
146227
146615
|
const sanitized = repoUrl.replace(/\/+$/, "");
|
|
146228
146616
|
const lastSegment = sanitized.split("/").pop();
|
|
@@ -146312,9 +146700,11 @@ function normalizeGenesisRequest(params, existingPayload) {
|
|
|
146312
146700
|
throw new Error('phase is required and must be "discover" or "commit"');
|
|
146313
146701
|
}
|
|
146314
146702
|
const repoUrl = normalizeOptionalString(params.repo_url) ?? existingPayload?.metadata.repo_url ?? null;
|
|
146703
|
+
const freeTextProjectSource = normalizeOptionalString(params.idea) ?? normalizeOptionalString(params.command) ?? existingPayload?.raw_idea ?? null;
|
|
146315
146704
|
const projectName = deriveProjectName(
|
|
146316
146705
|
repoUrl,
|
|
146317
|
-
normalizeOptionalString(params.project_name) ?? existingPayload?.metadata.project_name ?? null
|
|
146706
|
+
normalizeOptionalString(params.project_name) ?? existingPayload?.metadata.project_name ?? null,
|
|
146707
|
+
freeTextProjectSource
|
|
146318
146708
|
);
|
|
146319
146709
|
const answersJson = {
|
|
146320
146710
|
...existingPayload?.metadata.answers_json ?? {},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mestreyoda/fabrica",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.21",
|
|
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",
|