@jonit-dev/night-watch-cli 1.8.12-beta.3 → 1.8.12-beta.5
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/cli.js +1732 -383
- package/dist/commands/analytics.d.ts.map +1 -1
- package/dist/commands/analytics.js +60 -0
- package/dist/commands/analytics.js.map +1 -1
- package/dist/commands/audit.d.ts.map +1 -1
- package/dist/commands/audit.js +45 -0
- package/dist/commands/audit.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +1 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +30 -3
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +45 -0
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/qa.d.ts.map +1 -1
- package/dist/commands/qa.js +24 -0
- package/dist/commands/qa.js.map +1 -1
- package/dist/commands/resolve.d.ts.map +1 -1
- package/dist/commands/resolve.js +26 -0
- package/dist/commands/resolve.js.map +1 -1
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +46 -1
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts +16 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +85 -1
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/shared/feedback.d.ts +24 -0
- package/dist/commands/shared/feedback.d.ts.map +1 -0
- package/dist/commands/shared/feedback.js +38 -0
- package/dist/commands/shared/feedback.js.map +1 -0
- package/dist/commands/slice.d.ts.map +1 -1
- package/dist/commands/slice.js +48 -1
- package/dist/commands/slice.js.map +1 -1
- package/dist/scripts/night-watch-cron.sh +185 -23
- package/dist/scripts/night-watch-pr-reviewer-cron.sh +4 -0
- package/dist/web/assets/index-DpvzoXEv.js +442 -0
- package/dist/web/assets/index-DyME41HV.css +1 -0
- package/dist/web/index.html +2 -2
- package/package.json +3 -1
package/dist/cli.js
CHANGED
|
@@ -390,7 +390,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
390
390
|
return `claude-proxy:${baseUrl}`;
|
|
391
391
|
}
|
|
392
392
|
}
|
|
393
|
-
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT_TARGET_COLUMN, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_PR_RESOLVER_ENABLED, DEFAULT_PR_RESOLVER_SCHEDULE, DEFAULT_PR_RESOLVER_MAX_RUNTIME, DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN, DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT, DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION, DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION, DEFAULT_PR_RESOLVER_READY_LABEL, DEFAULT_PR_RESOLVER, DEFAULT_MERGER_ENABLED, DEFAULT_MERGER_SCHEDULE, DEFAULT_MERGER_MAX_RUNTIME, DEFAULT_MERGER_MERGE_METHOD, DEFAULT_MERGER_MIN_REVIEW_SCORE, DEFAULT_MERGER_REBASE_BEFORE_MERGE, DEFAULT_MERGER_MAX_PRS_PER_RUN, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
|
|
393
|
+
var DEFAULT_DEFAULT_BRANCH, DEFAULT_PRD_DIR, DEFAULT_SUMMARY_WINDOW_HOURS, DEFAULT_MAX_RUNTIME, DEFAULT_REVIEWER_MAX_RUNTIME, DEFAULT_CRON_SCHEDULE, DEFAULT_REVIEWER_SCHEDULE, DEFAULT_CRON_SCHEDULE_OFFSET, DEFAULT_MAX_RETRIES, DEFAULT_REVIEWER_MAX_RETRIES, DEFAULT_REVIEWER_RETRY_DELAY, DEFAULT_REVIEWER_MAX_PRS_PER_RUN, DEFAULT_FEEDBACK, DEFAULT_BRANCH_PREFIX, DEFAULT_BRANCH_PATTERNS, DEFAULT_MIN_REVIEW_SCORE, DEFAULT_MAX_LOG_SIZE, DEFAULT_PROVIDER, DEFAULT_EXECUTOR_ENABLED, DEFAULT_REVIEWER_ENABLED, DEFAULT_PROVIDER_ENV, DEFAULT_FALLBACK_ON_RATE_LIMIT, DEFAULT_CLAUDE_MODEL, DEFAULT_PRIMARY_FALLBACK_MODEL, DEFAULT_SECONDARY_FALLBACK_MODEL, VALID_CLAUDE_MODELS, CLAUDE_MODEL_IDS, DEFAULT_NOTIFICATIONS, DEFAULT_PRD_PRIORITY, DEFAULT_SLICER_SCHEDULE, DEFAULT_SLICER_MAX_RUNTIME, DEFAULT_ROADMAP_SCANNER, DEFAULT_TEMPLATES_DIR, DEFAULT_BOARD_PROVIDER, DEFAULT_LOCAL_BOARD_INFO, DEFAULT_AUTO_MERGE, DEFAULT_AUTO_MERGE_METHOD, VALID_MERGE_METHODS, DEFAULT_QA_ENABLED, DEFAULT_QA_SCHEDULE, DEFAULT_QA_MAX_RUNTIME, DEFAULT_QA_ARTIFACTS, DEFAULT_QA_SKIP_LABEL, DEFAULT_QA_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_QA_VALIDATED_LABEL, DEFAULT_QA, QA_LOG_NAME, DEFAULT_AUDIT_ENABLED, DEFAULT_AUDIT_SCHEDULE, DEFAULT_AUDIT_MAX_RUNTIME, DEFAULT_AUDIT_TARGET_COLUMN, DEFAULT_AUDIT, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_PR_RESOLVER_ENABLED, DEFAULT_PR_RESOLVER_SCHEDULE, DEFAULT_PR_RESOLVER_MAX_RUNTIME, DEFAULT_PR_RESOLVER_MAX_PRS_PER_RUN, DEFAULT_PR_RESOLVER_PER_PR_TIMEOUT, DEFAULT_PR_RESOLVER_AI_CONFLICT_RESOLUTION, DEFAULT_PR_RESOLVER_AI_REVIEW_RESOLUTION, DEFAULT_PR_RESOLVER_READY_LABEL, DEFAULT_PR_RESOLVER, DEFAULT_MERGER_ENABLED, DEFAULT_MERGER_SCHEDULE, DEFAULT_MERGER_MAX_RUNTIME, DEFAULT_MERGER_MERGE_METHOD, DEFAULT_MERGER_MIN_REVIEW_SCORE, DEFAULT_MERGER_REBASE_BEFORE_MERGE, DEFAULT_MERGER_MAX_PRS_PER_RUN, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, VALID_PROVIDERS, VALID_JOB_TYPES, DEFAULT_JOB_PROVIDERS, DEFAULT_PROVIDER_SCHEDULE_OVERRIDES, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS, DEFAULT_WEBHOOK_TRIGGERS, BUILT_IN_PRESETS, BUILT_IN_PRESET_IDS, PROVIDER_COMMANDS, CONFIG_FILE_NAME, LOCK_FILE_PREFIX, LOG_DIR, CLAIM_FILE_EXTENSION, EXECUTOR_LOG_NAME, REVIEWER_LOG_NAME, EXECUTOR_LOG_FILE, REVIEWER_LOG_FILE, LOG_FILE_NAMES, GLOBAL_CONFIG_DIR, REGISTRY_FILE_NAME, HISTORY_FILE_NAME, PRD_STATES_FILE_NAME, STATE_DB_FILE_NAME, GLOBAL_NOTIFICATIONS_FILE_NAME, MAX_HISTORY_RECORDS_PER_PRD, DEFAULT_QUEUE_ENABLED, DEFAULT_QUEUE_MODE, DEFAULT_QUEUE_MAX_CONCURRENCY, DEFAULT_QUEUE_MAX_WAIT_TIME, DEFAULT_QUEUE_PRIORITY, DEFAULT_QUEUE, DEFAULT_SCHEDULING_PRIORITY;
|
|
394
394
|
var init_constants = __esm({
|
|
395
395
|
"../core/dist/constants.js"() {
|
|
396
396
|
"use strict";
|
|
@@ -407,6 +407,13 @@ var init_constants = __esm({
|
|
|
407
407
|
DEFAULT_REVIEWER_MAX_RETRIES = 2;
|
|
408
408
|
DEFAULT_REVIEWER_RETRY_DELAY = 30;
|
|
409
409
|
DEFAULT_REVIEWER_MAX_PRS_PER_RUN = 0;
|
|
410
|
+
DEFAULT_FEEDBACK = {
|
|
411
|
+
enabled: true,
|
|
412
|
+
confidenceThreshold: 0.75,
|
|
413
|
+
augmentationTtlDays: 14,
|
|
414
|
+
maxActiveAugmentations: 3,
|
|
415
|
+
successStreakToExpire: 3
|
|
416
|
+
};
|
|
410
417
|
DEFAULT_BRANCH_PREFIX = "night-watch";
|
|
411
418
|
DEFAULT_BRANCH_PATTERNS = ["feat/", "night-watch/"];
|
|
412
419
|
DEFAULT_MIN_REVIEW_SCORE = 80;
|
|
@@ -692,6 +699,16 @@ function normalizeConfig(rawConfig) {
|
|
|
692
699
|
normalized.reviewerMaxRetries = readNumber(rawConfig.reviewerMaxRetries);
|
|
693
700
|
normalized.reviewerRetryDelay = readNumber(rawConfig.reviewerRetryDelay);
|
|
694
701
|
normalized.reviewerMaxPrsPerRun = readNumber(rawConfig.reviewerMaxPrsPerRun);
|
|
702
|
+
const rawFeedback = readObject2(rawConfig.feedback);
|
|
703
|
+
if (rawFeedback) {
|
|
704
|
+
normalized.feedback = {
|
|
705
|
+
enabled: readBoolean(rawFeedback.enabled) ?? true,
|
|
706
|
+
confidenceThreshold: readNumber(rawFeedback.confidenceThreshold) ?? 0.75,
|
|
707
|
+
augmentationTtlDays: readNumber(rawFeedback.augmentationTtlDays) ?? 14,
|
|
708
|
+
maxActiveAugmentations: readNumber(rawFeedback.maxActiveAugmentations) ?? 3,
|
|
709
|
+
successStreakToExpire: readNumber(rawFeedback.successStreakToExpire) ?? 3
|
|
710
|
+
};
|
|
711
|
+
}
|
|
695
712
|
normalized.provider = validateProvider(String(rawConfig.provider ?? "")) ?? void 0;
|
|
696
713
|
normalized.executorEnabled = readBoolean(rawConfig.executorEnabled);
|
|
697
714
|
normalized.reviewerEnabled = readBoolean(rawConfig.reviewerEnabled);
|
|
@@ -1082,6 +1099,27 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
1082
1099
|
if (!isNaN(v) && v >= 0)
|
|
1083
1100
|
env.reviewerMaxPrsPerRun = v;
|
|
1084
1101
|
}
|
|
1102
|
+
if (process.env.NW_FEEDBACK_ENABLED !== void 0 || process.env.NW_FEEDBACK_CONFIDENCE_THRESHOLD !== void 0 || process.env.NW_FEEDBACK_AUGMENTATION_TTL_DAYS !== void 0 || process.env.NW_FEEDBACK_MAX_ACTIVE_AUGMENTATIONS !== void 0 || process.env.NW_FEEDBACK_SUCCESS_STREAK_TO_EXPIRE !== void 0) {
|
|
1103
|
+
const feedback = { ...fileConfig?.feedback ?? {} };
|
|
1104
|
+
const enabled = process.env.NW_FEEDBACK_ENABLED ? parseBoolean(process.env.NW_FEEDBACK_ENABLED) : null;
|
|
1105
|
+
if (enabled !== null)
|
|
1106
|
+
feedback.enabled = enabled;
|
|
1107
|
+
const confidenceThreshold = parseFloat(process.env.NW_FEEDBACK_CONFIDENCE_THRESHOLD ?? "");
|
|
1108
|
+
if (!Number.isNaN(confidenceThreshold))
|
|
1109
|
+
feedback.confidenceThreshold = confidenceThreshold;
|
|
1110
|
+
const augmentationTtlDays = parseInt(process.env.NW_FEEDBACK_AUGMENTATION_TTL_DAYS ?? "", 10);
|
|
1111
|
+
if (!Number.isNaN(augmentationTtlDays))
|
|
1112
|
+
feedback.augmentationTtlDays = augmentationTtlDays;
|
|
1113
|
+
const maxActiveAugmentations = parseInt(process.env.NW_FEEDBACK_MAX_ACTIVE_AUGMENTATIONS ?? "", 10);
|
|
1114
|
+
if (!Number.isNaN(maxActiveAugmentations)) {
|
|
1115
|
+
feedback.maxActiveAugmentations = maxActiveAugmentations;
|
|
1116
|
+
}
|
|
1117
|
+
const successStreakToExpire = parseInt(process.env.NW_FEEDBACK_SUCCESS_STREAK_TO_EXPIRE ?? "", 10);
|
|
1118
|
+
if (!Number.isNaN(successStreakToExpire)) {
|
|
1119
|
+
feedback.successStreakToExpire = successStreakToExpire;
|
|
1120
|
+
}
|
|
1121
|
+
env.feedback = feedback;
|
|
1122
|
+
}
|
|
1085
1123
|
if (process.env.NW_PROVIDER) {
|
|
1086
1124
|
const p = validateProvider(process.env.NW_PROVIDER);
|
|
1087
1125
|
if (p !== null)
|
|
@@ -1297,6 +1335,7 @@ function getDefaultConfig() {
|
|
|
1297
1335
|
qa: { ...DEFAULT_QA },
|
|
1298
1336
|
audit: { ...DEFAULT_AUDIT },
|
|
1299
1337
|
analytics: { ...DEFAULT_ANALYTICS },
|
|
1338
|
+
feedback: { ...DEFAULT_FEEDBACK },
|
|
1300
1339
|
prResolver: { ...DEFAULT_PR_RESOLVER },
|
|
1301
1340
|
merger: { ...DEFAULT_MERGER },
|
|
1302
1341
|
jobProviders: { ...DEFAULT_JOB_PROVIDERS },
|
|
@@ -1495,7 +1534,7 @@ function mergeConfigLayer(base, layer) {
|
|
|
1495
1534
|
})
|
|
1496
1535
|
}
|
|
1497
1536
|
};
|
|
1498
|
-
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "prResolver" || _key === "merger") {
|
|
1537
|
+
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "feedback" || _key === "prResolver" || _key === "merger") {
|
|
1499
1538
|
base[_key] = {
|
|
1500
1539
|
...base[_key],
|
|
1501
1540
|
...value
|
|
@@ -1529,6 +1568,13 @@ function mergeConfigs(base, fileConfig, envConfig) {
|
|
|
1529
1568
|
merged.reviewerMaxRetries = sanitizeReviewerMaxRetries(merged.reviewerMaxRetries, DEFAULT_REVIEWER_MAX_RETRIES);
|
|
1530
1569
|
merged.reviewerRetryDelay = sanitizeReviewerRetryDelay(merged.reviewerRetryDelay, DEFAULT_REVIEWER_RETRY_DELAY);
|
|
1531
1570
|
merged.reviewerMaxPrsPerRun = sanitizeReviewerMaxPrsPerRun(merged.reviewerMaxPrsPerRun, DEFAULT_REVIEWER_MAX_PRS_PER_RUN);
|
|
1571
|
+
merged.feedback = {
|
|
1572
|
+
enabled: merged.feedback.enabled !== false,
|
|
1573
|
+
confidenceThreshold: Math.max(0, Math.min(1, merged.feedback.confidenceThreshold)),
|
|
1574
|
+
augmentationTtlDays: Math.max(1, Math.min(365, Math.floor(merged.feedback.augmentationTtlDays))),
|
|
1575
|
+
maxActiveAugmentations: Math.max(0, Math.min(10, Math.floor(merged.feedback.maxActiveAugmentations))),
|
|
1576
|
+
successStreakToExpire: Math.max(0, Math.min(20, Math.floor(merged.feedback.successStreakToExpire)))
|
|
1577
|
+
};
|
|
1532
1578
|
if (merged.secondaryFallbackModel === void 0) {
|
|
1533
1579
|
merged.secondaryFallbackModel = merged.primaryFallbackModel === void 0 ? DEFAULT_SECONDARY_FALLBACK_MODEL : merged.primaryFallbackModel;
|
|
1534
1580
|
}
|
|
@@ -2053,339 +2099,337 @@ var init_roadmap_state_repository = __esm({
|
|
|
2053
2099
|
}
|
|
2054
2100
|
});
|
|
2055
2101
|
|
|
2056
|
-
// ../core/
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2102
|
+
// ../core/dist/storage/repositories/sqlite/session-outcome.repository.js
|
|
2103
|
+
import Database6 from "better-sqlite3";
|
|
2104
|
+
import { inject as inject6, injectable as injectable6 } from "tsyringe";
|
|
2105
|
+
function redactText(value) {
|
|
2106
|
+
return SECRET_TEXT_PATTERNS.reduce((current, [pattern, replacement]) => current.replace(pattern, replacement), value);
|
|
2107
|
+
}
|
|
2108
|
+
function redactOptionalText(value) {
|
|
2109
|
+
return value == null ? null : redactText(value);
|
|
2110
|
+
}
|
|
2111
|
+
function redactMetadataValue(value, key, seen) {
|
|
2112
|
+
if (key && SECRET_KEY_PATTERN.test(key)) {
|
|
2113
|
+
return SECRET_PLACEHOLDER;
|
|
2062
2114
|
}
|
|
2063
|
-
|
|
2064
|
-
|
|
2065
|
-
|
|
2066
|
-
|
|
2067
|
-
|
|
2115
|
+
if (typeof value === "string") {
|
|
2116
|
+
return redactText(value);
|
|
2117
|
+
}
|
|
2118
|
+
if (value === null || typeof value !== "object") {
|
|
2119
|
+
return value;
|
|
2120
|
+
}
|
|
2121
|
+
if (seen.has(value)) {
|
|
2122
|
+
return "[Circular]";
|
|
2123
|
+
}
|
|
2124
|
+
seen.add(value);
|
|
2125
|
+
if (Array.isArray(value)) {
|
|
2126
|
+
const redactedArray = value.map((item) => redactMetadataValue(item, void 0, seen));
|
|
2127
|
+
seen.delete(value);
|
|
2128
|
+
return redactedArray;
|
|
2129
|
+
}
|
|
2130
|
+
const redacted = {};
|
|
2131
|
+
for (const [entryKey, entryValue] of Object.entries(value)) {
|
|
2132
|
+
redacted[entryKey] = redactMetadataValue(entryValue, entryKey, seen);
|
|
2133
|
+
}
|
|
2134
|
+
seen.delete(value);
|
|
2135
|
+
return redacted;
|
|
2068
2136
|
}
|
|
2069
|
-
function
|
|
2070
|
-
const
|
|
2071
|
-
|
|
2072
|
-
|
|
2137
|
+
function redactMetadata(metadata) {
|
|
2138
|
+
const value = redactMetadataValue(metadata ?? {}, void 0, /* @__PURE__ */ new WeakSet());
|
|
2139
|
+
if (typeof value === "object" && value !== null && !Array.isArray(value)) {
|
|
2140
|
+
return value;
|
|
2073
2141
|
}
|
|
2074
|
-
return
|
|
2142
|
+
return {};
|
|
2075
2143
|
}
|
|
2076
|
-
function
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
|
|
2081
|
-
result[job.cliCommand] = job.logName;
|
|
2144
|
+
function parseMetadata(metadataJson) {
|
|
2145
|
+
try {
|
|
2146
|
+
const parsed = JSON.parse(metadataJson);
|
|
2147
|
+
if (typeof parsed === "object" && parsed !== null && !Array.isArray(parsed)) {
|
|
2148
|
+
return parsed;
|
|
2082
2149
|
}
|
|
2150
|
+
} catch {
|
|
2151
|
+
return {};
|
|
2083
2152
|
}
|
|
2084
|
-
return
|
|
2153
|
+
return {};
|
|
2085
2154
|
}
|
|
2086
|
-
|
|
2087
|
-
|
|
2088
|
-
|
|
2155
|
+
function rowToOutcome(row) {
|
|
2156
|
+
return {
|
|
2157
|
+
id: row.id,
|
|
2158
|
+
projectPath: row.project_path,
|
|
2159
|
+
jobType: row.job_type,
|
|
2160
|
+
providerKey: row.provider_key,
|
|
2161
|
+
prdFile: row.prd_file,
|
|
2162
|
+
prNumber: row.pr_number,
|
|
2163
|
+
branchName: row.branch_name,
|
|
2164
|
+
startedAt: row.started_at,
|
|
2165
|
+
finishedAt: row.finished_at,
|
|
2166
|
+
durationSeconds: row.duration_seconds,
|
|
2167
|
+
outcome: row.outcome,
|
|
2168
|
+
exitCode: row.exit_code,
|
|
2169
|
+
attempt: row.attempt,
|
|
2170
|
+
retryCount: row.retry_count,
|
|
2171
|
+
reviewScore: row.review_score,
|
|
2172
|
+
ciStatus: row.ci_status,
|
|
2173
|
+
failureCategory: row.failure_category,
|
|
2174
|
+
failureSignature: row.failure_signature,
|
|
2175
|
+
metadata: parseMetadata(row.metadata_json)
|
|
2176
|
+
};
|
|
2177
|
+
}
|
|
2178
|
+
function rowToPattern(row) {
|
|
2179
|
+
return {
|
|
2180
|
+
id: row.id,
|
|
2181
|
+
projectPath: row.project_path,
|
|
2182
|
+
patternKey: row.pattern_key,
|
|
2183
|
+
jobType: row.job_type,
|
|
2184
|
+
category: row.category,
|
|
2185
|
+
title: row.title,
|
|
2186
|
+
description: row.description,
|
|
2187
|
+
sampleCount: row.sample_count,
|
|
2188
|
+
confidence: row.confidence,
|
|
2189
|
+
firstSeenAt: row.first_seen_at,
|
|
2190
|
+
lastSeenAt: row.last_seen_at,
|
|
2191
|
+
status: row.status,
|
|
2192
|
+
metadata: parseMetadata(row.metadata_json)
|
|
2193
|
+
};
|
|
2194
|
+
}
|
|
2195
|
+
function rowToAugmentation(row) {
|
|
2196
|
+
return {
|
|
2197
|
+
id: row.id,
|
|
2198
|
+
projectPath: row.project_path,
|
|
2199
|
+
patternId: row.pattern_id,
|
|
2200
|
+
jobType: row.job_type,
|
|
2201
|
+
promptText: row.prompt_text,
|
|
2202
|
+
status: row.status,
|
|
2203
|
+
createdAt: row.created_at,
|
|
2204
|
+
updatedAt: row.updated_at,
|
|
2205
|
+
expiresAt: row.expires_at,
|
|
2206
|
+
appliedCount: row.applied_count,
|
|
2207
|
+
successCount: row.success_count
|
|
2208
|
+
};
|
|
2209
|
+
}
|
|
2210
|
+
function buildOutcomeWhere(input) {
|
|
2211
|
+
const clauses = ["project_path = ?"];
|
|
2212
|
+
const params = [input.projectPath];
|
|
2213
|
+
if (input.jobType) {
|
|
2214
|
+
clauses.push("job_type = ?");
|
|
2215
|
+
params.push(input.jobType);
|
|
2216
|
+
}
|
|
2217
|
+
if ("outcome" in input && input.outcome) {
|
|
2218
|
+
clauses.push("outcome = ?");
|
|
2219
|
+
params.push(input.outcome);
|
|
2220
|
+
}
|
|
2221
|
+
if (input.fromFinishedAt != null) {
|
|
2222
|
+
clauses.push("finished_at >= ?");
|
|
2223
|
+
params.push(input.fromFinishedAt);
|
|
2224
|
+
}
|
|
2225
|
+
if (input.toFinishedAt != null) {
|
|
2226
|
+
clauses.push("finished_at <= ?");
|
|
2227
|
+
params.push(input.toFinishedAt);
|
|
2228
|
+
}
|
|
2229
|
+
return { params, where: clauses.join(" AND ") };
|
|
2230
|
+
}
|
|
2231
|
+
var __decorate6, __metadata6, __param6, SECRET_PLACEHOLDER, SECRET_KEY_PATTERN, SECRET_TEXT_PATTERNS, SqliteSessionOutcomeRepository;
|
|
2232
|
+
var init_session_outcome_repository = __esm({
|
|
2233
|
+
"../core/dist/storage/repositories/sqlite/session-outcome.repository.js"() {
|
|
2089
2234
|
"use strict";
|
|
2090
|
-
|
|
2091
|
-
|
|
2092
|
-
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
{
|
|
2123
|
-
id: "pr-resolver",
|
|
2124
|
-
name: "PR Conflict Solver",
|
|
2125
|
-
description: "Resolves merge conflicts via AI rebase; optionally addresses review comments and labels PRs ready-to-merge",
|
|
2126
|
-
cliCommand: "resolve",
|
|
2127
|
-
logName: "pr-resolver",
|
|
2128
|
-
lockSuffix: "-pr-resolver.lock",
|
|
2129
|
-
queuePriority: 35,
|
|
2130
|
-
envPrefix: "NW_PR_RESOLVER",
|
|
2131
|
-
extraFields: [
|
|
2132
|
-
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
2133
|
-
{ name: "maxPrsPerRun", type: "number", defaultValue: 0 },
|
|
2134
|
-
{ name: "perPrTimeout", type: "number", defaultValue: 600 },
|
|
2135
|
-
{ name: "aiConflictResolution", type: "boolean", defaultValue: true },
|
|
2136
|
-
{ name: "aiReviewResolution", type: "boolean", defaultValue: false },
|
|
2137
|
-
{ name: "readyLabel", type: "string", defaultValue: "ready-to-merge" }
|
|
2138
|
-
],
|
|
2139
|
-
defaultConfig: {
|
|
2140
|
-
enabled: true,
|
|
2141
|
-
schedule: "15 6,14,22 * * *",
|
|
2142
|
-
maxRuntime: 3600,
|
|
2143
|
-
branchPatterns: [],
|
|
2144
|
-
maxPrsPerRun: 0,
|
|
2145
|
-
perPrTimeout: 600,
|
|
2146
|
-
aiConflictResolution: true,
|
|
2147
|
-
aiReviewResolution: false,
|
|
2148
|
-
readyLabel: "ready-to-merge"
|
|
2149
|
-
}
|
|
2150
|
-
},
|
|
2151
|
-
{
|
|
2152
|
-
id: "slicer",
|
|
2153
|
-
name: "Slicer",
|
|
2154
|
-
description: "Generates PRDs from roadmap items",
|
|
2155
|
-
cliCommand: "planner",
|
|
2156
|
-
logName: "slicer",
|
|
2157
|
-
lockSuffix: "-slicer.lock",
|
|
2158
|
-
queuePriority: 30,
|
|
2159
|
-
envPrefix: "NW_SLICER",
|
|
2160
|
-
defaultConfig: {
|
|
2161
|
-
enabled: true,
|
|
2162
|
-
schedule: "35 */6 * * *",
|
|
2163
|
-
maxRuntime: 600
|
|
2164
|
-
}
|
|
2165
|
-
},
|
|
2166
|
-
{
|
|
2167
|
-
id: "qa",
|
|
2168
|
-
name: "QA",
|
|
2169
|
-
description: "Runs end-to-end tests on PRs",
|
|
2170
|
-
cliCommand: "qa",
|
|
2171
|
-
logName: "night-watch-qa",
|
|
2172
|
-
lockSuffix: "-qa.lock",
|
|
2173
|
-
queuePriority: 20,
|
|
2174
|
-
envPrefix: "NW_QA",
|
|
2175
|
-
extraFields: [
|
|
2176
|
-
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
2177
|
-
{
|
|
2178
|
-
name: "artifacts",
|
|
2179
|
-
type: "enum",
|
|
2180
|
-
enumValues: ["screenshot", "video", "both"],
|
|
2181
|
-
defaultValue: "both"
|
|
2182
|
-
},
|
|
2183
|
-
{ name: "skipLabel", type: "string", defaultValue: "skip-qa" },
|
|
2184
|
-
{ name: "autoInstallPlaywright", type: "boolean", defaultValue: true },
|
|
2185
|
-
{ name: "validatedLabel", type: "string", defaultValue: "e2e-validated" }
|
|
2186
|
-
],
|
|
2187
|
-
defaultConfig: {
|
|
2188
|
-
enabled: true,
|
|
2189
|
-
schedule: "45 2,10,18 * * *",
|
|
2190
|
-
maxRuntime: 3600,
|
|
2191
|
-
branchPatterns: [],
|
|
2192
|
-
artifacts: "both",
|
|
2193
|
-
skipLabel: "skip-qa",
|
|
2194
|
-
autoInstallPlaywright: true,
|
|
2195
|
-
validatedLabel: "e2e-validated"
|
|
2196
|
-
}
|
|
2197
|
-
},
|
|
2198
|
-
{
|
|
2199
|
-
id: "audit",
|
|
2200
|
-
name: "Auditor",
|
|
2201
|
-
description: "Performs code audits and creates issues for findings",
|
|
2202
|
-
cliCommand: "audit",
|
|
2203
|
-
logName: "audit",
|
|
2204
|
-
lockSuffix: "-audit.lock",
|
|
2205
|
-
queuePriority: 10,
|
|
2206
|
-
envPrefix: "NW_AUDIT",
|
|
2207
|
-
extraFields: [
|
|
2208
|
-
{
|
|
2209
|
-
name: "targetColumn",
|
|
2210
|
-
type: "enum",
|
|
2211
|
-
enumValues: [...BOARD_COLUMNS2],
|
|
2212
|
-
defaultValue: "Draft"
|
|
2213
|
-
}
|
|
2214
|
-
],
|
|
2215
|
-
defaultConfig: {
|
|
2216
|
-
enabled: true,
|
|
2217
|
-
schedule: "50 3 * * 1",
|
|
2218
|
-
maxRuntime: 1800,
|
|
2219
|
-
targetColumn: "Draft"
|
|
2220
|
-
}
|
|
2221
|
-
},
|
|
2222
|
-
{
|
|
2223
|
-
id: "analytics",
|
|
2224
|
-
name: "Analytics",
|
|
2225
|
-
description: "Analyzes product analytics and creates issues for trends",
|
|
2226
|
-
cliCommand: "analytics",
|
|
2227
|
-
logName: "analytics",
|
|
2228
|
-
lockSuffix: "-analytics.lock",
|
|
2229
|
-
queuePriority: 10,
|
|
2230
|
-
envPrefix: "NW_ANALYTICS",
|
|
2231
|
-
extraFields: [
|
|
2232
|
-
{ name: "lookbackDays", type: "number", defaultValue: 7 },
|
|
2233
|
-
{
|
|
2234
|
-
name: "targetColumn",
|
|
2235
|
-
type: "enum",
|
|
2236
|
-
enumValues: [...BOARD_COLUMNS2],
|
|
2237
|
-
defaultValue: "Draft"
|
|
2238
|
-
},
|
|
2239
|
-
{ name: "analysisPrompt", type: "string", defaultValue: "" }
|
|
2240
|
-
],
|
|
2241
|
-
defaultConfig: {
|
|
2242
|
-
enabled: false,
|
|
2243
|
-
schedule: "0 6 * * 1",
|
|
2244
|
-
maxRuntime: 900,
|
|
2245
|
-
lookbackDays: 7,
|
|
2246
|
-
targetColumn: "Draft",
|
|
2247
|
-
analysisPrompt: ""
|
|
2248
|
-
}
|
|
2249
|
-
},
|
|
2250
|
-
{
|
|
2251
|
-
id: "merger",
|
|
2252
|
-
name: "Merge Orchestrator",
|
|
2253
|
-
description: "Repo-wide PR merge coordinator \u2014 scans, rebases, and merges in FIFO order",
|
|
2254
|
-
cliCommand: "merge",
|
|
2255
|
-
logName: "merger",
|
|
2256
|
-
lockSuffix: "-merger.lock",
|
|
2257
|
-
queuePriority: 45,
|
|
2258
|
-
envPrefix: "NW_MERGER",
|
|
2259
|
-
extraFields: [
|
|
2260
|
-
{
|
|
2261
|
-
name: "mergeMethod",
|
|
2262
|
-
type: "enum",
|
|
2263
|
-
enumValues: ["squash", "merge", "rebase"],
|
|
2264
|
-
defaultValue: "squash"
|
|
2265
|
-
},
|
|
2266
|
-
{ name: "minReviewScore", type: "number", defaultValue: 80 },
|
|
2267
|
-
{ name: "branchPatterns", type: "string[]", defaultValue: [] },
|
|
2268
|
-
{ name: "rebaseBeforeMerge", type: "boolean", defaultValue: true },
|
|
2269
|
-
{ name: "maxPrsPerRun", type: "number", defaultValue: 0 }
|
|
2270
|
-
],
|
|
2271
|
-
defaultConfig: {
|
|
2272
|
-
enabled: false,
|
|
2273
|
-
schedule: "55 */4 * * *",
|
|
2274
|
-
maxRuntime: 1800,
|
|
2275
|
-
mergeMethod: "squash",
|
|
2276
|
-
minReviewScore: 80,
|
|
2277
|
-
branchPatterns: [],
|
|
2278
|
-
rebaseBeforeMerge: true,
|
|
2279
|
-
maxPrsPerRun: 0
|
|
2280
|
-
}
|
|
2235
|
+
__decorate6 = function(decorators, target, key, desc) {
|
|
2236
|
+
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
2237
|
+
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
2238
|
+
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
2239
|
+
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
2240
|
+
};
|
|
2241
|
+
__metadata6 = function(k, v) {
|
|
2242
|
+
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
|
|
2243
|
+
};
|
|
2244
|
+
__param6 = function(paramIndex, decorator) {
|
|
2245
|
+
return function(target, key) {
|
|
2246
|
+
decorator(target, key, paramIndex);
|
|
2247
|
+
};
|
|
2248
|
+
};
|
|
2249
|
+
SECRET_PLACEHOLDER = "[REDACTED_SECRET]";
|
|
2250
|
+
SECRET_KEY_PATTERN = /(?:api[_-]?key|authorization|client[_-]?secret|cookie|password|private[_-]?key|secret|token)/i;
|
|
2251
|
+
SECRET_TEXT_PATTERNS = [
|
|
2252
|
+
[
|
|
2253
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
2254
|
+
SECRET_PLACEHOLDER
|
|
2255
|
+
],
|
|
2256
|
+
[/\bsk-ant-[\w-]{20,}\b/g, SECRET_PLACEHOLDER],
|
|
2257
|
+
[/\bsk-[\w-]{20,}\b/g, SECRET_PLACEHOLDER],
|
|
2258
|
+
[/\bgh[opsru]_\w{30,}\b/g, SECRET_PLACEHOLDER],
|
|
2259
|
+
[/\bxox[baprs]-[\w-]{20,}\b/g, SECRET_PLACEHOLDER],
|
|
2260
|
+
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, SECRET_PLACEHOLDER],
|
|
2261
|
+
[/\b(Bearer|Basic)\s+[\w.~+/=-]{12,}/gi, `$1 ${SECRET_PLACEHOLDER}`]
|
|
2262
|
+
];
|
|
2263
|
+
SqliteSessionOutcomeRepository = class SqliteSessionOutcomeRepository2 {
|
|
2264
|
+
db;
|
|
2265
|
+
constructor(db) {
|
|
2266
|
+
this.db = db;
|
|
2281
2267
|
}
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
|
|
2291
|
-
|
|
2292
|
-
|
|
2293
|
-
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2268
|
+
insertOutcome(input) {
|
|
2269
|
+
const metadataJson = JSON.stringify(redactMetadata(input.metadata));
|
|
2270
|
+
const result = this.db.prepare(`INSERT INTO session_outcomes
|
|
2271
|
+
(project_path, job_type, provider_key, prd_file, pr_number, branch_name,
|
|
2272
|
+
started_at, finished_at, duration_seconds, outcome, exit_code, attempt,
|
|
2273
|
+
retry_count, review_score, ci_status, failure_category, failure_signature,
|
|
2274
|
+
metadata_json)
|
|
2275
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`).run(input.projectPath, input.jobType, input.providerKey, input.prdFile ?? null, input.prNumber ?? null, redactOptionalText(input.branchName), input.startedAt, input.finishedAt, input.durationSeconds ?? null, input.outcome, input.exitCode ?? null, input.attempt ?? 1, input.retryCount ?? 0, input.reviewScore ?? null, redactOptionalText(input.ciStatus), redactOptionalText(input.failureCategory), redactOptionalText(input.failureSignature), metadataJson);
|
|
2276
|
+
return this.getOutcomeById(Number(result.lastInsertRowid));
|
|
2277
|
+
}
|
|
2278
|
+
queryOutcomes(input) {
|
|
2279
|
+
const { params, where } = buildOutcomeWhere(input);
|
|
2280
|
+
const limit = Math.min(Math.max(input.limit ?? 100, 1), 500);
|
|
2281
|
+
const rows = this.db.prepare(`SELECT *
|
|
2282
|
+
FROM session_outcomes
|
|
2283
|
+
WHERE ${where}
|
|
2284
|
+
ORDER BY finished_at DESC, id DESC
|
|
2285
|
+
LIMIT ?`).all(...params, limit);
|
|
2286
|
+
return rows.map(rowToOutcome);
|
|
2287
|
+
}
|
|
2288
|
+
querySummary(input) {
|
|
2289
|
+
const { params, where } = buildOutcomeWhere(input);
|
|
2290
|
+
const outcomeRows = this.db.prepare(`SELECT outcome as key, COUNT(*) as count
|
|
2291
|
+
FROM session_outcomes
|
|
2292
|
+
WHERE ${where}
|
|
2293
|
+
GROUP BY outcome`).all(...params);
|
|
2294
|
+
const categoryRows = this.db.prepare(`SELECT failure_category as key, COUNT(*) as count
|
|
2295
|
+
FROM session_outcomes
|
|
2296
|
+
WHERE ${where} AND failure_category IS NOT NULL
|
|
2297
|
+
GROUP BY failure_category`).all(...params);
|
|
2298
|
+
const averageRow = this.db.prepare(`SELECT AVG(duration_seconds) as average_duration
|
|
2299
|
+
FROM session_outcomes
|
|
2300
|
+
WHERE ${where} AND duration_seconds IS NOT NULL`).get(...params);
|
|
2301
|
+
const byOutcome = Object.fromEntries(outcomeRows.map((row) => [row.key ?? "unknown", row.count]));
|
|
2302
|
+
const byFailureCategory = Object.fromEntries(categoryRows.map((row) => [row.key ?? "unknown", row.count]));
|
|
2303
|
+
return {
|
|
2304
|
+
totalCount: outcomeRows.reduce((total, row) => total + row.count, 0),
|
|
2305
|
+
successCount: byOutcome.success ?? 0,
|
|
2306
|
+
failureCount: byOutcome.failure ?? 0,
|
|
2307
|
+
timeoutCount: byOutcome.timeout ?? 0,
|
|
2308
|
+
rateLimitedCount: byOutcome.rate_limited ?? 0,
|
|
2309
|
+
skippedCount: byOutcome.skipped ?? 0,
|
|
2310
|
+
averageDurationSeconds: averageRow?.average_duration ?? null,
|
|
2311
|
+
byOutcome,
|
|
2312
|
+
byFailureCategory
|
|
2313
|
+
};
|
|
2307
2314
|
}
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
|
|
2317
|
-
|
|
2318
|
-
|
|
2319
|
-
|
|
2320
|
-
|
|
2321
|
-
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2325
|
-
|
|
2326
|
-
|
|
2327
|
-
|
|
2328
|
-
|
|
2329
|
-
|
|
2330
|
-
|
|
2331
|
-
}
|
|
2332
|
-
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
},
|
|
2339
|
-
"glm-47": {
|
|
2340
|
-
name: "GLM-4.7",
|
|
2341
|
-
command: "claude",
|
|
2342
|
-
promptFlag: "-p",
|
|
2343
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2344
|
-
modelFlag: "--model",
|
|
2345
|
-
model: "glm-4.7",
|
|
2346
|
-
envVars: {
|
|
2347
|
-
ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
|
|
2348
|
-
API_TIMEOUT_MS: "3000000",
|
|
2349
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-4.7",
|
|
2350
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-4.7"
|
|
2315
|
+
upsertPattern(input) {
|
|
2316
|
+
const now = Date.now();
|
|
2317
|
+
const existing = this.getPattern(input.projectPath, input.patternKey, input.jobType);
|
|
2318
|
+
const firstSeenAt = existing?.firstSeenAt ?? input.firstSeenAt ?? now;
|
|
2319
|
+
const lastSeenAt = input.lastSeenAt ?? now;
|
|
2320
|
+
const sampleCount = input.sampleCount ?? (existing ? existing.sampleCount + 1 : 1);
|
|
2321
|
+
const confidence = input.confidence ?? existing?.confidence ?? 0;
|
|
2322
|
+
const status = input.status ?? existing?.status ?? "observing";
|
|
2323
|
+
const metadataJson = JSON.stringify(redactMetadata(input.metadata ?? existing?.metadata));
|
|
2324
|
+
this.db.prepare(`INSERT INTO feedback_patterns
|
|
2325
|
+
(project_path, pattern_key, job_type, category, title, description, sample_count,
|
|
2326
|
+
confidence, first_seen_at, last_seen_at, status, metadata_json)
|
|
2327
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
2328
|
+
ON CONFLICT(project_path, pattern_key, job_type)
|
|
2329
|
+
DO UPDATE SET category = excluded.category,
|
|
2330
|
+
title = excluded.title,
|
|
2331
|
+
description = excluded.description,
|
|
2332
|
+
sample_count = excluded.sample_count,
|
|
2333
|
+
confidence = excluded.confidence,
|
|
2334
|
+
last_seen_at = excluded.last_seen_at,
|
|
2335
|
+
status = excluded.status,
|
|
2336
|
+
metadata_json = excluded.metadata_json`).run(input.projectPath, input.patternKey, input.jobType, redactText(input.category), redactText(input.title), redactText(input.description), sampleCount, confidence, firstSeenAt, lastSeenAt, status, metadataJson);
|
|
2337
|
+
return this.getPattern(input.projectPath, input.patternKey, input.jobType);
|
|
2338
|
+
}
|
|
2339
|
+
listPatterns(input) {
|
|
2340
|
+
const clauses = ["project_path = ?"];
|
|
2341
|
+
const params = [input.projectPath];
|
|
2342
|
+
if (input.jobType) {
|
|
2343
|
+
clauses.push("job_type = ?");
|
|
2344
|
+
params.push(input.jobType);
|
|
2351
2345
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2346
|
+
if (input.status) {
|
|
2347
|
+
clauses.push("status = ?");
|
|
2348
|
+
params.push(input.status);
|
|
2349
|
+
}
|
|
2350
|
+
const limit = Math.min(Math.max(input.limit ?? 25, 1), 100);
|
|
2351
|
+
const rows = this.db.prepare(`SELECT *
|
|
2352
|
+
FROM feedback_patterns
|
|
2353
|
+
WHERE ${clauses.join(" AND ")}
|
|
2354
|
+
ORDER BY sample_count DESC, confidence DESC, last_seen_at DESC, id DESC
|
|
2355
|
+
LIMIT ?`).all(...params, limit);
|
|
2356
|
+
return rows.map(rowToPattern);
|
|
2357
|
+
}
|
|
2358
|
+
createAugmentation(input) {
|
|
2359
|
+
const now = Date.now();
|
|
2360
|
+
const createdAt = input.createdAt ?? now;
|
|
2361
|
+
const updatedAt = input.updatedAt ?? createdAt;
|
|
2362
|
+
const result = this.db.prepare(`INSERT INTO prompt_augmentations
|
|
2363
|
+
(project_path, pattern_id, job_type, prompt_text, status, created_at, updated_at, expires_at)
|
|
2364
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?)`).run(input.projectPath, input.patternId ?? null, input.jobType, redactText(input.promptText), input.status ?? "active", createdAt, updatedAt, input.expiresAt ?? null);
|
|
2365
|
+
return this.getAugmentationById(Number(result.lastInsertRowid));
|
|
2366
|
+
}
|
|
2367
|
+
listAugmentations(input) {
|
|
2368
|
+
const clauses = ["project_path = ?"];
|
|
2369
|
+
const params = [input.projectPath];
|
|
2370
|
+
if (input.jobType) {
|
|
2371
|
+
clauses.push("job_type = ?");
|
|
2372
|
+
params.push(input.jobType);
|
|
2373
|
+
}
|
|
2374
|
+
if (input.status) {
|
|
2375
|
+
clauses.push("status = ?");
|
|
2376
|
+
params.push(input.status);
|
|
2365
2377
|
}
|
|
2378
|
+
if (!input.includeExpired) {
|
|
2379
|
+
clauses.push("(expires_at IS NULL OR expires_at > ?)");
|
|
2380
|
+
params.push(input.now ?? Date.now());
|
|
2381
|
+
}
|
|
2382
|
+
const limit = Math.min(Math.max(input.limit ?? 100, 1), 250);
|
|
2383
|
+
const rows = this.db.prepare(`SELECT *
|
|
2384
|
+
FROM prompt_augmentations
|
|
2385
|
+
WHERE ${clauses.join(" AND ")}
|
|
2386
|
+
ORDER BY created_at ASC, id ASC
|
|
2387
|
+
LIMIT ?`).all(...params, limit);
|
|
2388
|
+
return rows.map(rowToAugmentation);
|
|
2389
|
+
}
|
|
2390
|
+
listActiveAugmentations(projectPath, jobType, now = Date.now()) {
|
|
2391
|
+
const rows = this.db.prepare(`SELECT *
|
|
2392
|
+
FROM prompt_augmentations
|
|
2393
|
+
WHERE project_path = ?
|
|
2394
|
+
AND job_type = ?
|
|
2395
|
+
AND status = 'active'
|
|
2396
|
+
AND (expires_at IS NULL OR expires_at > ?)
|
|
2397
|
+
ORDER BY created_at ASC, id ASC`).all(projectPath, jobType, now);
|
|
2398
|
+
return rows.map(rowToAugmentation);
|
|
2399
|
+
}
|
|
2400
|
+
updateAugmentationStatus(id, status, projectPath) {
|
|
2401
|
+
const result = projectPath === void 0 ? this.db.prepare("UPDATE prompt_augmentations SET status = ?, updated_at = ? WHERE id = ?").run(status, Date.now(), id) : this.db.prepare(`UPDATE prompt_augmentations
|
|
2402
|
+
SET status = ?, updated_at = ?
|
|
2403
|
+
WHERE id = ? AND project_path = ?`).run(status, Date.now(), id, projectPath);
|
|
2404
|
+
return result.changes > 0 ? this.getAugmentationById(id) : null;
|
|
2405
|
+
}
|
|
2406
|
+
incrementAugmentationCounts(id, success2 = false) {
|
|
2407
|
+
this.db.prepare(`UPDATE prompt_augmentations
|
|
2408
|
+
SET applied_count = applied_count + 1,
|
|
2409
|
+
success_count = success_count + ?,
|
|
2410
|
+
updated_at = ?
|
|
2411
|
+
WHERE id = ?`).run(success2 ? 1 : 0, Date.now(), id);
|
|
2412
|
+
}
|
|
2413
|
+
getOutcomeById(id) {
|
|
2414
|
+
const row = this.db.prepare("SELECT * FROM session_outcomes WHERE id = ?").get(id);
|
|
2415
|
+
return row ? rowToOutcome(row) : null;
|
|
2416
|
+
}
|
|
2417
|
+
getPattern(projectPath, patternKey, jobType) {
|
|
2418
|
+
const row = this.db.prepare(`SELECT *
|
|
2419
|
+
FROM feedback_patterns
|
|
2420
|
+
WHERE project_path = ? AND pattern_key = ? AND job_type = ?`).get(projectPath, patternKey, jobType);
|
|
2421
|
+
return row ? rowToPattern(row) : null;
|
|
2422
|
+
}
|
|
2423
|
+
getAugmentationById(id) {
|
|
2424
|
+
const row = this.db.prepare("SELECT * FROM prompt_augmentations WHERE id = ?").get(id);
|
|
2425
|
+
return row ? rowToAugmentation(row) : null;
|
|
2366
2426
|
}
|
|
2367
2427
|
};
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
LOG_FILE_NAMES2 = getLogFileNames2();
|
|
2374
|
-
GLOBAL_CONFIG_DIR2 = ".night-watch";
|
|
2375
|
-
STATE_DB_FILE_NAME2 = "state.db";
|
|
2376
|
-
DEFAULT_QUEUE_ENABLED2 = true;
|
|
2377
|
-
DEFAULT_QUEUE_MODE2 = "auto";
|
|
2378
|
-
DEFAULT_QUEUE_MAX_CONCURRENCY2 = 3;
|
|
2379
|
-
DEFAULT_QUEUE_MAX_WAIT_TIME2 = 7200;
|
|
2380
|
-
DEFAULT_QUEUE_PRIORITY2 = getDefaultQueuePriority2();
|
|
2381
|
-
DEFAULT_QUEUE2 = {
|
|
2382
|
-
enabled: DEFAULT_QUEUE_ENABLED2,
|
|
2383
|
-
mode: DEFAULT_QUEUE_MODE2,
|
|
2384
|
-
maxConcurrency: DEFAULT_QUEUE_MAX_CONCURRENCY2,
|
|
2385
|
-
maxWaitTime: DEFAULT_QUEUE_MAX_WAIT_TIME2,
|
|
2386
|
-
priority: { ...DEFAULT_QUEUE_PRIORITY2 },
|
|
2387
|
-
providerBuckets: {}
|
|
2388
|
-
};
|
|
2428
|
+
SqliteSessionOutcomeRepository = __decorate6([
|
|
2429
|
+
injectable6(),
|
|
2430
|
+
__param6(0, inject6("Database")),
|
|
2431
|
+
__metadata6("design:paramtypes", [Object])
|
|
2432
|
+
], SqliteSessionOutcomeRepository);
|
|
2389
2433
|
}
|
|
2390
2434
|
});
|
|
2391
2435
|
|
|
@@ -2393,10 +2437,10 @@ var init_constants2 = __esm({
|
|
|
2393
2437
|
import * as fs2 from "fs";
|
|
2394
2438
|
import * as os from "os";
|
|
2395
2439
|
import * as path2 from "path";
|
|
2396
|
-
import
|
|
2440
|
+
import Database7 from "better-sqlite3";
|
|
2397
2441
|
function getDbPath() {
|
|
2398
|
-
const base = process.env.NIGHT_WATCH_HOME || path2.join(os.homedir(),
|
|
2399
|
-
return path2.join(base,
|
|
2442
|
+
const base = process.env.NIGHT_WATCH_HOME || path2.join(os.homedir(), GLOBAL_CONFIG_DIR);
|
|
2443
|
+
return path2.join(base, STATE_DB_FILE_NAME);
|
|
2400
2444
|
}
|
|
2401
2445
|
function getDb() {
|
|
2402
2446
|
if (_db) {
|
|
@@ -2404,7 +2448,7 @@ function getDb() {
|
|
|
2404
2448
|
}
|
|
2405
2449
|
const dbPath = getDbPath();
|
|
2406
2450
|
fs2.mkdirSync(path2.dirname(dbPath), { recursive: true });
|
|
2407
|
-
const db = new
|
|
2451
|
+
const db = new Database7(dbPath);
|
|
2408
2452
|
db.pragma("journal_mode = WAL");
|
|
2409
2453
|
db.pragma("busy_timeout = 5000");
|
|
2410
2454
|
_db = db;
|
|
@@ -2418,8 +2462,8 @@ function closeDb() {
|
|
|
2418
2462
|
}
|
|
2419
2463
|
function createDbForDir(projectDir) {
|
|
2420
2464
|
fs2.mkdirSync(projectDir, { recursive: true });
|
|
2421
|
-
const dbPath = path2.join(projectDir,
|
|
2422
|
-
const db = new
|
|
2465
|
+
const dbPath = path2.join(projectDir, STATE_DB_FILE_NAME);
|
|
2466
|
+
const db = new Database7(dbPath);
|
|
2423
2467
|
db.pragma("journal_mode = WAL");
|
|
2424
2468
|
db.pragma("busy_timeout = 5000");
|
|
2425
2469
|
return db;
|
|
@@ -2428,7 +2472,7 @@ var _db;
|
|
|
2428
2472
|
var init_client = __esm({
|
|
2429
2473
|
"../core/dist/storage/sqlite/client.js"() {
|
|
2430
2474
|
"use strict";
|
|
2431
|
-
|
|
2475
|
+
init_constants();
|
|
2432
2476
|
_db = null;
|
|
2433
2477
|
}
|
|
2434
2478
|
});
|
|
@@ -2545,6 +2589,65 @@ function runMigrations(db) {
|
|
|
2545
2589
|
);
|
|
2546
2590
|
CREATE INDEX IF NOT EXISTS idx_job_runs_lookup
|
|
2547
2591
|
ON job_runs(project_path, started_at DESC, job_type, provider_key);
|
|
2592
|
+
|
|
2593
|
+
CREATE TABLE IF NOT EXISTS session_outcomes (
|
|
2594
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2595
|
+
project_path TEXT NOT NULL,
|
|
2596
|
+
job_type TEXT NOT NULL,
|
|
2597
|
+
provider_key TEXT NOT NULL,
|
|
2598
|
+
prd_file TEXT,
|
|
2599
|
+
pr_number INTEGER,
|
|
2600
|
+
branch_name TEXT,
|
|
2601
|
+
started_at INTEGER NOT NULL,
|
|
2602
|
+
finished_at INTEGER NOT NULL,
|
|
2603
|
+
duration_seconds INTEGER,
|
|
2604
|
+
outcome TEXT NOT NULL,
|
|
2605
|
+
exit_code INTEGER,
|
|
2606
|
+
attempt INTEGER NOT NULL DEFAULT 1,
|
|
2607
|
+
retry_count INTEGER NOT NULL DEFAULT 0,
|
|
2608
|
+
review_score INTEGER,
|
|
2609
|
+
ci_status TEXT,
|
|
2610
|
+
failure_category TEXT,
|
|
2611
|
+
failure_signature TEXT,
|
|
2612
|
+
metadata_json TEXT NOT NULL DEFAULT '{}'
|
|
2613
|
+
);
|
|
2614
|
+
CREATE INDEX IF NOT EXISTS idx_session_outcomes_lookup
|
|
2615
|
+
ON session_outcomes(project_path, finished_at DESC, job_type, outcome);
|
|
2616
|
+
|
|
2617
|
+
CREATE TABLE IF NOT EXISTS feedback_patterns (
|
|
2618
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2619
|
+
project_path TEXT NOT NULL,
|
|
2620
|
+
pattern_key TEXT NOT NULL,
|
|
2621
|
+
job_type TEXT NOT NULL,
|
|
2622
|
+
category TEXT NOT NULL,
|
|
2623
|
+
title TEXT NOT NULL,
|
|
2624
|
+
description TEXT NOT NULL,
|
|
2625
|
+
sample_count INTEGER NOT NULL DEFAULT 0,
|
|
2626
|
+
confidence REAL NOT NULL DEFAULT 0,
|
|
2627
|
+
first_seen_at INTEGER NOT NULL,
|
|
2628
|
+
last_seen_at INTEGER NOT NULL,
|
|
2629
|
+
status TEXT NOT NULL DEFAULT 'observing',
|
|
2630
|
+
metadata_json TEXT NOT NULL DEFAULT '{}',
|
|
2631
|
+
UNIQUE(project_path, pattern_key, job_type)
|
|
2632
|
+
);
|
|
2633
|
+
CREATE INDEX IF NOT EXISTS idx_feedback_patterns_lookup
|
|
2634
|
+
ON feedback_patterns(project_path, job_type, status, confidence DESC);
|
|
2635
|
+
|
|
2636
|
+
CREATE TABLE IF NOT EXISTS prompt_augmentations (
|
|
2637
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
2638
|
+
project_path TEXT NOT NULL,
|
|
2639
|
+
pattern_id INTEGER REFERENCES feedback_patterns(id),
|
|
2640
|
+
job_type TEXT NOT NULL,
|
|
2641
|
+
prompt_text TEXT NOT NULL,
|
|
2642
|
+
status TEXT NOT NULL DEFAULT 'active',
|
|
2643
|
+
created_at INTEGER NOT NULL,
|
|
2644
|
+
updated_at INTEGER NOT NULL,
|
|
2645
|
+
expires_at INTEGER,
|
|
2646
|
+
applied_count INTEGER NOT NULL DEFAULT 0,
|
|
2647
|
+
success_count INTEGER NOT NULL DEFAULT 0
|
|
2648
|
+
);
|
|
2649
|
+
CREATE INDEX IF NOT EXISTS idx_prompt_augmentations_active
|
|
2650
|
+
ON prompt_augmentations(project_path, job_type, status, expires_at);
|
|
2548
2651
|
`);
|
|
2549
2652
|
db.exec(`DROP TABLE IF EXISTS slack_discussions`);
|
|
2550
2653
|
try {
|
|
@@ -2603,6 +2706,7 @@ function initContainer(projectDir) {
|
|
|
2603
2706
|
container.registerSingleton(SqlitePrdStateRepository);
|
|
2604
2707
|
container.registerSingleton(SqliteProjectRegistryRepository);
|
|
2605
2708
|
container.registerSingleton(SqliteRoadmapStateRepository);
|
|
2709
|
+
container.registerSingleton(SqliteSessionOutcomeRepository);
|
|
2606
2710
|
}
|
|
2607
2711
|
function isContainerInitialized() {
|
|
2608
2712
|
return container.isRegistered(DATABASE_TOKEN);
|
|
@@ -2616,6 +2720,7 @@ var init_container = __esm({
|
|
|
2616
2720
|
init_prd_state_repository();
|
|
2617
2721
|
init_project_registry_repository();
|
|
2618
2722
|
init_roadmap_state_repository();
|
|
2723
|
+
init_session_outcome_repository();
|
|
2619
2724
|
init_client();
|
|
2620
2725
|
init_migrations();
|
|
2621
2726
|
DATABASE_TOKEN = "Database";
|
|
@@ -3390,21 +3495,21 @@ var LocalKanbanProvider;
|
|
|
3390
3495
|
var init_local_kanban = __esm({
|
|
3391
3496
|
"../core/dist/board/providers/local-kanban.js"() {
|
|
3392
3497
|
"use strict";
|
|
3393
|
-
|
|
3394
|
-
|
|
3498
|
+
init_types2();
|
|
3499
|
+
init_constants();
|
|
3395
3500
|
LocalKanbanProvider = class {
|
|
3396
3501
|
repo;
|
|
3397
3502
|
constructor(repo) {
|
|
3398
3503
|
this.repo = repo;
|
|
3399
3504
|
}
|
|
3400
3505
|
async setupBoard(title) {
|
|
3401
|
-
return { ...
|
|
3506
|
+
return { ...DEFAULT_LOCAL_BOARD_INFO, title };
|
|
3402
3507
|
}
|
|
3403
3508
|
async getBoard() {
|
|
3404
|
-
return
|
|
3509
|
+
return DEFAULT_LOCAL_BOARD_INFO;
|
|
3405
3510
|
}
|
|
3406
3511
|
async getColumns() {
|
|
3407
|
-
return
|
|
3512
|
+
return BOARD_COLUMNS.map((name, i) => ({ id: String(i), name }));
|
|
3408
3513
|
}
|
|
3409
3514
|
async createIssue(input) {
|
|
3410
3515
|
const row = this.repo.create({
|
|
@@ -3810,7 +3915,8 @@ function getRepositories() {
|
|
|
3810
3915
|
projectRegistry: container.resolve(SqliteProjectRegistryRepository),
|
|
3811
3916
|
executionHistory: container.resolve(SqliteExecutionHistoryRepository),
|
|
3812
3917
|
prdState: container.resolve(SqlitePrdStateRepository),
|
|
3813
|
-
roadmapState: container.resolve(SqliteRoadmapStateRepository)
|
|
3918
|
+
roadmapState: container.resolve(SqliteRoadmapStateRepository),
|
|
3919
|
+
sessionOutcomes: container.resolve(SqliteSessionOutcomeRepository)
|
|
3814
3920
|
};
|
|
3815
3921
|
}
|
|
3816
3922
|
const db = getDb();
|
|
@@ -3822,7 +3928,8 @@ function getRepositories() {
|
|
|
3822
3928
|
projectRegistry: new SqliteProjectRegistryRepository(db),
|
|
3823
3929
|
executionHistory: new SqliteExecutionHistoryRepository(db),
|
|
3824
3930
|
prdState: new SqlitePrdStateRepository(db),
|
|
3825
|
-
roadmapState: new SqliteRoadmapStateRepository(db)
|
|
3931
|
+
roadmapState: new SqliteRoadmapStateRepository(db),
|
|
3932
|
+
sessionOutcomes: new SqliteSessionOutcomeRepository(db)
|
|
3826
3933
|
};
|
|
3827
3934
|
}
|
|
3828
3935
|
function resetRepositories() {
|
|
@@ -3839,6 +3946,8 @@ var init_repositories = __esm({
|
|
|
3839
3946
|
init_execution_history_repository();
|
|
3840
3947
|
init_prd_state_repository();
|
|
3841
3948
|
init_roadmap_state_repository();
|
|
3949
|
+
init_session_outcome_repository();
|
|
3950
|
+
init_session_outcome_repository();
|
|
3842
3951
|
_initialized = false;
|
|
3843
3952
|
}
|
|
3844
3953
|
});
|
|
@@ -5758,7 +5867,9 @@ function buildDescription(ctx) {
|
|
|
5758
5867
|
const lines = [];
|
|
5759
5868
|
lines.push(`Project: ${ctx.projectName}`);
|
|
5760
5869
|
lines.push(`Provider: ${ctx.provider}`);
|
|
5761
|
-
|
|
5870
|
+
if (ctx.event !== "run_started") {
|
|
5871
|
+
lines.push(`Exit code: ${ctx.exitCode}`);
|
|
5872
|
+
}
|
|
5762
5873
|
if (ctx.prdName) {
|
|
5763
5874
|
lines.push(`PRD: ${ctx.prdName}`);
|
|
5764
5875
|
}
|
|
@@ -5782,7 +5893,13 @@ function buildDescription(ctx) {
|
|
|
5782
5893
|
}
|
|
5783
5894
|
if (ctx.event === "run_timeout") {
|
|
5784
5895
|
lines.push("Cause: Execution hit the max runtime limit and was terminated.");
|
|
5785
|
-
|
|
5896
|
+
if (ctx.checkpointStatus === "none") {
|
|
5897
|
+
lines.push("Resume: No checkpoint was created because the run produced no local changes or branch commits.");
|
|
5898
|
+
} else if (ctx.checkpointStatus === "available") {
|
|
5899
|
+
lines.push("Resume: Existing branch progress is available for the next run.");
|
|
5900
|
+
} else {
|
|
5901
|
+
lines.push("Resume: Progress was checkpointed on timeout, and the next run resumes from that branch state.");
|
|
5902
|
+
}
|
|
5786
5903
|
lines.push("Recommendation: Avoid huge PRDs; slice large work into smaller PRDs/phases.");
|
|
5787
5904
|
}
|
|
5788
5905
|
if (ctx.event === "review_completed" && ctx.attempts !== void 0 && ctx.attempts > 1) {
|
|
@@ -7476,14 +7593,14 @@ var init_worktree_manager = __esm({
|
|
|
7476
7593
|
import * as fs21 from "fs";
|
|
7477
7594
|
import * as os7 from "os";
|
|
7478
7595
|
import * as path20 from "path";
|
|
7479
|
-
import
|
|
7596
|
+
import Database8 from "better-sqlite3";
|
|
7480
7597
|
function getStateDbPath() {
|
|
7481
7598
|
const base = process.env.NIGHT_WATCH_HOME || path20.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
7482
7599
|
return path20.join(base, STATE_DB_FILE_NAME);
|
|
7483
7600
|
}
|
|
7484
7601
|
function openDb() {
|
|
7485
7602
|
const dbPath = getStateDbPath();
|
|
7486
|
-
const db = new
|
|
7603
|
+
const db = new Database8(dbPath);
|
|
7487
7604
|
db.pragma("journal_mode = WAL");
|
|
7488
7605
|
db.pragma("busy_timeout = 5000");
|
|
7489
7606
|
if (!_migrationsApplied) {
|
|
@@ -8508,32 +8625,671 @@ async function syncAuditFindingsToBoard(config, projectDir) {
|
|
|
8508
8625
|
summary: `found ${findings.length} actionable audit finding(s), but failed to create board issue(s)`
|
|
8509
8626
|
};
|
|
8510
8627
|
}
|
|
8511
|
-
return {
|
|
8512
|
-
status: "partial",
|
|
8513
|
-
findingsCount: findings.length,
|
|
8514
|
-
issuesCreated: created,
|
|
8515
|
-
issuesFailed: failed,
|
|
8516
|
-
targetColumn,
|
|
8517
|
-
summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
|
|
8518
|
-
};
|
|
8628
|
+
return {
|
|
8629
|
+
status: "partial",
|
|
8630
|
+
findingsCount: findings.length,
|
|
8631
|
+
issuesCreated: created,
|
|
8632
|
+
issuesFailed: failed,
|
|
8633
|
+
targetColumn,
|
|
8634
|
+
summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
|
|
8635
|
+
};
|
|
8636
|
+
}
|
|
8637
|
+
var logger5;
|
|
8638
|
+
var init_board_sync = __esm({
|
|
8639
|
+
"../core/dist/audit/board-sync.js"() {
|
|
8640
|
+
"use strict";
|
|
8641
|
+
init_factory();
|
|
8642
|
+
init_logger();
|
|
8643
|
+
init_report();
|
|
8644
|
+
logger5 = createLogger("audit-sync");
|
|
8645
|
+
}
|
|
8646
|
+
});
|
|
8647
|
+
|
|
8648
|
+
// ../core/dist/audit/index.js
|
|
8649
|
+
var init_audit = __esm({
|
|
8650
|
+
"../core/dist/audit/index.js"() {
|
|
8651
|
+
"use strict";
|
|
8652
|
+
init_report();
|
|
8653
|
+
init_board_sync();
|
|
8654
|
+
}
|
|
8655
|
+
});
|
|
8656
|
+
|
|
8657
|
+
// ../core/dist/feedback/outcome-parser.js
|
|
8658
|
+
function stripAnsi2(value) {
|
|
8659
|
+
return value.replace(ANSI_PATTERN, "");
|
|
8660
|
+
}
|
|
8661
|
+
function redactOutcomeText(value) {
|
|
8662
|
+
return SECRET_TEXT_PATTERNS2.reduce((current, [pattern, replacement]) => current.replace(pattern, replacement), value);
|
|
8663
|
+
}
|
|
8664
|
+
function trimLine(value, maxLength) {
|
|
8665
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 3)}...`;
|
|
8666
|
+
}
|
|
8667
|
+
function normalizeLine(value, projectPath) {
|
|
8668
|
+
let normalized = stripAnsi2(redactOutcomeText(value)).trim();
|
|
8669
|
+
if (projectPath) {
|
|
8670
|
+
normalized = normalized.replaceAll(projectPath, "<project>");
|
|
8671
|
+
}
|
|
8672
|
+
return trimLine(normalized.replace(/:\d+:\d+/g, ":<line>:<col>").replace(/:\d+\b/g, ":<line>").replace(/\b0x[0-9a-f]+\b/gi, "<hex>").replace(/\b\d{4,}\b/g, "<num>").replace(/\s+/g, " ").toLowerCase(), MAX_ERROR_LINE_LENGTH);
|
|
8673
|
+
}
|
|
8674
|
+
function getOutputLines(stdout, stderr) {
|
|
8675
|
+
return `${stdout ?? ""}
|
|
8676
|
+
${stderr ?? ""}`.split(/\r?\n/).map((line) => stripAnsi2(redactOutcomeText(line)).trim()).filter((line) => line.length > 0 && !line.startsWith("NIGHT_WATCH_RESULT:"));
|
|
8677
|
+
}
|
|
8678
|
+
function extractFilePath(line, projectPath) {
|
|
8679
|
+
const normalizedLine = line.replaceAll("\\", "/");
|
|
8680
|
+
const normalizedProjectPath = projectPath.replaceAll("\\", "/");
|
|
8681
|
+
if (normalizedProjectPath) {
|
|
8682
|
+
const projectPrefix = `${normalizedProjectPath}/`;
|
|
8683
|
+
const projectIndex = normalizedLine.indexOf(projectPrefix);
|
|
8684
|
+
if (projectIndex >= 0) {
|
|
8685
|
+
const relativeLine = normalizedLine.slice(projectIndex + projectPrefix.length);
|
|
8686
|
+
const relativePath = extractFilePathToken(relativeLine.split(TOKEN_SPLIT_PATTERN));
|
|
8687
|
+
if (relativePath) {
|
|
8688
|
+
return relativePath;
|
|
8689
|
+
}
|
|
8690
|
+
}
|
|
8691
|
+
}
|
|
8692
|
+
return extractFilePathToken(normalizedLine.split(TOKEN_SPLIT_PATTERN));
|
|
8693
|
+
}
|
|
8694
|
+
function extractFilePathToken(tokens) {
|
|
8695
|
+
for (const token of tokens) {
|
|
8696
|
+
const withoutLocation = cleanFilePathToken(token);
|
|
8697
|
+
if (FILE_PATH_PATTERN.test(withoutLocation)) {
|
|
8698
|
+
return withoutLocation;
|
|
8699
|
+
}
|
|
8700
|
+
}
|
|
8701
|
+
return null;
|
|
8702
|
+
}
|
|
8703
|
+
function cleanFilePathToken(token) {
|
|
8704
|
+
let candidate = token;
|
|
8705
|
+
const locationIndex = findLocationIndex(candidate);
|
|
8706
|
+
if (locationIndex >= 0) {
|
|
8707
|
+
candidate = candidate.slice(0, locationIndex);
|
|
8708
|
+
}
|
|
8709
|
+
while (candidate.startsWith("(") || candidate.startsWith("[") || candidate.startsWith("{")) {
|
|
8710
|
+
candidate = candidate.slice(1);
|
|
8711
|
+
}
|
|
8712
|
+
while (candidate.endsWith(")") || candidate.endsWith(",") || candidate.endsWith(";")) {
|
|
8713
|
+
candidate = candidate.slice(0, -1);
|
|
8714
|
+
}
|
|
8715
|
+
return candidate.startsWith("./") ? candidate.slice(2) : candidate;
|
|
8716
|
+
}
|
|
8717
|
+
function findLocationIndex(value) {
|
|
8718
|
+
for (let index = 0; index < value.length - 1; index += 1) {
|
|
8719
|
+
if (value[index] === ":" && value[index + 1] >= "0" && value[index + 1] <= "9") {
|
|
8720
|
+
return index;
|
|
8721
|
+
}
|
|
8722
|
+
}
|
|
8723
|
+
return -1;
|
|
8724
|
+
}
|
|
8725
|
+
function filePathToArea(filePath) {
|
|
8726
|
+
if (!filePath) {
|
|
8727
|
+
return null;
|
|
8728
|
+
}
|
|
8729
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
8730
|
+
if (segments.length <= 1) {
|
|
8731
|
+
return ".";
|
|
8732
|
+
}
|
|
8733
|
+
return segments.slice(0, Math.min(segments.length - 1, 3)).join("/");
|
|
8734
|
+
}
|
|
8735
|
+
function findFirstMatchingLine(lines, category) {
|
|
8736
|
+
const rule = CLASSIFIER_RULES.find((entry) => entry.category === category);
|
|
8737
|
+
if (rule) {
|
|
8738
|
+
const matched = lines.find((line) => rule.pattern.test(line));
|
|
8739
|
+
if (matched) {
|
|
8740
|
+
return matched;
|
|
8741
|
+
}
|
|
8742
|
+
}
|
|
8743
|
+
return lines.find((line) => /\b(error|failed|failure|fatal|exception|conflict|timeout)\b/i.test(line)) ?? lines[0] ?? null;
|
|
8744
|
+
}
|
|
8745
|
+
function classifyCategory(lines, scriptResult, minReviewScore, exitCode) {
|
|
8746
|
+
const status = scriptResult?.status ?? "";
|
|
8747
|
+
const data = scriptResult?.data ?? {};
|
|
8748
|
+
const combined = [...lines, status, data.reason ?? "", data.detail ?? ""].join("\n");
|
|
8749
|
+
if (exitCode === 124 || status === "timeout") {
|
|
8750
|
+
return "timeout";
|
|
8751
|
+
}
|
|
8752
|
+
if (status === "rate_limited" || data.rate_limit_fallback === "1") {
|
|
8753
|
+
return "rate-limit";
|
|
8754
|
+
}
|
|
8755
|
+
const reviewScore = parseOptionalNumber(data.final_score ?? data.review_score);
|
|
8756
|
+
if (reviewScore != null && minReviewScore != null && Number.isFinite(minReviewScore) && reviewScore < minReviewScore) {
|
|
8757
|
+
return "review-score";
|
|
8758
|
+
}
|
|
8759
|
+
for (const rule of CLASSIFIER_RULES) {
|
|
8760
|
+
if (rule.pattern.test(combined)) {
|
|
8761
|
+
return rule.category;
|
|
8762
|
+
}
|
|
8763
|
+
}
|
|
8764
|
+
return "unknown";
|
|
8765
|
+
}
|
|
8766
|
+
function classifyFailure(input) {
|
|
8767
|
+
const lines = getOutputLines(input.stdout, input.stderr);
|
|
8768
|
+
const category = classifyCategory(lines, input.scriptResult, input.minReviewScore, input.exitCode);
|
|
8769
|
+
const firstErrorLine = findFirstMatchingLine(lines, category);
|
|
8770
|
+
const filePath = (firstErrorLine ? extractFilePath(firstErrorLine, input.projectPath) : null) ?? lines.map((line) => extractFilePath(line, input.projectPath)).find((value) => value != null) ?? null;
|
|
8771
|
+
const fileArea = filePathToArea(filePath);
|
|
8772
|
+
const normalizedLine = firstErrorLine ? normalizeLine(firstErrorLine, input.projectPath) : "no-error-line";
|
|
8773
|
+
const failureSignature = trimLine(`${category}|${fileArea ?? "unknown-area"}|${normalizedLine}`, MAX_SIGNATURE_LENGTH);
|
|
8774
|
+
return {
|
|
8775
|
+
category,
|
|
8776
|
+
failureSignature,
|
|
8777
|
+
fileArea,
|
|
8778
|
+
firstErrorLine: firstErrorLine ? trimLine(firstErrorLine, MAX_ERROR_LINE_LENGTH) : null
|
|
8779
|
+
};
|
|
8780
|
+
}
|
|
8781
|
+
function parseOptionalNumber(value) {
|
|
8782
|
+
if (!value) {
|
|
8783
|
+
return null;
|
|
8784
|
+
}
|
|
8785
|
+
const normalized = value.trim().replace(/^#/, "");
|
|
8786
|
+
const parsed = parseInt(normalized, 10);
|
|
8787
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
8788
|
+
}
|
|
8789
|
+
function parseFirstPrNumber(scriptResult) {
|
|
8790
|
+
const data = scriptResult?.data ?? {};
|
|
8791
|
+
const direct = parseOptionalNumber(data.pr_number) ?? parseOptionalNumber(data.prNumber) ?? parseOptionalNumber(data.pr) ?? parseOptionalNumber(data.failed_pr);
|
|
8792
|
+
if (direct != null) {
|
|
8793
|
+
return direct;
|
|
8794
|
+
}
|
|
8795
|
+
const urlMatch = data.pr_url?.match(/\/pull\/(\d+)/);
|
|
8796
|
+
if (urlMatch?.[1]) {
|
|
8797
|
+
return parseOptionalNumber(urlMatch[1]);
|
|
8798
|
+
}
|
|
8799
|
+
const prsRaw = data.prs ?? data.auto_merged;
|
|
8800
|
+
if (!prsRaw) {
|
|
8801
|
+
return null;
|
|
8802
|
+
}
|
|
8803
|
+
const firstToken = prsRaw.split(",").find((token) => parseOptionalNumber(token) != null);
|
|
8804
|
+
return parseOptionalNumber(firstToken);
|
|
8805
|
+
}
|
|
8806
|
+
function parseAttemptCount(scriptResult, lines) {
|
|
8807
|
+
const fromData = parseOptionalNumber(scriptResult?.data.attempt) ?? parseOptionalNumber(scriptResult?.data.attempts);
|
|
8808
|
+
if (fromData != null && fromData > 0) {
|
|
8809
|
+
return fromData;
|
|
8810
|
+
}
|
|
8811
|
+
let maxAttempt = 1;
|
|
8812
|
+
for (const line of lines) {
|
|
8813
|
+
const match = /\bATTEMPT:\s*(\d+)\//i.exec(line) ?? /\bStarting attempt\s+(\d+)\//i.exec(line);
|
|
8814
|
+
if (match?.[1]) {
|
|
8815
|
+
maxAttempt = Math.max(maxAttempt, parseInt(match[1], 10));
|
|
8816
|
+
}
|
|
8817
|
+
}
|
|
8818
|
+
return maxAttempt;
|
|
8819
|
+
}
|
|
8820
|
+
function parseRetryCount(scriptResult, attempt) {
|
|
8821
|
+
const retryCount = parseOptionalNumber(scriptResult?.data.retry_count);
|
|
8822
|
+
if (retryCount != null && retryCount >= 0) {
|
|
8823
|
+
return retryCount;
|
|
8824
|
+
}
|
|
8825
|
+
return Math.max(0, attempt - 1);
|
|
8826
|
+
}
|
|
8827
|
+
function markerIndicatesFailure(scriptResult) {
|
|
8828
|
+
const data = scriptResult?.data ?? {};
|
|
8829
|
+
const positiveFailureCount = (parseOptionalNumber(data.failed) ?? 0) > 0 || (parseOptionalNumber(data.prs_failed) ?? 0) > 0 || (parseOptionalNumber(data.failed_count) ?? 0) > 0;
|
|
8830
|
+
if (positiveFailureCount) {
|
|
8831
|
+
return true;
|
|
8832
|
+
}
|
|
8833
|
+
return [data.failed_pr, data.auto_merge_failed, data.failed_automation].filter((value) => typeof value === "string").some((value) => value.trim().length > 0 && value.trim().toLowerCase() !== "none");
|
|
8834
|
+
}
|
|
8835
|
+
function determineOutcome(exitCode, scriptResult, category) {
|
|
8836
|
+
const status = scriptResult?.status ?? "";
|
|
8837
|
+
if (status === "queued" || status.startsWith("skip_")) {
|
|
8838
|
+
return "skipped";
|
|
8839
|
+
}
|
|
8840
|
+
if (exitCode === 124 || status === "timeout" || category === "timeout") {
|
|
8841
|
+
return "timeout";
|
|
8842
|
+
}
|
|
8843
|
+
if (status === "rate_limited" || exitCode !== 0 && category === "rate-limit") {
|
|
8844
|
+
return "rate_limited";
|
|
8845
|
+
}
|
|
8846
|
+
if (status.startsWith("failure") || category === "review-score" || markerIndicatesFailure(scriptResult)) {
|
|
8847
|
+
return "failure";
|
|
8848
|
+
}
|
|
8849
|
+
return exitCode === 0 ? "success" : "failure";
|
|
8850
|
+
}
|
|
8851
|
+
function redactMetadata2(metadata) {
|
|
8852
|
+
return JSON.parse(redactOutcomeText(JSON.stringify(metadata)));
|
|
8853
|
+
}
|
|
8854
|
+
function buildSessionOutcomeInput(input) {
|
|
8855
|
+
const lines = getOutputLines(input.stdout, input.stderr);
|
|
8856
|
+
const classification = classifyFailure(input);
|
|
8857
|
+
const outcome = determineOutcome(input.exitCode, input.scriptResult, classification.category);
|
|
8858
|
+
const attempt = parseAttemptCount(input.scriptResult, lines);
|
|
8859
|
+
const retryCount = parseRetryCount(input.scriptResult, attempt);
|
|
8860
|
+
const reviewScore = parseOptionalNumber(input.scriptResult?.data.final_score ?? input.scriptResult?.data.review_score);
|
|
8861
|
+
const failureCategory = outcome === "failure" || outcome === "timeout" || outcome === "rate_limited" ? classification.category : null;
|
|
8862
|
+
return {
|
|
8863
|
+
projectPath: input.projectPath,
|
|
8864
|
+
jobType: input.jobType,
|
|
8865
|
+
providerKey: input.providerKey || "unknown",
|
|
8866
|
+
prdFile: input.scriptResult?.data.prd ?? input.scriptResult?.data.prd_file ?? null,
|
|
8867
|
+
prNumber: parseFirstPrNumber(input.scriptResult),
|
|
8868
|
+
branchName: input.scriptResult?.data.branch ?? null,
|
|
8869
|
+
startedAt: input.startedAt,
|
|
8870
|
+
finishedAt: input.finishedAt,
|
|
8871
|
+
durationSeconds: Math.max(0, Math.round((input.finishedAt - input.startedAt) / 1e3)),
|
|
8872
|
+
outcome,
|
|
8873
|
+
exitCode: input.exitCode,
|
|
8874
|
+
attempt,
|
|
8875
|
+
retryCount,
|
|
8876
|
+
reviewScore,
|
|
8877
|
+
ciStatus: failureCategory === "ci" ? "fail" : input.scriptResult?.data.ci_status ?? null,
|
|
8878
|
+
failureCategory,
|
|
8879
|
+
failureSignature: failureCategory ? classification.failureSignature : null,
|
|
8880
|
+
metadata: redactMetadata2({
|
|
8881
|
+
...input.metadata ?? {},
|
|
8882
|
+
scriptStatus: input.scriptResult?.status ?? null,
|
|
8883
|
+
scriptData: input.scriptResult?.data ?? {},
|
|
8884
|
+
minReviewScore: input.minReviewScore ?? null,
|
|
8885
|
+
firstErrorLine: classification.firstErrorLine,
|
|
8886
|
+
fileArea: classification.fileArea
|
|
8887
|
+
})
|
|
8888
|
+
};
|
|
8889
|
+
}
|
|
8890
|
+
var FAILURE_CATEGORIES, SECRET_PLACEHOLDER2, MAX_SIGNATURE_LENGTH, MAX_ERROR_LINE_LENGTH, ANSI_PATTERN, FILE_PATH_PATTERN, TOKEN_SPLIT_PATTERN, SECRET_TEXT_PATTERNS2, CLASSIFIER_RULES;
|
|
8891
|
+
var init_outcome_parser = __esm({
|
|
8892
|
+
"../core/dist/feedback/outcome-parser.js"() {
|
|
8893
|
+
"use strict";
|
|
8894
|
+
FAILURE_CATEGORIES = [
|
|
8895
|
+
"typescript",
|
|
8896
|
+
"eslint",
|
|
8897
|
+
"test",
|
|
8898
|
+
"ci",
|
|
8899
|
+
"review-score",
|
|
8900
|
+
"rate-limit",
|
|
8901
|
+
"timeout",
|
|
8902
|
+
"conflict",
|
|
8903
|
+
"unknown"
|
|
8904
|
+
];
|
|
8905
|
+
SECRET_PLACEHOLDER2 = "[REDACTED_SECRET]";
|
|
8906
|
+
MAX_SIGNATURE_LENGTH = 240;
|
|
8907
|
+
MAX_ERROR_LINE_LENGTH = 300;
|
|
8908
|
+
ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
8909
|
+
FILE_PATH_PATTERN = /\.(?:[cm]?[jt]sx?|json|md|css|scss|ya?ml)$/i;
|
|
8910
|
+
TOKEN_SPLIT_PATTERN = /[\s('"`]+/;
|
|
8911
|
+
SECRET_TEXT_PATTERNS2 = [
|
|
8912
|
+
[
|
|
8913
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
8914
|
+
SECRET_PLACEHOLDER2
|
|
8915
|
+
],
|
|
8916
|
+
[/\bsk-ant-[\w-]{20,}\b/g, SECRET_PLACEHOLDER2],
|
|
8917
|
+
[/\bsk-[\w-]{20,}\b/g, SECRET_PLACEHOLDER2],
|
|
8918
|
+
[/\bgh[opsru]_\w{30,}\b/g, SECRET_PLACEHOLDER2],
|
|
8919
|
+
[/\bxox[baprs]-[\w-]{20,}\b/g, SECRET_PLACEHOLDER2],
|
|
8920
|
+
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, SECRET_PLACEHOLDER2],
|
|
8921
|
+
[/\b(Bearer|Basic)\s+[\w.~+/=-]{12,}/gi, `$1 ${SECRET_PLACEHOLDER2}`],
|
|
8922
|
+
[/\b(token|api[_-]?key|password|secret)=["']?[\w.~+/=-]{12,}/gi, `$1=${SECRET_PLACEHOLDER2}`]
|
|
8923
|
+
];
|
|
8924
|
+
CLASSIFIER_RULES = [
|
|
8925
|
+
{
|
|
8926
|
+
category: "timeout",
|
|
8927
|
+
pattern: /\b(timed?\s*out|timeout|etimedout|operation was aborted|exit code 124|signal sigterm)\b/i
|
|
8928
|
+
},
|
|
8929
|
+
{
|
|
8930
|
+
category: "rate-limit",
|
|
8931
|
+
pattern: /\b(429|rate[- ]?limit(?:ed)?|too many requests|quota exceeded|resource_exhausted|overloaded)\b/i
|
|
8932
|
+
},
|
|
8933
|
+
{
|
|
8934
|
+
category: "conflict",
|
|
8935
|
+
pattern: /\b(merge conflict|conflict \(|conflict:|unmerged files|needs merge|automatic merge failed|both modified:)\b/i
|
|
8936
|
+
},
|
|
8937
|
+
{
|
|
8938
|
+
category: "typescript",
|
|
8939
|
+
pattern: /\b(TS\d{4}|typescript error|tsc\b.*(?:failed|error)|error TS\d{4})\b/i
|
|
8940
|
+
},
|
|
8941
|
+
{
|
|
8942
|
+
category: "eslint",
|
|
8943
|
+
pattern: /\b(eslint|@typescript-eslint|no-unused-vars|no-explicit-any|react-hooks\/rules-of-hooks)\b/i
|
|
8944
|
+
},
|
|
8945
|
+
{
|
|
8946
|
+
category: "test",
|
|
8947
|
+
pattern: /\b(vitest|jest|playwright|cypress|mocha|assertionerror)\b/i
|
|
8948
|
+
},
|
|
8949
|
+
{
|
|
8950
|
+
category: "test",
|
|
8951
|
+
pattern: /\b(test files?|tests?)\b.*\bfailed\b/i
|
|
8952
|
+
},
|
|
8953
|
+
{
|
|
8954
|
+
category: "test",
|
|
8955
|
+
pattern: /\b(expect\(|locator\(|FAIL\s+\S+\.(?:test|spec)\.)/i
|
|
8956
|
+
},
|
|
8957
|
+
{
|
|
8958
|
+
category: "review-score",
|
|
8959
|
+
pattern: /\b(review score|final_score|score)\b.*\b(below|minimum|min|required|threshold|failed|miss)\b/i
|
|
8960
|
+
},
|
|
8961
|
+
{
|
|
8962
|
+
category: "ci",
|
|
8963
|
+
pattern: /\b(ci|github actions|workflow|status check|required check|check run|failing checks?)\b.*\b(fail|error|cancel|timed out|action_required)\b/i
|
|
8964
|
+
}
|
|
8965
|
+
];
|
|
8966
|
+
}
|
|
8967
|
+
});
|
|
8968
|
+
|
|
8969
|
+
// ../core/dist/feedback/pattern-analyzer.js
|
|
8970
|
+
function isFailureOutcome(outcome) {
|
|
8971
|
+
return outcome.outcome === "failure" || outcome.outcome === "timeout" || outcome.outcome === "rate_limited";
|
|
8972
|
+
}
|
|
8973
|
+
function truncateText(value, maxLength = MAX_PATTERN_TEXT_LENGTH) {
|
|
8974
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
8975
|
+
if (normalized.length <= maxLength) {
|
|
8976
|
+
return normalized;
|
|
8977
|
+
}
|
|
8978
|
+
return `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
|
|
8979
|
+
}
|
|
8980
|
+
function getStringMetadata(metadata, key) {
|
|
8981
|
+
const value = metadata[key];
|
|
8982
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
8983
|
+
}
|
|
8984
|
+
function getFileArea(outcome) {
|
|
8985
|
+
return getStringMetadata(outcome.metadata, "fileArea") ?? "unknown area";
|
|
8986
|
+
}
|
|
8987
|
+
function countRecentStreaks(repository, outcome) {
|
|
8988
|
+
const recentOutcomes = repository.queryOutcomes({
|
|
8989
|
+
projectPath: outcome.projectPath,
|
|
8990
|
+
jobType: outcome.jobType,
|
|
8991
|
+
limit: 25
|
|
8992
|
+
});
|
|
8993
|
+
let failureStreak = 0;
|
|
8994
|
+
let signatureStreak = 0;
|
|
8995
|
+
for (const recentOutcome of recentOutcomes) {
|
|
8996
|
+
if (!isFailureOutcome(recentOutcome)) {
|
|
8997
|
+
break;
|
|
8998
|
+
}
|
|
8999
|
+
failureStreak += 1;
|
|
9000
|
+
if (recentOutcome.failureSignature === outcome.failureSignature) {
|
|
9001
|
+
signatureStreak += 1;
|
|
9002
|
+
} else {
|
|
9003
|
+
break;
|
|
9004
|
+
}
|
|
9005
|
+
}
|
|
9006
|
+
return { failureStreak, signatureStreak };
|
|
9007
|
+
}
|
|
9008
|
+
function countSuccessStreak(repository, projectPath, jobType) {
|
|
9009
|
+
const recentOutcomes = repository.queryOutcomes({ projectPath, jobType, limit: 25 });
|
|
9010
|
+
let successStreak = 0;
|
|
9011
|
+
for (const outcome of recentOutcomes) {
|
|
9012
|
+
if (outcome.outcome !== "success") {
|
|
9013
|
+
break;
|
|
9014
|
+
}
|
|
9015
|
+
successStreak += 1;
|
|
9016
|
+
}
|
|
9017
|
+
return successStreak;
|
|
9018
|
+
}
|
|
9019
|
+
function calculateRecencyScore(now, lastSeenAt) {
|
|
9020
|
+
const ageMs = Math.max(0, now - lastSeenAt);
|
|
9021
|
+
if (ageMs <= RECENT_WINDOW_MS) {
|
|
9022
|
+
return 1;
|
|
9023
|
+
}
|
|
9024
|
+
if (ageMs <= STALE_WINDOW_MS) {
|
|
9025
|
+
return 0.5;
|
|
9026
|
+
}
|
|
9027
|
+
return 0.15;
|
|
9028
|
+
}
|
|
9029
|
+
function calculateConfidence(sampleCount, lastSeenAt, failureStreak, signatureStreak, now) {
|
|
9030
|
+
const sampleScore = Math.min(1, sampleCount / 2);
|
|
9031
|
+
const streakScore = Math.min(1, Math.max(failureStreak, signatureStreak) / 2);
|
|
9032
|
+
const recencyScore = calculateRecencyScore(now, lastSeenAt);
|
|
9033
|
+
const confidence = sampleScore * 0.45 + streakScore * 0.35 + recencyScore * 0.2;
|
|
9034
|
+
return Math.round(Math.min(1, confidence) * 100) / 100;
|
|
9035
|
+
}
|
|
9036
|
+
function buildPatternTitle(outcome) {
|
|
9037
|
+
const category = outcome.failureCategory ?? "unknown";
|
|
9038
|
+
return truncateText(`Repeated ${category} failure in ${getFileArea(outcome)}`, 120);
|
|
9039
|
+
}
|
|
9040
|
+
function buildPatternDescription(outcome, sampleCount) {
|
|
9041
|
+
return truncateText(`Failure signature has appeared ${sampleCount} times for ${outcome.jobType} sessions.`);
|
|
9042
|
+
}
|
|
9043
|
+
function buildAugmentationPrompt(pattern, outcome) {
|
|
9044
|
+
const category = outcome.failureCategory ?? pattern.category;
|
|
9045
|
+
const fileArea = getFileArea(outcome);
|
|
9046
|
+
const signature = truncateText(outcome.failureSignature ?? pattern.patternKey, MAX_SIGNATURE_PROMPT_LENGTH);
|
|
9047
|
+
const confidencePercent = Math.round(pattern.confidence * 100);
|
|
9048
|
+
const actionByCategory = {
|
|
9049
|
+
ci: "Check failing CI details before broad edits and prioritize the repeated failure area.",
|
|
9050
|
+
conflict: "Check merge conflicts before editing and resolve the repeated conflict area first.",
|
|
9051
|
+
eslint: "Run lint early and fix the repeated ESLint issue before final verification.",
|
|
9052
|
+
"rate-limit": "Avoid unnecessary provider calls and continue with local evidence when rate limits appear.",
|
|
9053
|
+
"review-score": "Read prior low-score review feedback before declaring the PR ready and address repeated concerns.",
|
|
9054
|
+
test: "Run the targeted test area early and fix the repeated failure before final verification.",
|
|
9055
|
+
timeout: "Keep the work scoped and verify incrementally because prior sessions timed out.",
|
|
9056
|
+
typescript: "Run typecheck early and fix the repeated TypeScript issue before final verification.",
|
|
9057
|
+
unknown: "Investigate the repeated failure signature before making broad changes."
|
|
9058
|
+
};
|
|
9059
|
+
return truncateText(`${actionByCategory[category] ?? actionByCategory.unknown} Area: ${fileArea}. Provenance: pattern #${pattern.id}, samples=${pattern.sampleCount}, confidence=${confidencePercent}%, signature="${signature}".`, 320);
|
|
9060
|
+
}
|
|
9061
|
+
function expireStaleAugmentations(repository, projectPath, jobType, now) {
|
|
9062
|
+
const expired = [];
|
|
9063
|
+
const activeAugmentations = repository.listAugmentations({
|
|
9064
|
+
includeExpired: true,
|
|
9065
|
+
jobType,
|
|
9066
|
+
projectPath,
|
|
9067
|
+
status: "active"
|
|
9068
|
+
});
|
|
9069
|
+
for (const augmentation of activeAugmentations) {
|
|
9070
|
+
if (augmentation.expiresAt != null && augmentation.expiresAt <= now) {
|
|
9071
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9072
|
+
expired.push(augmentation.id);
|
|
9073
|
+
}
|
|
9074
|
+
}
|
|
9075
|
+
return expired;
|
|
9076
|
+
}
|
|
9077
|
+
function expireAugmentationsAfterSuccessStreak(repository, projectPath, jobType, successStreakToExpire, now) {
|
|
9078
|
+
if (successStreakToExpire <= 0) {
|
|
9079
|
+
return [];
|
|
9080
|
+
}
|
|
9081
|
+
const successStreak = countSuccessStreak(repository, projectPath, jobType);
|
|
9082
|
+
if (successStreak < successStreakToExpire) {
|
|
9083
|
+
return [];
|
|
9084
|
+
}
|
|
9085
|
+
const expired = [];
|
|
9086
|
+
const activeAugmentations = repository.listActiveAugmentations(projectPath, jobType, now);
|
|
9087
|
+
for (const augmentation of activeAugmentations) {
|
|
9088
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9089
|
+
expired.push(augmentation.id);
|
|
9090
|
+
}
|
|
9091
|
+
return expired;
|
|
9092
|
+
}
|
|
9093
|
+
function enforceAugmentationCap(repository, projectPath, jobType, maxActiveAugmentations, now) {
|
|
9094
|
+
if (maxActiveAugmentations < 1) {
|
|
9095
|
+
return repository.listActiveAugmentations(projectPath, jobType, now).map((augmentation) => {
|
|
9096
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9097
|
+
return augmentation.id;
|
|
9098
|
+
});
|
|
9099
|
+
}
|
|
9100
|
+
const activeAugmentations = repository.listActiveAugmentations(projectPath, jobType, now);
|
|
9101
|
+
if (activeAugmentations.length <= maxActiveAugmentations) {
|
|
9102
|
+
return [];
|
|
9103
|
+
}
|
|
9104
|
+
const activePatterns = repository.listPatterns({
|
|
9105
|
+
jobType,
|
|
9106
|
+
projectPath,
|
|
9107
|
+
status: "active",
|
|
9108
|
+
limit: 100
|
|
9109
|
+
});
|
|
9110
|
+
const confidenceByPatternId = new Map(activePatterns.map((pattern) => [pattern.id, pattern.confidence]));
|
|
9111
|
+
const keepIds = new Set(activeAugmentations.slice().sort((left, right) => {
|
|
9112
|
+
const leftConfidence = left.patternId == null ? 0 : confidenceByPatternId.get(left.patternId) ?? 0;
|
|
9113
|
+
const rightConfidence = right.patternId == null ? 0 : confidenceByPatternId.get(right.patternId) ?? 0;
|
|
9114
|
+
if (leftConfidence !== rightConfidence) {
|
|
9115
|
+
return rightConfidence - leftConfidence;
|
|
9116
|
+
}
|
|
9117
|
+
if (left.createdAt !== right.createdAt) {
|
|
9118
|
+
return right.createdAt - left.createdAt;
|
|
9119
|
+
}
|
|
9120
|
+
return right.id - left.id;
|
|
9121
|
+
}).slice(0, maxActiveAugmentations).map((augmentation) => augmentation.id));
|
|
9122
|
+
const expired = [];
|
|
9123
|
+
for (const augmentation of activeAugmentations) {
|
|
9124
|
+
if (!keepIds.has(augmentation.id)) {
|
|
9125
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9126
|
+
expired.push(augmentation.id);
|
|
9127
|
+
}
|
|
9128
|
+
}
|
|
9129
|
+
return expired;
|
|
9130
|
+
}
|
|
9131
|
+
function findActiveAugmentationForPattern(repository, projectPath, jobType, patternId, now) {
|
|
9132
|
+
return repository.listActiveAugmentations(projectPath, jobType, now).find((augmentation) => augmentation.patternId === patternId) ?? null;
|
|
9133
|
+
}
|
|
9134
|
+
function analyzeFeedbackOutcome(repository, outcome, options = {}) {
|
|
9135
|
+
const now = options.now ?? outcome.finishedAt ?? Date.now();
|
|
9136
|
+
const expiredAugmentationIds = expireStaleAugmentations(repository, outcome.projectPath, outcome.jobType, now);
|
|
9137
|
+
if (!isFailureOutcome(outcome) || !outcome.failureSignature || !outcome.failureCategory) {
|
|
9138
|
+
expiredAugmentationIds.push(...expireAugmentationsAfterSuccessStreak(repository, outcome.projectPath, outcome.jobType, options.successStreakToExpire ?? DEFAULT_SUCCESS_STREAK_TO_EXPIRE, now));
|
|
9139
|
+
return { augmentation: null, expiredAugmentationIds, pattern: null };
|
|
9140
|
+
}
|
|
9141
|
+
const existingPattern = repository.listPatterns({
|
|
9142
|
+
jobType: outcome.jobType,
|
|
9143
|
+
projectPath: outcome.projectPath,
|
|
9144
|
+
limit: 100
|
|
9145
|
+
}).find((pattern2) => pattern2.patternKey === outcome.failureSignature) ?? null;
|
|
9146
|
+
const sampleCount = (existingPattern?.sampleCount ?? 0) + 1;
|
|
9147
|
+
const streakStats = countRecentStreaks(repository, outcome);
|
|
9148
|
+
const confidence = calculateConfidence(sampleCount, outcome.finishedAt, streakStats.failureStreak, streakStats.signatureStreak, now);
|
|
9149
|
+
const status = confidence >= (options.confidenceThreshold ?? DEFAULT_CONFIDENCE_THRESHOLD) ? "active" : existingPattern?.status ?? "observing";
|
|
9150
|
+
const pattern = repository.upsertPattern({
|
|
9151
|
+
category: outcome.failureCategory,
|
|
9152
|
+
confidence,
|
|
9153
|
+
description: buildPatternDescription(outcome, sampleCount),
|
|
9154
|
+
jobType: outcome.jobType,
|
|
9155
|
+
lastSeenAt: outcome.finishedAt,
|
|
9156
|
+
metadata: {
|
|
9157
|
+
confidenceInputs: {
|
|
9158
|
+
failureStreak: streakStats.failureStreak,
|
|
9159
|
+
recencyScore: calculateRecencyScore(now, outcome.finishedAt),
|
|
9160
|
+
sampleCount,
|
|
9161
|
+
signatureStreak: streakStats.signatureStreak
|
|
9162
|
+
},
|
|
9163
|
+
failureSignature: outcome.failureSignature,
|
|
9164
|
+
fileArea: getFileArea(outcome),
|
|
9165
|
+
firstErrorLine: getStringMetadata(outcome.metadata, "firstErrorLine"),
|
|
9166
|
+
lastOutcomeId: outcome.id
|
|
9167
|
+
},
|
|
9168
|
+
patternKey: outcome.failureSignature,
|
|
9169
|
+
projectPath: outcome.projectPath,
|
|
9170
|
+
sampleCount,
|
|
9171
|
+
status,
|
|
9172
|
+
title: buildPatternTitle(outcome)
|
|
9173
|
+
});
|
|
9174
|
+
let augmentation = null;
|
|
9175
|
+
if (pattern.status === "active") {
|
|
9176
|
+
augmentation = findActiveAugmentationForPattern(repository, outcome.projectPath, outcome.jobType, pattern.id, now);
|
|
9177
|
+
if (!augmentation) {
|
|
9178
|
+
augmentation = repository.createAugmentation({
|
|
9179
|
+
expiresAt: now + (options.augmentationTtlMs ?? DEFAULT_AUGMENTATION_TTL_MS),
|
|
9180
|
+
jobType: outcome.jobType,
|
|
9181
|
+
patternId: pattern.id,
|
|
9182
|
+
projectPath: outcome.projectPath,
|
|
9183
|
+
promptText: buildAugmentationPrompt(pattern, outcome),
|
|
9184
|
+
status: "active"
|
|
9185
|
+
});
|
|
9186
|
+
}
|
|
9187
|
+
}
|
|
9188
|
+
expiredAugmentationIds.push(...enforceAugmentationCap(repository, outcome.projectPath, outcome.jobType, options.maxActiveAugmentations ?? DEFAULT_MAX_ACTIVE_AUGMENTATIONS, now));
|
|
9189
|
+
return { augmentation, expiredAugmentationIds, pattern };
|
|
8519
9190
|
}
|
|
8520
|
-
var
|
|
8521
|
-
var
|
|
8522
|
-
"../core/dist/
|
|
9191
|
+
var DEFAULT_CONFIDENCE_THRESHOLD, DEFAULT_AUGMENTATION_TTL_MS, DEFAULT_MAX_ACTIVE_AUGMENTATIONS, DEFAULT_SUCCESS_STREAK_TO_EXPIRE, RECENT_WINDOW_MS, STALE_WINDOW_MS, MAX_PATTERN_TEXT_LENGTH, MAX_SIGNATURE_PROMPT_LENGTH;
|
|
9192
|
+
var init_pattern_analyzer = __esm({
|
|
9193
|
+
"../core/dist/feedback/pattern-analyzer.js"() {
|
|
8523
9194
|
"use strict";
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
9195
|
+
DEFAULT_CONFIDENCE_THRESHOLD = 0.75;
|
|
9196
|
+
DEFAULT_AUGMENTATION_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
9197
|
+
DEFAULT_MAX_ACTIVE_AUGMENTATIONS = 3;
|
|
9198
|
+
DEFAULT_SUCCESS_STREAK_TO_EXPIRE = 3;
|
|
9199
|
+
RECENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9200
|
+
STALE_WINDOW_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
9201
|
+
MAX_PATTERN_TEXT_LENGTH = 180;
|
|
9202
|
+
MAX_SIGNATURE_PROMPT_LENGTH = 90;
|
|
8528
9203
|
}
|
|
8529
9204
|
});
|
|
8530
9205
|
|
|
8531
|
-
// ../core/dist/
|
|
8532
|
-
|
|
8533
|
-
|
|
9206
|
+
// ../core/dist/feedback/prompt-augmenter.js
|
|
9207
|
+
function isActiveAt(augmentation, now) {
|
|
9208
|
+
return augmentation.status === "active" && (augmentation.expiresAt == null || augmentation.expiresAt > now);
|
|
9209
|
+
}
|
|
9210
|
+
function normalizeMaxActive(value) {
|
|
9211
|
+
if (value == null || !Number.isFinite(value)) {
|
|
9212
|
+
return DEFAULT_MAX_ACTIVE_AUGMENTATIONS2;
|
|
9213
|
+
}
|
|
9214
|
+
return Math.max(0, Math.floor(value));
|
|
9215
|
+
}
|
|
9216
|
+
function truncateSnippet(value) {
|
|
9217
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
9218
|
+
if (normalized.length <= MAX_SNIPPET_LENGTH) {
|
|
9219
|
+
return normalized;
|
|
9220
|
+
}
|
|
9221
|
+
return `${normalized.slice(0, MAX_SNIPPET_LENGTH - 3).trimEnd()}...`;
|
|
9222
|
+
}
|
|
9223
|
+
function isFeedbackPromptEnabled() {
|
|
9224
|
+
const raw = process.env.NW_FEEDBACK_ENABLED ?? process.env.NW_FEEDBACK_PROMPT_ENABLED;
|
|
9225
|
+
if (!raw) {
|
|
9226
|
+
return true;
|
|
9227
|
+
}
|
|
9228
|
+
return !DISABLED_VALUES.has(raw.trim().toLowerCase());
|
|
9229
|
+
}
|
|
9230
|
+
function selectPromptAugmentations(augmentations, options = {}) {
|
|
9231
|
+
if (options.feedbackEnabled === false) {
|
|
9232
|
+
return [];
|
|
9233
|
+
}
|
|
9234
|
+
const now = options.now ?? Date.now();
|
|
9235
|
+
const maxActive = normalizeMaxActive(options.maxActiveAugmentations);
|
|
9236
|
+
if (maxActive === 0) {
|
|
9237
|
+
return [];
|
|
9238
|
+
}
|
|
9239
|
+
return augmentations.filter((augmentation) => isActiveAt(augmentation, now)).sort((left, right) => {
|
|
9240
|
+
if (left.createdAt !== right.createdAt) {
|
|
9241
|
+
return left.createdAt - right.createdAt;
|
|
9242
|
+
}
|
|
9243
|
+
return left.id - right.id;
|
|
9244
|
+
}).slice(0, maxActive);
|
|
9245
|
+
}
|
|
9246
|
+
function renderProjectFeedbackBlock(augmentations, options = {}) {
|
|
9247
|
+
const selected = selectPromptAugmentations(augmentations, options);
|
|
9248
|
+
if (selected.length === 0) {
|
|
9249
|
+
return "";
|
|
9250
|
+
}
|
|
9251
|
+
const lines = [
|
|
9252
|
+
"## Project Feedback",
|
|
9253
|
+
"The following short notes come from repeated recent Night Watch failures. Treat them as targeted guardrails, not as replacements for the main task instructions.",
|
|
9254
|
+
"",
|
|
9255
|
+
...selected.map((augmentation) => `- ${truncateSnippet(augmentation.promptText)}`)
|
|
9256
|
+
];
|
|
9257
|
+
return lines.join("\n");
|
|
9258
|
+
}
|
|
9259
|
+
function buildProjectFeedbackPromptBlock(repository, projectPath, jobType, options = {}) {
|
|
9260
|
+
const enabled = options.feedbackEnabled ?? isFeedbackPromptEnabled();
|
|
9261
|
+
if (!enabled) {
|
|
9262
|
+
return { augmentationIds: [], promptBlock: "" };
|
|
9263
|
+
}
|
|
9264
|
+
const now = options.now ?? Date.now();
|
|
9265
|
+
const activeAugmentations = repository.listActiveAugmentations(projectPath, jobType, now);
|
|
9266
|
+
const selected = selectPromptAugmentations(activeAugmentations, {
|
|
9267
|
+
...options,
|
|
9268
|
+
feedbackEnabled: enabled,
|
|
9269
|
+
now
|
|
9270
|
+
});
|
|
9271
|
+
const promptBlock = renderProjectFeedbackBlock(selected, {
|
|
9272
|
+
...options,
|
|
9273
|
+
feedbackEnabled: enabled,
|
|
9274
|
+
now
|
|
9275
|
+
});
|
|
9276
|
+
if (options.markApplied === true && promptBlock.length > 0) {
|
|
9277
|
+
for (const augmentation of selected) {
|
|
9278
|
+
repository.incrementAugmentationCounts(augmentation.id);
|
|
9279
|
+
}
|
|
9280
|
+
}
|
|
9281
|
+
return {
|
|
9282
|
+
augmentationIds: selected.map((augmentation) => augmentation.id),
|
|
9283
|
+
promptBlock
|
|
9284
|
+
};
|
|
9285
|
+
}
|
|
9286
|
+
var DEFAULT_MAX_ACTIVE_AUGMENTATIONS2, MAX_SNIPPET_LENGTH, DISABLED_VALUES;
|
|
9287
|
+
var init_prompt_augmenter = __esm({
|
|
9288
|
+
"../core/dist/feedback/prompt-augmenter.js"() {
|
|
8534
9289
|
"use strict";
|
|
8535
|
-
|
|
8536
|
-
|
|
9290
|
+
DEFAULT_MAX_ACTIVE_AUGMENTATIONS2 = 3;
|
|
9291
|
+
MAX_SNIPPET_LENGTH = 260;
|
|
9292
|
+
DISABLED_VALUES = /* @__PURE__ */ new Set(["0", "false", "no", "off", "disabled"]);
|
|
8537
9293
|
}
|
|
8538
9294
|
});
|
|
8539
9295
|
|
|
@@ -8756,6 +9512,7 @@ __export(dist_exports, {
|
|
|
8756
9512
|
DEFAULT_DEFAULT_BRANCH: () => DEFAULT_DEFAULT_BRANCH,
|
|
8757
9513
|
DEFAULT_EXECUTOR_ENABLED: () => DEFAULT_EXECUTOR_ENABLED,
|
|
8758
9514
|
DEFAULT_FALLBACK_ON_RATE_LIMIT: () => DEFAULT_FALLBACK_ON_RATE_LIMIT,
|
|
9515
|
+
DEFAULT_FEEDBACK: () => DEFAULT_FEEDBACK,
|
|
8759
9516
|
DEFAULT_JOB_PROVIDERS: () => DEFAULT_JOB_PROVIDERS,
|
|
8760
9517
|
DEFAULT_LOCAL_BOARD_INFO: () => DEFAULT_LOCAL_BOARD_INFO,
|
|
8761
9518
|
DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
|
|
@@ -8821,6 +9578,7 @@ __export(dist_exports, {
|
|
|
8821
9578
|
EXECUTOR_PARTIAL_LABEL: () => EXECUTOR_PARTIAL_LABEL,
|
|
8822
9579
|
EXECUTOR_READY_REVIEW_LABEL: () => EXECUTOR_READY_REVIEW_LABEL,
|
|
8823
9580
|
EXECUTOR_RESUMABLE_LABEL: () => EXECUTOR_RESUMABLE_LABEL,
|
|
9581
|
+
FAILURE_CATEGORIES: () => FAILURE_CATEGORIES,
|
|
8824
9582
|
GLOBAL_CONFIG_DIR: () => GLOBAL_CONFIG_DIR,
|
|
8825
9583
|
GLOBAL_NOTIFICATIONS_FILE_NAME: () => GLOBAL_NOTIFICATIONS_FILE_NAME,
|
|
8826
9584
|
HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
|
|
@@ -8849,6 +9607,7 @@ __export(dist_exports, {
|
|
|
8849
9607
|
ROADMAP_SECTION_MAPPINGS: () => ROADMAP_SECTION_MAPPINGS,
|
|
8850
9608
|
STATE_DB_FILE_NAME: () => STATE_DB_FILE_NAME,
|
|
8851
9609
|
SqliteKanbanIssueRepository: () => SqliteKanbanIssueRepository,
|
|
9610
|
+
SqliteSessionOutcomeRepository: () => SqliteSessionOutcomeRepository,
|
|
8852
9611
|
VALID_CLAUDE_MODELS: () => VALID_CLAUDE_MODELS,
|
|
8853
9612
|
VALID_JOB_TYPES: () => VALID_JOB_TYPES,
|
|
8854
9613
|
VALID_MERGE_METHODS: () => VALID_MERGE_METHODS,
|
|
@@ -8857,9 +9616,12 @@ __export(dist_exports, {
|
|
|
8857
9616
|
addDelayToIsoString: () => addDelayToIsoString,
|
|
8858
9617
|
addEntry: () => addEntry,
|
|
8859
9618
|
analyticsLockPath: () => analyticsLockPath,
|
|
9619
|
+
analyzeFeedbackOutcome: () => analyzeFeedbackOutcome,
|
|
8860
9620
|
auditLockPath: () => auditLockPath,
|
|
8861
9621
|
buildDescription: () => buildDescription,
|
|
8862
9622
|
buildJobEnvOverrides: () => buildJobEnvOverrides,
|
|
9623
|
+
buildProjectFeedbackPromptBlock: () => buildProjectFeedbackPromptBlock,
|
|
9624
|
+
buildSessionOutcomeInput: () => buildSessionOutcomeInput,
|
|
8863
9625
|
calculateStringSimilarity: () => calculateStringSimilarity,
|
|
8864
9626
|
camelToUpperSnake: () => camelToUpperSnake,
|
|
8865
9627
|
canStartJob: () => canStartJob,
|
|
@@ -8875,6 +9637,7 @@ __export(dist_exports, {
|
|
|
8875
9637
|
checkProviderCli: () => checkProviderCli,
|
|
8876
9638
|
checkRateLimited: () => checkRateLimited,
|
|
8877
9639
|
claimJobSlot: () => claimJobSlot,
|
|
9640
|
+
classifyFailure: () => classifyFailure,
|
|
8878
9641
|
cleanupExpiredJobs: () => cleanupExpiredJobs,
|
|
8879
9642
|
cleanupWorktrees: () => cleanupWorktrees,
|
|
8880
9643
|
clearPrdState: () => clearPrdState,
|
|
@@ -8981,6 +9744,7 @@ __export(dist_exports, {
|
|
|
8981
9744
|
info: () => info,
|
|
8982
9745
|
initContainer: () => initContainer,
|
|
8983
9746
|
isContainerInitialized: () => isContainerInitialized,
|
|
9747
|
+
isFeedbackPromptEnabled: () => isFeedbackPromptEnabled,
|
|
8984
9748
|
isInCooldown: () => isInCooldown,
|
|
8985
9749
|
isItemProcessed: () => isItemProcessed,
|
|
8986
9750
|
isJobTypeEnabled: () => isJobTypeEnabled,
|
|
@@ -9024,6 +9788,7 @@ __export(dist_exports, {
|
|
|
9024
9788
|
readPrdStates: () => readPrdStates,
|
|
9025
9789
|
recordExecution: () => recordExecution,
|
|
9026
9790
|
recordJobRun: () => recordJobRun,
|
|
9791
|
+
redactOutcomeText: () => redactOutcomeText,
|
|
9027
9792
|
registerProject: () => registerProject,
|
|
9028
9793
|
releaseLock: () => releaseLock,
|
|
9029
9794
|
removeEntries: () => removeEntries,
|
|
@@ -9031,6 +9796,7 @@ __export(dist_exports, {
|
|
|
9031
9796
|
removeJob: () => removeJob,
|
|
9032
9797
|
removeProject: () => removeProject,
|
|
9033
9798
|
renderPrdTemplate: () => renderPrdTemplate,
|
|
9799
|
+
renderProjectFeedbackBlock: () => renderProjectFeedbackBlock,
|
|
9034
9800
|
renderSlicerPrompt: () => renderSlicerPrompt,
|
|
9035
9801
|
resetRepositories: () => resetRepositories,
|
|
9036
9802
|
resolveJobProvider: () => resolveJobProvider,
|
|
@@ -9048,6 +9814,7 @@ __export(dist_exports, {
|
|
|
9048
9814
|
saveRegistry: () => saveRegistry,
|
|
9049
9815
|
saveRoadmapState: () => saveRoadmapState,
|
|
9050
9816
|
scanRoadmap: () => scanRoadmap,
|
|
9817
|
+
selectPromptAugmentations: () => selectPromptAugmentations,
|
|
9051
9818
|
sendNotifications: () => sendNotifications,
|
|
9052
9819
|
sendWebhook: () => sendWebhook,
|
|
9053
9820
|
setConfigValue: () => setConfigValue,
|
|
@@ -9117,6 +9884,9 @@ var init_dist = __esm({
|
|
|
9117
9884
|
init_summary();
|
|
9118
9885
|
init_analytics();
|
|
9119
9886
|
init_audit();
|
|
9887
|
+
init_outcome_parser();
|
|
9888
|
+
init_pattern_analyzer();
|
|
9889
|
+
init_prompt_augmenter();
|
|
9120
9890
|
init_prd_template();
|
|
9121
9891
|
init_slicer_prompt();
|
|
9122
9892
|
init_jobs();
|
|
@@ -9394,6 +10164,7 @@ function buildInitConfig(params) {
|
|
|
9394
10164
|
},
|
|
9395
10165
|
audit: { ...defaults.audit },
|
|
9396
10166
|
analytics: { ...defaults.analytics },
|
|
10167
|
+
feedback: { ...defaults.feedback },
|
|
9397
10168
|
merger: { ...defaults.merger },
|
|
9398
10169
|
prResolver: { ...defaults.prResolver },
|
|
9399
10170
|
jobProviders: { ...defaults.jobProviders },
|
|
@@ -9958,6 +10729,47 @@ function getTelegramStatusWebhooks(config) {
|
|
|
9958
10729
|
).map((wh) => ({ botToken: wh.botToken, chatId: wh.chatId }));
|
|
9959
10730
|
}
|
|
9960
10731
|
|
|
10732
|
+
// src/commands/shared/feedback.ts
|
|
10733
|
+
init_dist();
|
|
10734
|
+
function getFeedbackAnalysisOptions(config) {
|
|
10735
|
+
const feedback = config.feedback ?? {
|
|
10736
|
+
augmentationTtlDays: 14,
|
|
10737
|
+
confidenceThreshold: 0.75,
|
|
10738
|
+
maxActiveAugmentations: 3,
|
|
10739
|
+
successStreakToExpire: 3
|
|
10740
|
+
};
|
|
10741
|
+
return {
|
|
10742
|
+
augmentationTtlMs: feedback.augmentationTtlDays * 24 * 60 * 60 * 1e3,
|
|
10743
|
+
confidenceThreshold: feedback.confidenceThreshold,
|
|
10744
|
+
maxActiveAugmentations: feedback.maxActiveAugmentations,
|
|
10745
|
+
successStreakToExpire: feedback.successStreakToExpire
|
|
10746
|
+
};
|
|
10747
|
+
}
|
|
10748
|
+
function isFeedbackEnabled(config) {
|
|
10749
|
+
return config.feedback?.enabled !== false && isFeedbackPromptEnabled();
|
|
10750
|
+
}
|
|
10751
|
+
function recordJobOutcome(input) {
|
|
10752
|
+
const repository = getRepositories().sessionOutcomes;
|
|
10753
|
+
const storedOutcome = repository.insertOutcome(
|
|
10754
|
+
buildSessionOutcomeInput({
|
|
10755
|
+
exitCode: input.exitCode,
|
|
10756
|
+
finishedAt: input.finishedAt,
|
|
10757
|
+
jobType: input.jobType,
|
|
10758
|
+
metadata: input.metadata,
|
|
10759
|
+
minReviewScore: input.minReviewScore,
|
|
10760
|
+
projectPath: input.projectDir,
|
|
10761
|
+
providerKey: input.providerKey ?? resolveJobProvider(input.config, input.jobType),
|
|
10762
|
+
scriptResult: input.scriptResult,
|
|
10763
|
+
startedAt: input.startedAt,
|
|
10764
|
+
stderr: input.stderr,
|
|
10765
|
+
stdout: input.stdout
|
|
10766
|
+
})
|
|
10767
|
+
);
|
|
10768
|
+
if (isFeedbackEnabled(input.config)) {
|
|
10769
|
+
analyzeFeedbackOutcome(repository, storedOutcome, getFeedbackAnalysisOptions(input.config));
|
|
10770
|
+
}
|
|
10771
|
+
}
|
|
10772
|
+
|
|
9961
10773
|
// src/commands/run.ts
|
|
9962
10774
|
import * as fs24 from "fs";
|
|
9963
10775
|
import * as path23 from "path";
|
|
@@ -10037,6 +10849,8 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
10037
10849
|
}
|
|
10038
10850
|
if (event) {
|
|
10039
10851
|
const timeoutDuration = event === "run_timeout" ? config.maxRuntime : void 0;
|
|
10852
|
+
const checkpointValue = scriptResult?.data.checkpoint;
|
|
10853
|
+
const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
|
|
10040
10854
|
const _ctx = {
|
|
10041
10855
|
event,
|
|
10042
10856
|
projectName: path23.basename(projectDir),
|
|
@@ -10048,6 +10862,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
10048
10862
|
scriptStatus: scriptResult?.status,
|
|
10049
10863
|
failureReason: scriptResult?.data.reason,
|
|
10050
10864
|
failureDetail: scriptResult?.data.detail,
|
|
10865
|
+
checkpointStatus,
|
|
10051
10866
|
prUrl: prDetails?.url,
|
|
10052
10867
|
prTitle: prDetails?.title,
|
|
10053
10868
|
prBody: prDetails?.body,
|
|
@@ -10072,16 +10887,34 @@ async function runCrossProjectFallback(currentProjectDir, options) {
|
|
|
10072
10887
|
let candidateConfig = loadConfig(candidate.path);
|
|
10073
10888
|
candidateConfig = applyCliOverrides(candidateConfig, options);
|
|
10074
10889
|
const envVars = buildEnvVars(candidateConfig, options);
|
|
10890
|
+
applyProjectFeedbackPromptEnv(envVars, candidate.path, "executor");
|
|
10075
10891
|
envVars.NW_CROSS_PROJECT_FALLBACK_ACTIVE = "1";
|
|
10076
10892
|
try {
|
|
10893
|
+
const startedAt = Date.now();
|
|
10077
10894
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10078
10895
|
scriptPath,
|
|
10079
10896
|
[candidate.path],
|
|
10080
10897
|
envVars,
|
|
10081
10898
|
{ cwd: candidate.path }
|
|
10082
10899
|
);
|
|
10900
|
+
const finishedAt = Date.now();
|
|
10083
10901
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10084
10902
|
${stderr}`);
|
|
10903
|
+
try {
|
|
10904
|
+
recordRunSessionOutcome({
|
|
10905
|
+
projectDir: candidate.path,
|
|
10906
|
+
config: candidateConfig,
|
|
10907
|
+
envVars,
|
|
10908
|
+
startedAt,
|
|
10909
|
+
finishedAt,
|
|
10910
|
+
exitCode,
|
|
10911
|
+
stdout,
|
|
10912
|
+
stderr,
|
|
10913
|
+
scriptResult,
|
|
10914
|
+
metadata: { crossProjectFallback: true }
|
|
10915
|
+
});
|
|
10916
|
+
} catch {
|
|
10917
|
+
}
|
|
10085
10918
|
if (!options.dryRun) {
|
|
10086
10919
|
await sendRunCompletionNotifications(
|
|
10087
10920
|
candidateConfig,
|
|
@@ -10118,6 +10951,48 @@ function getRateLimitFallbackTelegramWebhooks(config) {
|
|
|
10118
10951
|
function isRateLimitFallbackTriggered(resultData) {
|
|
10119
10952
|
return resultData?.rate_limit_fallback === "1";
|
|
10120
10953
|
}
|
|
10954
|
+
function recordRunSessionOutcome(input) {
|
|
10955
|
+
const outcome = buildSessionOutcomeInput({
|
|
10956
|
+
projectPath: input.projectDir,
|
|
10957
|
+
jobType: "executor",
|
|
10958
|
+
providerKey: input.envVars.NW_PROVIDER_KEY ?? resolveJobProvider(input.config, "executor"),
|
|
10959
|
+
startedAt: input.startedAt,
|
|
10960
|
+
finishedAt: input.finishedAt,
|
|
10961
|
+
exitCode: input.exitCode,
|
|
10962
|
+
stdout: input.stdout,
|
|
10963
|
+
stderr: input.stderr,
|
|
10964
|
+
scriptResult: input.scriptResult,
|
|
10965
|
+
metadata: {
|
|
10966
|
+
providerCommand: input.envVars.NW_PROVIDER_CMD,
|
|
10967
|
+
providerLabel: input.envVars.NW_PROVIDER_LABEL,
|
|
10968
|
+
...input.metadata ?? {}
|
|
10969
|
+
}
|
|
10970
|
+
});
|
|
10971
|
+
const repository = getRepositories().sessionOutcomes;
|
|
10972
|
+
const storedOutcome = repository.insertOutcome(outcome);
|
|
10973
|
+
if (isFeedbackEnabled(input.config)) {
|
|
10974
|
+
analyzeFeedbackOutcome(repository, storedOutcome, getFeedbackAnalysisOptions(input.config));
|
|
10975
|
+
}
|
|
10976
|
+
}
|
|
10977
|
+
function applyProjectFeedbackPromptEnv(envVars, projectDir, jobType, markApplied = true) {
|
|
10978
|
+
delete envVars.NW_PROJECT_FEEDBACK_PROMPT;
|
|
10979
|
+
const config = loadConfig(projectDir);
|
|
10980
|
+
if (!isFeedbackPromptEnabled() || config.feedback?.enabled === false) {
|
|
10981
|
+
return;
|
|
10982
|
+
}
|
|
10983
|
+
try {
|
|
10984
|
+
const { promptBlock } = buildProjectFeedbackPromptBlock(
|
|
10985
|
+
getRepositories().sessionOutcomes,
|
|
10986
|
+
projectDir,
|
|
10987
|
+
jobType,
|
|
10988
|
+
{ markApplied, maxActiveAugmentations: config.feedback?.maxActiveAugmentations }
|
|
10989
|
+
);
|
|
10990
|
+
if (promptBlock.length > 0) {
|
|
10991
|
+
envVars.NW_PROJECT_FEEDBACK_PROMPT = promptBlock;
|
|
10992
|
+
}
|
|
10993
|
+
} catch {
|
|
10994
|
+
}
|
|
10995
|
+
}
|
|
10121
10996
|
function buildEnvVars(config, options) {
|
|
10122
10997
|
const env = buildBaseEnvVars(config, "executor", options.dryRun);
|
|
10123
10998
|
env.NW_MAX_RUNTIME = String(config.maxRuntime);
|
|
@@ -10256,6 +11131,7 @@ function runCommand(program2) {
|
|
|
10256
11131
|
process.exit(0);
|
|
10257
11132
|
}
|
|
10258
11133
|
const envVars = buildEnvVars(config, options);
|
|
11134
|
+
applyProjectFeedbackPromptEnv(envVars, projectDir, "executor", !options.dryRun);
|
|
10259
11135
|
const scriptPath = getScriptPath("night-watch-cron.sh");
|
|
10260
11136
|
if (options.dryRun) {
|
|
10261
11137
|
header("Dry Run: PRD Executor");
|
|
@@ -10348,6 +11224,7 @@ function runCommand(program2) {
|
|
|
10348
11224
|
const spinner = createSpinner("Running PRD executor...");
|
|
10349
11225
|
spinner.start();
|
|
10350
11226
|
try {
|
|
11227
|
+
const startedAt = Date.now();
|
|
10351
11228
|
await maybeApplyCronSchedulingDelay(config, "executor", projectDir);
|
|
10352
11229
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10353
11230
|
scriptPath,
|
|
@@ -10355,6 +11232,7 @@ function runCommand(program2) {
|
|
|
10355
11232
|
envVars,
|
|
10356
11233
|
{ cwd: projectDir }
|
|
10357
11234
|
);
|
|
11235
|
+
const finishedAt = Date.now();
|
|
10358
11236
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10359
11237
|
${stderr}`);
|
|
10360
11238
|
if (exitCode === 0) {
|
|
@@ -10371,6 +11249,20 @@ ${stderr}`);
|
|
|
10371
11249
|
spinner.fail(`PRD executor exited with code ${exitCode}`);
|
|
10372
11250
|
}
|
|
10373
11251
|
if (!options.dryRun) {
|
|
11252
|
+
try {
|
|
11253
|
+
recordRunSessionOutcome({
|
|
11254
|
+
projectDir,
|
|
11255
|
+
config,
|
|
11256
|
+
envVars,
|
|
11257
|
+
startedAt,
|
|
11258
|
+
finishedAt,
|
|
11259
|
+
exitCode,
|
|
11260
|
+
stdout,
|
|
11261
|
+
stderr,
|
|
11262
|
+
scriptResult
|
|
11263
|
+
});
|
|
11264
|
+
} catch {
|
|
11265
|
+
}
|
|
10374
11266
|
await sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult);
|
|
10375
11267
|
}
|
|
10376
11268
|
if (shouldAttemptCrossProjectFallback(options, scriptResult?.status)) {
|
|
@@ -10440,6 +11332,25 @@ function buildReviewNotificationTargets(reviewedPrNumbers, noChangesPrNumbers, l
|
|
|
10440
11332
|
noChangesNeeded: noChangesSet.has(prNumber)
|
|
10441
11333
|
}));
|
|
10442
11334
|
}
|
|
11335
|
+
function applyProjectFeedbackPromptEnv2(envVars, projectDir, jobType, markApplied = true) {
|
|
11336
|
+
delete envVars.NW_PROJECT_FEEDBACK_PROMPT;
|
|
11337
|
+
const config = loadConfig(projectDir);
|
|
11338
|
+
if (!isFeedbackPromptEnabled() || config.feedback?.enabled === false) {
|
|
11339
|
+
return;
|
|
11340
|
+
}
|
|
11341
|
+
try {
|
|
11342
|
+
const { promptBlock } = buildProjectFeedbackPromptBlock(
|
|
11343
|
+
getRepositories().sessionOutcomes,
|
|
11344
|
+
projectDir,
|
|
11345
|
+
jobType,
|
|
11346
|
+
{ markApplied, maxActiveAugmentations: config.feedback?.maxActiveAugmentations }
|
|
11347
|
+
);
|
|
11348
|
+
if (promptBlock.length > 0) {
|
|
11349
|
+
envVars.NW_PROJECT_FEEDBACK_PROMPT = promptBlock;
|
|
11350
|
+
}
|
|
11351
|
+
} catch {
|
|
11352
|
+
}
|
|
11353
|
+
}
|
|
10443
11354
|
function parseRetryAttempts(raw) {
|
|
10444
11355
|
if (!raw) {
|
|
10445
11356
|
return 1;
|
|
@@ -10550,6 +11461,7 @@ function reviewCommand(program2) {
|
|
|
10550
11461
|
process.exit(0);
|
|
10551
11462
|
}
|
|
10552
11463
|
const envVars = buildEnvVars2(config, options);
|
|
11464
|
+
applyProjectFeedbackPromptEnv2(envVars, projectDir, "reviewer", !options.dryRun);
|
|
10553
11465
|
const scriptPath = getScriptPath("night-watch-pr-reviewer-cron.sh");
|
|
10554
11466
|
if (options.dryRun) {
|
|
10555
11467
|
header("Dry Run: PR Reviewer");
|
|
@@ -10614,12 +11526,14 @@ function reviewCommand(program2) {
|
|
|
10614
11526
|
const spinner = createSpinner("Running PR reviewer...");
|
|
10615
11527
|
spinner.start();
|
|
10616
11528
|
try {
|
|
11529
|
+
const startedAt = Date.now();
|
|
10617
11530
|
await maybeApplyCronSchedulingDelay(config, "reviewer", projectDir);
|
|
10618
11531
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10619
11532
|
scriptPath,
|
|
10620
11533
|
[projectDir],
|
|
10621
11534
|
envVars
|
|
10622
11535
|
);
|
|
11536
|
+
const finishedAt = Date.now();
|
|
10623
11537
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10624
11538
|
${stderr}`);
|
|
10625
11539
|
if (exitCode === 0) {
|
|
@@ -10634,6 +11548,31 @@ ${stderr}`);
|
|
|
10634
11548
|
spinner.fail(`PR reviewer exited with code ${exitCode}`);
|
|
10635
11549
|
}
|
|
10636
11550
|
if (!options.dryRun) {
|
|
11551
|
+
try {
|
|
11552
|
+
const repository = getRepositories().sessionOutcomes;
|
|
11553
|
+
const storedOutcome = repository.insertOutcome(
|
|
11554
|
+
buildSessionOutcomeInput({
|
|
11555
|
+
exitCode,
|
|
11556
|
+
finishedAt,
|
|
11557
|
+
jobType: "reviewer",
|
|
11558
|
+
metadata: {
|
|
11559
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11560
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
11561
|
+
},
|
|
11562
|
+
minReviewScore: config.minReviewScore,
|
|
11563
|
+
projectPath: projectDir,
|
|
11564
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "reviewer"),
|
|
11565
|
+
scriptResult,
|
|
11566
|
+
startedAt,
|
|
11567
|
+
stderr,
|
|
11568
|
+
stdout
|
|
11569
|
+
})
|
|
11570
|
+
);
|
|
11571
|
+
if (isFeedbackEnabled(config)) {
|
|
11572
|
+
analyzeFeedbackOutcome(repository, storedOutcome, getFeedbackAnalysisOptions(config));
|
|
11573
|
+
}
|
|
11574
|
+
} catch {
|
|
11575
|
+
}
|
|
10637
11576
|
const shouldNotifyCompletion = shouldSendReviewCompletionNotification(
|
|
10638
11577
|
exitCode,
|
|
10639
11578
|
scriptResult?.status
|
|
@@ -10847,12 +11786,14 @@ function qaCommand(program2) {
|
|
|
10847
11786
|
const spinner = createSpinner("Running QA process...");
|
|
10848
11787
|
spinner.start();
|
|
10849
11788
|
try {
|
|
11789
|
+
const startedAt = Date.now();
|
|
10850
11790
|
await maybeApplyCronSchedulingDelay(config, "qa", projectDir);
|
|
10851
11791
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10852
11792
|
scriptPath,
|
|
10853
11793
|
[projectDir],
|
|
10854
11794
|
envVars
|
|
10855
11795
|
);
|
|
11796
|
+
const finishedAt = Date.now();
|
|
10856
11797
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10857
11798
|
${stderr}`);
|
|
10858
11799
|
if (exitCode === 0) {
|
|
@@ -10869,6 +11810,25 @@ ${stderr}`);
|
|
|
10869
11810
|
spinner.fail(`QA process exited with code ${exitCode}`);
|
|
10870
11811
|
}
|
|
10871
11812
|
if (!options.dryRun) {
|
|
11813
|
+
try {
|
|
11814
|
+
recordJobOutcome({
|
|
11815
|
+
config,
|
|
11816
|
+
exitCode,
|
|
11817
|
+
finishedAt,
|
|
11818
|
+
jobType: "qa",
|
|
11819
|
+
metadata: {
|
|
11820
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11821
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
11822
|
+
},
|
|
11823
|
+
projectDir,
|
|
11824
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "qa"),
|
|
11825
|
+
scriptResult,
|
|
11826
|
+
startedAt,
|
|
11827
|
+
stderr,
|
|
11828
|
+
stdout
|
|
11829
|
+
});
|
|
11830
|
+
} catch {
|
|
11831
|
+
}
|
|
10872
11832
|
const skipNotification = !shouldSendQaNotification(scriptResult?.status);
|
|
10873
11833
|
if (skipNotification) {
|
|
10874
11834
|
info("Skipping QA notification (no actionable QA result)");
|
|
@@ -10971,6 +11931,7 @@ function auditCommand(program2) {
|
|
|
10971
11931
|
}
|
|
10972
11932
|
const spinner = createSpinner("Running code audit...");
|
|
10973
11933
|
spinner.start();
|
|
11934
|
+
const startedAt = Date.now();
|
|
10974
11935
|
try {
|
|
10975
11936
|
await maybeApplyCronSchedulingDelay(config, "audit", projectDir);
|
|
10976
11937
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
@@ -10978,8 +11939,30 @@ function auditCommand(program2) {
|
|
|
10978
11939
|
[projectDir],
|
|
10979
11940
|
envVars
|
|
10980
11941
|
);
|
|
11942
|
+
const finishedAt = Date.now();
|
|
10981
11943
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10982
11944
|
${stderr}`);
|
|
11945
|
+
if (!options.dryRun) {
|
|
11946
|
+
try {
|
|
11947
|
+
recordJobOutcome({
|
|
11948
|
+
config,
|
|
11949
|
+
exitCode,
|
|
11950
|
+
finishedAt,
|
|
11951
|
+
jobType: "audit",
|
|
11952
|
+
metadata: {
|
|
11953
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11954
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
11955
|
+
},
|
|
11956
|
+
projectDir,
|
|
11957
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "audit"),
|
|
11958
|
+
scriptResult,
|
|
11959
|
+
startedAt,
|
|
11960
|
+
stderr,
|
|
11961
|
+
stdout
|
|
11962
|
+
});
|
|
11963
|
+
} catch {
|
|
11964
|
+
}
|
|
11965
|
+
}
|
|
10983
11966
|
if (exitCode === 0) {
|
|
10984
11967
|
if (scriptResult?.status === "queued") {
|
|
10985
11968
|
spinner.succeed("Code audit queued \u2014 another job is currently running");
|
|
@@ -11016,6 +11999,23 @@ ${stderr}`);
|
|
|
11016
11999
|
process.exit(exitCode || 1);
|
|
11017
12000
|
}
|
|
11018
12001
|
} catch (err) {
|
|
12002
|
+
try {
|
|
12003
|
+
recordJobOutcome({
|
|
12004
|
+
config,
|
|
12005
|
+
exitCode: 1,
|
|
12006
|
+
finishedAt: Date.now(),
|
|
12007
|
+
jobType: "audit",
|
|
12008
|
+
metadata: {
|
|
12009
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
12010
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
12011
|
+
},
|
|
12012
|
+
projectDir,
|
|
12013
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "audit"),
|
|
12014
|
+
startedAt,
|
|
12015
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
12016
|
+
});
|
|
12017
|
+
} catch {
|
|
12018
|
+
}
|
|
11019
12019
|
spinner.fail(`Code audit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11020
12020
|
process.exit(1);
|
|
11021
12021
|
}
|
|
@@ -11047,6 +12047,25 @@ function analyticsCommand(program2) {
|
|
|
11047
12047
|
const apiKey = config.providerEnv?.AMPLITUDE_API_KEY;
|
|
11048
12048
|
const secretKey = config.providerEnv?.AMPLITUDE_SECRET_KEY;
|
|
11049
12049
|
if (!apiKey || !secretKey) {
|
|
12050
|
+
const now = Date.now();
|
|
12051
|
+
if (!options.dryRun) {
|
|
12052
|
+
try {
|
|
12053
|
+
recordJobOutcome({
|
|
12054
|
+
config,
|
|
12055
|
+
exitCode: 1,
|
|
12056
|
+
finishedAt: now,
|
|
12057
|
+
jobType: "analytics",
|
|
12058
|
+
metadata: {
|
|
12059
|
+
missingAmplitudeCredentials: true
|
|
12060
|
+
},
|
|
12061
|
+
projectDir,
|
|
12062
|
+
providerKey: resolveJobProvider(config, "analytics"),
|
|
12063
|
+
startedAt: now,
|
|
12064
|
+
stderr: "AMPLITUDE_API_KEY and AMPLITUDE_SECRET_KEY must be set in providerEnv to run analytics."
|
|
12065
|
+
});
|
|
12066
|
+
} catch {
|
|
12067
|
+
}
|
|
12068
|
+
}
|
|
11050
12069
|
info(
|
|
11051
12070
|
"AMPLITUDE_API_KEY and AMPLITUDE_SECRET_KEY must be set in providerEnv to run analytics."
|
|
11052
12071
|
);
|
|
@@ -11068,11 +12087,45 @@ function analyticsCommand(program2) {
|
|
|
11068
12087
|
}
|
|
11069
12088
|
const spinner = createSpinner("Running analytics job...");
|
|
11070
12089
|
spinner.start();
|
|
12090
|
+
const startedAt = Date.now();
|
|
11071
12091
|
try {
|
|
11072
12092
|
await maybeApplyCronSchedulingDelay(config, "analytics", projectDir);
|
|
11073
12093
|
const result = await runAnalytics(config, projectDir);
|
|
12094
|
+
try {
|
|
12095
|
+
recordJobOutcome({
|
|
12096
|
+
config,
|
|
12097
|
+
exitCode: 0,
|
|
12098
|
+
finishedAt: Date.now(),
|
|
12099
|
+
jobType: "analytics",
|
|
12100
|
+
metadata: {
|
|
12101
|
+
lookbackDays: config.analytics.lookbackDays,
|
|
12102
|
+
summary: result.summary
|
|
12103
|
+
},
|
|
12104
|
+
projectDir,
|
|
12105
|
+
providerKey: resolveJobProvider(config, "analytics"),
|
|
12106
|
+
startedAt,
|
|
12107
|
+
stdout: result.summary
|
|
12108
|
+
});
|
|
12109
|
+
} catch {
|
|
12110
|
+
}
|
|
11074
12111
|
spinner.succeed(`Analytics complete \u2014 ${result.summary}`);
|
|
11075
12112
|
} catch (err) {
|
|
12113
|
+
try {
|
|
12114
|
+
recordJobOutcome({
|
|
12115
|
+
config,
|
|
12116
|
+
exitCode: 1,
|
|
12117
|
+
finishedAt: Date.now(),
|
|
12118
|
+
jobType: "analytics",
|
|
12119
|
+
metadata: {
|
|
12120
|
+
lookbackDays: config.analytics.lookbackDays
|
|
12121
|
+
},
|
|
12122
|
+
projectDir,
|
|
12123
|
+
providerKey: resolveJobProvider(config, "analytics"),
|
|
12124
|
+
startedAt,
|
|
12125
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
12126
|
+
});
|
|
12127
|
+
} catch {
|
|
12128
|
+
}
|
|
11076
12129
|
spinner.fail(`Analytics failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11077
12130
|
process.exit(1);
|
|
11078
12131
|
}
|
|
@@ -15787,11 +16840,217 @@ function createProjectDoctorRoutes() {
|
|
|
15787
16840
|
return router;
|
|
15788
16841
|
}
|
|
15789
16842
|
|
|
16843
|
+
// ../server/dist/routes/feedback.routes.js
|
|
16844
|
+
init_dist();
|
|
16845
|
+
import { Router as Router5 } from "express";
|
|
16846
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
16847
|
+
var WINDOW_DAYS = [7, 30];
|
|
16848
|
+
var VALID_AUGMENTATION_STATUSES = [
|
|
16849
|
+
"active",
|
|
16850
|
+
"paused",
|
|
16851
|
+
"expired",
|
|
16852
|
+
"archived"
|
|
16853
|
+
];
|
|
16854
|
+
function emptyBreakdown() {
|
|
16855
|
+
return {
|
|
16856
|
+
totalCount: 0,
|
|
16857
|
+
successCount: 0,
|
|
16858
|
+
failureCount: 0,
|
|
16859
|
+
timeoutCount: 0,
|
|
16860
|
+
rateLimitedCount: 0,
|
|
16861
|
+
skippedCount: 0,
|
|
16862
|
+
successRate: null
|
|
16863
|
+
};
|
|
16864
|
+
}
|
|
16865
|
+
function applyOutcome(summary, outcome) {
|
|
16866
|
+
summary.totalCount += 1;
|
|
16867
|
+
if (outcome === "success")
|
|
16868
|
+
summary.successCount += 1;
|
|
16869
|
+
if (outcome === "failure")
|
|
16870
|
+
summary.failureCount += 1;
|
|
16871
|
+
if (outcome === "timeout")
|
|
16872
|
+
summary.timeoutCount += 1;
|
|
16873
|
+
if (outcome === "rate_limited")
|
|
16874
|
+
summary.rateLimitedCount += 1;
|
|
16875
|
+
if (outcome === "skipped")
|
|
16876
|
+
summary.skippedCount += 1;
|
|
16877
|
+
}
|
|
16878
|
+
function finalizeBreakdown(summary) {
|
|
16879
|
+
return {
|
|
16880
|
+
...summary,
|
|
16881
|
+
successRate: summary.totalCount > 0 ? summary.successCount / summary.totalCount : null
|
|
16882
|
+
};
|
|
16883
|
+
}
|
|
16884
|
+
function summarizeOutcomesBy(outcomes, getKey) {
|
|
16885
|
+
const grouped = {};
|
|
16886
|
+
for (const outcome of outcomes) {
|
|
16887
|
+
const key = getKey(outcome);
|
|
16888
|
+
grouped[key] ??= emptyBreakdown();
|
|
16889
|
+
applyOutcome(grouped[key], outcome.outcome);
|
|
16890
|
+
}
|
|
16891
|
+
return Object.fromEntries(Object.entries(grouped).map(([key, summary]) => [key, finalizeBreakdown(summary)]));
|
|
16892
|
+
}
|
|
16893
|
+
function buildWindowSummary(projectPath, days) {
|
|
16894
|
+
const repo = getRepositories().sessionOutcomes;
|
|
16895
|
+
const toFinishedAt = Date.now();
|
|
16896
|
+
const fromFinishedAt = toFinishedAt - days * DAY_MS;
|
|
16897
|
+
const base = repo.querySummary({ projectPath, fromFinishedAt, toFinishedAt });
|
|
16898
|
+
const outcomes = repo.queryOutcomes({ projectPath, fromFinishedAt, toFinishedAt, limit: 500 });
|
|
16899
|
+
const byJobType = Object.fromEntries(getValidJobTypes().map((jobType) => {
|
|
16900
|
+
const summary = repo.querySummary({ projectPath, jobType, fromFinishedAt, toFinishedAt });
|
|
16901
|
+
return [
|
|
16902
|
+
jobType,
|
|
16903
|
+
finalizeBreakdown({
|
|
16904
|
+
totalCount: summary.totalCount,
|
|
16905
|
+
successCount: summary.successCount,
|
|
16906
|
+
failureCount: summary.failureCount,
|
|
16907
|
+
timeoutCount: summary.timeoutCount,
|
|
16908
|
+
rateLimitedCount: summary.rateLimitedCount,
|
|
16909
|
+
skippedCount: summary.skippedCount,
|
|
16910
|
+
successRate: null
|
|
16911
|
+
})
|
|
16912
|
+
];
|
|
16913
|
+
}).filter(([, summary]) => summary.totalCount > 0));
|
|
16914
|
+
return {
|
|
16915
|
+
...base,
|
|
16916
|
+
days,
|
|
16917
|
+
fromFinishedAt,
|
|
16918
|
+
toFinishedAt,
|
|
16919
|
+
successRate: base.totalCount > 0 ? base.successCount / base.totalCount : null,
|
|
16920
|
+
byJobType,
|
|
16921
|
+
byProvider: summarizeOutcomesBy(outcomes, (outcome) => outcome.providerKey)
|
|
16922
|
+
};
|
|
16923
|
+
}
|
|
16924
|
+
function getActiveAugmentations(projectPath) {
|
|
16925
|
+
return getRepositories().sessionOutcomes.listAugmentations({
|
|
16926
|
+
projectPath,
|
|
16927
|
+
status: "active",
|
|
16928
|
+
includeExpired: false,
|
|
16929
|
+
limit: 250
|
|
16930
|
+
});
|
|
16931
|
+
}
|
|
16932
|
+
function buildFailurePatterns(projectPath) {
|
|
16933
|
+
const outcomes = getRepositories().sessionOutcomes.queryOutcomes({
|
|
16934
|
+
projectPath,
|
|
16935
|
+
outcome: "failure",
|
|
16936
|
+
limit: 500
|
|
16937
|
+
});
|
|
16938
|
+
const patterns = /* @__PURE__ */ new Map();
|
|
16939
|
+
for (const outcome of outcomes) {
|
|
16940
|
+
const key = [
|
|
16941
|
+
outcome.jobType,
|
|
16942
|
+
outcome.providerKey,
|
|
16943
|
+
outcome.failureCategory ?? "uncategorized",
|
|
16944
|
+
outcome.failureSignature ?? "unknown"
|
|
16945
|
+
].join(":");
|
|
16946
|
+
const current = patterns.get(key);
|
|
16947
|
+
if (current) {
|
|
16948
|
+
current.sampleCount += 1;
|
|
16949
|
+
current.lastSeenAt = Math.max(current.lastSeenAt, outcome.finishedAt);
|
|
16950
|
+
continue;
|
|
16951
|
+
}
|
|
16952
|
+
patterns.set(key, {
|
|
16953
|
+
key,
|
|
16954
|
+
jobType: outcome.jobType,
|
|
16955
|
+
providerKey: outcome.providerKey,
|
|
16956
|
+
category: outcome.failureCategory,
|
|
16957
|
+
signature: outcome.failureSignature,
|
|
16958
|
+
sampleCount: 1,
|
|
16959
|
+
lastSeenAt: outcome.finishedAt
|
|
16960
|
+
});
|
|
16961
|
+
}
|
|
16962
|
+
return [...patterns.values()].sort((a, b) => b.sampleCount - a.sampleCount || b.lastSeenAt - a.lastSeenAt).slice(0, 10);
|
|
16963
|
+
}
|
|
16964
|
+
function resolveAugmentationStatus(body) {
|
|
16965
|
+
if (body.action === "enable")
|
|
16966
|
+
return "active";
|
|
16967
|
+
if (body.action === "disable")
|
|
16968
|
+
return "paused";
|
|
16969
|
+
if (body.action === "expire")
|
|
16970
|
+
return "expired";
|
|
16971
|
+
if (body.enabled === true)
|
|
16972
|
+
return "active";
|
|
16973
|
+
if (body.enabled === false)
|
|
16974
|
+
return "paused";
|
|
16975
|
+
if (body.status && VALID_AUGMENTATION_STATUSES.includes(body.status))
|
|
16976
|
+
return body.status;
|
|
16977
|
+
return null;
|
|
16978
|
+
}
|
|
16979
|
+
function createFeedbackRouteHandlers(ctx) {
|
|
16980
|
+
const router = Router5({ mergeParams: true });
|
|
16981
|
+
const p = ctx.pathPrefix;
|
|
16982
|
+
router.get(`/${p}summary`, (req, res) => {
|
|
16983
|
+
try {
|
|
16984
|
+
const projectPath = ctx.getProjectDir(req);
|
|
16985
|
+
const response = {
|
|
16986
|
+
projectPath,
|
|
16987
|
+
windows: {
|
|
16988
|
+
last7Days: buildWindowSummary(projectPath, WINDOW_DAYS[0]),
|
|
16989
|
+
last30Days: buildWindowSummary(projectPath, WINDOW_DAYS[1])
|
|
16990
|
+
},
|
|
16991
|
+
activeAugmentations: getActiveAugmentations(projectPath)
|
|
16992
|
+
};
|
|
16993
|
+
res.json(response);
|
|
16994
|
+
} catch (error2) {
|
|
16995
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
16996
|
+
}
|
|
16997
|
+
});
|
|
16998
|
+
router.get(`/${p}patterns`, (req, res) => {
|
|
16999
|
+
try {
|
|
17000
|
+
const projectPath = ctx.getProjectDir(req);
|
|
17001
|
+
const response = {
|
|
17002
|
+
projectPath,
|
|
17003
|
+
patterns: getRepositories().sessionOutcomes.listPatterns({ projectPath, limit: 25 }),
|
|
17004
|
+
topFailurePatterns: buildFailurePatterns(projectPath)
|
|
17005
|
+
};
|
|
17006
|
+
res.json(response);
|
|
17007
|
+
} catch (error2) {
|
|
17008
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
17009
|
+
}
|
|
17010
|
+
});
|
|
17011
|
+
router.patch(`/${p}augmentations/:id`, (req, res) => {
|
|
17012
|
+
try {
|
|
17013
|
+
const id = parseInt(req.params.id, 10);
|
|
17014
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
17015
|
+
res.status(400).json({ error: "Invalid augmentation id" });
|
|
17016
|
+
return;
|
|
17017
|
+
}
|
|
17018
|
+
const status = resolveAugmentationStatus(req.body);
|
|
17019
|
+
if (!status) {
|
|
17020
|
+
res.status(400).json({ error: "Expected action, enabled, or status update" });
|
|
17021
|
+
return;
|
|
17022
|
+
}
|
|
17023
|
+
const projectPath = ctx.getProjectDir(req);
|
|
17024
|
+
const augmentation = getRepositories().sessionOutcomes.updateAugmentationStatus(id, status, projectPath);
|
|
17025
|
+
if (!augmentation) {
|
|
17026
|
+
res.status(404).json({ error: "Augmentation not found" });
|
|
17027
|
+
return;
|
|
17028
|
+
}
|
|
17029
|
+
res.json({ augmentation });
|
|
17030
|
+
} catch (error2) {
|
|
17031
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
17032
|
+
}
|
|
17033
|
+
});
|
|
17034
|
+
return router;
|
|
17035
|
+
}
|
|
17036
|
+
function createFeedbackRoutes(deps) {
|
|
17037
|
+
return createFeedbackRouteHandlers({
|
|
17038
|
+
getProjectDir: () => deps.projectDir,
|
|
17039
|
+
pathPrefix: ""
|
|
17040
|
+
});
|
|
17041
|
+
}
|
|
17042
|
+
function createProjectFeedbackRoutes() {
|
|
17043
|
+
return createFeedbackRouteHandlers({
|
|
17044
|
+
getProjectDir: (req) => req.projectDir,
|
|
17045
|
+
pathPrefix: "feedback/"
|
|
17046
|
+
});
|
|
17047
|
+
}
|
|
17048
|
+
|
|
15790
17049
|
// ../server/dist/routes/job.routes.js
|
|
15791
17050
|
init_dist();
|
|
15792
17051
|
import { spawn as spawn7 } from "child_process";
|
|
15793
17052
|
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
15794
|
-
import { Router as
|
|
17053
|
+
import { Router as Router6 } from "express";
|
|
15795
17054
|
var SUPPORTED_GITHUB_EVENTS = [
|
|
15796
17055
|
"workflow_run",
|
|
15797
17056
|
"check_suite",
|
|
@@ -15984,7 +17243,7 @@ function getLockPathForJob2(projectDir, jobId) {
|
|
|
15984
17243
|
}
|
|
15985
17244
|
}
|
|
15986
17245
|
function createJobRouteHandlers(ctx) {
|
|
15987
|
-
const router =
|
|
17246
|
+
const router = Router6({ mergeParams: true });
|
|
15988
17247
|
const p = ctx.pathPrefix;
|
|
15989
17248
|
router.post(`/${p}:id/run`, (req, res) => {
|
|
15990
17249
|
try {
|
|
@@ -16108,10 +17367,10 @@ function createProjectJobRoutes() {
|
|
|
16108
17367
|
// ../server/dist/routes/log.routes.js
|
|
16109
17368
|
init_dist();
|
|
16110
17369
|
import * as path35 from "path";
|
|
16111
|
-
import { Router as
|
|
17370
|
+
import { Router as Router7 } from "express";
|
|
16112
17371
|
function createLogRoutes(deps) {
|
|
16113
17372
|
const { projectDir } = deps;
|
|
16114
|
-
const router =
|
|
17373
|
+
const router = Router7();
|
|
16115
17374
|
router.get("/:name", (req, res) => {
|
|
16116
17375
|
try {
|
|
16117
17376
|
const { name } = req.params;
|
|
@@ -16136,7 +17395,7 @@ function createLogRoutes(deps) {
|
|
|
16136
17395
|
return router;
|
|
16137
17396
|
}
|
|
16138
17397
|
function createProjectLogRoutes() {
|
|
16139
|
-
const router =
|
|
17398
|
+
const router = Router7({ mergeParams: true });
|
|
16140
17399
|
router.get("/logs/:name", (req, res) => {
|
|
16141
17400
|
try {
|
|
16142
17401
|
const projectDir = req.projectDir;
|
|
@@ -16163,9 +17422,9 @@ function createProjectLogRoutes() {
|
|
|
16163
17422
|
}
|
|
16164
17423
|
|
|
16165
17424
|
// ../server/dist/routes/prd.routes.js
|
|
16166
|
-
import { Router as
|
|
17425
|
+
import { Router as Router8 } from "express";
|
|
16167
17426
|
function createPrdRoutes(_deps) {
|
|
16168
|
-
const router =
|
|
17427
|
+
const router = Router8();
|
|
16169
17428
|
router.get("/", (_req, res) => {
|
|
16170
17429
|
res.status(410).json({ error: "PRDs endpoint deprecated - use GitHub Board instead" });
|
|
16171
17430
|
});
|
|
@@ -16175,7 +17434,7 @@ function createPrdRoutes(_deps) {
|
|
|
16175
17434
|
return router;
|
|
16176
17435
|
}
|
|
16177
17436
|
function createProjectPrdRoutes() {
|
|
16178
|
-
const router =
|
|
17437
|
+
const router = Router8({ mergeParams: true });
|
|
16179
17438
|
router.get("/prds", (_req, res) => {
|
|
16180
17439
|
res.status(410).json({ error: "PRDs endpoint deprecated - use GitHub Board instead" });
|
|
16181
17440
|
});
|
|
@@ -16188,9 +17447,9 @@ function createProjectPrdRoutes() {
|
|
|
16188
17447
|
// ../server/dist/routes/roadmap.routes.js
|
|
16189
17448
|
init_dist();
|
|
16190
17449
|
import * as path36 from "path";
|
|
16191
|
-
import { Router as
|
|
17450
|
+
import { Router as Router9 } from "express";
|
|
16192
17451
|
function createRoadmapRouteHandlers(ctx) {
|
|
16193
|
-
const router =
|
|
17452
|
+
const router = Router9({ mergeParams: true });
|
|
16194
17453
|
const p = ctx.pathPrefix;
|
|
16195
17454
|
router.get(`/${p}`, (req, res) => {
|
|
16196
17455
|
try {
|
|
@@ -16271,11 +17530,11 @@ function createProjectRoadmapRoutes() {
|
|
|
16271
17530
|
|
|
16272
17531
|
// ../server/dist/routes/status.routes.js
|
|
16273
17532
|
init_dist();
|
|
16274
|
-
import { Router as
|
|
17533
|
+
import { Router as Router10 } from "express";
|
|
16275
17534
|
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
16276
17535
|
function createStatusRoutes(deps) {
|
|
16277
17536
|
const { projectDir, getConfig, sseClients } = deps;
|
|
16278
|
-
const router =
|
|
17537
|
+
const router = Router10();
|
|
16279
17538
|
router.get("/events", (req, res) => {
|
|
16280
17539
|
res.setHeader("Content-Type", "text/event-stream");
|
|
16281
17540
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -16404,7 +17663,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
16404
17663
|
}
|
|
16405
17664
|
function createScheduleInfoRoutes(deps) {
|
|
16406
17665
|
const { projectDir, getConfig } = deps;
|
|
16407
|
-
const router =
|
|
17666
|
+
const router = Router10();
|
|
16408
17667
|
router.get("/", async (_req, res) => {
|
|
16409
17668
|
try {
|
|
16410
17669
|
const config = getConfig();
|
|
@@ -16418,7 +17677,7 @@ function createScheduleInfoRoutes(deps) {
|
|
|
16418
17677
|
}
|
|
16419
17678
|
function createProjectSseRoutes(deps) {
|
|
16420
17679
|
const { projectSseClients, projectSseWatchers } = deps;
|
|
16421
|
-
const router =
|
|
17680
|
+
const router = Router10({ mergeParams: true });
|
|
16422
17681
|
router.get("/status/events", (req, res) => {
|
|
16423
17682
|
const projectDir = req.projectDir;
|
|
16424
17683
|
if (!projectSseClients.has(projectDir)) {
|
|
@@ -16475,9 +17734,9 @@ data: ${JSON.stringify(snapshot)}
|
|
|
16475
17734
|
|
|
16476
17735
|
// ../server/dist/routes/queue.routes.js
|
|
16477
17736
|
init_dist();
|
|
16478
|
-
import { Router as
|
|
17737
|
+
import { Router as Router11 } from "express";
|
|
16479
17738
|
function createGlobalQueueRoutes() {
|
|
16480
|
-
const router =
|
|
17739
|
+
const router = Router11();
|
|
16481
17740
|
router.get("/status", async (_req, res) => {
|
|
16482
17741
|
try {
|
|
16483
17742
|
const status = getQueueStatus();
|
|
@@ -16512,7 +17771,7 @@ function createGlobalQueueRoutes() {
|
|
|
16512
17771
|
}
|
|
16513
17772
|
function createQueueRoutes(deps) {
|
|
16514
17773
|
const { getConfig } = deps;
|
|
16515
|
-
const router =
|
|
17774
|
+
const router = Router11();
|
|
16516
17775
|
router.get("/status", async (_req, res) => {
|
|
16517
17776
|
try {
|
|
16518
17777
|
const config = getConfig();
|
|
@@ -16622,6 +17881,7 @@ function createApp(projectDir) {
|
|
|
16622
17881
|
app.use("/api/logs", createLogRoutes({ projectDir }));
|
|
16623
17882
|
app.use("/api/doctor", createDoctorRoutes({ projectDir, getConfig: () => config }));
|
|
16624
17883
|
app.use("/api/queue", createQueueRoutes({ getConfig: () => config }));
|
|
17884
|
+
app.use("/api/feedback", createFeedbackRoutes({ projectDir }));
|
|
16625
17885
|
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
16626
17886
|
app.get("/api/prs", async (_req, res) => {
|
|
16627
17887
|
try {
|
|
@@ -16669,6 +17929,7 @@ function createProjectRouter() {
|
|
|
16669
17929
|
router.use(createProjectActionRoutes({ projectSseClients }));
|
|
16670
17930
|
router.use(createProjectJobRoutes());
|
|
16671
17931
|
router.use(createProjectRoadmapRoutes());
|
|
17932
|
+
router.use(createProjectFeedbackRoutes());
|
|
16672
17933
|
router.get("/prs", async (req, res) => {
|
|
16673
17934
|
try {
|
|
16674
17935
|
res.json(await collectPrInfo(req.projectDir, req.projectConfig.branchPatterns));
|
|
@@ -17698,6 +18959,7 @@ function sliceCommand(program2) {
|
|
|
17698
18959
|
}
|
|
17699
18960
|
const spinner = createSpinner("Running Planner...");
|
|
17700
18961
|
spinner.start();
|
|
18962
|
+
const startedAt = Date.now();
|
|
17701
18963
|
try {
|
|
17702
18964
|
await maybeApplyCronSchedulingDelay(config, "slicer", projectDir);
|
|
17703
18965
|
const result = await sliceNextItem(projectDir, config);
|
|
@@ -17727,6 +18989,28 @@ function sliceCommand(program2) {
|
|
|
17727
18989
|
}
|
|
17728
18990
|
const nothingPending = result.error === "No pending items to process";
|
|
17729
18991
|
const exitCode = result.sliced || nothingPending ? 0 : 1;
|
|
18992
|
+
if (!options.dryRun) {
|
|
18993
|
+
try {
|
|
18994
|
+
recordJobOutcome({
|
|
18995
|
+
config,
|
|
18996
|
+
exitCode,
|
|
18997
|
+
finishedAt: Date.now(),
|
|
18998
|
+
jobType: "planner",
|
|
18999
|
+
metadata: {
|
|
19000
|
+
error: result.error ?? null,
|
|
19001
|
+
file: result.file ?? null,
|
|
19002
|
+
itemTitle: result.item?.title ?? null,
|
|
19003
|
+
sliced: result.sliced
|
|
19004
|
+
},
|
|
19005
|
+
projectDir,
|
|
19006
|
+
providerKey: resolveJobProvider(config, "slicer"),
|
|
19007
|
+
startedAt,
|
|
19008
|
+
stderr: result.error,
|
|
19009
|
+
stdout: result.file ? `Created ${result.file}` : void 0
|
|
19010
|
+
});
|
|
19011
|
+
} catch {
|
|
19012
|
+
}
|
|
19013
|
+
}
|
|
17730
19014
|
if (!options.dryRun && result.sliced) {
|
|
17731
19015
|
await sendNotifications(config, {
|
|
17732
19016
|
event: "run_succeeded",
|
|
@@ -17745,6 +19029,22 @@ function sliceCommand(program2) {
|
|
|
17745
19029
|
}
|
|
17746
19030
|
process.exit(exitCode);
|
|
17747
19031
|
} catch (err) {
|
|
19032
|
+
try {
|
|
19033
|
+
recordJobOutcome({
|
|
19034
|
+
config,
|
|
19035
|
+
exitCode: 1,
|
|
19036
|
+
finishedAt: Date.now(),
|
|
19037
|
+
jobType: "planner",
|
|
19038
|
+
metadata: {
|
|
19039
|
+
error: err instanceof Error ? err.message : String(err)
|
|
19040
|
+
},
|
|
19041
|
+
projectDir,
|
|
19042
|
+
providerKey: resolveJobProvider(config, "slicer"),
|
|
19043
|
+
startedAt,
|
|
19044
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
19045
|
+
});
|
|
19046
|
+
} catch {
|
|
19047
|
+
}
|
|
17748
19048
|
spinner.fail("Failed to execute planner command");
|
|
17749
19049
|
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
17750
19050
|
process.exit(1);
|
|
@@ -18407,7 +19707,7 @@ import { spawn as spawn8 } from "child_process";
|
|
|
18407
19707
|
import chalk7 from "chalk";
|
|
18408
19708
|
import { Command as Command2 } from "commander";
|
|
18409
19709
|
var logger6 = createLogger("queue");
|
|
18410
|
-
var
|
|
19710
|
+
var VALID_JOB_TYPES2 = [
|
|
18411
19711
|
"executor",
|
|
18412
19712
|
"reviewer",
|
|
18413
19713
|
"qa",
|
|
@@ -18515,18 +19815,18 @@ function createQueueCommand() {
|
|
|
18515
19815
|
}
|
|
18516
19816
|
});
|
|
18517
19817
|
queue.command("clear").description("Clear pending jobs from the queue").option("--type <type>", "Only clear jobs of this type").option("--all", "Clear all entries including running (dangerous)").action((opts) => {
|
|
18518
|
-
if (opts.type && !
|
|
19818
|
+
if (opts.type && !VALID_JOB_TYPES2.includes(opts.type)) {
|
|
18519
19819
|
console.error(chalk7.red(`Invalid job type: ${opts.type}`));
|
|
18520
|
-
console.error(chalk7.dim(`Valid types: ${
|
|
19820
|
+
console.error(chalk7.dim(`Valid types: ${VALID_JOB_TYPES2.join(", ")}`));
|
|
18521
19821
|
process.exit(1);
|
|
18522
19822
|
}
|
|
18523
19823
|
const count = clearQueue(opts.type);
|
|
18524
19824
|
console.log(chalk7.green(`Cleared ${count} pending job(s) from the queue.`));
|
|
18525
19825
|
});
|
|
18526
19826
|
queue.command("enqueue <job-type> <project-dir>").description("Manually enqueue a job").option("--env <json>", "JSON object of environment variables to store", "{}").option("--provider-key <key>", "Provider bucket key (e.g. claude-native, codex)").action((jobType, projectDir, opts) => {
|
|
18527
|
-
if (!
|
|
19827
|
+
if (!VALID_JOB_TYPES2.includes(jobType)) {
|
|
18528
19828
|
console.error(chalk7.red(`Invalid job type: ${jobType}`));
|
|
18529
|
-
console.error(chalk7.dim(`Valid types: ${
|
|
19829
|
+
console.error(chalk7.dim(`Valid types: ${VALID_JOB_TYPES2.join(", ")}`));
|
|
18530
19830
|
process.exit(1);
|
|
18531
19831
|
}
|
|
18532
19832
|
let envVars = {};
|
|
@@ -18630,9 +19930,9 @@ function createQueueCommand() {
|
|
|
18630
19930
|
queue.command("claim <job-type> <project-dir>").description(
|
|
18631
19931
|
"Atomically claim a concurrency slot and insert a running entry (used by cron scripts)"
|
|
18632
19932
|
).option("--provider-key <key>", "Provider bucket key (e.g. claude-native, codex)").option("--pid <pid>", "PID of the calling process (stored for stale-job detection)").action((jobType, projectDir, opts) => {
|
|
18633
|
-
if (!
|
|
19933
|
+
if (!VALID_JOB_TYPES2.includes(jobType)) {
|
|
18634
19934
|
console.error(`Invalid job type: ${jobType}`);
|
|
18635
|
-
console.error(`Valid types: ${
|
|
19935
|
+
console.error(`Valid types: ${VALID_JOB_TYPES2.join(", ")}`);
|
|
18636
19936
|
process.exit(1);
|
|
18637
19937
|
}
|
|
18638
19938
|
const queueConfig = loadConfig(projectDir).queue;
|
|
@@ -19039,12 +20339,14 @@ function resolveCommand(program2) {
|
|
|
19039
20339
|
const spinner = createSpinner("Running PR resolver...");
|
|
19040
20340
|
spinner.start();
|
|
19041
20341
|
try {
|
|
20342
|
+
const startedAt = Date.now();
|
|
19042
20343
|
await maybeApplyCronSchedulingDelay(config, "pr-resolver", projectDir);
|
|
19043
20344
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
19044
20345
|
scriptPath,
|
|
19045
20346
|
[projectDir],
|
|
19046
20347
|
envVars
|
|
19047
20348
|
);
|
|
20349
|
+
const finishedAt = Date.now();
|
|
19048
20350
|
const scriptResult = parseScriptResult(`${stdout}
|
|
19049
20351
|
${stderr}`);
|
|
19050
20352
|
if (exitCode === 0) {
|
|
@@ -19059,6 +20361,27 @@ ${stderr}`);
|
|
|
19059
20361
|
spinner.fail(`PR resolver exited with code ${exitCode}`);
|
|
19060
20362
|
}
|
|
19061
20363
|
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
20364
|
+
if (!options.dryRun) {
|
|
20365
|
+
try {
|
|
20366
|
+
recordJobOutcome({
|
|
20367
|
+
config,
|
|
20368
|
+
exitCode,
|
|
20369
|
+
finishedAt,
|
|
20370
|
+
jobType: "pr-resolver",
|
|
20371
|
+
metadata: {
|
|
20372
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
20373
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
20374
|
+
},
|
|
20375
|
+
projectDir,
|
|
20376
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "pr-resolver"),
|
|
20377
|
+
scriptResult,
|
|
20378
|
+
startedAt,
|
|
20379
|
+
stderr,
|
|
20380
|
+
stdout
|
|
20381
|
+
});
|
|
20382
|
+
} catch {
|
|
20383
|
+
}
|
|
20384
|
+
}
|
|
19062
20385
|
await sendNotifications(config, {
|
|
19063
20386
|
event: notificationEvent,
|
|
19064
20387
|
projectName: path45.basename(projectDir),
|
|
@@ -19157,12 +20480,14 @@ function mergeCommand(program2) {
|
|
|
19157
20480
|
const spinner = createSpinner("Running merge orchestrator...");
|
|
19158
20481
|
spinner.start();
|
|
19159
20482
|
try {
|
|
20483
|
+
const startedAt = Date.now();
|
|
19160
20484
|
await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
|
|
19161
20485
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
19162
20486
|
scriptPath,
|
|
19163
20487
|
[projectDir],
|
|
19164
20488
|
envVars
|
|
19165
20489
|
);
|
|
20490
|
+
const finishedAt = Date.now();
|
|
19166
20491
|
const scriptResult = parseScriptResult(`${stdout}
|
|
19167
20492
|
${stderr}`);
|
|
19168
20493
|
if (exitCode === 0) {
|
|
@@ -19179,6 +20504,30 @@ ${stderr}`);
|
|
|
19179
20504
|
const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
|
|
19180
20505
|
const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
|
|
19181
20506
|
const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
|
|
20507
|
+
if (!options.dryRun) {
|
|
20508
|
+
try {
|
|
20509
|
+
recordJobOutcome({
|
|
20510
|
+
config,
|
|
20511
|
+
exitCode,
|
|
20512
|
+
finishedAt,
|
|
20513
|
+
jobType: "merger",
|
|
20514
|
+
metadata: {
|
|
20515
|
+
failedCount,
|
|
20516
|
+
mergedCount,
|
|
20517
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
20518
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
20519
|
+
},
|
|
20520
|
+
minReviewScore: config.merger.minReviewScore,
|
|
20521
|
+
projectDir,
|
|
20522
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "merger"),
|
|
20523
|
+
scriptResult,
|
|
20524
|
+
startedAt,
|
|
20525
|
+
stderr,
|
|
20526
|
+
stdout
|
|
20527
|
+
});
|
|
20528
|
+
} catch {
|
|
20529
|
+
}
|
|
20530
|
+
}
|
|
19182
20531
|
if (notificationEvent) {
|
|
19183
20532
|
await sendNotifications(config, {
|
|
19184
20533
|
event: notificationEvent,
|