@archal/cli 0.7.7 → 0.7.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 +1135 -32
- package/harnesses/_lib/model-configs.mjs +26 -19
- package/harnesses/_lib/providers.mjs +24 -8
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -2982,10 +2982,11 @@ var LlmApiError = class extends Error {
|
|
|
2982
2982
|
};
|
|
2983
2983
|
var RETRYABLE_STATUS_CODES2 = /* @__PURE__ */ new Set([429, 500, 502, 503, 529]);
|
|
2984
2984
|
function detectProvider(model) {
|
|
2985
|
-
|
|
2986
|
-
if (
|
|
2987
|
-
if (
|
|
2988
|
-
if (
|
|
2985
|
+
const normalized = model.toLowerCase();
|
|
2986
|
+
if (normalized.startsWith("gemini-")) return "gemini";
|
|
2987
|
+
if (normalized.startsWith("claude-") || normalized.startsWith("sonnet-") || normalized.startsWith("haiku-") || normalized.startsWith("opus-")) return "anthropic";
|
|
2988
|
+
if (normalized.startsWith("gpt-") || normalized.startsWith("o1-") || normalized.startsWith("o2-") || normalized.startsWith("o3-") || normalized.startsWith("o4-")) return "openai";
|
|
2989
|
+
if (normalized.startsWith("llama") || normalized.startsWith("mixtral") || normalized.startsWith("mistral") || normalized.startsWith("deepseek") || normalized.startsWith("qwen") || normalized.startsWith("codestral") || normalized.startsWith("command")) return "openai-compatible";
|
|
2989
2990
|
return "openai-compatible";
|
|
2990
2991
|
}
|
|
2991
2992
|
var PROVIDER_ENV_VARS = {
|
|
@@ -3011,8 +3012,18 @@ function validateKeyForProvider(key, provider) {
|
|
|
3011
3012
|
return void 0;
|
|
3012
3013
|
}
|
|
3013
3014
|
function resolveProviderApiKey(explicitKey, provider) {
|
|
3014
|
-
|
|
3015
|
-
|
|
3015
|
+
const normalizedExplicit = explicitKey.trim();
|
|
3016
|
+
const providerEnvKey = process.env[PROVIDER_ENV_VARS[provider]]?.trim() ?? "";
|
|
3017
|
+
if (!normalizedExplicit) return providerEnvKey;
|
|
3018
|
+
const mismatch = validateKeyForProvider(normalizedExplicit, provider);
|
|
3019
|
+
if (mismatch && providerEnvKey) {
|
|
3020
|
+
debug("Configured API key appears mismatched for provider; falling back to provider env key", {
|
|
3021
|
+
provider,
|
|
3022
|
+
providerEnvVar: PROVIDER_ENV_VARS[provider]
|
|
3023
|
+
});
|
|
3024
|
+
return providerEnvKey;
|
|
3025
|
+
}
|
|
3026
|
+
return normalizedExplicit;
|
|
3016
3027
|
}
|
|
3017
3028
|
var REQUEST_TIMEOUT_MS3 = 6e4;
|
|
3018
3029
|
var MAX_RETRIES2 = 3;
|
|
@@ -7933,7 +7944,11 @@ function mergeCollectionSchema(entitySchema, overrides) {
|
|
|
7933
7944
|
if (override.default !== void 0) def.default = override.default;
|
|
7934
7945
|
}
|
|
7935
7946
|
if (isRequired) {
|
|
7936
|
-
|
|
7947
|
+
if (nullableSet.has(field)) {
|
|
7948
|
+
def.default = null;
|
|
7949
|
+
} else {
|
|
7950
|
+
delete def.default;
|
|
7951
|
+
}
|
|
7937
7952
|
delete def.required;
|
|
7938
7953
|
}
|
|
7939
7954
|
merged[field] = def;
|
|
@@ -7951,6 +7966,9 @@ function buildSchemaFromOverrides(overrides) {
|
|
|
7951
7966
|
const type = nullableSet.has(field) && !baseType.includes("null") && !baseType.includes("[]") ? `${baseType}|null` : baseType;
|
|
7952
7967
|
schema[field] = {
|
|
7953
7968
|
type,
|
|
7969
|
+
// If field is both required AND nullable, give it a null default so
|
|
7970
|
+
// the LLM can omit it without triggering a hard validation error.
|
|
7971
|
+
...nullableSet.has(field) && { default: null },
|
|
7954
7972
|
...override?.aliases && { aliases: override.aliases },
|
|
7955
7973
|
...override?.description && { description: override.description },
|
|
7956
7974
|
...override?.fk && { fk: override.fk },
|
|
@@ -8063,6 +8081,8 @@ function validateSeedAgainstSchema(twinName, seed, baseEntityCounts) {
|
|
|
8063
8081
|
errors.push(
|
|
8064
8082
|
`${entityLabel}: wrong field name "${usedAlias}" - use "${fieldName}" instead`
|
|
8065
8083
|
);
|
|
8084
|
+
} else if (def.type.includes("null")) {
|
|
8085
|
+
entity[fieldName] = null;
|
|
8066
8086
|
} else {
|
|
8067
8087
|
errors.push(
|
|
8068
8088
|
`${entityLabel}: missing required field "${fieldName}" (${def.type})`
|
|
@@ -8537,7 +8557,6 @@ var KIND_COLLECTION_HINTS = {
|
|
|
8537
8557
|
event: ["events"],
|
|
8538
8558
|
email: ["gmail_messages", "messages"]
|
|
8539
8559
|
};
|
|
8540
|
-
var STRICT_QUOTE_TWINS = /* @__PURE__ */ new Set(["slack", "google-workspace"]);
|
|
8541
8560
|
var ENTITY_KEY_ALIASES = {
|
|
8542
8561
|
"repo.owner": ["ownerLogin", "owner_login", "login", "owner.login", "owner.name"],
|
|
8543
8562
|
"issue.key": ["identifier"],
|
|
@@ -8696,13 +8715,20 @@ function validateSeedCoverage(intent, mergedSeed) {
|
|
|
8696
8715
|
const entityIssues = [];
|
|
8697
8716
|
const quoteErrors = [];
|
|
8698
8717
|
const quoteWarnings = [];
|
|
8718
|
+
const CORE_ENTITY_KEYS = /* @__PURE__ */ new Set(["owner", "name", "fullName", "channel_name", "key", "identifier", "number"]);
|
|
8719
|
+
const entityWarnings = [];
|
|
8699
8720
|
for (const entity of intent.entities) {
|
|
8700
8721
|
if (typeof entity.value === "boolean") continue;
|
|
8701
8722
|
if (!valueExistsInCollections(mergedSeed, entity.kind, entity.key, entity.value)) {
|
|
8702
|
-
|
|
8723
|
+
const issue = {
|
|
8703
8724
|
type: "missing_entity",
|
|
8704
8725
|
message: `Expected ${entity.kind}.${entity.key}=${String(entity.value)} to exist`
|
|
8705
|
-
}
|
|
8726
|
+
};
|
|
8727
|
+
if (CORE_ENTITY_KEYS.has(entity.key)) {
|
|
8728
|
+
entityIssues.push(issue);
|
|
8729
|
+
} else {
|
|
8730
|
+
entityWarnings.push(issue);
|
|
8731
|
+
}
|
|
8706
8732
|
}
|
|
8707
8733
|
}
|
|
8708
8734
|
for (const quote of intent.quotedStrings) {
|
|
@@ -8715,18 +8741,14 @@ function validateSeedCoverage(intent, mergedSeed) {
|
|
|
8715
8741
|
type: "missing_quote",
|
|
8716
8742
|
message: `Expected quoted text to exist: "${quote}"`
|
|
8717
8743
|
};
|
|
8718
|
-
|
|
8719
|
-
quoteErrors.push(issue);
|
|
8720
|
-
} else {
|
|
8721
|
-
quoteWarnings.push(issue);
|
|
8722
|
-
}
|
|
8744
|
+
quoteWarnings.push(issue);
|
|
8723
8745
|
}
|
|
8724
8746
|
}
|
|
8725
8747
|
const errors = [...entityIssues, ...quoteErrors];
|
|
8726
8748
|
return {
|
|
8727
8749
|
valid: errors.length === 0,
|
|
8728
8750
|
issues: errors,
|
|
8729
|
-
warnings: quoteWarnings
|
|
8751
|
+
warnings: [...quoteWarnings, ...entityWarnings]
|
|
8730
8752
|
};
|
|
8731
8753
|
}
|
|
8732
8754
|
|
|
@@ -9039,14 +9061,928 @@ function cacheNegativeSeed(twinName, baseSeedName, setupText, missingSlots, scop
|
|
|
9039
9061
|
}
|
|
9040
9062
|
}
|
|
9041
9063
|
|
|
9064
|
+
// src/runner/seed-blueprint.ts
|
|
9065
|
+
var BLUEPRINT_SYSTEM_PROMPT = `You are a precise data extraction tool. Given a scenario setup description, extract a structured JSON blueprint describing what test data needs to be created.
|
|
9066
|
+
|
|
9067
|
+
Output ONLY valid JSON matching this schema:
|
|
9068
|
+
{
|
|
9069
|
+
"identities": [
|
|
9070
|
+
{ "collection": "<collection_name>", "fields": { "<field>": "<value>", ... } }
|
|
9071
|
+
],
|
|
9072
|
+
"collections": [
|
|
9073
|
+
{
|
|
9074
|
+
"name": "<collection_name>",
|
|
9075
|
+
"totalCount": <number>,
|
|
9076
|
+
"groups": [
|
|
9077
|
+
{
|
|
9078
|
+
"count": <number>,
|
|
9079
|
+
"properties": { "<property>": <value>, ... }
|
|
9080
|
+
}
|
|
9081
|
+
],
|
|
9082
|
+
"contentHint": "<optional description of what content should look like>"
|
|
9083
|
+
}
|
|
9084
|
+
]
|
|
9085
|
+
}
|
|
9086
|
+
|
|
9087
|
+
Rules:
|
|
9088
|
+
- "identities" are named entities that MUST exist exactly (repos, channels, users with specific names)
|
|
9089
|
+
- "collections" describe bulk entities with count distributions
|
|
9090
|
+
- Group counts MUST sum to totalCount exactly
|
|
9091
|
+
- Use these property names for temporal attributes:
|
|
9092
|
+
- "stale": true/false (not updated in 90+ days)
|
|
9093
|
+
- "recentlyActive": true/false (updated within 30 days)
|
|
9094
|
+
- Use "state" for entity state: "open", "closed", "merged", etc.
|
|
9095
|
+
- Use "labels" for label arrays: ["bug", "keep-open"]
|
|
9096
|
+
- Use "assigned" for assignment: true/false or assignee name
|
|
9097
|
+
- Use "hasComments": true/false for whether entity has comments
|
|
9098
|
+
- Use "priority" for priority: "P0", "P1", "P2", "critical", "high", "medium", "low"
|
|
9099
|
+
- Use "private" for visibility: true/false
|
|
9100
|
+
- Keep it simple \u2014 only include what the setup text explicitly states
|
|
9101
|
+
- If the setup says "N of those" or "N of the X", that's a subset of the previous group
|
|
9102
|
+
- Do NOT include fields not mentioned in the setup text`;
|
|
9103
|
+
async function extractBlueprint(setupText, twinName, availableCollections, config) {
|
|
9104
|
+
const userPrompt = `Twin: ${twinName}
|
|
9105
|
+
Available collections: ${availableCollections.join(", ")}
|
|
9106
|
+
|
|
9107
|
+
Setup text:
|
|
9108
|
+
${setupText}
|
|
9109
|
+
|
|
9110
|
+
Extract the seed blueprint as JSON.`;
|
|
9111
|
+
try {
|
|
9112
|
+
const provider = detectProvider(config.model);
|
|
9113
|
+
const apiKey = resolveProviderApiKey(config.apiKey, provider);
|
|
9114
|
+
const responseText = await callLlm({
|
|
9115
|
+
provider,
|
|
9116
|
+
model: config.model,
|
|
9117
|
+
apiKey,
|
|
9118
|
+
systemPrompt: BLUEPRINT_SYSTEM_PROMPT,
|
|
9119
|
+
userPrompt,
|
|
9120
|
+
maxTokens: 4096,
|
|
9121
|
+
baseUrl: config.baseUrl,
|
|
9122
|
+
providerMode: config.providerMode,
|
|
9123
|
+
intent: "seed-generate",
|
|
9124
|
+
responseFormat: "json"
|
|
9125
|
+
});
|
|
9126
|
+
if (!responseText) {
|
|
9127
|
+
warn("Blueprint extraction returned no text");
|
|
9128
|
+
return null;
|
|
9129
|
+
}
|
|
9130
|
+
const parsed = parseBlueprint(responseText, twinName);
|
|
9131
|
+
if (!parsed) return null;
|
|
9132
|
+
for (const col of parsed.collections) {
|
|
9133
|
+
const groupSum = col.groups.reduce((sum, g) => sum + g.count, 0);
|
|
9134
|
+
if (groupSum !== col.totalCount) {
|
|
9135
|
+
debug(`Blueprint group count mismatch for ${col.name}: groups sum to ${groupSum}, totalCount is ${col.totalCount}. Adjusting.`);
|
|
9136
|
+
col.totalCount = groupSum;
|
|
9137
|
+
}
|
|
9138
|
+
}
|
|
9139
|
+
return parsed;
|
|
9140
|
+
} catch (err) {
|
|
9141
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
9142
|
+
warn(`Blueprint extraction failed: ${msg}`);
|
|
9143
|
+
return null;
|
|
9144
|
+
}
|
|
9145
|
+
}
|
|
9146
|
+
function parseBlueprint(text, twinName) {
|
|
9147
|
+
try {
|
|
9148
|
+
let cleaned = text.trim();
|
|
9149
|
+
if (cleaned.startsWith("```")) {
|
|
9150
|
+
cleaned = cleaned.replace(/^```(?:json)?\s*\n?/, "").replace(/\n?```\s*$/, "");
|
|
9151
|
+
}
|
|
9152
|
+
const raw = JSON.parse(cleaned);
|
|
9153
|
+
if (!raw || typeof raw !== "object") return null;
|
|
9154
|
+
const identities = [];
|
|
9155
|
+
for (const id of raw.identities ?? []) {
|
|
9156
|
+
if (id?.collection && typeof id.fields === "object") {
|
|
9157
|
+
identities.push({ collection: String(id.collection), fields: id.fields });
|
|
9158
|
+
}
|
|
9159
|
+
}
|
|
9160
|
+
const collections = [];
|
|
9161
|
+
for (const col of raw.collections ?? []) {
|
|
9162
|
+
if (!col?.name || typeof col.totalCount !== "number") continue;
|
|
9163
|
+
const groups = [];
|
|
9164
|
+
for (const g of col.groups ?? []) {
|
|
9165
|
+
if (typeof g?.count === "number" && g.count > 0) {
|
|
9166
|
+
groups.push({ count: g.count, properties: g.properties ?? {} });
|
|
9167
|
+
}
|
|
9168
|
+
}
|
|
9169
|
+
if (groups.length === 0) {
|
|
9170
|
+
groups.push({ count: col.totalCount, properties: {} });
|
|
9171
|
+
}
|
|
9172
|
+
collections.push({
|
|
9173
|
+
name: String(col.name),
|
|
9174
|
+
totalCount: col.totalCount,
|
|
9175
|
+
groups,
|
|
9176
|
+
contentHint: col.contentHint
|
|
9177
|
+
});
|
|
9178
|
+
}
|
|
9179
|
+
return { twin: twinName, identities, collections };
|
|
9180
|
+
} catch {
|
|
9181
|
+
warn("Failed to parse blueprint JSON");
|
|
9182
|
+
return null;
|
|
9183
|
+
}
|
|
9184
|
+
}
|
|
9185
|
+
|
|
9186
|
+
// src/runner/seed-builder.ts
|
|
9187
|
+
var MS_PER_DAY = 864e5;
|
|
9188
|
+
function daysAgo(days, base) {
|
|
9189
|
+
const now = base ?? /* @__PURE__ */ new Date();
|
|
9190
|
+
return new Date(now.getTime() - days * MS_PER_DAY).toISOString();
|
|
9191
|
+
}
|
|
9192
|
+
function recentDate(withinDays = 7, base) {
|
|
9193
|
+
const now = base ?? /* @__PURE__ */ new Date();
|
|
9194
|
+
const offset = Math.floor(Math.random() * withinDays) * MS_PER_DAY;
|
|
9195
|
+
return new Date(now.getTime() - offset).toISOString();
|
|
9196
|
+
}
|
|
9197
|
+
function staleDate(minDays = 91, maxDays = 300, base) {
|
|
9198
|
+
const now = base ?? /* @__PURE__ */ new Date();
|
|
9199
|
+
const range = maxDays - minDays;
|
|
9200
|
+
const offset = (minDays + Math.floor(Math.random() * range)) * MS_PER_DAY;
|
|
9201
|
+
return new Date(now.getTime() - offset).toISOString();
|
|
9202
|
+
}
|
|
9203
|
+
var ISSUE_TITLES = [
|
|
9204
|
+
"Fix login redirect loop on Safari",
|
|
9205
|
+
"Add dark mode support to settings page",
|
|
9206
|
+
"Rate limiter returns 500 instead of 429",
|
|
9207
|
+
"Memory leak in WebSocket connection manager",
|
|
9208
|
+
"Dropdown menus do not close on mobile",
|
|
9209
|
+
"Search results do not highlight matching terms",
|
|
9210
|
+
"File upload fails silently for large files",
|
|
9211
|
+
"Add CSV export to analytics dashboard",
|
|
9212
|
+
"Implement bulk actions for notifications",
|
|
9213
|
+
"GraphQL query returns stale cached data",
|
|
9214
|
+
"Table component lacks keyboard navigation",
|
|
9215
|
+
"Create onboarding tutorial for new members",
|
|
9216
|
+
"API response times degrade for large date ranges",
|
|
9217
|
+
"Add two-factor authentication for admin accounts",
|
|
9218
|
+
"Deprecated crypto API warnings in test suite",
|
|
9219
|
+
"Evaluate replacing Moment.js with date-fns",
|
|
9220
|
+
"Broken tooltip positioning on Safari 17",
|
|
9221
|
+
"Login page shows blank screen on slow 3G",
|
|
9222
|
+
"Intermittent 502 errors on reports endpoint",
|
|
9223
|
+
"Tracking: migrate auth from cookies to JWT",
|
|
9224
|
+
"Roadmap: accessibility audit and WCAG compliance",
|
|
9225
|
+
"Upgrade Node.js runtime from 18 to 20 LTS",
|
|
9226
|
+
"Refactor database connection pool configuration",
|
|
9227
|
+
"Add retry logic for third-party API calls",
|
|
9228
|
+
"Improve error messages for form validation",
|
|
9229
|
+
"Pagination breaks when filtering by status",
|
|
9230
|
+
"Email notification templates are not responsive",
|
|
9231
|
+
"CI pipeline takes 45 minutes on large PRs",
|
|
9232
|
+
"Localization support for date and number formats",
|
|
9233
|
+
"Dashboard widgets do not resize on mobile"
|
|
9234
|
+
];
|
|
9235
|
+
var ISSUE_BODIES = [
|
|
9236
|
+
"This has been reported multiple times by users. Needs investigation and a fix.",
|
|
9237
|
+
"No one has started working on this yet. Low priority but would improve UX.",
|
|
9238
|
+
"This is affecting production traffic. We need to address this soon.",
|
|
9239
|
+
"Users have requested this feature multiple times. Would be a nice enhancement.",
|
|
9240
|
+
"This is a long-running tracking issue. Should remain open for visibility.",
|
|
9241
|
+
"Repro steps: 1) Open the app 2) Navigate to settings 3) Observe the issue.",
|
|
9242
|
+
"This blocks other work. Please prioritize.",
|
|
9243
|
+
"Nice to have. Not urgent but would reduce tech debt."
|
|
9244
|
+
];
|
|
9245
|
+
var PR_TITLES = [
|
|
9246
|
+
"Fix null check in JWT validation",
|
|
9247
|
+
"Add pagination to search results",
|
|
9248
|
+
"Refactor database connection pooling",
|
|
9249
|
+
"Update dependencies to latest versions",
|
|
9250
|
+
"Improve error handling in API middleware",
|
|
9251
|
+
"Add unit tests for auth module",
|
|
9252
|
+
"Fix race condition in WebSocket handler",
|
|
9253
|
+
"Migrate CSS to custom properties for theming"
|
|
9254
|
+
];
|
|
9255
|
+
var CHANNEL_PURPOSES = [
|
|
9256
|
+
"General discussion",
|
|
9257
|
+
"Engineering team updates",
|
|
9258
|
+
"Product announcements",
|
|
9259
|
+
"Customer support escalations",
|
|
9260
|
+
"Random fun stuff",
|
|
9261
|
+
"Incident response",
|
|
9262
|
+
"Design feedback",
|
|
9263
|
+
"Deploy notifications"
|
|
9264
|
+
];
|
|
9265
|
+
var MESSAGE_TEXTS = [
|
|
9266
|
+
"Hey team, just a heads up about the deploy today.",
|
|
9267
|
+
"Can someone review my PR? It's been open for a while.",
|
|
9268
|
+
"The CI pipeline is green again after the fix.",
|
|
9269
|
+
"Meeting notes from today's standup are in the doc.",
|
|
9270
|
+
"Has anyone seen this error before? Getting a 502 intermittently.",
|
|
9271
|
+
"Thanks for the quick turnaround on that bug fix!",
|
|
9272
|
+
"Reminder: retro is at 3pm today.",
|
|
9273
|
+
"I pushed a hotfix for the login issue. Please verify."
|
|
9274
|
+
];
|
|
9275
|
+
function generateNodeId(prefix, id) {
|
|
9276
|
+
return `${prefix}_kgDOB${String(id).padStart(5, "0")}`;
|
|
9277
|
+
}
|
|
9278
|
+
function generateSha() {
|
|
9279
|
+
const hex = "0123456789abcdef";
|
|
9280
|
+
let sha = "";
|
|
9281
|
+
for (let i = 0; i < 40; i++) sha += hex[Math.floor(Math.random() * 16)];
|
|
9282
|
+
return sha;
|
|
9283
|
+
}
|
|
9284
|
+
function generateSlackId(prefix, index) {
|
|
9285
|
+
const base = "ABCDEFG0123456789";
|
|
9286
|
+
let id = prefix;
|
|
9287
|
+
let n = index + 100;
|
|
9288
|
+
for (let i = 0; i < 8; i++) {
|
|
9289
|
+
id += base[n % base.length];
|
|
9290
|
+
n = Math.floor(n / base.length) + i + 1;
|
|
9291
|
+
}
|
|
9292
|
+
return id;
|
|
9293
|
+
}
|
|
9294
|
+
function generateSlackTs(baseTs, index) {
|
|
9295
|
+
return `${baseTs + index * 60}.${String(100001 + index * 100).padStart(6, "0")}`;
|
|
9296
|
+
}
|
|
9297
|
+
function generateUuid() {
|
|
9298
|
+
const hex = "0123456789abcdef";
|
|
9299
|
+
const parts = [8, 4, 4, 4, 12];
|
|
9300
|
+
return parts.map((len) => {
|
|
9301
|
+
let s = "";
|
|
9302
|
+
for (let i = 0; i < len; i++) s += hex[Math.floor(Math.random() * 16)];
|
|
9303
|
+
return s;
|
|
9304
|
+
}).join("-");
|
|
9305
|
+
}
|
|
9306
|
+
function generateStripeId(prefix, index) {
|
|
9307
|
+
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
|
9308
|
+
let id = prefix;
|
|
9309
|
+
let n = index + 42;
|
|
9310
|
+
for (let i = 0; i < 14; i++) {
|
|
9311
|
+
id += chars[n % chars.length];
|
|
9312
|
+
n = Math.floor(n / chars.length) + i + 7;
|
|
9313
|
+
}
|
|
9314
|
+
return id;
|
|
9315
|
+
}
|
|
9316
|
+
var DEFAULT_REACTIONS = {
|
|
9317
|
+
totalCount: 0,
|
|
9318
|
+
plusOne: 0,
|
|
9319
|
+
minusOne: 0,
|
|
9320
|
+
laugh: 0,
|
|
9321
|
+
hooray: 0,
|
|
9322
|
+
confused: 0,
|
|
9323
|
+
heart: 0,
|
|
9324
|
+
rocket: 0,
|
|
9325
|
+
eyes: 0
|
|
9326
|
+
};
|
|
9327
|
+
function resolveTemporalProperties(props, base) {
|
|
9328
|
+
const now = base ?? /* @__PURE__ */ new Date();
|
|
9329
|
+
if (props["stale"] === true) {
|
|
9330
|
+
const created2 = daysAgo(200 + Math.floor(Math.random() * 200), now);
|
|
9331
|
+
const updated2 = staleDate(91, 300, now);
|
|
9332
|
+
return { createdAt: created2, updatedAt: updated2 };
|
|
9333
|
+
}
|
|
9334
|
+
if (props["recentlyActive"] === true) {
|
|
9335
|
+
const created2 = daysAgo(30 + Math.floor(Math.random() * 100), now);
|
|
9336
|
+
const updated2 = recentDate(30, now);
|
|
9337
|
+
return { createdAt: created2, updatedAt: updated2 };
|
|
9338
|
+
}
|
|
9339
|
+
const created = daysAgo(14 + Math.floor(Math.random() * 60), now);
|
|
9340
|
+
const updated = recentDate(14, now);
|
|
9341
|
+
return { createdAt: created, updatedAt: updated };
|
|
9342
|
+
}
|
|
9343
|
+
function resolveLabels(props, availableLabels) {
|
|
9344
|
+
const labels = props["labels"];
|
|
9345
|
+
if (Array.isArray(labels)) return labels.map(String);
|
|
9346
|
+
return [];
|
|
9347
|
+
}
|
|
9348
|
+
function buildSeedFromBlueprint(blueprint, baseSeed) {
|
|
9349
|
+
const seed = structuredClone(baseSeed);
|
|
9350
|
+
const warnings = [];
|
|
9351
|
+
const now = /* @__PURE__ */ new Date();
|
|
9352
|
+
const existingLabels = /* @__PURE__ */ new Set();
|
|
9353
|
+
for (const label of seed["labels"] ?? []) {
|
|
9354
|
+
if (typeof label["name"] === "string") existingLabels.add(label["name"]);
|
|
9355
|
+
}
|
|
9356
|
+
for (const identity of blueprint.identities) {
|
|
9357
|
+
processIdentity(identity, seed, warnings);
|
|
9358
|
+
}
|
|
9359
|
+
for (const spec of blueprint.collections) {
|
|
9360
|
+
processCollection(spec, seed, blueprint.twin, existingLabels, warnings, now);
|
|
9361
|
+
}
|
|
9362
|
+
return { seed, warnings };
|
|
9363
|
+
}
|
|
9364
|
+
function processIdentity(identity, seed, warnings) {
|
|
9365
|
+
const collection = identity.collection;
|
|
9366
|
+
if (!seed[collection]) {
|
|
9367
|
+
seed[collection] = [];
|
|
9368
|
+
}
|
|
9369
|
+
const existing = seed[collection].find((entity2) => {
|
|
9370
|
+
for (const [key, value] of Object.entries(identity.fields)) {
|
|
9371
|
+
if (entity2[key] !== value) return false;
|
|
9372
|
+
}
|
|
9373
|
+
return true;
|
|
9374
|
+
});
|
|
9375
|
+
if (existing) {
|
|
9376
|
+
debug(`Identity already exists in ${collection}: ${JSON.stringify(identity.fields)}`);
|
|
9377
|
+
return;
|
|
9378
|
+
}
|
|
9379
|
+
const nextId = getMaxId(seed[collection]) + 1;
|
|
9380
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
9381
|
+
const entity = {
|
|
9382
|
+
id: nextId,
|
|
9383
|
+
...identity.fields,
|
|
9384
|
+
createdAt: now,
|
|
9385
|
+
updatedAt: now
|
|
9386
|
+
};
|
|
9387
|
+
fillDefaults(entity, collection);
|
|
9388
|
+
seed[collection].push(entity);
|
|
9389
|
+
debug(`Added identity to ${collection}: ${JSON.stringify(identity.fields)}`);
|
|
9390
|
+
}
|
|
9391
|
+
function processCollection(spec, seed, twinName, existingLabels, warnings, now) {
|
|
9392
|
+
const collection = spec.name;
|
|
9393
|
+
if (!seed[collection]) {
|
|
9394
|
+
seed[collection] = [];
|
|
9395
|
+
}
|
|
9396
|
+
const existingCount = seed[collection].length;
|
|
9397
|
+
if (existingCount > 0) {
|
|
9398
|
+
debug(`Clearing ${existingCount} existing ${collection} entities (blueprint replaces)`);
|
|
9399
|
+
seed[collection] = [];
|
|
9400
|
+
}
|
|
9401
|
+
const referencedLabels = /* @__PURE__ */ new Set();
|
|
9402
|
+
for (const group of spec.groups) {
|
|
9403
|
+
const labels = group.properties["labels"];
|
|
9404
|
+
if (Array.isArray(labels)) {
|
|
9405
|
+
for (const l of labels) {
|
|
9406
|
+
const name = String(l);
|
|
9407
|
+
if (!existingLabels.has(name)) referencedLabels.add(name);
|
|
9408
|
+
}
|
|
9409
|
+
}
|
|
9410
|
+
}
|
|
9411
|
+
if (referencedLabels.size > 0 && seed["labels"]) {
|
|
9412
|
+
const labelColors = ["d73a4a", "a2eeef", "0e8a16", "fbca04", "d876e3", "e4e669", "f9d0c4", "bfdadc"];
|
|
9413
|
+
let labelId = getMaxId(seed["labels"]) + 1;
|
|
9414
|
+
const repoId = findFirstRepoId(seed);
|
|
9415
|
+
for (const name of referencedLabels) {
|
|
9416
|
+
const color = labelColors[labelId % labelColors.length];
|
|
9417
|
+
seed["labels"].push({
|
|
9418
|
+
id: labelId,
|
|
9419
|
+
repoId: repoId ?? 1,
|
|
9420
|
+
nodeId: generateNodeId("LA_kwDOBlab", labelId),
|
|
9421
|
+
name,
|
|
9422
|
+
description: `Label: ${name}`,
|
|
9423
|
+
color,
|
|
9424
|
+
isDefault: false,
|
|
9425
|
+
createdAt: daysAgo(60, now),
|
|
9426
|
+
updatedAt: daysAgo(60, now)
|
|
9427
|
+
});
|
|
9428
|
+
existingLabels.add(name);
|
|
9429
|
+
labelId++;
|
|
9430
|
+
}
|
|
9431
|
+
}
|
|
9432
|
+
let entityIndex = 0;
|
|
9433
|
+
for (const group of spec.groups) {
|
|
9434
|
+
for (let i = 0; i < group.count; i++) {
|
|
9435
|
+
const entity = buildEntity(
|
|
9436
|
+
collection,
|
|
9437
|
+
twinName,
|
|
9438
|
+
group.properties,
|
|
9439
|
+
seed,
|
|
9440
|
+
entityIndex,
|
|
9441
|
+
spec.contentHint,
|
|
9442
|
+
now
|
|
9443
|
+
);
|
|
9444
|
+
seed[collection].push(entity);
|
|
9445
|
+
entityIndex++;
|
|
9446
|
+
}
|
|
9447
|
+
}
|
|
9448
|
+
debug(`Built ${entityIndex} entities for ${collection}`);
|
|
9449
|
+
}
|
|
9450
|
+
function buildEntity(collection, twinName, properties, seed, index, contentHint, now) {
|
|
9451
|
+
const nextId = getMaxId(seed[collection]) + 1;
|
|
9452
|
+
const temporal = resolveTemporalProperties(properties, now);
|
|
9453
|
+
if (twinName === "github") return buildGitHubEntity(collection, nextId, properties, seed, index, temporal, contentHint);
|
|
9454
|
+
if (twinName === "slack") return buildSlackEntity(collection, nextId, properties, seed, index, temporal, contentHint);
|
|
9455
|
+
if (twinName === "linear") return buildLinearEntity(collection, nextId, properties, seed, index, temporal, contentHint);
|
|
9456
|
+
if (twinName === "stripe") return buildStripeEntity(collection, nextId, properties, seed, index, temporal, contentHint);
|
|
9457
|
+
if (twinName === "jira") return buildJiraEntity(collection, nextId, properties, seed, index, temporal, contentHint);
|
|
9458
|
+
return {
|
|
9459
|
+
id: nextId,
|
|
9460
|
+
...properties,
|
|
9461
|
+
createdAt: temporal.createdAt,
|
|
9462
|
+
updatedAt: temporal.updatedAt
|
|
9463
|
+
};
|
|
9464
|
+
}
|
|
9465
|
+
function buildGitHubEntity(collection, id, props, seed, index, temporal, contentHint) {
|
|
9466
|
+
const repoId = findFirstRepoId(seed) ?? 1;
|
|
9467
|
+
const owner = findRepoOwner(seed) ?? "acme";
|
|
9468
|
+
switch (collection) {
|
|
9469
|
+
case "issues": {
|
|
9470
|
+
const state = String(props["state"] ?? "open");
|
|
9471
|
+
const number = getMaxIssueNumber(seed) + 1;
|
|
9472
|
+
const labels = resolveLabels(props, []);
|
|
9473
|
+
const title = ISSUE_TITLES[index % ISSUE_TITLES.length];
|
|
9474
|
+
const body = ISSUE_BODIES[index % ISSUE_BODIES.length];
|
|
9475
|
+
const assigned = props["assigned"] === true;
|
|
9476
|
+
return {
|
|
9477
|
+
id,
|
|
9478
|
+
repoId,
|
|
9479
|
+
nodeId: generateNodeId("I_kwDOBiss", id),
|
|
9480
|
+
number,
|
|
9481
|
+
title: contentHint ? `${title} (${contentHint})` : title,
|
|
9482
|
+
body,
|
|
9483
|
+
state,
|
|
9484
|
+
stateReason: state === "closed" ? "completed" : null,
|
|
9485
|
+
locked: false,
|
|
9486
|
+
assignees: assigned ? [owner] : [],
|
|
9487
|
+
labels,
|
|
9488
|
+
milestone: null,
|
|
9489
|
+
authorLogin: owner,
|
|
9490
|
+
closedAt: state === "closed" ? temporal.updatedAt : null,
|
|
9491
|
+
closedBy: state === "closed" ? owner : null,
|
|
9492
|
+
htmlUrl: `https://github.com/${owner}/webapp/issues/${number}`,
|
|
9493
|
+
isPullRequest: false,
|
|
9494
|
+
reactions: { ...DEFAULT_REACTIONS },
|
|
9495
|
+
createdAt: temporal.createdAt,
|
|
9496
|
+
updatedAt: temporal.updatedAt
|
|
9497
|
+
};
|
|
9498
|
+
}
|
|
9499
|
+
case "pullRequests": {
|
|
9500
|
+
const state = String(props["state"] ?? "open");
|
|
9501
|
+
const number = getMaxPRNumber(seed) + 1;
|
|
9502
|
+
const title = PR_TITLES[index % PR_TITLES.length];
|
|
9503
|
+
const sha = generateSha();
|
|
9504
|
+
const baseSha = generateSha();
|
|
9505
|
+
return {
|
|
9506
|
+
id,
|
|
9507
|
+
repoId,
|
|
9508
|
+
nodeId: generateNodeId("PR_kwDOBpr", id),
|
|
9509
|
+
number,
|
|
9510
|
+
title,
|
|
9511
|
+
body: `This PR addresses ${title.toLowerCase()}.`,
|
|
9512
|
+
state,
|
|
9513
|
+
merged: props["merged"] === true || state === "closed",
|
|
9514
|
+
draft: props["draft"] === true,
|
|
9515
|
+
headRef: `feature/fix-${index}`,
|
|
9516
|
+
headSha: sha,
|
|
9517
|
+
baseRef: "main",
|
|
9518
|
+
baseSha,
|
|
9519
|
+
authorLogin: owner,
|
|
9520
|
+
labels: resolveLabels(props, []),
|
|
9521
|
+
assignees: [],
|
|
9522
|
+
requestedReviewers: [],
|
|
9523
|
+
milestone: null,
|
|
9524
|
+
additions: 10 + index * 5,
|
|
9525
|
+
deletions: 3 + index * 2,
|
|
9526
|
+
changedFiles: 1 + index % 5,
|
|
9527
|
+
commits: 1 + index % 3,
|
|
9528
|
+
comments: 0,
|
|
9529
|
+
reviewComments: 0,
|
|
9530
|
+
maintainerCanModify: true,
|
|
9531
|
+
mergeable: state === "open",
|
|
9532
|
+
mergedAt: props["merged"] === true ? temporal.updatedAt : null,
|
|
9533
|
+
mergedBy: props["merged"] === true ? owner : null,
|
|
9534
|
+
mergeCommitSha: props["merged"] === true ? generateSha() : null,
|
|
9535
|
+
closedAt: state === "closed" ? temporal.updatedAt : null,
|
|
9536
|
+
autoMerge: null,
|
|
9537
|
+
htmlUrl: `https://github.com/${owner}/webapp/pull/${number}`,
|
|
9538
|
+
diffUrl: `https://github.com/${owner}/webapp/pull/${number}.diff`,
|
|
9539
|
+
patchUrl: `https://github.com/${owner}/webapp/pull/${number}.patch`,
|
|
9540
|
+
createdAt: temporal.createdAt,
|
|
9541
|
+
updatedAt: temporal.updatedAt
|
|
9542
|
+
};
|
|
9543
|
+
}
|
|
9544
|
+
case "comments": {
|
|
9545
|
+
const issueNumber = resolveIssueNumberForComment(seed, index);
|
|
9546
|
+
return {
|
|
9547
|
+
id,
|
|
9548
|
+
repoId,
|
|
9549
|
+
nodeId: generateNodeId("IC_kwDOBcom", id),
|
|
9550
|
+
issueNumber,
|
|
9551
|
+
body: props["body"] ? String(props["body"]) : `Comment on issue #${issueNumber}`,
|
|
9552
|
+
authorLogin: owner,
|
|
9553
|
+
authorAssociation: "MEMBER",
|
|
9554
|
+
htmlUrl: `https://github.com/${owner}/webapp/issues/${issueNumber}#issuecomment-${id}`,
|
|
9555
|
+
reactions: { ...DEFAULT_REACTIONS },
|
|
9556
|
+
createdAt: temporal.createdAt,
|
|
9557
|
+
updatedAt: temporal.updatedAt
|
|
9558
|
+
};
|
|
9559
|
+
}
|
|
9560
|
+
case "labels": {
|
|
9561
|
+
const colors = ["d73a4a", "a2eeef", "0e8a16", "fbca04", "d876e3"];
|
|
9562
|
+
return {
|
|
9563
|
+
id,
|
|
9564
|
+
repoId,
|
|
9565
|
+
nodeId: generateNodeId("LA_kwDOBlab", id),
|
|
9566
|
+
name: props["name"] ? String(props["name"]) : `label-${id}`,
|
|
9567
|
+
description: props["description"] ? String(props["description"]) : null,
|
|
9568
|
+
color: colors[id % colors.length],
|
|
9569
|
+
isDefault: false,
|
|
9570
|
+
createdAt: temporal.createdAt,
|
|
9571
|
+
updatedAt: temporal.updatedAt
|
|
9572
|
+
};
|
|
9573
|
+
}
|
|
9574
|
+
default:
|
|
9575
|
+
return {
|
|
9576
|
+
id,
|
|
9577
|
+
repoId,
|
|
9578
|
+
...props,
|
|
9579
|
+
createdAt: temporal.createdAt,
|
|
9580
|
+
updatedAt: temporal.updatedAt
|
|
9581
|
+
};
|
|
9582
|
+
}
|
|
9583
|
+
}
|
|
9584
|
+
function buildSlackEntity(collection, id, props, seed, index, temporal, contentHint) {
|
|
9585
|
+
const teamId = findSlackTeamId(seed) ?? "T0001TEAM";
|
|
9586
|
+
switch (collection) {
|
|
9587
|
+
case "channels": {
|
|
9588
|
+
const channelId = generateSlackId("C", index);
|
|
9589
|
+
const name = props["name"] ? String(props["name"]) : `channel-${index + 1}`;
|
|
9590
|
+
const userIds = (seed["users"] ?? []).map((u) => u["user_id"]).filter(Boolean);
|
|
9591
|
+
return {
|
|
9592
|
+
id,
|
|
9593
|
+
channel_id: channelId,
|
|
9594
|
+
name,
|
|
9595
|
+
is_channel: true,
|
|
9596
|
+
is_group: false,
|
|
9597
|
+
is_im: false,
|
|
9598
|
+
is_mpim: false,
|
|
9599
|
+
is_private: props["private"] === true,
|
|
9600
|
+
is_archived: false,
|
|
9601
|
+
is_general: name === "general",
|
|
9602
|
+
creator: userIds[0] ?? "U0001AAAA",
|
|
9603
|
+
topic: { value: "", creator: "", last_set: 0 },
|
|
9604
|
+
purpose: { value: CHANNEL_PURPOSES[index % CHANNEL_PURPOSES.length], creator: "", last_set: 0 },
|
|
9605
|
+
members: userIds,
|
|
9606
|
+
num_members: userIds.length,
|
|
9607
|
+
created: Math.floor(new Date(temporal.createdAt).getTime() / 1e3),
|
|
9608
|
+
updated: Math.floor(new Date(temporal.updatedAt).getTime() / 1e3),
|
|
9609
|
+
createdAt: temporal.createdAt,
|
|
9610
|
+
updatedAt: temporal.updatedAt
|
|
9611
|
+
};
|
|
9612
|
+
}
|
|
9613
|
+
case "messages": {
|
|
9614
|
+
const channels = seed["channels"] ?? [];
|
|
9615
|
+
const channelId = channels.length > 0 ? String(channels[index % channels.length]["channel_id"] ?? "C0001AAAA") : "C0001AAAA";
|
|
9616
|
+
const users = seed["users"] ?? [];
|
|
9617
|
+
const userId = users.length > 0 ? String(users[index % users.length]["user_id"] ?? "U0001AAAA") : "U0001AAAA";
|
|
9618
|
+
const baseTs = Math.floor(new Date(temporal.createdAt).getTime() / 1e3);
|
|
9619
|
+
const ts = generateSlackTs(baseTs, index);
|
|
9620
|
+
return {
|
|
9621
|
+
id,
|
|
9622
|
+
ts,
|
|
9623
|
+
channel_id: channelId,
|
|
9624
|
+
user_id: userId,
|
|
9625
|
+
text: props["text"] ? String(props["text"]) : MESSAGE_TEXTS[index % MESSAGE_TEXTS.length],
|
|
9626
|
+
type: "message",
|
|
9627
|
+
subtype: null,
|
|
9628
|
+
thread_ts: props["isReply"] === true ? generateSlackTs(baseTs, 0) : null,
|
|
9629
|
+
reply_count: 0,
|
|
9630
|
+
reply_users: [],
|
|
9631
|
+
reply_users_count: 0,
|
|
9632
|
+
latest_reply: null,
|
|
9633
|
+
createdAt: temporal.createdAt,
|
|
9634
|
+
updatedAt: temporal.updatedAt
|
|
9635
|
+
};
|
|
9636
|
+
}
|
|
9637
|
+
case "users": {
|
|
9638
|
+
const userId = generateSlackId("U", index);
|
|
9639
|
+
const name = props["name"] ? String(props["name"]) : `user${index + 1}`;
|
|
9640
|
+
return {
|
|
9641
|
+
id,
|
|
9642
|
+
user_id: userId,
|
|
9643
|
+
team_id: teamId,
|
|
9644
|
+
name,
|
|
9645
|
+
real_name: props["real_name"] ? String(props["real_name"]) : `User ${index + 1}`,
|
|
9646
|
+
display_name: name,
|
|
9647
|
+
email: `${name}@example.com`,
|
|
9648
|
+
is_admin: props["is_admin"] === true,
|
|
9649
|
+
is_owner: false,
|
|
9650
|
+
is_bot: props["is_bot"] === true,
|
|
9651
|
+
is_restricted: false,
|
|
9652
|
+
is_ultra_restricted: false,
|
|
9653
|
+
deleted: false,
|
|
9654
|
+
color: "4bbe2e",
|
|
9655
|
+
timezone: "America/Los_Angeles",
|
|
9656
|
+
tz_label: "Pacific Daylight Time",
|
|
9657
|
+
tz_offset: -25200,
|
|
9658
|
+
is_email_confirmed: true,
|
|
9659
|
+
who_can_share_contact_card: "EVERYONE",
|
|
9660
|
+
createdAt: temporal.createdAt,
|
|
9661
|
+
updatedAt: temporal.updatedAt
|
|
9662
|
+
};
|
|
9663
|
+
}
|
|
9664
|
+
default:
|
|
9665
|
+
return {
|
|
9666
|
+
id,
|
|
9667
|
+
...props,
|
|
9668
|
+
createdAt: temporal.createdAt,
|
|
9669
|
+
updatedAt: temporal.updatedAt
|
|
9670
|
+
};
|
|
9671
|
+
}
|
|
9672
|
+
}
|
|
9673
|
+
function buildLinearEntity(collection, id, props, seed, index, temporal, contentHint) {
|
|
9674
|
+
switch (collection) {
|
|
9675
|
+
case "issues": {
|
|
9676
|
+
const teamId = findFirstId(seed, "teams") ?? 1;
|
|
9677
|
+
const team = (seed["teams"] ?? []).find((t) => t["id"] === teamId);
|
|
9678
|
+
const teamKey = team ? String(team["key"] ?? "ENG") : "ENG";
|
|
9679
|
+
const number = index + 1;
|
|
9680
|
+
const stateId = resolveLinearStateId(seed, props);
|
|
9681
|
+
const priority = typeof props["priority"] === "number" ? props["priority"] : 3;
|
|
9682
|
+
const priorityMap = {
|
|
9683
|
+
0: "No priority",
|
|
9684
|
+
1: "Urgent",
|
|
9685
|
+
2: "High",
|
|
9686
|
+
3: "Medium",
|
|
9687
|
+
4: "Low"
|
|
9688
|
+
};
|
|
9689
|
+
return {
|
|
9690
|
+
id,
|
|
9691
|
+
linearId: generateUuid(),
|
|
9692
|
+
teamId,
|
|
9693
|
+
number,
|
|
9694
|
+
identifier: `${teamKey}-${number}`,
|
|
9695
|
+
title: ISSUE_TITLES[index % ISSUE_TITLES.length],
|
|
9696
|
+
description: null,
|
|
9697
|
+
descriptionData: null,
|
|
9698
|
+
priority,
|
|
9699
|
+
priorityLabel: priorityMap[priority] ?? "Medium",
|
|
9700
|
+
stateId,
|
|
9701
|
+
assigneeId: null,
|
|
9702
|
+
creatorId: null,
|
|
9703
|
+
projectId: null,
|
|
9704
|
+
cycleId: null,
|
|
9705
|
+
parentId: null,
|
|
9706
|
+
labelIds: [],
|
|
9707
|
+
estimate: null,
|
|
9708
|
+
dueDate: null,
|
|
9709
|
+
subIssueSortOrder: null,
|
|
9710
|
+
sortOrder: index * -100,
|
|
9711
|
+
snoozedUntilAt: null,
|
|
9712
|
+
startedAt: null,
|
|
9713
|
+
completedAt: null,
|
|
9714
|
+
canceledAt: null,
|
|
9715
|
+
archivedAt: null,
|
|
9716
|
+
trashed: false,
|
|
9717
|
+
createdAt: temporal.createdAt,
|
|
9718
|
+
updatedAt: temporal.updatedAt
|
|
9719
|
+
};
|
|
9720
|
+
}
|
|
9721
|
+
default:
|
|
9722
|
+
return {
|
|
9723
|
+
id,
|
|
9724
|
+
linearId: generateUuid(),
|
|
9725
|
+
...props,
|
|
9726
|
+
createdAt: temporal.createdAt,
|
|
9727
|
+
updatedAt: temporal.updatedAt
|
|
9728
|
+
};
|
|
9729
|
+
}
|
|
9730
|
+
}
|
|
9731
|
+
function buildStripeEntity(collection, id, props, seed, index, temporal, contentHint) {
|
|
9732
|
+
switch (collection) {
|
|
9733
|
+
case "paymentIntents": {
|
|
9734
|
+
const piId = generateStripeId("pi_", index);
|
|
9735
|
+
const amount = typeof props["amount"] === "number" ? props["amount"] : 1e3 + index * 500;
|
|
9736
|
+
const status = String(props["status"] ?? "succeeded");
|
|
9737
|
+
return {
|
|
9738
|
+
id,
|
|
9739
|
+
paymentIntentId: piId,
|
|
9740
|
+
amount,
|
|
9741
|
+
currency: String(props["currency"] ?? "usd"),
|
|
9742
|
+
status,
|
|
9743
|
+
customerId: props["customerId"] ? String(props["customerId"]) : null,
|
|
9744
|
+
description: props["description"] ? String(props["description"]) : null,
|
|
9745
|
+
paymentMethodId: null,
|
|
9746
|
+
captureMethod: "automatic",
|
|
9747
|
+
confirmationMethod: "automatic",
|
|
9748
|
+
canceledAt: null,
|
|
9749
|
+
cancellationReason: null,
|
|
9750
|
+
latestChargeId: null,
|
|
9751
|
+
metadata: {},
|
|
9752
|
+
livemode: false,
|
|
9753
|
+
created: Math.floor(new Date(temporal.createdAt).getTime() / 1e3),
|
|
9754
|
+
createdAt: temporal.createdAt,
|
|
9755
|
+
updatedAt: temporal.updatedAt
|
|
9756
|
+
};
|
|
9757
|
+
}
|
|
9758
|
+
case "customers": {
|
|
9759
|
+
const custId = generateStripeId("cus_", index);
|
|
9760
|
+
return {
|
|
9761
|
+
id,
|
|
9762
|
+
customerId: custId,
|
|
9763
|
+
name: props["name"] ? String(props["name"]) : `Customer ${index + 1}`,
|
|
9764
|
+
email: props["email"] ? String(props["email"]) : `customer${index + 1}@example.com`,
|
|
9765
|
+
phone: null,
|
|
9766
|
+
description: null,
|
|
9767
|
+
currency: null,
|
|
9768
|
+
defaultPaymentMethod: null,
|
|
9769
|
+
address: null,
|
|
9770
|
+
shipping: null,
|
|
9771
|
+
balance: 0,
|
|
9772
|
+
delinquent: false,
|
|
9773
|
+
metadata: {},
|
|
9774
|
+
livemode: false,
|
|
9775
|
+
created: Math.floor(new Date(temporal.createdAt).getTime() / 1e3),
|
|
9776
|
+
createdAt: temporal.createdAt,
|
|
9777
|
+
updatedAt: temporal.updatedAt
|
|
9778
|
+
};
|
|
9779
|
+
}
|
|
9780
|
+
default:
|
|
9781
|
+
return {
|
|
9782
|
+
id,
|
|
9783
|
+
...props,
|
|
9784
|
+
created: Math.floor(new Date(temporal.createdAt).getTime() / 1e3),
|
|
9785
|
+
createdAt: temporal.createdAt,
|
|
9786
|
+
updatedAt: temporal.updatedAt
|
|
9787
|
+
};
|
|
9788
|
+
}
|
|
9789
|
+
}
|
|
9790
|
+
function buildJiraEntity(collection, id, props, seed, index, temporal, contentHint) {
|
|
9791
|
+
switch (collection) {
|
|
9792
|
+
case "issues": {
|
|
9793
|
+
const projectId = findFirstId(seed, "projects") ?? 1;
|
|
9794
|
+
const project = (seed["projects"] ?? []).find((p) => p["id"] === projectId);
|
|
9795
|
+
const projectKey = project ? String(project["key"] ?? "PROJ") : "PROJ";
|
|
9796
|
+
const number = index + 1;
|
|
9797
|
+
return {
|
|
9798
|
+
id,
|
|
9799
|
+
key: `${projectKey}-${number}`,
|
|
9800
|
+
projectId,
|
|
9801
|
+
issueTypeId: findFirstId(seed, "issueTypes") ?? 1,
|
|
9802
|
+
summary: ISSUE_TITLES[index % ISSUE_TITLES.length],
|
|
9803
|
+
description: null,
|
|
9804
|
+
statusId: findFirstId(seed, "statuses") ?? 1,
|
|
9805
|
+
priorityId: findFirstId(seed, "priorities") ?? 1,
|
|
9806
|
+
reporterAccountId: findFirstAccountId(seed),
|
|
9807
|
+
assigneeAccountId: props["assigned"] === true ? findFirstAccountId(seed) : null,
|
|
9808
|
+
parentKey: null,
|
|
9809
|
+
storyPoints: null,
|
|
9810
|
+
resolution: null,
|
|
9811
|
+
resolutionDate: null,
|
|
9812
|
+
labels: resolveLabels(props, []),
|
|
9813
|
+
components: [],
|
|
9814
|
+
fixVersions: [],
|
|
9815
|
+
createdAt: temporal.createdAt,
|
|
9816
|
+
updatedAt: temporal.updatedAt
|
|
9817
|
+
};
|
|
9818
|
+
}
|
|
9819
|
+
default:
|
|
9820
|
+
return {
|
|
9821
|
+
id,
|
|
9822
|
+
...props,
|
|
9823
|
+
createdAt: temporal.createdAt,
|
|
9824
|
+
updatedAt: temporal.updatedAt
|
|
9825
|
+
};
|
|
9826
|
+
}
|
|
9827
|
+
}
|
|
9828
|
+
function findFirstRepoId(seed) {
|
|
9829
|
+
const repos = seed["repos"];
|
|
9830
|
+
return repos?.[0]?.["id"];
|
|
9831
|
+
}
|
|
9832
|
+
function findRepoOwner(seed) {
|
|
9833
|
+
const repos = seed["repos"];
|
|
9834
|
+
return repos?.[0]?.["owner"];
|
|
9835
|
+
}
|
|
9836
|
+
function findFirstId(seed, collection) {
|
|
9837
|
+
const entities = seed[collection];
|
|
9838
|
+
return entities?.[0]?.["id"];
|
|
9839
|
+
}
|
|
9840
|
+
function findFirstAccountId(seed) {
|
|
9841
|
+
const users = seed["users"];
|
|
9842
|
+
return String(users?.[0]?.["accountId"] ?? "account-1");
|
|
9843
|
+
}
|
|
9844
|
+
function findSlackTeamId(seed) {
|
|
9845
|
+
const workspaces = seed["workspaces"];
|
|
9846
|
+
if (workspaces?.[0]) return String(workspaces[0]["team_id"]);
|
|
9847
|
+
const users = seed["users"];
|
|
9848
|
+
if (users?.[0]) return String(users[0]["team_id"] ?? "T0001TEAM");
|
|
9849
|
+
return void 0;
|
|
9850
|
+
}
|
|
9851
|
+
function getMaxIssueNumber(seed) {
|
|
9852
|
+
let max = 0;
|
|
9853
|
+
for (const issue of seed["issues"] ?? []) {
|
|
9854
|
+
const n = issue["number"];
|
|
9855
|
+
if (typeof n === "number" && n > max) max = n;
|
|
9856
|
+
}
|
|
9857
|
+
return max;
|
|
9858
|
+
}
|
|
9859
|
+
function getMaxPRNumber(seed) {
|
|
9860
|
+
let max = 0;
|
|
9861
|
+
for (const pr of seed["pullRequests"] ?? []) {
|
|
9862
|
+
const n = pr["number"];
|
|
9863
|
+
if (typeof n === "number" && n > max) max = n;
|
|
9864
|
+
}
|
|
9865
|
+
return Math.max(max, getMaxIssueNumber(seed));
|
|
9866
|
+
}
|
|
9867
|
+
function resolveIssueNumberForComment(seed, index) {
|
|
9868
|
+
const issues = seed["issues"] ?? [];
|
|
9869
|
+
if (issues.length === 0) return 1;
|
|
9870
|
+
const issue = issues[index % issues.length];
|
|
9871
|
+
return issue["number"] ?? 1;
|
|
9872
|
+
}
|
|
9873
|
+
function resolveLinearStateId(seed, props) {
|
|
9874
|
+
const states = seed["workflowStates"] ?? [];
|
|
9875
|
+
if (states.length === 0) return 1;
|
|
9876
|
+
const state = props["state"];
|
|
9877
|
+
if (state === "completed" || state === "closed" || state === "done") {
|
|
9878
|
+
const found = states.find((s) => s["type"] === "completed");
|
|
9879
|
+
if (found) return found["id"];
|
|
9880
|
+
}
|
|
9881
|
+
if (state === "cancelled" || state === "canceled") {
|
|
9882
|
+
const found = states.find((s) => s["type"] === "cancelled");
|
|
9883
|
+
if (found) return found["id"];
|
|
9884
|
+
}
|
|
9885
|
+
if (state === "in_progress" || state === "started") {
|
|
9886
|
+
const found = states.find((s) => s["type"] === "started");
|
|
9887
|
+
if (found) return found["id"];
|
|
9888
|
+
}
|
|
9889
|
+
if (state === "backlog") {
|
|
9890
|
+
const found = states.find((s) => s["type"] === "backlog");
|
|
9891
|
+
if (found) return found["id"];
|
|
9892
|
+
}
|
|
9893
|
+
const unstarted = states.find((s) => s["type"] === "unstarted");
|
|
9894
|
+
return unstarted?.["id"] ?? states[0]["id"];
|
|
9895
|
+
}
|
|
9896
|
+
function fillDefaults(entity, collection) {
|
|
9897
|
+
switch (collection) {
|
|
9898
|
+
case "repos":
|
|
9899
|
+
entity["fullName"] = entity["fullName"] ?? `${entity["owner"] ?? "org"}/${entity["name"] ?? "repo"}`;
|
|
9900
|
+
entity["private"] = entity["private"] ?? false;
|
|
9901
|
+
entity["fork"] = entity["fork"] ?? false;
|
|
9902
|
+
entity["defaultBranch"] = entity["defaultBranch"] ?? "main";
|
|
9903
|
+
entity["archived"] = entity["archived"] ?? false;
|
|
9904
|
+
entity["disabled"] = entity["disabled"] ?? false;
|
|
9905
|
+
entity["visibility"] = entity["visibility"] ?? "public";
|
|
9906
|
+
entity["hasIssues"] = entity["hasIssues"] ?? true;
|
|
9907
|
+
entity["topics"] = entity["topics"] ?? [];
|
|
9908
|
+
entity["openIssuesCount"] = entity["openIssuesCount"] ?? 0;
|
|
9909
|
+
entity["stargazersCount"] = entity["stargazersCount"] ?? 0;
|
|
9910
|
+
entity["forksCount"] = entity["forksCount"] ?? 0;
|
|
9911
|
+
entity["watchersCount"] = entity["watchersCount"] ?? 0;
|
|
9912
|
+
break;
|
|
9913
|
+
case "users":
|
|
9914
|
+
entity["type"] = entity["type"] ?? "User";
|
|
9915
|
+
entity["siteAdmin"] = entity["siteAdmin"] ?? false;
|
|
9916
|
+
entity["publicRepos"] = entity["publicRepos"] ?? 0;
|
|
9917
|
+
entity["followers"] = entity["followers"] ?? 0;
|
|
9918
|
+
entity["following"] = entity["following"] ?? 0;
|
|
9919
|
+
break;
|
|
9920
|
+
}
|
|
9921
|
+
}
|
|
9922
|
+
|
|
9042
9923
|
// src/runner/dynamic-seed-generator.ts
|
|
9924
|
+
function extractCountRequirements(setupText) {
|
|
9925
|
+
const requirements = [];
|
|
9926
|
+
const seen = /* @__PURE__ */ new Set();
|
|
9927
|
+
const NON_ENTITY = /* @__PURE__ */ new Set([
|
|
9928
|
+
"minutes",
|
|
9929
|
+
"minute",
|
|
9930
|
+
"hours",
|
|
9931
|
+
"hour",
|
|
9932
|
+
"days",
|
|
9933
|
+
"day",
|
|
9934
|
+
"weeks",
|
|
9935
|
+
"week",
|
|
9936
|
+
"months",
|
|
9937
|
+
"month",
|
|
9938
|
+
"years",
|
|
9939
|
+
"year",
|
|
9940
|
+
"seconds",
|
|
9941
|
+
"second",
|
|
9942
|
+
"ms",
|
|
9943
|
+
"am",
|
|
9944
|
+
"pm",
|
|
9945
|
+
"st",
|
|
9946
|
+
"nd",
|
|
9947
|
+
"rd",
|
|
9948
|
+
"th",
|
|
9949
|
+
"usd",
|
|
9950
|
+
"eur",
|
|
9951
|
+
"gbp",
|
|
9952
|
+
"percent",
|
|
9953
|
+
"kb",
|
|
9954
|
+
"mb",
|
|
9955
|
+
"gb",
|
|
9956
|
+
"tb"
|
|
9957
|
+
]);
|
|
9958
|
+
const patterns = [
|
|
9959
|
+
/\b(\d+)\s+([\w\s]+?)(?:\s+(?:that|which|are|with|in|labeled|assigned|have|has)\b)/gi,
|
|
9960
|
+
/\b(\d+)\s+([\w\s]+?)(?:[.,;:)]|$)/gm
|
|
9961
|
+
];
|
|
9962
|
+
for (const pattern of patterns) {
|
|
9963
|
+
for (const match of setupText.matchAll(pattern)) {
|
|
9964
|
+
const count = parseInt(match[1], 10);
|
|
9965
|
+
const subject = match[2].trim().toLowerCase();
|
|
9966
|
+
if (!subject || count <= 0 || count > 200) continue;
|
|
9967
|
+
const firstWord = subject.split(/\s+/)[0] ?? "";
|
|
9968
|
+
if (NON_ENTITY.has(firstWord)) continue;
|
|
9969
|
+
if (/^(?:of|and|or|the|those|these|them)\b/.test(subject)) continue;
|
|
9970
|
+
const key = `${count}:${subject}`;
|
|
9971
|
+
if (seen.has(key)) continue;
|
|
9972
|
+
seen.add(key);
|
|
9973
|
+
requirements.push(`- ${subject}: exactly ${count}`);
|
|
9974
|
+
}
|
|
9975
|
+
}
|
|
9976
|
+
return requirements;
|
|
9977
|
+
}
|
|
9043
9978
|
var DynamicSeedError = class extends Error {
|
|
9044
9979
|
twinName;
|
|
9045
9980
|
validationErrors;
|
|
9046
9981
|
constructor(twinName, validationErrors) {
|
|
9047
9982
|
const details = validationErrors.length > 0 ? `:
|
|
9048
9983
|
${validationErrors.map((e) => ` - ${e}`).join("\n")}` : ".";
|
|
9049
|
-
|
|
9984
|
+
const hint = "\n\nHint: Try `--static-seed` to skip dynamic generation and use pre-built seeds instead.";
|
|
9985
|
+
super(`Dynamic seed generation failed for twin "${twinName}"${details}${hint}`);
|
|
9050
9986
|
this.name = "DynamicSeedError";
|
|
9051
9987
|
this.twinName = twinName;
|
|
9052
9988
|
this.validationErrors = validationErrors;
|
|
@@ -9280,6 +10216,14 @@ ${context.expectedBehavior}
|
|
|
9280
10216
|
prompt += context.successCriteria.map((criterion) => `- ${criterion}`).join("\n");
|
|
9281
10217
|
prompt += "\n\n";
|
|
9282
10218
|
}
|
|
10219
|
+
const countReqs = extractCountRequirements(setupDescription);
|
|
10220
|
+
if (countReqs.length > 0) {
|
|
10221
|
+
prompt += `## MANDATORY Count Requirements
|
|
10222
|
+
The final seed state MUST have these exact counts. Do NOT deviate:
|
|
10223
|
+
`;
|
|
10224
|
+
prompt += countReqs.join("\n");
|
|
10225
|
+
prompt += "\n\n";
|
|
10226
|
+
}
|
|
9283
10227
|
prompt += `## Setup Description
|
|
9284
10228
|
This is a multi-service scenario. Generate ONLY the ${twinName} seed data. Ignore setup details that belong to other services. Faithfully reproduce EVERY detail relevant to ${twinName}. Specific names, messages, amounts, and entities mentioned MUST exist in the generated data.
|
|
9285
10229
|
|
|
@@ -9682,6 +10626,87 @@ function parseSeedPatchResponse(text, twinName) {
|
|
|
9682
10626
|
});
|
|
9683
10627
|
return null;
|
|
9684
10628
|
}
|
|
10629
|
+
async function tryBlueprintPath(twinName, baseSeedData, setupDescription, availableCollections, config, intent) {
|
|
10630
|
+
try {
|
|
10631
|
+
const blueprintConfig = {
|
|
10632
|
+
apiKey: config.apiKey,
|
|
10633
|
+
model: config.model,
|
|
10634
|
+
baseUrl: config.baseUrl,
|
|
10635
|
+
providerMode: config.providerMode
|
|
10636
|
+
};
|
|
10637
|
+
const blueprint = await extractBlueprint(
|
|
10638
|
+
setupDescription,
|
|
10639
|
+
twinName,
|
|
10640
|
+
availableCollections,
|
|
10641
|
+
blueprintConfig
|
|
10642
|
+
);
|
|
10643
|
+
if (!blueprint) {
|
|
10644
|
+
debug("Blueprint extraction returned null");
|
|
10645
|
+
return null;
|
|
10646
|
+
}
|
|
10647
|
+
if (blueprint.collections.length === 0 && blueprint.identities.length === 0) {
|
|
10648
|
+
debug("Blueprint extraction returned empty blueprint");
|
|
10649
|
+
return null;
|
|
10650
|
+
}
|
|
10651
|
+
debug("Blueprint extracted", {
|
|
10652
|
+
identities: String(blueprint.identities.length),
|
|
10653
|
+
collections: blueprint.collections.map((c) => `${c.name}:${c.totalCount}`).join(", ")
|
|
10654
|
+
});
|
|
10655
|
+
const { seed: builtSeed, warnings } = buildSeedFromBlueprint(blueprint, baseSeedData);
|
|
10656
|
+
if (warnings.length > 0) {
|
|
10657
|
+
debug("Blueprint builder warnings", { warnings: warnings.join("; ") });
|
|
10658
|
+
}
|
|
10659
|
+
let finalSeed = normalizeSeedData(builtSeed, twinName);
|
|
10660
|
+
finalSeed = autoFillMissingFKs(finalSeed, twinName);
|
|
10661
|
+
const relValidation = validateSeedRelationships(finalSeed, twinName);
|
|
10662
|
+
if (!relValidation.valid) {
|
|
10663
|
+
warn("Blueprint seed failed relationship validation", {
|
|
10664
|
+
errors: relValidation.errors.slice(0, 5).join("; ")
|
|
10665
|
+
});
|
|
10666
|
+
return null;
|
|
10667
|
+
}
|
|
10668
|
+
if (intent) {
|
|
10669
|
+
const coverage = validateSeedCoverage(intent, finalSeed);
|
|
10670
|
+
if (!coverage.valid) {
|
|
10671
|
+
warn("Blueprint seed failed coverage validation", {
|
|
10672
|
+
errors: coverage.issues.map((i) => i.message).join("; ")
|
|
10673
|
+
});
|
|
10674
|
+
return null;
|
|
10675
|
+
}
|
|
10676
|
+
}
|
|
10677
|
+
const flatForVerify = {};
|
|
10678
|
+
flatForVerify[twinName] = finalSeed;
|
|
10679
|
+
const countMismatches = verifySeedCounts(setupDescription, flatForVerify);
|
|
10680
|
+
if (countMismatches.length > 0) {
|
|
10681
|
+
debug("Blueprint seed has count mismatches (acceptable)", {
|
|
10682
|
+
mismatches: countMismatches.map((m) => `${m.subject}: ${m.expected} vs ${m.actual}`).join("; ")
|
|
10683
|
+
});
|
|
10684
|
+
}
|
|
10685
|
+
const syntheticPatch = {
|
|
10686
|
+
add: {}
|
|
10687
|
+
};
|
|
10688
|
+
for (const [collection, entities] of Object.entries(finalSeed)) {
|
|
10689
|
+
const baseCount = baseSeedData[collection]?.length ?? 0;
|
|
10690
|
+
if (entities.length > baseCount) {
|
|
10691
|
+
syntheticPatch.add[collection] = entities.slice(baseCount).map((e) => {
|
|
10692
|
+
const { id: _id, createdAt: _ca, updatedAt: _ua, ...rest } = e;
|
|
10693
|
+
return rest;
|
|
10694
|
+
});
|
|
10695
|
+
}
|
|
10696
|
+
}
|
|
10697
|
+
return {
|
|
10698
|
+
seed: finalSeed,
|
|
10699
|
+
patch: syntheticPatch,
|
|
10700
|
+
fromCache: false,
|
|
10701
|
+
source: "llm"
|
|
10702
|
+
// Blueprint still uses LLM for extraction
|
|
10703
|
+
};
|
|
10704
|
+
} catch (err) {
|
|
10705
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
10706
|
+
warn(`Blueprint path failed: ${msg}`);
|
|
10707
|
+
return null;
|
|
10708
|
+
}
|
|
10709
|
+
}
|
|
9685
10710
|
async function generateDynamicSeed(twinName, baseSeedName, baseSeedData, setupDescription, config, intent, context) {
|
|
9686
10711
|
const cacheScope = {
|
|
9687
10712
|
baseSeedData,
|
|
@@ -9703,6 +10728,28 @@ async function generateDynamicSeed(twinName, baseSeedName, baseSeedData, setupDe
|
|
|
9703
10728
|
"No API key configured for seed generation. Set ARCHAL_TOKEN or configure a provider API key."
|
|
9704
10729
|
]);
|
|
9705
10730
|
}
|
|
10731
|
+
progress(`Generating dynamic seed for ${twinName}...`);
|
|
10732
|
+
const availableCollections = Object.keys(baseSeedData);
|
|
10733
|
+
const blueprintResult = await tryBlueprintPath(
|
|
10734
|
+
twinName,
|
|
10735
|
+
baseSeedData,
|
|
10736
|
+
setupDescription,
|
|
10737
|
+
availableCollections,
|
|
10738
|
+
config,
|
|
10739
|
+
intent
|
|
10740
|
+
);
|
|
10741
|
+
if (blueprintResult) {
|
|
10742
|
+
info("Dynamic seed generated via blueprint", { twin: twinName });
|
|
10743
|
+
if (!config.noCache) {
|
|
10744
|
+
const cacheContext = buildSeedCacheContext(twinName, intent, context);
|
|
10745
|
+
cacheSeed(twinName, baseSeedName, setupDescription, blueprintResult.seed, blueprintResult.patch, {
|
|
10746
|
+
baseSeedData,
|
|
10747
|
+
cacheContext
|
|
10748
|
+
});
|
|
10749
|
+
}
|
|
10750
|
+
return blueprintResult;
|
|
10751
|
+
}
|
|
10752
|
+
debug("Blueprint path failed or produced invalid seed, falling back to full LLM generation");
|
|
9706
10753
|
const userPrompt = buildSeedGenerationPrompt(
|
|
9707
10754
|
twinName,
|
|
9708
10755
|
baseSeedData,
|
|
@@ -9710,7 +10757,6 @@ async function generateDynamicSeed(twinName, baseSeedName, baseSeedData, setupDe
|
|
|
9710
10757
|
intent,
|
|
9711
10758
|
context
|
|
9712
10759
|
);
|
|
9713
|
-
progress(`Generating dynamic seed for ${twinName}...`);
|
|
9714
10760
|
let patch = null;
|
|
9715
10761
|
let mergedSeed = null;
|
|
9716
10762
|
let lastErrors = [];
|
|
@@ -9860,6 +10906,22 @@ Fix these issues:
|
|
|
9860
10906
|
continue;
|
|
9861
10907
|
}
|
|
9862
10908
|
}
|
|
10909
|
+
if (mergedSeed && setupDescription && validationAttempts < MAX_ATTEMPTS - 1) {
|
|
10910
|
+
const flatForVerify = {};
|
|
10911
|
+
flatForVerify[twinName] = mergedSeed;
|
|
10912
|
+
const countMismatches = verifySeedCounts(setupDescription, flatForVerify);
|
|
10913
|
+
if (countMismatches.length > 0) {
|
|
10914
|
+
const countErrors = countMismatches.map(
|
|
10915
|
+
(m) => `Count mismatch: "${m.subject}" should be ${m.expected} but got ${m.actual}`
|
|
10916
|
+
);
|
|
10917
|
+
warn(`Seed count mismatch (attempt ${attempt + 1})`, {
|
|
10918
|
+
errors: countErrors.join("; ")
|
|
10919
|
+
});
|
|
10920
|
+
lastErrors = countErrors;
|
|
10921
|
+
validationAttempts++;
|
|
10922
|
+
continue;
|
|
10923
|
+
}
|
|
10924
|
+
}
|
|
9863
10925
|
break;
|
|
9864
10926
|
} catch (err) {
|
|
9865
10927
|
const message = err instanceof Error ? err.message : String(err);
|
|
@@ -12132,7 +13194,10 @@ function createRunCommand() {
|
|
|
12132
13194
|
).addOption(new Option("--openclaw-url <url>", "Deprecated alias for --engine-endpoint").hideHelp()).addOption(new Option("--openclaw-token <token>", "Deprecated alias for --engine-token").hideHelp()).addOption(new Option("--openclaw-agent <id>", "Deprecated alias for --engine-model").hideHelp()).addOption(new Option("--openclaw-twin-urls <path>", "Deprecated alias for --engine-twin-urls").hideHelp()).addOption(new Option("--openclaw-timeout <seconds>", "Deprecated alias for --engine-timeout").hideHelp()).option("--api-base-urls <path>", "Path to JSON mapping service names to clone API base URLs for raw API code routing").option("--api-proxy-url <url>", "Proxy URL for raw API code routing metadata").option("--preflight-only", "Run environment/config preflight checks only and exit").option("--seed-cache", "Enable seed cache for dynamic generation (off by default)").option("--static-seed", "Use seed files as-is without LLM mutation (uses --seed name or auto-selected per twin)").option("--no-failure-analysis", "Skip LLM failure analysis on imperfect scores").option(
|
|
12133
13195
|
"--allow-ambiguous-seed",
|
|
12134
13196
|
"Allow dynamic seed generation when setup is underspecified"
|
|
12135
|
-
).option("--tag <tag>", "Only run if scenario has this tag (exit 0 if not)").option("-q, --quiet", "Suppress non-error output").option("-v, --verbose", "Enable debug logging").action(async (scenarioArg, opts) => {
|
|
13197
|
+
).option("--tag <tag>", "Only run if scenario has this tag (exit 0 if not)").option("-q, --quiet", "Suppress non-error output").option("-v, --verbose", "Enable debug logging").action(async (scenarioArg, opts, command) => {
|
|
13198
|
+
const parentOpts = command.parent?.opts() ?? {};
|
|
13199
|
+
if (parentOpts.quiet) opts.quiet = true;
|
|
13200
|
+
if (parentOpts.verbose) opts.verbose = true;
|
|
12136
13201
|
if (opts.quiet) {
|
|
12137
13202
|
configureLogger({ quiet: true });
|
|
12138
13203
|
}
|
|
@@ -12405,9 +13470,16 @@ function createRunCommand() {
|
|
|
12405
13470
|
}
|
|
12406
13471
|
if (opts.apiKey?.trim()) {
|
|
12407
13472
|
warnIfKeyLooksInvalid(opts.apiKey.trim(), "--api-key");
|
|
12408
|
-
|
|
13473
|
+
const key = opts.apiKey.trim();
|
|
13474
|
+
process.env["ARCHAL_ENGINE_API_KEY"] = key;
|
|
13475
|
+
if (key.startsWith("AIza")) {
|
|
13476
|
+
process.env["GEMINI_API_KEY"] = process.env["GEMINI_API_KEY"] || key;
|
|
13477
|
+
} else if (key.startsWith("sk-ant-")) {
|
|
13478
|
+
process.env["ANTHROPIC_API_KEY"] = process.env["ANTHROPIC_API_KEY"] || key;
|
|
13479
|
+
} else if (key.startsWith("sk-")) {
|
|
13480
|
+
process.env["OPENAI_API_KEY"] = process.env["OPENAI_API_KEY"] || key;
|
|
13481
|
+
}
|
|
12409
13482
|
if (!opts.engineModel && !process.env["ARCHAL_ENGINE_MODEL"] && !opts.model?.trim()) {
|
|
12410
|
-
const key = opts.apiKey.trim();
|
|
12411
13483
|
if (key.startsWith("AIza")) {
|
|
12412
13484
|
opts.engineModel = "gemini-2.0-flash";
|
|
12413
13485
|
} else if (key.startsWith("sk-ant-")) {
|
|
@@ -12578,13 +13650,13 @@ function createRunCommand() {
|
|
|
12578
13650
|
let statusReadySinceMs = null;
|
|
12579
13651
|
const isRetryablePollFailure = (result) => result.offline || typeof result.status === "number" && result.status >= 500;
|
|
12580
13652
|
const sleepForPollInterval = async () => new Promise((resolve12) => setTimeout(resolve12, SESSION_POLL_INTERVAL_MS));
|
|
12581
|
-
process.stderr.write("Starting cloud session...\n");
|
|
13653
|
+
if (!opts.quiet) process.stderr.write("Starting cloud session...\n");
|
|
12582
13654
|
let pollCount = 0;
|
|
12583
13655
|
while (Date.now() < readyDeadline) {
|
|
12584
13656
|
pollCount++;
|
|
12585
13657
|
if (pollCount % 4 === 0) {
|
|
12586
13658
|
const elapsedSec = Math.round((Date.now() - (readyDeadline - SESSION_READY_TIMEOUT_MS)) / 1e3);
|
|
12587
|
-
process.stderr.write(` Still waiting for session to be ready (${elapsedSec}s)...
|
|
13659
|
+
if (!opts.quiet) process.stderr.write(` Still waiting for session to be ready (${elapsedSec}s)...
|
|
12588
13660
|
`);
|
|
12589
13661
|
}
|
|
12590
13662
|
const freshCreds = getCredentials();
|
|
@@ -12655,7 +13727,7 @@ function createRunCommand() {
|
|
|
12655
13727
|
}
|
|
12656
13728
|
if (sessionReady) {
|
|
12657
13729
|
const warmupSec = Math.round((Date.now() - (readyDeadline - SESSION_READY_TIMEOUT_MS)) / 1e3);
|
|
12658
|
-
process.stderr.write(`Cloud session ready (${warmupSec}s).
|
|
13730
|
+
if (!opts.quiet) process.stderr.write(`Cloud session ready (${warmupSec}s).
|
|
12659
13731
|
`);
|
|
12660
13732
|
}
|
|
12661
13733
|
if (!sessionReady && !runFailureMessage) {
|
|
@@ -13018,12 +14090,33 @@ function buildEvidenceArtifacts(report) {
|
|
|
13018
14090
|
runIndex: run.runIndex,
|
|
13019
14091
|
steps: buildAgentTraceSteps(run)
|
|
13020
14092
|
})).filter((run) => run.steps.length > 0);
|
|
14093
|
+
const evaluations = reportRuns.flatMap(
|
|
14094
|
+
(run) => (run.evaluations ?? []).map((ev) => ({
|
|
14095
|
+
runIndex: run.runIndex,
|
|
14096
|
+
criterionId: ev.criterionId,
|
|
14097
|
+
status: ev.status,
|
|
14098
|
+
result: ev.status,
|
|
14099
|
+
confidence: ev.confidence,
|
|
14100
|
+
explanation: ev.explanation
|
|
14101
|
+
}))
|
|
14102
|
+
);
|
|
14103
|
+
const latestEvaluationByCriterion = /* @__PURE__ */ new Map();
|
|
14104
|
+
for (const evaluation of evaluations) {
|
|
14105
|
+
const current = latestEvaluationByCriterion.get(evaluation.criterionId);
|
|
14106
|
+
if (!current || evaluation.runIndex >= current.runIndex) {
|
|
14107
|
+
latestEvaluationByCriterion.set(evaluation.criterionId, evaluation);
|
|
14108
|
+
}
|
|
14109
|
+
}
|
|
13021
14110
|
const criteria = Object.entries(report.criterionDescriptions ?? {}).map(
|
|
13022
|
-
([id, description]) =>
|
|
13023
|
-
id
|
|
13024
|
-
|
|
13025
|
-
|
|
13026
|
-
|
|
14111
|
+
([id, description]) => {
|
|
14112
|
+
const latest = latestEvaluationByCriterion.get(id);
|
|
14113
|
+
return {
|
|
14114
|
+
id,
|
|
14115
|
+
label: description,
|
|
14116
|
+
kind: report.criterionTypes?.[id] ?? null,
|
|
14117
|
+
result: latest?.result ?? null
|
|
14118
|
+
};
|
|
14119
|
+
}
|
|
13027
14120
|
);
|
|
13028
14121
|
const runs = reportRuns.map((run) => ({
|
|
13029
14122
|
runIndex: run.runIndex,
|
|
@@ -13033,6 +14126,7 @@ function buildEvidenceArtifacts(report) {
|
|
|
13033
14126
|
evaluations: (run.evaluations ?? []).map((ev) => ({
|
|
13034
14127
|
criterionId: ev.criterionId,
|
|
13035
14128
|
status: ev.status,
|
|
14129
|
+
result: ev.status,
|
|
13036
14130
|
confidence: ev.confidence,
|
|
13037
14131
|
explanation: ev.explanation
|
|
13038
14132
|
}))
|
|
@@ -13041,6 +14135,7 @@ function buildEvidenceArtifacts(report) {
|
|
|
13041
14135
|
satisfaction: report.satisfactionScore,
|
|
13042
14136
|
scores: reportRuns.map((r) => r.overallScore),
|
|
13043
14137
|
criteria,
|
|
14138
|
+
evaluations,
|
|
13044
14139
|
runs,
|
|
13045
14140
|
traceEntries,
|
|
13046
14141
|
thinkingTraceEntries,
|
|
@@ -15118,9 +16213,10 @@ function findBundledScenarios() {
|
|
|
15118
16213
|
return results;
|
|
15119
16214
|
}
|
|
15120
16215
|
function detectProviderName(model) {
|
|
15121
|
-
|
|
15122
|
-
if (
|
|
15123
|
-
if (
|
|
16216
|
+
const normalized = model.toLowerCase();
|
|
16217
|
+
if (normalized.startsWith("gemini-")) return "Gemini";
|
|
16218
|
+
if (normalized.startsWith("claude-") || normalized.startsWith("sonnet-") || normalized.startsWith("haiku-") || normalized.startsWith("opus-")) return "Anthropic";
|
|
16219
|
+
if (normalized.startsWith("gpt-") || normalized.startsWith("o1-") || normalized.startsWith("o3-") || normalized.startsWith("o4-")) return "OpenAI";
|
|
15124
16220
|
return "OpenAI-compatible";
|
|
15125
16221
|
}
|
|
15126
16222
|
function resolveEngineApiKey(explicitKey) {
|
|
@@ -15189,6 +16285,13 @@ Set one via:
|
|
|
15189
16285
|
process.exit(1);
|
|
15190
16286
|
}
|
|
15191
16287
|
process.env["ARCHAL_ENGINE_API_KEY"] = engineApiKey;
|
|
16288
|
+
if (engineApiKey.startsWith("AIza")) {
|
|
16289
|
+
process.env["GEMINI_API_KEY"] = process.env["GEMINI_API_KEY"] || engineApiKey;
|
|
16290
|
+
} else if (engineApiKey.startsWith("sk-ant-")) {
|
|
16291
|
+
process.env["ANTHROPIC_API_KEY"] = process.env["ANTHROPIC_API_KEY"] || engineApiKey;
|
|
16292
|
+
} else if (engineApiKey.startsWith("sk-")) {
|
|
16293
|
+
process.env["OPENAI_API_KEY"] = process.env["OPENAI_API_KEY"] || engineApiKey;
|
|
16294
|
+
}
|
|
15192
16295
|
const runs = parseInt(opts.runs, 10);
|
|
15193
16296
|
if (Number.isNaN(runs) || runs <= 0) {
|
|
15194
16297
|
process.stderr.write("Error: --runs must be a positive integer\n");
|