@mestreyoda/fabrica 0.2.20 → 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 +311 -32
- 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",
|
|
@@ -126741,6 +126788,10 @@ function createTaskCreateTool(ctx) {
|
|
|
126741
126788
|
pickup: {
|
|
126742
126789
|
type: "boolean",
|
|
126743
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."
|
|
126744
126795
|
}
|
|
126745
126796
|
}
|
|
126746
126797
|
},
|
|
@@ -126749,12 +126800,17 @@ function createTaskCreateTool(ctx) {
|
|
|
126749
126800
|
const description = params.description ?? "";
|
|
126750
126801
|
const assignees = params.assignees ?? [];
|
|
126751
126802
|
const pickup = params.pickup ?? false;
|
|
126803
|
+
const parentIssueId = Number.isFinite(params.parentIssueId) ? Number(params.parentIssueId) : null;
|
|
126752
126804
|
const workspaceDir = requireWorkspaceDir(toolCtx);
|
|
126753
126805
|
const { project, route } = await resolveProjectFromContext(workspaceDir, toolCtx, params.channelId);
|
|
126754
126806
|
const resolvedConfig = await loadConfig(workspaceDir, project.slug);
|
|
126755
126807
|
const label = resolvedConfig.workflow.states[resolvedConfig.workflow.initial]?.label ?? "Planning";
|
|
126756
126808
|
const { provider, type: providerType } = await resolveProvider(project, ctx.runCommand);
|
|
126757
|
-
|
|
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);
|
|
126758
126814
|
provider.reactToIssue(issue2.iid, "eyes").catch(() => {
|
|
126759
126815
|
});
|
|
126760
126816
|
const primaryChannel = findPrimaryChannel(project);
|
|
@@ -126768,13 +126824,31 @@ function createTaskCreateTool(ctx) {
|
|
|
126768
126824
|
);
|
|
126769
126825
|
autoAssignOwnerLabel(workspaceDir, provider, issue2.iid, project).catch(() => {
|
|
126770
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
|
+
}
|
|
126771
126844
|
await log(workspaceDir, "task_create", {
|
|
126772
126845
|
project: project.name,
|
|
126773
126846
|
issueId: issue2.iid,
|
|
126774
126847
|
title,
|
|
126775
126848
|
label,
|
|
126776
126849
|
provider: providerType,
|
|
126777
|
-
pickup
|
|
126850
|
+
pickup,
|
|
126851
|
+
parentIssueId
|
|
126778
126852
|
});
|
|
126779
126853
|
const hasBody = description && description.trim().length > 0;
|
|
126780
126854
|
let announcement = `\u{1F4CB} Created #${issue2.iid}: "${title}" (${label})`;
|
|
@@ -128835,6 +128909,7 @@ async function dispatchTask(opts) {
|
|
|
128835
128909
|
inconclusiveCompletionAt: null,
|
|
128836
128910
|
inconclusiveCompletionReason: null,
|
|
128837
128911
|
sessionCompletedAt: null,
|
|
128912
|
+
progressNotifiedAt: null,
|
|
128838
128913
|
lastSessionKey: sessionKey
|
|
128839
128914
|
}).catch((err) => {
|
|
128840
128915
|
log(workspaceDir, "dispatch_warning", { step: "record_dispatch_requested", issue: issueId, err: String(err) }).catch(() => {
|
|
@@ -131332,6 +131407,7 @@ async function checkWorkerHealth(opts) {
|
|
|
131332
131407
|
completionRecoveryWindowMs = COMPLETION_RECOVERY_WINDOW_MS,
|
|
131333
131408
|
executionContractRecoveryWindowMs = EXECUTION_CONTRACT_RECOVERY_WINDOW_MS,
|
|
131334
131409
|
runCommand,
|
|
131410
|
+
stallTimeoutMinutes = 25,
|
|
131335
131411
|
notificationConfig
|
|
131336
131412
|
} = opts;
|
|
131337
131413
|
const fixes = [];
|
|
@@ -131831,7 +131907,120 @@ async function checkWorkerHealth(opts) {
|
|
|
131831
131907
|
continue;
|
|
131832
131908
|
}
|
|
131833
131909
|
if (slot.active && slot.startTime && sessionKey && sessions && sessionAlive) {
|
|
131834
|
-
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
|
+
}
|
|
131835
132024
|
if (hours > staleWorkerHours) {
|
|
131836
132025
|
const fix = {
|
|
131837
132026
|
issue: {
|
|
@@ -139694,14 +139883,14 @@ function detectRawIdeaComplexity(rawIdea) {
|
|
|
139694
139883
|
const text = rawIdea.toLowerCase();
|
|
139695
139884
|
const signals = [];
|
|
139696
139885
|
const subsystemPatterns = [
|
|
139697
|
-
[/\b(worker|background.?job|queue|celery|bull|sidekiq|task.?runner)\b/i, "background-worker"],
|
|
139886
|
+
[/\b(worker|background.?job|background process|background service|background worker|queue|celery|bull|sidekiq|task.?runner|scheduler|job processor)\b/i, "background-worker"],
|
|
139698
139887
|
[/\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
|
|
139701
|
-
[/\b(database|banco|db|postgres|mysql|mongodb|redis|sqlite|orm)\b/i, "database"],
|
|
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"],
|
|
139702
139891
|
[/\b(api\s+rest|rest\s+api|endpoint|rota|route|graphql|grpc)\b/i, "api-layer"],
|
|
139703
139892
|
[/\b(docker|kubernetes|k8s|deploy|ci|cd|pipeline)\b/i, "infra"],
|
|
139704
|
-
[/\b(dashboard|frontend|interface|ui|tela|p[aá]gina)\b/i, "frontend"]
|
|
139893
|
+
[/\b(dashboard|frontend|interface|ui|tela|p[aá]gina|admin view|admin panel|admin console)\b/i, "frontend"]
|
|
139705
139894
|
];
|
|
139706
139895
|
for (const [regex, label] of subsystemPatterns) {
|
|
139707
139896
|
if (regex.test(text)) signals.push(label);
|
|
@@ -139858,6 +140047,38 @@ function loadMatrix() {
|
|
|
139858
140047
|
cachedMatrix = _require3("../configs/triage-matrix.json");
|
|
139859
140048
|
return cachedMatrix;
|
|
139860
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
|
+
}
|
|
139861
140082
|
var triageStep = {
|
|
139862
140083
|
name: "triage",
|
|
139863
140084
|
shouldRun: (payload) => !!payload.issues?.length && !payload.dry_run,
|
|
@@ -139877,9 +140098,9 @@ var triageStep = {
|
|
|
139877
140098
|
totalRisks: (impact?.risk_areas?.length ?? 0) + (payload.security?.spec_security_notes?.length ?? 0),
|
|
139878
140099
|
objective: spec.objective,
|
|
139879
140100
|
rawIdea: payload.raw_idea,
|
|
139880
|
-
acText: spec.acceptance_criteria.join("
|
|
139881
|
-
scopeText: spec.scope_v1.join("
|
|
139882
|
-
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"),
|
|
139883
140104
|
authSignal: payload.metadata?.auth_gate?.signal ?? false
|
|
139884
140105
|
}, matrix);
|
|
139885
140106
|
const repoUrl = payload.scaffold?.repo_url ?? payload.metadata?.repo_url ?? "";
|
|
@@ -139889,8 +140110,12 @@ var triageStep = {
|
|
|
139889
140110
|
const initialLabel = resolvedConfig?.workflow.states[resolvedConfig.workflow.initial]?.label ?? "Planning";
|
|
139890
140111
|
const allLabels = [decision.priorityLabel, decision.effortLabel];
|
|
139891
140112
|
if (decision.typeLabel) allLabels.push(decision.typeLabel);
|
|
139892
|
-
if (!decision.readyForDispatch) allLabels.push("needs-human");
|
|
140113
|
+
if (!decision.readyForDispatch || decision.specQualityBlock) allLabels.push("needs-human");
|
|
139893
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 = [];
|
|
139894
140119
|
if (ctx.createIssueProvider && repoPath) {
|
|
139895
140120
|
try {
|
|
139896
140121
|
const { provider } = await ctx.createIssueProvider({
|
|
@@ -139900,7 +140125,36 @@ var triageStep = {
|
|
|
139900
140125
|
for (const label of uniqueLabels) {
|
|
139901
140126
|
await provider.addLabel(issue2.number, label);
|
|
139902
140127
|
}
|
|
139903
|
-
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) {
|
|
139904
140158
|
await provider.transitionLabel(issue2.number, initialLabel, decision.targetState);
|
|
139905
140159
|
const dispatchLabels = [];
|
|
139906
140160
|
if (decision.targetState === "To Do" && decision.dispatchLabel) {
|
|
@@ -139965,8 +140219,14 @@ var triageStep = {
|
|
|
139965
140219
|
project_channel_id: null,
|
|
139966
140220
|
labels_applied: uniqueLabels,
|
|
139967
140221
|
issue_number: issue2.number,
|
|
139968
|
-
ready_for_dispatch: decision.readyForDispatch && decision.errors.length === 0,
|
|
139969
|
-
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
|
|
139970
140230
|
};
|
|
139971
140231
|
ctx.log(`Triage: ${triage.priority}, effort=${triage.effort}, ready=${triage.ready_for_dispatch}`);
|
|
139972
140232
|
return {
|
|
@@ -141099,6 +141359,11 @@ function inferProjectSlug(text) {
|
|
|
141099
141359
|
const slug = cleaned.toLowerCase().normalize("NFKD").replace(/[^\w\s-]/g, "").trim().replace(/\s+/g, "-").replace(/-+/g, "-").slice(0, 64);
|
|
141100
141360
|
return slug || void 0;
|
|
141101
141361
|
}
|
|
141362
|
+
function normalizeProjectNameCandidate(value) {
|
|
141363
|
+
const normalized = normalizeText3(value);
|
|
141364
|
+
if (!normalized) return void 0;
|
|
141365
|
+
return inferProjectSlug(normalized) ?? void 0;
|
|
141366
|
+
}
|
|
141102
141367
|
function normalizeText3(value) {
|
|
141103
141368
|
const trimmed = value?.trim();
|
|
141104
141369
|
return trimmed ? trimmed : void 0;
|
|
@@ -141122,7 +141387,7 @@ function detectStackHint(text) {
|
|
|
141122
141387
|
}
|
|
141123
141388
|
function parseField(text, labels) {
|
|
141124
141389
|
for (const label of labels) {
|
|
141125
|
-
const regex = new RegExp(
|
|
141390
|
+
const regex = new RegExp(`(?:^|[\\n.,;!?]\\s*)${label}\\s*:\\s*(.+)$`, "im");
|
|
141126
141391
|
const match = text.match(regex);
|
|
141127
141392
|
if (match?.[1]) return normalizeText3(match[1]);
|
|
141128
141393
|
}
|
|
@@ -141134,12 +141399,12 @@ function parseIdeaBlock(text) {
|
|
|
141134
141399
|
}
|
|
141135
141400
|
function parseExplicitProjectName(text) {
|
|
141136
141401
|
const match = text.match(/\b(?:called|named|chamado)\s+[`"'“”‘’]?([a-z0-9][a-z0-9-]{1,63})[`"'“”‘’]?(?=$|[\s.,!?;:])/i);
|
|
141137
|
-
return match?.[1]?.toLowerCase();
|
|
141402
|
+
return normalizeProjectNameCandidate(match?.[1]?.toLowerCase());
|
|
141138
141403
|
}
|
|
141139
141404
|
function parseBootstrapRequest(text) {
|
|
141140
141405
|
const repoUrl = parseField(text, ["repository url", "repo url", "reposit[o\xF3]rio url", "github repo"]);
|
|
141141
141406
|
const rawIdea = parseIdeaBlock(text) ?? text.trim();
|
|
141142
|
-
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);
|
|
141143
141408
|
const repoPath = parseField(text, ["local repository path", "repo path", "caminho local", "local path"]);
|
|
141144
141409
|
const explicitStack = parseField(text, ["stack", "framework", "linguagem"]);
|
|
141145
141410
|
const stackHint = (explicitStack ? normalizeStackHint(explicitStack) : "") || detectStackHint(text);
|
|
@@ -141318,25 +141583,26 @@ function parseClarificationResponse(text, session) {
|
|
|
141318
141583
|
if (autoPatterns.test(normalizeUserResponse(text))) {
|
|
141319
141584
|
return { recognized: true, projectName: inferProjectSlug(session.rawIdea) ?? `project-${Date.now()}`, stackHint: session.stackHint ?? void 0 };
|
|
141320
141585
|
}
|
|
141321
|
-
const nameField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
141586
|
+
const nameField = normalizeProjectNameCandidate(parseField(text, ["project name", "nome do projeto", "nome", "name"]));
|
|
141322
141587
|
if (nameField) {
|
|
141323
141588
|
return { recognized: true, projectName: nameField, stackHint: session.stackHint ?? void 0 };
|
|
141324
141589
|
}
|
|
141325
|
-
|
|
141326
|
-
|
|
141590
|
+
const normalizedInlineName = trimmed.length <= 64 ? normalizeProjectNameCandidate(trimmed) : void 0;
|
|
141591
|
+
if (normalizedInlineName) {
|
|
141592
|
+
return { recognized: true, projectName: normalizedInlineName, stackHint: session.stackHint ?? void 0 };
|
|
141327
141593
|
}
|
|
141328
141594
|
return { recognized: false };
|
|
141329
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]);
|
|
141330
141599
|
const stackField = parseField(text, ["stack", "framework", "linguagem", "language"]);
|
|
141331
141600
|
if (stackField) {
|
|
141332
141601
|
const normalizedStack = normalizeStackHint(stackField) || detectStackHint(stackField) || stackField;
|
|
141333
|
-
return { recognized: true, stackHint: normalizedStack };
|
|
141602
|
+
return { recognized: true, stackHint: normalizedStack, projectName: inlineName };
|
|
141334
141603
|
}
|
|
141335
141604
|
const detectedStack = detectStackHint(text);
|
|
141336
141605
|
if (detectedStack) {
|
|
141337
|
-
const projectNameFromField = parseField(text, ["project name", "nome do projeto", "nome", "name"]);
|
|
141338
|
-
const nameItMatch = !projectNameFromField ? text.match(/(?:name|call|chamar?)\s+(?:it\s+)?([\w-]{2,64})/i) : null;
|
|
141339
|
-
const inlineName = projectNameFromField ?? (nameItMatch ? nameItMatch[1].toLowerCase().replace(/[^a-z0-9-]/g, "").replace(/-+/g, "-") || void 0 : void 0);
|
|
141340
141606
|
return { recognized: true, stackHint: detectedStack, projectName: inlineName };
|
|
141341
141607
|
}
|
|
141342
141608
|
const lower2 = normalizeUserResponse(text);
|
|
@@ -141569,7 +141835,7 @@ async function handleTelegramBootstrapDmMessage(ctx, rawConversationId, content)
|
|
|
141569
141835
|
conversationId,
|
|
141570
141836
|
rawIdea: content,
|
|
141571
141837
|
stackHint: parsed.stackHint ?? void 0,
|
|
141572
|
-
projectName: parsed.projectSlug ?? void 0,
|
|
141838
|
+
projectName: parsed.projectName ?? parsed.projectSlug ?? void 0,
|
|
141573
141839
|
status: "clarifying",
|
|
141574
141840
|
pendingClarification: "scope",
|
|
141575
141841
|
language
|
|
@@ -143955,6 +144221,7 @@ async function performHealthPass(workspaceDir, projectSlug, project, sessions, p
|
|
|
143955
144221
|
workflow: resolvedConfig?.workflow,
|
|
143956
144222
|
dispatchConfirmTimeoutMs: resolvedConfig?.timeouts?.dispatchConfirmTimeoutMs,
|
|
143957
144223
|
healthGracePeriodMs: resolvedConfig?.timeouts?.healthGracePeriodMs,
|
|
144224
|
+
stallTimeoutMinutes,
|
|
143958
144225
|
runCommand,
|
|
143959
144226
|
notificationConfig: notifyConfig
|
|
143960
144227
|
});
|
|
@@ -146332,8 +146599,18 @@ function stringifyAnswerRecord(record2) {
|
|
|
146332
146599
|
}
|
|
146333
146600
|
return normalized;
|
|
146334
146601
|
}
|
|
146335
|
-
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) {
|
|
146336
146611
|
if (projectName) return projectName;
|
|
146612
|
+
const parsedFromText = extractExplicitProjectName(freeText ?? null);
|
|
146613
|
+
if (parsedFromText) return parsedFromText;
|
|
146337
146614
|
if (!repoUrl) return null;
|
|
146338
146615
|
const sanitized = repoUrl.replace(/\/+$/, "");
|
|
146339
146616
|
const lastSegment = sanitized.split("/").pop();
|
|
@@ -146423,9 +146700,11 @@ function normalizeGenesisRequest(params, existingPayload) {
|
|
|
146423
146700
|
throw new Error('phase is required and must be "discover" or "commit"');
|
|
146424
146701
|
}
|
|
146425
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;
|
|
146426
146704
|
const projectName = deriveProjectName(
|
|
146427
146705
|
repoUrl,
|
|
146428
|
-
normalizeOptionalString(params.project_name) ?? existingPayload?.metadata.project_name ?? null
|
|
146706
|
+
normalizeOptionalString(params.project_name) ?? existingPayload?.metadata.project_name ?? null,
|
|
146707
|
+
freeTextProjectSource
|
|
146429
146708
|
);
|
|
146430
146709
|
const answersJson = {
|
|
146431
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",
|