@jonit-dev/night-watch-cli 1.8.12-beta.3 → 1.8.12-beta.4
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 +1718 -380
- 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 +80 -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 +5 -0
- 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 {};
|
|
2154
|
+
}
|
|
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 ") };
|
|
2085
2230
|
}
|
|
2086
|
-
var
|
|
2087
|
-
var
|
|
2088
|
-
"../core/
|
|
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
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
2189
|
-
|
|
2190
|
-
|
|
2191
|
-
|
|
2192
|
-
|
|
2193
|
-
|
|
2194
|
-
|
|
2195
|
-
|
|
2196
|
-
|
|
2197
|
-
|
|
2198
|
-
|
|
2199
|
-
|
|
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"
|
|
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;
|
|
2267
|
+
}
|
|
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
|
+
};
|
|
2314
|
+
}
|
|
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);
|
|
2220
2345
|
}
|
|
2221
|
-
|
|
2222
|
-
|
|
2223
|
-
|
|
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: ""
|
|
2346
|
+
if (input.status) {
|
|
2347
|
+
clauses.push("status = ?");
|
|
2348
|
+
params.push(input.status);
|
|
2248
2349
|
}
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
enabled: false,
|
|
2273
|
-
schedule: "55 */4 * * *",
|
|
2274
|
-
maxRuntime: 1800,
|
|
2275
|
-
mergeMethod: "squash",
|
|
2276
|
-
minReviewScore: 80,
|
|
2277
|
-
branchPatterns: [],
|
|
2278
|
-
rebaseBeforeMerge: true,
|
|
2279
|
-
maxPrsPerRun: 0
|
|
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);
|
|
2280
2373
|
}
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
|
|
2284
|
-
}
|
|
2285
|
-
});
|
|
2286
|
-
|
|
2287
|
-
// ../core/src/constants.ts
|
|
2288
|
-
var DEFAULT_LOCAL_BOARD_INFO2, VALID_JOB_TYPES2, DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV2, DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS2, DEFAULT_WEBHOOK_TRIGGERS2, BUILT_IN_PRESETS2, BUILT_IN_PRESET_IDS2, PROVIDER_COMMANDS2, LOG_FILE_NAMES2, GLOBAL_CONFIG_DIR2, STATE_DB_FILE_NAME2, DEFAULT_QUEUE_ENABLED2, DEFAULT_QUEUE_MODE2, DEFAULT_QUEUE_MAX_CONCURRENCY2, DEFAULT_QUEUE_MAX_WAIT_TIME2, DEFAULT_QUEUE_PRIORITY2, DEFAULT_QUEUE2;
|
|
2289
|
-
var init_constants2 = __esm({
|
|
2290
|
-
"../core/src/constants.ts"() {
|
|
2291
|
-
"use strict";
|
|
2292
|
-
init_job_registry2();
|
|
2293
|
-
DEFAULT_LOCAL_BOARD_INFO2 = { id: "local", number: 0, title: "Local Kanban", url: "" };
|
|
2294
|
-
VALID_JOB_TYPES2 = getValidJobTypes2();
|
|
2295
|
-
DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV2 = "NIGHT_WATCH_WEBHOOK_SECRET";
|
|
2296
|
-
DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS2 = 300;
|
|
2297
|
-
DEFAULT_WEBHOOK_TRIGGERS2 = {
|
|
2298
|
-
enabled: false,
|
|
2299
|
-
secretEnv: DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV2,
|
|
2300
|
-
allowedJobIds: getValidJobTypes2(),
|
|
2301
|
-
requireTimestamp: false,
|
|
2302
|
-
maxSkewSeconds: DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS2,
|
|
2303
|
-
github: {
|
|
2304
|
-
enabled: false,
|
|
2305
|
-
events: [],
|
|
2306
|
-
rules: []
|
|
2307
|
-
}
|
|
2308
|
-
};
|
|
2309
|
-
BUILT_IN_PRESETS2 = {
|
|
2310
|
-
claude: {
|
|
2311
|
-
name: "Claude",
|
|
2312
|
-
command: "claude",
|
|
2313
|
-
promptFlag: "-p",
|
|
2314
|
-
autoApproveFlag: "--dangerously-skip-permissions"
|
|
2315
|
-
},
|
|
2316
|
-
"claude-sonnet-4-6": {
|
|
2317
|
-
name: "Claude Sonnet 4.6",
|
|
2318
|
-
command: "claude",
|
|
2319
|
-
promptFlag: "-p",
|
|
2320
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2321
|
-
modelFlag: "--model",
|
|
2322
|
-
model: "claude-sonnet-4-6"
|
|
2323
|
-
},
|
|
2324
|
-
"claude-opus-4-6": {
|
|
2325
|
-
name: "Claude Opus 4.6",
|
|
2326
|
-
command: "claude",
|
|
2327
|
-
promptFlag: "-p",
|
|
2328
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2329
|
-
modelFlag: "--model",
|
|
2330
|
-
model: "claude-opus-4-6"
|
|
2331
|
-
},
|
|
2332
|
-
codex: {
|
|
2333
|
-
name: "Codex",
|
|
2334
|
-
command: "codex",
|
|
2335
|
-
subcommand: "exec",
|
|
2336
|
-
autoApproveFlag: "--yolo",
|
|
2337
|
-
workdirFlag: "-C"
|
|
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"
|
|
2374
|
+
if (input.status) {
|
|
2375
|
+
clauses.push("status = ?");
|
|
2376
|
+
params.push(input.status);
|
|
2351
2377
|
}
|
|
2352
|
-
|
|
2353
|
-
|
|
2354
|
-
|
|
2355
|
-
command: "claude",
|
|
2356
|
-
promptFlag: "-p",
|
|
2357
|
-
autoApproveFlag: "--dangerously-skip-permissions",
|
|
2358
|
-
modelFlag: "--model",
|
|
2359
|
-
model: "glm-5",
|
|
2360
|
-
envVars: {
|
|
2361
|
-
ANTHROPIC_BASE_URL: "https://api.z.ai/api/anthropic",
|
|
2362
|
-
API_TIMEOUT_MS: "3000000",
|
|
2363
|
-
ANTHROPIC_DEFAULT_OPUS_MODEL: "glm-5",
|
|
2364
|
-
ANTHROPIC_DEFAULT_SONNET_MODEL: "glm-5"
|
|
2378
|
+
if (!input.includeExpired) {
|
|
2379
|
+
clauses.push("(expires_at IS NULL OR expires_at > ?)");
|
|
2380
|
+
params.push(input.now ?? Date.now());
|
|
2365
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
|
});
|
|
@@ -7476,14 +7585,14 @@ var init_worktree_manager = __esm({
|
|
|
7476
7585
|
import * as fs21 from "fs";
|
|
7477
7586
|
import * as os7 from "os";
|
|
7478
7587
|
import * as path20 from "path";
|
|
7479
|
-
import
|
|
7588
|
+
import Database8 from "better-sqlite3";
|
|
7480
7589
|
function getStateDbPath() {
|
|
7481
7590
|
const base = process.env.NIGHT_WATCH_HOME || path20.join(os7.homedir(), GLOBAL_CONFIG_DIR);
|
|
7482
7591
|
return path20.join(base, STATE_DB_FILE_NAME);
|
|
7483
7592
|
}
|
|
7484
7593
|
function openDb() {
|
|
7485
7594
|
const dbPath = getStateDbPath();
|
|
7486
|
-
const db = new
|
|
7595
|
+
const db = new Database8(dbPath);
|
|
7487
7596
|
db.pragma("journal_mode = WAL");
|
|
7488
7597
|
db.pragma("busy_timeout = 5000");
|
|
7489
7598
|
if (!_migrationsApplied) {
|
|
@@ -8508,32 +8617,671 @@ async function syncAuditFindingsToBoard(config, projectDir) {
|
|
|
8508
8617
|
summary: `found ${findings.length} actionable audit finding(s), but failed to create board issue(s)`
|
|
8509
8618
|
};
|
|
8510
8619
|
}
|
|
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
|
-
};
|
|
8620
|
+
return {
|
|
8621
|
+
status: "partial",
|
|
8622
|
+
findingsCount: findings.length,
|
|
8623
|
+
issuesCreated: created,
|
|
8624
|
+
issuesFailed: failed,
|
|
8625
|
+
targetColumn,
|
|
8626
|
+
summary: `created ${created} of ${findings.length} audit issue(s) in ${targetColumn} (${failed} failed)`
|
|
8627
|
+
};
|
|
8628
|
+
}
|
|
8629
|
+
var logger5;
|
|
8630
|
+
var init_board_sync = __esm({
|
|
8631
|
+
"../core/dist/audit/board-sync.js"() {
|
|
8632
|
+
"use strict";
|
|
8633
|
+
init_factory();
|
|
8634
|
+
init_logger();
|
|
8635
|
+
init_report();
|
|
8636
|
+
logger5 = createLogger("audit-sync");
|
|
8637
|
+
}
|
|
8638
|
+
});
|
|
8639
|
+
|
|
8640
|
+
// ../core/dist/audit/index.js
|
|
8641
|
+
var init_audit = __esm({
|
|
8642
|
+
"../core/dist/audit/index.js"() {
|
|
8643
|
+
"use strict";
|
|
8644
|
+
init_report();
|
|
8645
|
+
init_board_sync();
|
|
8646
|
+
}
|
|
8647
|
+
});
|
|
8648
|
+
|
|
8649
|
+
// ../core/dist/feedback/outcome-parser.js
|
|
8650
|
+
function stripAnsi2(value) {
|
|
8651
|
+
return value.replace(ANSI_PATTERN, "");
|
|
8652
|
+
}
|
|
8653
|
+
function redactOutcomeText(value) {
|
|
8654
|
+
return SECRET_TEXT_PATTERNS2.reduce((current, [pattern, replacement]) => current.replace(pattern, replacement), value);
|
|
8655
|
+
}
|
|
8656
|
+
function trimLine(value, maxLength) {
|
|
8657
|
+
return value.length <= maxLength ? value : `${value.slice(0, maxLength - 3)}...`;
|
|
8658
|
+
}
|
|
8659
|
+
function normalizeLine(value, projectPath) {
|
|
8660
|
+
let normalized = stripAnsi2(redactOutcomeText(value)).trim();
|
|
8661
|
+
if (projectPath) {
|
|
8662
|
+
normalized = normalized.replaceAll(projectPath, "<project>");
|
|
8663
|
+
}
|
|
8664
|
+
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);
|
|
8665
|
+
}
|
|
8666
|
+
function getOutputLines(stdout, stderr) {
|
|
8667
|
+
return `${stdout ?? ""}
|
|
8668
|
+
${stderr ?? ""}`.split(/\r?\n/).map((line) => stripAnsi2(redactOutcomeText(line)).trim()).filter((line) => line.length > 0 && !line.startsWith("NIGHT_WATCH_RESULT:"));
|
|
8669
|
+
}
|
|
8670
|
+
function extractFilePath(line, projectPath) {
|
|
8671
|
+
const normalizedLine = line.replaceAll("\\", "/");
|
|
8672
|
+
const normalizedProjectPath = projectPath.replaceAll("\\", "/");
|
|
8673
|
+
if (normalizedProjectPath) {
|
|
8674
|
+
const projectPrefix = `${normalizedProjectPath}/`;
|
|
8675
|
+
const projectIndex = normalizedLine.indexOf(projectPrefix);
|
|
8676
|
+
if (projectIndex >= 0) {
|
|
8677
|
+
const relativeLine = normalizedLine.slice(projectIndex + projectPrefix.length);
|
|
8678
|
+
const relativePath = extractFilePathToken(relativeLine.split(TOKEN_SPLIT_PATTERN));
|
|
8679
|
+
if (relativePath) {
|
|
8680
|
+
return relativePath;
|
|
8681
|
+
}
|
|
8682
|
+
}
|
|
8683
|
+
}
|
|
8684
|
+
return extractFilePathToken(normalizedLine.split(TOKEN_SPLIT_PATTERN));
|
|
8685
|
+
}
|
|
8686
|
+
function extractFilePathToken(tokens) {
|
|
8687
|
+
for (const token of tokens) {
|
|
8688
|
+
const withoutLocation = cleanFilePathToken(token);
|
|
8689
|
+
if (FILE_PATH_PATTERN.test(withoutLocation)) {
|
|
8690
|
+
return withoutLocation;
|
|
8691
|
+
}
|
|
8692
|
+
}
|
|
8693
|
+
return null;
|
|
8694
|
+
}
|
|
8695
|
+
function cleanFilePathToken(token) {
|
|
8696
|
+
let candidate = token;
|
|
8697
|
+
const locationIndex = findLocationIndex(candidate);
|
|
8698
|
+
if (locationIndex >= 0) {
|
|
8699
|
+
candidate = candidate.slice(0, locationIndex);
|
|
8700
|
+
}
|
|
8701
|
+
while (candidate.startsWith("(") || candidate.startsWith("[") || candidate.startsWith("{")) {
|
|
8702
|
+
candidate = candidate.slice(1);
|
|
8703
|
+
}
|
|
8704
|
+
while (candidate.endsWith(")") || candidate.endsWith(",") || candidate.endsWith(";")) {
|
|
8705
|
+
candidate = candidate.slice(0, -1);
|
|
8706
|
+
}
|
|
8707
|
+
return candidate.startsWith("./") ? candidate.slice(2) : candidate;
|
|
8708
|
+
}
|
|
8709
|
+
function findLocationIndex(value) {
|
|
8710
|
+
for (let index = 0; index < value.length - 1; index += 1) {
|
|
8711
|
+
if (value[index] === ":" && value[index + 1] >= "0" && value[index + 1] <= "9") {
|
|
8712
|
+
return index;
|
|
8713
|
+
}
|
|
8714
|
+
}
|
|
8715
|
+
return -1;
|
|
8716
|
+
}
|
|
8717
|
+
function filePathToArea(filePath) {
|
|
8718
|
+
if (!filePath) {
|
|
8719
|
+
return null;
|
|
8720
|
+
}
|
|
8721
|
+
const segments = filePath.split("/").filter(Boolean);
|
|
8722
|
+
if (segments.length <= 1) {
|
|
8723
|
+
return ".";
|
|
8724
|
+
}
|
|
8725
|
+
return segments.slice(0, Math.min(segments.length - 1, 3)).join("/");
|
|
8726
|
+
}
|
|
8727
|
+
function findFirstMatchingLine(lines, category) {
|
|
8728
|
+
const rule = CLASSIFIER_RULES.find((entry) => entry.category === category);
|
|
8729
|
+
if (rule) {
|
|
8730
|
+
const matched = lines.find((line) => rule.pattern.test(line));
|
|
8731
|
+
if (matched) {
|
|
8732
|
+
return matched;
|
|
8733
|
+
}
|
|
8734
|
+
}
|
|
8735
|
+
return lines.find((line) => /\b(error|failed|failure|fatal|exception|conflict|timeout)\b/i.test(line)) ?? lines[0] ?? null;
|
|
8736
|
+
}
|
|
8737
|
+
function classifyCategory(lines, scriptResult, minReviewScore, exitCode) {
|
|
8738
|
+
const status = scriptResult?.status ?? "";
|
|
8739
|
+
const data = scriptResult?.data ?? {};
|
|
8740
|
+
const combined = [...lines, status, data.reason ?? "", data.detail ?? ""].join("\n");
|
|
8741
|
+
if (exitCode === 124 || status === "timeout") {
|
|
8742
|
+
return "timeout";
|
|
8743
|
+
}
|
|
8744
|
+
if (status === "rate_limited" || data.rate_limit_fallback === "1") {
|
|
8745
|
+
return "rate-limit";
|
|
8746
|
+
}
|
|
8747
|
+
const reviewScore = parseOptionalNumber(data.final_score ?? data.review_score);
|
|
8748
|
+
if (reviewScore != null && minReviewScore != null && Number.isFinite(minReviewScore) && reviewScore < minReviewScore) {
|
|
8749
|
+
return "review-score";
|
|
8750
|
+
}
|
|
8751
|
+
for (const rule of CLASSIFIER_RULES) {
|
|
8752
|
+
if (rule.pattern.test(combined)) {
|
|
8753
|
+
return rule.category;
|
|
8754
|
+
}
|
|
8755
|
+
}
|
|
8756
|
+
return "unknown";
|
|
8757
|
+
}
|
|
8758
|
+
function classifyFailure(input) {
|
|
8759
|
+
const lines = getOutputLines(input.stdout, input.stderr);
|
|
8760
|
+
const category = classifyCategory(lines, input.scriptResult, input.minReviewScore, input.exitCode);
|
|
8761
|
+
const firstErrorLine = findFirstMatchingLine(lines, category);
|
|
8762
|
+
const filePath = (firstErrorLine ? extractFilePath(firstErrorLine, input.projectPath) : null) ?? lines.map((line) => extractFilePath(line, input.projectPath)).find((value) => value != null) ?? null;
|
|
8763
|
+
const fileArea = filePathToArea(filePath);
|
|
8764
|
+
const normalizedLine = firstErrorLine ? normalizeLine(firstErrorLine, input.projectPath) : "no-error-line";
|
|
8765
|
+
const failureSignature = trimLine(`${category}|${fileArea ?? "unknown-area"}|${normalizedLine}`, MAX_SIGNATURE_LENGTH);
|
|
8766
|
+
return {
|
|
8767
|
+
category,
|
|
8768
|
+
failureSignature,
|
|
8769
|
+
fileArea,
|
|
8770
|
+
firstErrorLine: firstErrorLine ? trimLine(firstErrorLine, MAX_ERROR_LINE_LENGTH) : null
|
|
8771
|
+
};
|
|
8772
|
+
}
|
|
8773
|
+
function parseOptionalNumber(value) {
|
|
8774
|
+
if (!value) {
|
|
8775
|
+
return null;
|
|
8776
|
+
}
|
|
8777
|
+
const normalized = value.trim().replace(/^#/, "");
|
|
8778
|
+
const parsed = parseInt(normalized, 10);
|
|
8779
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
8780
|
+
}
|
|
8781
|
+
function parseFirstPrNumber(scriptResult) {
|
|
8782
|
+
const data = scriptResult?.data ?? {};
|
|
8783
|
+
const direct = parseOptionalNumber(data.pr_number) ?? parseOptionalNumber(data.prNumber) ?? parseOptionalNumber(data.pr) ?? parseOptionalNumber(data.failed_pr);
|
|
8784
|
+
if (direct != null) {
|
|
8785
|
+
return direct;
|
|
8786
|
+
}
|
|
8787
|
+
const urlMatch = data.pr_url?.match(/\/pull\/(\d+)/);
|
|
8788
|
+
if (urlMatch?.[1]) {
|
|
8789
|
+
return parseOptionalNumber(urlMatch[1]);
|
|
8790
|
+
}
|
|
8791
|
+
const prsRaw = data.prs ?? data.auto_merged;
|
|
8792
|
+
if (!prsRaw) {
|
|
8793
|
+
return null;
|
|
8794
|
+
}
|
|
8795
|
+
const firstToken = prsRaw.split(",").find((token) => parseOptionalNumber(token) != null);
|
|
8796
|
+
return parseOptionalNumber(firstToken);
|
|
8797
|
+
}
|
|
8798
|
+
function parseAttemptCount(scriptResult, lines) {
|
|
8799
|
+
const fromData = parseOptionalNumber(scriptResult?.data.attempt) ?? parseOptionalNumber(scriptResult?.data.attempts);
|
|
8800
|
+
if (fromData != null && fromData > 0) {
|
|
8801
|
+
return fromData;
|
|
8802
|
+
}
|
|
8803
|
+
let maxAttempt = 1;
|
|
8804
|
+
for (const line of lines) {
|
|
8805
|
+
const match = /\bATTEMPT:\s*(\d+)\//i.exec(line) ?? /\bStarting attempt\s+(\d+)\//i.exec(line);
|
|
8806
|
+
if (match?.[1]) {
|
|
8807
|
+
maxAttempt = Math.max(maxAttempt, parseInt(match[1], 10));
|
|
8808
|
+
}
|
|
8809
|
+
}
|
|
8810
|
+
return maxAttempt;
|
|
8811
|
+
}
|
|
8812
|
+
function parseRetryCount(scriptResult, attempt) {
|
|
8813
|
+
const retryCount = parseOptionalNumber(scriptResult?.data.retry_count);
|
|
8814
|
+
if (retryCount != null && retryCount >= 0) {
|
|
8815
|
+
return retryCount;
|
|
8816
|
+
}
|
|
8817
|
+
return Math.max(0, attempt - 1);
|
|
8818
|
+
}
|
|
8819
|
+
function markerIndicatesFailure(scriptResult) {
|
|
8820
|
+
const data = scriptResult?.data ?? {};
|
|
8821
|
+
const positiveFailureCount = (parseOptionalNumber(data.failed) ?? 0) > 0 || (parseOptionalNumber(data.prs_failed) ?? 0) > 0 || (parseOptionalNumber(data.failed_count) ?? 0) > 0;
|
|
8822
|
+
if (positiveFailureCount) {
|
|
8823
|
+
return true;
|
|
8824
|
+
}
|
|
8825
|
+
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");
|
|
8826
|
+
}
|
|
8827
|
+
function determineOutcome(exitCode, scriptResult, category) {
|
|
8828
|
+
const status = scriptResult?.status ?? "";
|
|
8829
|
+
if (status === "queued" || status.startsWith("skip_")) {
|
|
8830
|
+
return "skipped";
|
|
8831
|
+
}
|
|
8832
|
+
if (exitCode === 124 || status === "timeout" || category === "timeout") {
|
|
8833
|
+
return "timeout";
|
|
8834
|
+
}
|
|
8835
|
+
if (status === "rate_limited" || exitCode !== 0 && category === "rate-limit") {
|
|
8836
|
+
return "rate_limited";
|
|
8837
|
+
}
|
|
8838
|
+
if (status.startsWith("failure") || category === "review-score" || markerIndicatesFailure(scriptResult)) {
|
|
8839
|
+
return "failure";
|
|
8840
|
+
}
|
|
8841
|
+
return exitCode === 0 ? "success" : "failure";
|
|
8842
|
+
}
|
|
8843
|
+
function redactMetadata2(metadata) {
|
|
8844
|
+
return JSON.parse(redactOutcomeText(JSON.stringify(metadata)));
|
|
8845
|
+
}
|
|
8846
|
+
function buildSessionOutcomeInput(input) {
|
|
8847
|
+
const lines = getOutputLines(input.stdout, input.stderr);
|
|
8848
|
+
const classification = classifyFailure(input);
|
|
8849
|
+
const outcome = determineOutcome(input.exitCode, input.scriptResult, classification.category);
|
|
8850
|
+
const attempt = parseAttemptCount(input.scriptResult, lines);
|
|
8851
|
+
const retryCount = parseRetryCount(input.scriptResult, attempt);
|
|
8852
|
+
const reviewScore = parseOptionalNumber(input.scriptResult?.data.final_score ?? input.scriptResult?.data.review_score);
|
|
8853
|
+
const failureCategory = outcome === "failure" || outcome === "timeout" || outcome === "rate_limited" ? classification.category : null;
|
|
8854
|
+
return {
|
|
8855
|
+
projectPath: input.projectPath,
|
|
8856
|
+
jobType: input.jobType,
|
|
8857
|
+
providerKey: input.providerKey || "unknown",
|
|
8858
|
+
prdFile: input.scriptResult?.data.prd ?? input.scriptResult?.data.prd_file ?? null,
|
|
8859
|
+
prNumber: parseFirstPrNumber(input.scriptResult),
|
|
8860
|
+
branchName: input.scriptResult?.data.branch ?? null,
|
|
8861
|
+
startedAt: input.startedAt,
|
|
8862
|
+
finishedAt: input.finishedAt,
|
|
8863
|
+
durationSeconds: Math.max(0, Math.round((input.finishedAt - input.startedAt) / 1e3)),
|
|
8864
|
+
outcome,
|
|
8865
|
+
exitCode: input.exitCode,
|
|
8866
|
+
attempt,
|
|
8867
|
+
retryCount,
|
|
8868
|
+
reviewScore,
|
|
8869
|
+
ciStatus: failureCategory === "ci" ? "fail" : input.scriptResult?.data.ci_status ?? null,
|
|
8870
|
+
failureCategory,
|
|
8871
|
+
failureSignature: failureCategory ? classification.failureSignature : null,
|
|
8872
|
+
metadata: redactMetadata2({
|
|
8873
|
+
...input.metadata ?? {},
|
|
8874
|
+
scriptStatus: input.scriptResult?.status ?? null,
|
|
8875
|
+
scriptData: input.scriptResult?.data ?? {},
|
|
8876
|
+
minReviewScore: input.minReviewScore ?? null,
|
|
8877
|
+
firstErrorLine: classification.firstErrorLine,
|
|
8878
|
+
fileArea: classification.fileArea
|
|
8879
|
+
})
|
|
8880
|
+
};
|
|
8881
|
+
}
|
|
8882
|
+
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;
|
|
8883
|
+
var init_outcome_parser = __esm({
|
|
8884
|
+
"../core/dist/feedback/outcome-parser.js"() {
|
|
8885
|
+
"use strict";
|
|
8886
|
+
FAILURE_CATEGORIES = [
|
|
8887
|
+
"typescript",
|
|
8888
|
+
"eslint",
|
|
8889
|
+
"test",
|
|
8890
|
+
"ci",
|
|
8891
|
+
"review-score",
|
|
8892
|
+
"rate-limit",
|
|
8893
|
+
"timeout",
|
|
8894
|
+
"conflict",
|
|
8895
|
+
"unknown"
|
|
8896
|
+
];
|
|
8897
|
+
SECRET_PLACEHOLDER2 = "[REDACTED_SECRET]";
|
|
8898
|
+
MAX_SIGNATURE_LENGTH = 240;
|
|
8899
|
+
MAX_ERROR_LINE_LENGTH = 300;
|
|
8900
|
+
ANSI_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
8901
|
+
FILE_PATH_PATTERN = /\.(?:[cm]?[jt]sx?|json|md|css|scss|ya?ml)$/i;
|
|
8902
|
+
TOKEN_SPLIT_PATTERN = /[\s('"`]+/;
|
|
8903
|
+
SECRET_TEXT_PATTERNS2 = [
|
|
8904
|
+
[
|
|
8905
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/g,
|
|
8906
|
+
SECRET_PLACEHOLDER2
|
|
8907
|
+
],
|
|
8908
|
+
[/\bsk-ant-[\w-]{20,}\b/g, SECRET_PLACEHOLDER2],
|
|
8909
|
+
[/\bsk-[\w-]{20,}\b/g, SECRET_PLACEHOLDER2],
|
|
8910
|
+
[/\bgh[opsru]_\w{30,}\b/g, SECRET_PLACEHOLDER2],
|
|
8911
|
+
[/\bxox[baprs]-[\w-]{20,}\b/g, SECRET_PLACEHOLDER2],
|
|
8912
|
+
[/\b(?:AKIA|ASIA)[A-Z0-9]{16}\b/g, SECRET_PLACEHOLDER2],
|
|
8913
|
+
[/\b(Bearer|Basic)\s+[\w.~+/=-]{12,}/gi, `$1 ${SECRET_PLACEHOLDER2}`],
|
|
8914
|
+
[/\b(token|api[_-]?key|password|secret)=["']?[\w.~+/=-]{12,}/gi, `$1=${SECRET_PLACEHOLDER2}`]
|
|
8915
|
+
];
|
|
8916
|
+
CLASSIFIER_RULES = [
|
|
8917
|
+
{
|
|
8918
|
+
category: "timeout",
|
|
8919
|
+
pattern: /\b(timed?\s*out|timeout|etimedout|operation was aborted|exit code 124|signal sigterm)\b/i
|
|
8920
|
+
},
|
|
8921
|
+
{
|
|
8922
|
+
category: "rate-limit",
|
|
8923
|
+
pattern: /\b(429|rate[- ]?limit(?:ed)?|too many requests|quota exceeded|resource_exhausted|overloaded)\b/i
|
|
8924
|
+
},
|
|
8925
|
+
{
|
|
8926
|
+
category: "conflict",
|
|
8927
|
+
pattern: /\b(merge conflict|conflict \(|conflict:|unmerged files|needs merge|automatic merge failed|both modified:)\b/i
|
|
8928
|
+
},
|
|
8929
|
+
{
|
|
8930
|
+
category: "typescript",
|
|
8931
|
+
pattern: /\b(TS\d{4}|typescript error|tsc\b.*(?:failed|error)|error TS\d{4})\b/i
|
|
8932
|
+
},
|
|
8933
|
+
{
|
|
8934
|
+
category: "eslint",
|
|
8935
|
+
pattern: /\b(eslint|@typescript-eslint|no-unused-vars|no-explicit-any|react-hooks\/rules-of-hooks)\b/i
|
|
8936
|
+
},
|
|
8937
|
+
{
|
|
8938
|
+
category: "test",
|
|
8939
|
+
pattern: /\b(vitest|jest|playwright|cypress|mocha|assertionerror)\b/i
|
|
8940
|
+
},
|
|
8941
|
+
{
|
|
8942
|
+
category: "test",
|
|
8943
|
+
pattern: /\b(test files?|tests?)\b.*\bfailed\b/i
|
|
8944
|
+
},
|
|
8945
|
+
{
|
|
8946
|
+
category: "test",
|
|
8947
|
+
pattern: /\b(expect\(|locator\(|FAIL\s+\S+\.(?:test|spec)\.)/i
|
|
8948
|
+
},
|
|
8949
|
+
{
|
|
8950
|
+
category: "review-score",
|
|
8951
|
+
pattern: /\b(review score|final_score|score)\b.*\b(below|minimum|min|required|threshold|failed|miss)\b/i
|
|
8952
|
+
},
|
|
8953
|
+
{
|
|
8954
|
+
category: "ci",
|
|
8955
|
+
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
|
|
8956
|
+
}
|
|
8957
|
+
];
|
|
8958
|
+
}
|
|
8959
|
+
});
|
|
8960
|
+
|
|
8961
|
+
// ../core/dist/feedback/pattern-analyzer.js
|
|
8962
|
+
function isFailureOutcome(outcome) {
|
|
8963
|
+
return outcome.outcome === "failure" || outcome.outcome === "timeout" || outcome.outcome === "rate_limited";
|
|
8964
|
+
}
|
|
8965
|
+
function truncateText(value, maxLength = MAX_PATTERN_TEXT_LENGTH) {
|
|
8966
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
8967
|
+
if (normalized.length <= maxLength) {
|
|
8968
|
+
return normalized;
|
|
8969
|
+
}
|
|
8970
|
+
return `${normalized.slice(0, maxLength - 3).trimEnd()}...`;
|
|
8971
|
+
}
|
|
8972
|
+
function getStringMetadata(metadata, key) {
|
|
8973
|
+
const value = metadata[key];
|
|
8974
|
+
return typeof value === "string" && value.trim().length > 0 ? value : null;
|
|
8975
|
+
}
|
|
8976
|
+
function getFileArea(outcome) {
|
|
8977
|
+
return getStringMetadata(outcome.metadata, "fileArea") ?? "unknown area";
|
|
8978
|
+
}
|
|
8979
|
+
function countRecentStreaks(repository, outcome) {
|
|
8980
|
+
const recentOutcomes = repository.queryOutcomes({
|
|
8981
|
+
projectPath: outcome.projectPath,
|
|
8982
|
+
jobType: outcome.jobType,
|
|
8983
|
+
limit: 25
|
|
8984
|
+
});
|
|
8985
|
+
let failureStreak = 0;
|
|
8986
|
+
let signatureStreak = 0;
|
|
8987
|
+
for (const recentOutcome of recentOutcomes) {
|
|
8988
|
+
if (!isFailureOutcome(recentOutcome)) {
|
|
8989
|
+
break;
|
|
8990
|
+
}
|
|
8991
|
+
failureStreak += 1;
|
|
8992
|
+
if (recentOutcome.failureSignature === outcome.failureSignature) {
|
|
8993
|
+
signatureStreak += 1;
|
|
8994
|
+
} else {
|
|
8995
|
+
break;
|
|
8996
|
+
}
|
|
8997
|
+
}
|
|
8998
|
+
return { failureStreak, signatureStreak };
|
|
8999
|
+
}
|
|
9000
|
+
function countSuccessStreak(repository, projectPath, jobType) {
|
|
9001
|
+
const recentOutcomes = repository.queryOutcomes({ projectPath, jobType, limit: 25 });
|
|
9002
|
+
let successStreak = 0;
|
|
9003
|
+
for (const outcome of recentOutcomes) {
|
|
9004
|
+
if (outcome.outcome !== "success") {
|
|
9005
|
+
break;
|
|
9006
|
+
}
|
|
9007
|
+
successStreak += 1;
|
|
9008
|
+
}
|
|
9009
|
+
return successStreak;
|
|
9010
|
+
}
|
|
9011
|
+
function calculateRecencyScore(now, lastSeenAt) {
|
|
9012
|
+
const ageMs = Math.max(0, now - lastSeenAt);
|
|
9013
|
+
if (ageMs <= RECENT_WINDOW_MS) {
|
|
9014
|
+
return 1;
|
|
9015
|
+
}
|
|
9016
|
+
if (ageMs <= STALE_WINDOW_MS) {
|
|
9017
|
+
return 0.5;
|
|
9018
|
+
}
|
|
9019
|
+
return 0.15;
|
|
9020
|
+
}
|
|
9021
|
+
function calculateConfidence(sampleCount, lastSeenAt, failureStreak, signatureStreak, now) {
|
|
9022
|
+
const sampleScore = Math.min(1, sampleCount / 2);
|
|
9023
|
+
const streakScore = Math.min(1, Math.max(failureStreak, signatureStreak) / 2);
|
|
9024
|
+
const recencyScore = calculateRecencyScore(now, lastSeenAt);
|
|
9025
|
+
const confidence = sampleScore * 0.45 + streakScore * 0.35 + recencyScore * 0.2;
|
|
9026
|
+
return Math.round(Math.min(1, confidence) * 100) / 100;
|
|
9027
|
+
}
|
|
9028
|
+
function buildPatternTitle(outcome) {
|
|
9029
|
+
const category = outcome.failureCategory ?? "unknown";
|
|
9030
|
+
return truncateText(`Repeated ${category} failure in ${getFileArea(outcome)}`, 120);
|
|
9031
|
+
}
|
|
9032
|
+
function buildPatternDescription(outcome, sampleCount) {
|
|
9033
|
+
return truncateText(`Failure signature has appeared ${sampleCount} times for ${outcome.jobType} sessions.`);
|
|
9034
|
+
}
|
|
9035
|
+
function buildAugmentationPrompt(pattern, outcome) {
|
|
9036
|
+
const category = outcome.failureCategory ?? pattern.category;
|
|
9037
|
+
const fileArea = getFileArea(outcome);
|
|
9038
|
+
const signature = truncateText(outcome.failureSignature ?? pattern.patternKey, MAX_SIGNATURE_PROMPT_LENGTH);
|
|
9039
|
+
const confidencePercent = Math.round(pattern.confidence * 100);
|
|
9040
|
+
const actionByCategory = {
|
|
9041
|
+
ci: "Check failing CI details before broad edits and prioritize the repeated failure area.",
|
|
9042
|
+
conflict: "Check merge conflicts before editing and resolve the repeated conflict area first.",
|
|
9043
|
+
eslint: "Run lint early and fix the repeated ESLint issue before final verification.",
|
|
9044
|
+
"rate-limit": "Avoid unnecessary provider calls and continue with local evidence when rate limits appear.",
|
|
9045
|
+
"review-score": "Read prior low-score review feedback before declaring the PR ready and address repeated concerns.",
|
|
9046
|
+
test: "Run the targeted test area early and fix the repeated failure before final verification.",
|
|
9047
|
+
timeout: "Keep the work scoped and verify incrementally because prior sessions timed out.",
|
|
9048
|
+
typescript: "Run typecheck early and fix the repeated TypeScript issue before final verification.",
|
|
9049
|
+
unknown: "Investigate the repeated failure signature before making broad changes."
|
|
9050
|
+
};
|
|
9051
|
+
return truncateText(`${actionByCategory[category] ?? actionByCategory.unknown} Area: ${fileArea}. Provenance: pattern #${pattern.id}, samples=${pattern.sampleCount}, confidence=${confidencePercent}%, signature="${signature}".`, 320);
|
|
9052
|
+
}
|
|
9053
|
+
function expireStaleAugmentations(repository, projectPath, jobType, now) {
|
|
9054
|
+
const expired = [];
|
|
9055
|
+
const activeAugmentations = repository.listAugmentations({
|
|
9056
|
+
includeExpired: true,
|
|
9057
|
+
jobType,
|
|
9058
|
+
projectPath,
|
|
9059
|
+
status: "active"
|
|
9060
|
+
});
|
|
9061
|
+
for (const augmentation of activeAugmentations) {
|
|
9062
|
+
if (augmentation.expiresAt != null && augmentation.expiresAt <= now) {
|
|
9063
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9064
|
+
expired.push(augmentation.id);
|
|
9065
|
+
}
|
|
9066
|
+
}
|
|
9067
|
+
return expired;
|
|
9068
|
+
}
|
|
9069
|
+
function expireAugmentationsAfterSuccessStreak(repository, projectPath, jobType, successStreakToExpire, now) {
|
|
9070
|
+
if (successStreakToExpire <= 0) {
|
|
9071
|
+
return [];
|
|
9072
|
+
}
|
|
9073
|
+
const successStreak = countSuccessStreak(repository, projectPath, jobType);
|
|
9074
|
+
if (successStreak < successStreakToExpire) {
|
|
9075
|
+
return [];
|
|
9076
|
+
}
|
|
9077
|
+
const expired = [];
|
|
9078
|
+
const activeAugmentations = repository.listActiveAugmentations(projectPath, jobType, now);
|
|
9079
|
+
for (const augmentation of activeAugmentations) {
|
|
9080
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9081
|
+
expired.push(augmentation.id);
|
|
9082
|
+
}
|
|
9083
|
+
return expired;
|
|
9084
|
+
}
|
|
9085
|
+
function enforceAugmentationCap(repository, projectPath, jobType, maxActiveAugmentations, now) {
|
|
9086
|
+
if (maxActiveAugmentations < 1) {
|
|
9087
|
+
return repository.listActiveAugmentations(projectPath, jobType, now).map((augmentation) => {
|
|
9088
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9089
|
+
return augmentation.id;
|
|
9090
|
+
});
|
|
9091
|
+
}
|
|
9092
|
+
const activeAugmentations = repository.listActiveAugmentations(projectPath, jobType, now);
|
|
9093
|
+
if (activeAugmentations.length <= maxActiveAugmentations) {
|
|
9094
|
+
return [];
|
|
9095
|
+
}
|
|
9096
|
+
const activePatterns = repository.listPatterns({
|
|
9097
|
+
jobType,
|
|
9098
|
+
projectPath,
|
|
9099
|
+
status: "active",
|
|
9100
|
+
limit: 100
|
|
9101
|
+
});
|
|
9102
|
+
const confidenceByPatternId = new Map(activePatterns.map((pattern) => [pattern.id, pattern.confidence]));
|
|
9103
|
+
const keepIds = new Set(activeAugmentations.slice().sort((left, right) => {
|
|
9104
|
+
const leftConfidence = left.patternId == null ? 0 : confidenceByPatternId.get(left.patternId) ?? 0;
|
|
9105
|
+
const rightConfidence = right.patternId == null ? 0 : confidenceByPatternId.get(right.patternId) ?? 0;
|
|
9106
|
+
if (leftConfidence !== rightConfidence) {
|
|
9107
|
+
return rightConfidence - leftConfidence;
|
|
9108
|
+
}
|
|
9109
|
+
if (left.createdAt !== right.createdAt) {
|
|
9110
|
+
return right.createdAt - left.createdAt;
|
|
9111
|
+
}
|
|
9112
|
+
return right.id - left.id;
|
|
9113
|
+
}).slice(0, maxActiveAugmentations).map((augmentation) => augmentation.id));
|
|
9114
|
+
const expired = [];
|
|
9115
|
+
for (const augmentation of activeAugmentations) {
|
|
9116
|
+
if (!keepIds.has(augmentation.id)) {
|
|
9117
|
+
repository.updateAugmentationStatus(augmentation.id, "expired", projectPath);
|
|
9118
|
+
expired.push(augmentation.id);
|
|
9119
|
+
}
|
|
9120
|
+
}
|
|
9121
|
+
return expired;
|
|
9122
|
+
}
|
|
9123
|
+
function findActiveAugmentationForPattern(repository, projectPath, jobType, patternId, now) {
|
|
9124
|
+
return repository.listActiveAugmentations(projectPath, jobType, now).find((augmentation) => augmentation.patternId === patternId) ?? null;
|
|
9125
|
+
}
|
|
9126
|
+
function analyzeFeedbackOutcome(repository, outcome, options = {}) {
|
|
9127
|
+
const now = options.now ?? outcome.finishedAt ?? Date.now();
|
|
9128
|
+
const expiredAugmentationIds = expireStaleAugmentations(repository, outcome.projectPath, outcome.jobType, now);
|
|
9129
|
+
if (!isFailureOutcome(outcome) || !outcome.failureSignature || !outcome.failureCategory) {
|
|
9130
|
+
expiredAugmentationIds.push(...expireAugmentationsAfterSuccessStreak(repository, outcome.projectPath, outcome.jobType, options.successStreakToExpire ?? DEFAULT_SUCCESS_STREAK_TO_EXPIRE, now));
|
|
9131
|
+
return { augmentation: null, expiredAugmentationIds, pattern: null };
|
|
9132
|
+
}
|
|
9133
|
+
const existingPattern = repository.listPatterns({
|
|
9134
|
+
jobType: outcome.jobType,
|
|
9135
|
+
projectPath: outcome.projectPath,
|
|
9136
|
+
limit: 100
|
|
9137
|
+
}).find((pattern2) => pattern2.patternKey === outcome.failureSignature) ?? null;
|
|
9138
|
+
const sampleCount = (existingPattern?.sampleCount ?? 0) + 1;
|
|
9139
|
+
const streakStats = countRecentStreaks(repository, outcome);
|
|
9140
|
+
const confidence = calculateConfidence(sampleCount, outcome.finishedAt, streakStats.failureStreak, streakStats.signatureStreak, now);
|
|
9141
|
+
const status = confidence >= (options.confidenceThreshold ?? DEFAULT_CONFIDENCE_THRESHOLD) ? "active" : existingPattern?.status ?? "observing";
|
|
9142
|
+
const pattern = repository.upsertPattern({
|
|
9143
|
+
category: outcome.failureCategory,
|
|
9144
|
+
confidence,
|
|
9145
|
+
description: buildPatternDescription(outcome, sampleCount),
|
|
9146
|
+
jobType: outcome.jobType,
|
|
9147
|
+
lastSeenAt: outcome.finishedAt,
|
|
9148
|
+
metadata: {
|
|
9149
|
+
confidenceInputs: {
|
|
9150
|
+
failureStreak: streakStats.failureStreak,
|
|
9151
|
+
recencyScore: calculateRecencyScore(now, outcome.finishedAt),
|
|
9152
|
+
sampleCount,
|
|
9153
|
+
signatureStreak: streakStats.signatureStreak
|
|
9154
|
+
},
|
|
9155
|
+
failureSignature: outcome.failureSignature,
|
|
9156
|
+
fileArea: getFileArea(outcome),
|
|
9157
|
+
firstErrorLine: getStringMetadata(outcome.metadata, "firstErrorLine"),
|
|
9158
|
+
lastOutcomeId: outcome.id
|
|
9159
|
+
},
|
|
9160
|
+
patternKey: outcome.failureSignature,
|
|
9161
|
+
projectPath: outcome.projectPath,
|
|
9162
|
+
sampleCount,
|
|
9163
|
+
status,
|
|
9164
|
+
title: buildPatternTitle(outcome)
|
|
9165
|
+
});
|
|
9166
|
+
let augmentation = null;
|
|
9167
|
+
if (pattern.status === "active") {
|
|
9168
|
+
augmentation = findActiveAugmentationForPattern(repository, outcome.projectPath, outcome.jobType, pattern.id, now);
|
|
9169
|
+
if (!augmentation) {
|
|
9170
|
+
augmentation = repository.createAugmentation({
|
|
9171
|
+
expiresAt: now + (options.augmentationTtlMs ?? DEFAULT_AUGMENTATION_TTL_MS),
|
|
9172
|
+
jobType: outcome.jobType,
|
|
9173
|
+
patternId: pattern.id,
|
|
9174
|
+
projectPath: outcome.projectPath,
|
|
9175
|
+
promptText: buildAugmentationPrompt(pattern, outcome),
|
|
9176
|
+
status: "active"
|
|
9177
|
+
});
|
|
9178
|
+
}
|
|
9179
|
+
}
|
|
9180
|
+
expiredAugmentationIds.push(...enforceAugmentationCap(repository, outcome.projectPath, outcome.jobType, options.maxActiveAugmentations ?? DEFAULT_MAX_ACTIVE_AUGMENTATIONS, now));
|
|
9181
|
+
return { augmentation, expiredAugmentationIds, pattern };
|
|
8519
9182
|
}
|
|
8520
|
-
var
|
|
8521
|
-
var
|
|
8522
|
-
"../core/dist/
|
|
9183
|
+
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;
|
|
9184
|
+
var init_pattern_analyzer = __esm({
|
|
9185
|
+
"../core/dist/feedback/pattern-analyzer.js"() {
|
|
8523
9186
|
"use strict";
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
|
|
8527
|
-
|
|
9187
|
+
DEFAULT_CONFIDENCE_THRESHOLD = 0.75;
|
|
9188
|
+
DEFAULT_AUGMENTATION_TTL_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
9189
|
+
DEFAULT_MAX_ACTIVE_AUGMENTATIONS = 3;
|
|
9190
|
+
DEFAULT_SUCCESS_STREAK_TO_EXPIRE = 3;
|
|
9191
|
+
RECENT_WINDOW_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
9192
|
+
STALE_WINDOW_MS = 14 * 24 * 60 * 60 * 1e3;
|
|
9193
|
+
MAX_PATTERN_TEXT_LENGTH = 180;
|
|
9194
|
+
MAX_SIGNATURE_PROMPT_LENGTH = 90;
|
|
8528
9195
|
}
|
|
8529
9196
|
});
|
|
8530
9197
|
|
|
8531
|
-
// ../core/dist/
|
|
8532
|
-
|
|
8533
|
-
|
|
9198
|
+
// ../core/dist/feedback/prompt-augmenter.js
|
|
9199
|
+
function isActiveAt(augmentation, now) {
|
|
9200
|
+
return augmentation.status === "active" && (augmentation.expiresAt == null || augmentation.expiresAt > now);
|
|
9201
|
+
}
|
|
9202
|
+
function normalizeMaxActive(value) {
|
|
9203
|
+
if (value == null || !Number.isFinite(value)) {
|
|
9204
|
+
return DEFAULT_MAX_ACTIVE_AUGMENTATIONS2;
|
|
9205
|
+
}
|
|
9206
|
+
return Math.max(0, Math.floor(value));
|
|
9207
|
+
}
|
|
9208
|
+
function truncateSnippet(value) {
|
|
9209
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
9210
|
+
if (normalized.length <= MAX_SNIPPET_LENGTH) {
|
|
9211
|
+
return normalized;
|
|
9212
|
+
}
|
|
9213
|
+
return `${normalized.slice(0, MAX_SNIPPET_LENGTH - 3).trimEnd()}...`;
|
|
9214
|
+
}
|
|
9215
|
+
function isFeedbackPromptEnabled() {
|
|
9216
|
+
const raw = process.env.NW_FEEDBACK_ENABLED ?? process.env.NW_FEEDBACK_PROMPT_ENABLED;
|
|
9217
|
+
if (!raw) {
|
|
9218
|
+
return true;
|
|
9219
|
+
}
|
|
9220
|
+
return !DISABLED_VALUES.has(raw.trim().toLowerCase());
|
|
9221
|
+
}
|
|
9222
|
+
function selectPromptAugmentations(augmentations, options = {}) {
|
|
9223
|
+
if (options.feedbackEnabled === false) {
|
|
9224
|
+
return [];
|
|
9225
|
+
}
|
|
9226
|
+
const now = options.now ?? Date.now();
|
|
9227
|
+
const maxActive = normalizeMaxActive(options.maxActiveAugmentations);
|
|
9228
|
+
if (maxActive === 0) {
|
|
9229
|
+
return [];
|
|
9230
|
+
}
|
|
9231
|
+
return augmentations.filter((augmentation) => isActiveAt(augmentation, now)).sort((left, right) => {
|
|
9232
|
+
if (left.createdAt !== right.createdAt) {
|
|
9233
|
+
return left.createdAt - right.createdAt;
|
|
9234
|
+
}
|
|
9235
|
+
return left.id - right.id;
|
|
9236
|
+
}).slice(0, maxActive);
|
|
9237
|
+
}
|
|
9238
|
+
function renderProjectFeedbackBlock(augmentations, options = {}) {
|
|
9239
|
+
const selected = selectPromptAugmentations(augmentations, options);
|
|
9240
|
+
if (selected.length === 0) {
|
|
9241
|
+
return "";
|
|
9242
|
+
}
|
|
9243
|
+
const lines = [
|
|
9244
|
+
"## Project Feedback",
|
|
9245
|
+
"The following short notes come from repeated recent Night Watch failures. Treat them as targeted guardrails, not as replacements for the main task instructions.",
|
|
9246
|
+
"",
|
|
9247
|
+
...selected.map((augmentation) => `- ${truncateSnippet(augmentation.promptText)}`)
|
|
9248
|
+
];
|
|
9249
|
+
return lines.join("\n");
|
|
9250
|
+
}
|
|
9251
|
+
function buildProjectFeedbackPromptBlock(repository, projectPath, jobType, options = {}) {
|
|
9252
|
+
const enabled = options.feedbackEnabled ?? isFeedbackPromptEnabled();
|
|
9253
|
+
if (!enabled) {
|
|
9254
|
+
return { augmentationIds: [], promptBlock: "" };
|
|
9255
|
+
}
|
|
9256
|
+
const now = options.now ?? Date.now();
|
|
9257
|
+
const activeAugmentations = repository.listActiveAugmentations(projectPath, jobType, now);
|
|
9258
|
+
const selected = selectPromptAugmentations(activeAugmentations, {
|
|
9259
|
+
...options,
|
|
9260
|
+
feedbackEnabled: enabled,
|
|
9261
|
+
now
|
|
9262
|
+
});
|
|
9263
|
+
const promptBlock = renderProjectFeedbackBlock(selected, {
|
|
9264
|
+
...options,
|
|
9265
|
+
feedbackEnabled: enabled,
|
|
9266
|
+
now
|
|
9267
|
+
});
|
|
9268
|
+
if (options.markApplied === true && promptBlock.length > 0) {
|
|
9269
|
+
for (const augmentation of selected) {
|
|
9270
|
+
repository.incrementAugmentationCounts(augmentation.id);
|
|
9271
|
+
}
|
|
9272
|
+
}
|
|
9273
|
+
return {
|
|
9274
|
+
augmentationIds: selected.map((augmentation) => augmentation.id),
|
|
9275
|
+
promptBlock
|
|
9276
|
+
};
|
|
9277
|
+
}
|
|
9278
|
+
var DEFAULT_MAX_ACTIVE_AUGMENTATIONS2, MAX_SNIPPET_LENGTH, DISABLED_VALUES;
|
|
9279
|
+
var init_prompt_augmenter = __esm({
|
|
9280
|
+
"../core/dist/feedback/prompt-augmenter.js"() {
|
|
8534
9281
|
"use strict";
|
|
8535
|
-
|
|
8536
|
-
|
|
9282
|
+
DEFAULT_MAX_ACTIVE_AUGMENTATIONS2 = 3;
|
|
9283
|
+
MAX_SNIPPET_LENGTH = 260;
|
|
9284
|
+
DISABLED_VALUES = /* @__PURE__ */ new Set(["0", "false", "no", "off", "disabled"]);
|
|
8537
9285
|
}
|
|
8538
9286
|
});
|
|
8539
9287
|
|
|
@@ -8756,6 +9504,7 @@ __export(dist_exports, {
|
|
|
8756
9504
|
DEFAULT_DEFAULT_BRANCH: () => DEFAULT_DEFAULT_BRANCH,
|
|
8757
9505
|
DEFAULT_EXECUTOR_ENABLED: () => DEFAULT_EXECUTOR_ENABLED,
|
|
8758
9506
|
DEFAULT_FALLBACK_ON_RATE_LIMIT: () => DEFAULT_FALLBACK_ON_RATE_LIMIT,
|
|
9507
|
+
DEFAULT_FEEDBACK: () => DEFAULT_FEEDBACK,
|
|
8759
9508
|
DEFAULT_JOB_PROVIDERS: () => DEFAULT_JOB_PROVIDERS,
|
|
8760
9509
|
DEFAULT_LOCAL_BOARD_INFO: () => DEFAULT_LOCAL_BOARD_INFO,
|
|
8761
9510
|
DEFAULT_MAX_LOG_SIZE: () => DEFAULT_MAX_LOG_SIZE,
|
|
@@ -8821,6 +9570,7 @@ __export(dist_exports, {
|
|
|
8821
9570
|
EXECUTOR_PARTIAL_LABEL: () => EXECUTOR_PARTIAL_LABEL,
|
|
8822
9571
|
EXECUTOR_READY_REVIEW_LABEL: () => EXECUTOR_READY_REVIEW_LABEL,
|
|
8823
9572
|
EXECUTOR_RESUMABLE_LABEL: () => EXECUTOR_RESUMABLE_LABEL,
|
|
9573
|
+
FAILURE_CATEGORIES: () => FAILURE_CATEGORIES,
|
|
8824
9574
|
GLOBAL_CONFIG_DIR: () => GLOBAL_CONFIG_DIR,
|
|
8825
9575
|
GLOBAL_NOTIFICATIONS_FILE_NAME: () => GLOBAL_NOTIFICATIONS_FILE_NAME,
|
|
8826
9576
|
HISTORY_FILE_NAME: () => HISTORY_FILE_NAME,
|
|
@@ -8849,6 +9599,7 @@ __export(dist_exports, {
|
|
|
8849
9599
|
ROADMAP_SECTION_MAPPINGS: () => ROADMAP_SECTION_MAPPINGS,
|
|
8850
9600
|
STATE_DB_FILE_NAME: () => STATE_DB_FILE_NAME,
|
|
8851
9601
|
SqliteKanbanIssueRepository: () => SqliteKanbanIssueRepository,
|
|
9602
|
+
SqliteSessionOutcomeRepository: () => SqliteSessionOutcomeRepository,
|
|
8852
9603
|
VALID_CLAUDE_MODELS: () => VALID_CLAUDE_MODELS,
|
|
8853
9604
|
VALID_JOB_TYPES: () => VALID_JOB_TYPES,
|
|
8854
9605
|
VALID_MERGE_METHODS: () => VALID_MERGE_METHODS,
|
|
@@ -8857,9 +9608,12 @@ __export(dist_exports, {
|
|
|
8857
9608
|
addDelayToIsoString: () => addDelayToIsoString,
|
|
8858
9609
|
addEntry: () => addEntry,
|
|
8859
9610
|
analyticsLockPath: () => analyticsLockPath,
|
|
9611
|
+
analyzeFeedbackOutcome: () => analyzeFeedbackOutcome,
|
|
8860
9612
|
auditLockPath: () => auditLockPath,
|
|
8861
9613
|
buildDescription: () => buildDescription,
|
|
8862
9614
|
buildJobEnvOverrides: () => buildJobEnvOverrides,
|
|
9615
|
+
buildProjectFeedbackPromptBlock: () => buildProjectFeedbackPromptBlock,
|
|
9616
|
+
buildSessionOutcomeInput: () => buildSessionOutcomeInput,
|
|
8863
9617
|
calculateStringSimilarity: () => calculateStringSimilarity,
|
|
8864
9618
|
camelToUpperSnake: () => camelToUpperSnake,
|
|
8865
9619
|
canStartJob: () => canStartJob,
|
|
@@ -8875,6 +9629,7 @@ __export(dist_exports, {
|
|
|
8875
9629
|
checkProviderCli: () => checkProviderCli,
|
|
8876
9630
|
checkRateLimited: () => checkRateLimited,
|
|
8877
9631
|
claimJobSlot: () => claimJobSlot,
|
|
9632
|
+
classifyFailure: () => classifyFailure,
|
|
8878
9633
|
cleanupExpiredJobs: () => cleanupExpiredJobs,
|
|
8879
9634
|
cleanupWorktrees: () => cleanupWorktrees,
|
|
8880
9635
|
clearPrdState: () => clearPrdState,
|
|
@@ -8981,6 +9736,7 @@ __export(dist_exports, {
|
|
|
8981
9736
|
info: () => info,
|
|
8982
9737
|
initContainer: () => initContainer,
|
|
8983
9738
|
isContainerInitialized: () => isContainerInitialized,
|
|
9739
|
+
isFeedbackPromptEnabled: () => isFeedbackPromptEnabled,
|
|
8984
9740
|
isInCooldown: () => isInCooldown,
|
|
8985
9741
|
isItemProcessed: () => isItemProcessed,
|
|
8986
9742
|
isJobTypeEnabled: () => isJobTypeEnabled,
|
|
@@ -9024,6 +9780,7 @@ __export(dist_exports, {
|
|
|
9024
9780
|
readPrdStates: () => readPrdStates,
|
|
9025
9781
|
recordExecution: () => recordExecution,
|
|
9026
9782
|
recordJobRun: () => recordJobRun,
|
|
9783
|
+
redactOutcomeText: () => redactOutcomeText,
|
|
9027
9784
|
registerProject: () => registerProject,
|
|
9028
9785
|
releaseLock: () => releaseLock,
|
|
9029
9786
|
removeEntries: () => removeEntries,
|
|
@@ -9031,6 +9788,7 @@ __export(dist_exports, {
|
|
|
9031
9788
|
removeJob: () => removeJob,
|
|
9032
9789
|
removeProject: () => removeProject,
|
|
9033
9790
|
renderPrdTemplate: () => renderPrdTemplate,
|
|
9791
|
+
renderProjectFeedbackBlock: () => renderProjectFeedbackBlock,
|
|
9034
9792
|
renderSlicerPrompt: () => renderSlicerPrompt,
|
|
9035
9793
|
resetRepositories: () => resetRepositories,
|
|
9036
9794
|
resolveJobProvider: () => resolveJobProvider,
|
|
@@ -9048,6 +9806,7 @@ __export(dist_exports, {
|
|
|
9048
9806
|
saveRegistry: () => saveRegistry,
|
|
9049
9807
|
saveRoadmapState: () => saveRoadmapState,
|
|
9050
9808
|
scanRoadmap: () => scanRoadmap,
|
|
9809
|
+
selectPromptAugmentations: () => selectPromptAugmentations,
|
|
9051
9810
|
sendNotifications: () => sendNotifications,
|
|
9052
9811
|
sendWebhook: () => sendWebhook,
|
|
9053
9812
|
setConfigValue: () => setConfigValue,
|
|
@@ -9117,6 +9876,9 @@ var init_dist = __esm({
|
|
|
9117
9876
|
init_summary();
|
|
9118
9877
|
init_analytics();
|
|
9119
9878
|
init_audit();
|
|
9879
|
+
init_outcome_parser();
|
|
9880
|
+
init_pattern_analyzer();
|
|
9881
|
+
init_prompt_augmenter();
|
|
9120
9882
|
init_prd_template();
|
|
9121
9883
|
init_slicer_prompt();
|
|
9122
9884
|
init_jobs();
|
|
@@ -9394,6 +10156,7 @@ function buildInitConfig(params) {
|
|
|
9394
10156
|
},
|
|
9395
10157
|
audit: { ...defaults.audit },
|
|
9396
10158
|
analytics: { ...defaults.analytics },
|
|
10159
|
+
feedback: { ...defaults.feedback },
|
|
9397
10160
|
merger: { ...defaults.merger },
|
|
9398
10161
|
prResolver: { ...defaults.prResolver },
|
|
9399
10162
|
jobProviders: { ...defaults.jobProviders },
|
|
@@ -9958,6 +10721,47 @@ function getTelegramStatusWebhooks(config) {
|
|
|
9958
10721
|
).map((wh) => ({ botToken: wh.botToken, chatId: wh.chatId }));
|
|
9959
10722
|
}
|
|
9960
10723
|
|
|
10724
|
+
// src/commands/shared/feedback.ts
|
|
10725
|
+
init_dist();
|
|
10726
|
+
function getFeedbackAnalysisOptions(config) {
|
|
10727
|
+
const feedback = config.feedback ?? {
|
|
10728
|
+
augmentationTtlDays: 14,
|
|
10729
|
+
confidenceThreshold: 0.75,
|
|
10730
|
+
maxActiveAugmentations: 3,
|
|
10731
|
+
successStreakToExpire: 3
|
|
10732
|
+
};
|
|
10733
|
+
return {
|
|
10734
|
+
augmentationTtlMs: feedback.augmentationTtlDays * 24 * 60 * 60 * 1e3,
|
|
10735
|
+
confidenceThreshold: feedback.confidenceThreshold,
|
|
10736
|
+
maxActiveAugmentations: feedback.maxActiveAugmentations,
|
|
10737
|
+
successStreakToExpire: feedback.successStreakToExpire
|
|
10738
|
+
};
|
|
10739
|
+
}
|
|
10740
|
+
function isFeedbackEnabled(config) {
|
|
10741
|
+
return config.feedback?.enabled !== false && isFeedbackPromptEnabled();
|
|
10742
|
+
}
|
|
10743
|
+
function recordJobOutcome(input) {
|
|
10744
|
+
const repository = getRepositories().sessionOutcomes;
|
|
10745
|
+
const storedOutcome = repository.insertOutcome(
|
|
10746
|
+
buildSessionOutcomeInput({
|
|
10747
|
+
exitCode: input.exitCode,
|
|
10748
|
+
finishedAt: input.finishedAt,
|
|
10749
|
+
jobType: input.jobType,
|
|
10750
|
+
metadata: input.metadata,
|
|
10751
|
+
minReviewScore: input.minReviewScore,
|
|
10752
|
+
projectPath: input.projectDir,
|
|
10753
|
+
providerKey: input.providerKey ?? resolveJobProvider(input.config, input.jobType),
|
|
10754
|
+
scriptResult: input.scriptResult,
|
|
10755
|
+
startedAt: input.startedAt,
|
|
10756
|
+
stderr: input.stderr,
|
|
10757
|
+
stdout: input.stdout
|
|
10758
|
+
})
|
|
10759
|
+
);
|
|
10760
|
+
if (isFeedbackEnabled(input.config)) {
|
|
10761
|
+
analyzeFeedbackOutcome(repository, storedOutcome, getFeedbackAnalysisOptions(input.config));
|
|
10762
|
+
}
|
|
10763
|
+
}
|
|
10764
|
+
|
|
9961
10765
|
// src/commands/run.ts
|
|
9962
10766
|
import * as fs24 from "fs";
|
|
9963
10767
|
import * as path23 from "path";
|
|
@@ -10072,16 +10876,34 @@ async function runCrossProjectFallback(currentProjectDir, options) {
|
|
|
10072
10876
|
let candidateConfig = loadConfig(candidate.path);
|
|
10073
10877
|
candidateConfig = applyCliOverrides(candidateConfig, options);
|
|
10074
10878
|
const envVars = buildEnvVars(candidateConfig, options);
|
|
10879
|
+
applyProjectFeedbackPromptEnv(envVars, candidate.path, "executor");
|
|
10075
10880
|
envVars.NW_CROSS_PROJECT_FALLBACK_ACTIVE = "1";
|
|
10076
10881
|
try {
|
|
10882
|
+
const startedAt = Date.now();
|
|
10077
10883
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10078
10884
|
scriptPath,
|
|
10079
10885
|
[candidate.path],
|
|
10080
10886
|
envVars,
|
|
10081
10887
|
{ cwd: candidate.path }
|
|
10082
10888
|
);
|
|
10889
|
+
const finishedAt = Date.now();
|
|
10083
10890
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10084
10891
|
${stderr}`);
|
|
10892
|
+
try {
|
|
10893
|
+
recordRunSessionOutcome({
|
|
10894
|
+
projectDir: candidate.path,
|
|
10895
|
+
config: candidateConfig,
|
|
10896
|
+
envVars,
|
|
10897
|
+
startedAt,
|
|
10898
|
+
finishedAt,
|
|
10899
|
+
exitCode,
|
|
10900
|
+
stdout,
|
|
10901
|
+
stderr,
|
|
10902
|
+
scriptResult,
|
|
10903
|
+
metadata: { crossProjectFallback: true }
|
|
10904
|
+
});
|
|
10905
|
+
} catch {
|
|
10906
|
+
}
|
|
10085
10907
|
if (!options.dryRun) {
|
|
10086
10908
|
await sendRunCompletionNotifications(
|
|
10087
10909
|
candidateConfig,
|
|
@@ -10118,6 +10940,48 @@ function getRateLimitFallbackTelegramWebhooks(config) {
|
|
|
10118
10940
|
function isRateLimitFallbackTriggered(resultData) {
|
|
10119
10941
|
return resultData?.rate_limit_fallback === "1";
|
|
10120
10942
|
}
|
|
10943
|
+
function recordRunSessionOutcome(input) {
|
|
10944
|
+
const outcome = buildSessionOutcomeInput({
|
|
10945
|
+
projectPath: input.projectDir,
|
|
10946
|
+
jobType: "executor",
|
|
10947
|
+
providerKey: input.envVars.NW_PROVIDER_KEY ?? resolveJobProvider(input.config, "executor"),
|
|
10948
|
+
startedAt: input.startedAt,
|
|
10949
|
+
finishedAt: input.finishedAt,
|
|
10950
|
+
exitCode: input.exitCode,
|
|
10951
|
+
stdout: input.stdout,
|
|
10952
|
+
stderr: input.stderr,
|
|
10953
|
+
scriptResult: input.scriptResult,
|
|
10954
|
+
metadata: {
|
|
10955
|
+
providerCommand: input.envVars.NW_PROVIDER_CMD,
|
|
10956
|
+
providerLabel: input.envVars.NW_PROVIDER_LABEL,
|
|
10957
|
+
...input.metadata ?? {}
|
|
10958
|
+
}
|
|
10959
|
+
});
|
|
10960
|
+
const repository = getRepositories().sessionOutcomes;
|
|
10961
|
+
const storedOutcome = repository.insertOutcome(outcome);
|
|
10962
|
+
if (isFeedbackEnabled(input.config)) {
|
|
10963
|
+
analyzeFeedbackOutcome(repository, storedOutcome, getFeedbackAnalysisOptions(input.config));
|
|
10964
|
+
}
|
|
10965
|
+
}
|
|
10966
|
+
function applyProjectFeedbackPromptEnv(envVars, projectDir, jobType, markApplied = true) {
|
|
10967
|
+
delete envVars.NW_PROJECT_FEEDBACK_PROMPT;
|
|
10968
|
+
const config = loadConfig(projectDir);
|
|
10969
|
+
if (!isFeedbackPromptEnabled() || config.feedback?.enabled === false) {
|
|
10970
|
+
return;
|
|
10971
|
+
}
|
|
10972
|
+
try {
|
|
10973
|
+
const { promptBlock } = buildProjectFeedbackPromptBlock(
|
|
10974
|
+
getRepositories().sessionOutcomes,
|
|
10975
|
+
projectDir,
|
|
10976
|
+
jobType,
|
|
10977
|
+
{ markApplied, maxActiveAugmentations: config.feedback?.maxActiveAugmentations }
|
|
10978
|
+
);
|
|
10979
|
+
if (promptBlock.length > 0) {
|
|
10980
|
+
envVars.NW_PROJECT_FEEDBACK_PROMPT = promptBlock;
|
|
10981
|
+
}
|
|
10982
|
+
} catch {
|
|
10983
|
+
}
|
|
10984
|
+
}
|
|
10121
10985
|
function buildEnvVars(config, options) {
|
|
10122
10986
|
const env = buildBaseEnvVars(config, "executor", options.dryRun);
|
|
10123
10987
|
env.NW_MAX_RUNTIME = String(config.maxRuntime);
|
|
@@ -10256,6 +11120,7 @@ function runCommand(program2) {
|
|
|
10256
11120
|
process.exit(0);
|
|
10257
11121
|
}
|
|
10258
11122
|
const envVars = buildEnvVars(config, options);
|
|
11123
|
+
applyProjectFeedbackPromptEnv(envVars, projectDir, "executor", !options.dryRun);
|
|
10259
11124
|
const scriptPath = getScriptPath("night-watch-cron.sh");
|
|
10260
11125
|
if (options.dryRun) {
|
|
10261
11126
|
header("Dry Run: PRD Executor");
|
|
@@ -10348,6 +11213,7 @@ function runCommand(program2) {
|
|
|
10348
11213
|
const spinner = createSpinner("Running PRD executor...");
|
|
10349
11214
|
spinner.start();
|
|
10350
11215
|
try {
|
|
11216
|
+
const startedAt = Date.now();
|
|
10351
11217
|
await maybeApplyCronSchedulingDelay(config, "executor", projectDir);
|
|
10352
11218
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10353
11219
|
scriptPath,
|
|
@@ -10355,6 +11221,7 @@ function runCommand(program2) {
|
|
|
10355
11221
|
envVars,
|
|
10356
11222
|
{ cwd: projectDir }
|
|
10357
11223
|
);
|
|
11224
|
+
const finishedAt = Date.now();
|
|
10358
11225
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10359
11226
|
${stderr}`);
|
|
10360
11227
|
if (exitCode === 0) {
|
|
@@ -10371,6 +11238,20 @@ ${stderr}`);
|
|
|
10371
11238
|
spinner.fail(`PRD executor exited with code ${exitCode}`);
|
|
10372
11239
|
}
|
|
10373
11240
|
if (!options.dryRun) {
|
|
11241
|
+
try {
|
|
11242
|
+
recordRunSessionOutcome({
|
|
11243
|
+
projectDir,
|
|
11244
|
+
config,
|
|
11245
|
+
envVars,
|
|
11246
|
+
startedAt,
|
|
11247
|
+
finishedAt,
|
|
11248
|
+
exitCode,
|
|
11249
|
+
stdout,
|
|
11250
|
+
stderr,
|
|
11251
|
+
scriptResult
|
|
11252
|
+
});
|
|
11253
|
+
} catch {
|
|
11254
|
+
}
|
|
10374
11255
|
await sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult);
|
|
10375
11256
|
}
|
|
10376
11257
|
if (shouldAttemptCrossProjectFallback(options, scriptResult?.status)) {
|
|
@@ -10440,6 +11321,25 @@ function buildReviewNotificationTargets(reviewedPrNumbers, noChangesPrNumbers, l
|
|
|
10440
11321
|
noChangesNeeded: noChangesSet.has(prNumber)
|
|
10441
11322
|
}));
|
|
10442
11323
|
}
|
|
11324
|
+
function applyProjectFeedbackPromptEnv2(envVars, projectDir, jobType, markApplied = true) {
|
|
11325
|
+
delete envVars.NW_PROJECT_FEEDBACK_PROMPT;
|
|
11326
|
+
const config = loadConfig(projectDir);
|
|
11327
|
+
if (!isFeedbackPromptEnabled() || config.feedback?.enabled === false) {
|
|
11328
|
+
return;
|
|
11329
|
+
}
|
|
11330
|
+
try {
|
|
11331
|
+
const { promptBlock } = buildProjectFeedbackPromptBlock(
|
|
11332
|
+
getRepositories().sessionOutcomes,
|
|
11333
|
+
projectDir,
|
|
11334
|
+
jobType,
|
|
11335
|
+
{ markApplied, maxActiveAugmentations: config.feedback?.maxActiveAugmentations }
|
|
11336
|
+
);
|
|
11337
|
+
if (promptBlock.length > 0) {
|
|
11338
|
+
envVars.NW_PROJECT_FEEDBACK_PROMPT = promptBlock;
|
|
11339
|
+
}
|
|
11340
|
+
} catch {
|
|
11341
|
+
}
|
|
11342
|
+
}
|
|
10443
11343
|
function parseRetryAttempts(raw) {
|
|
10444
11344
|
if (!raw) {
|
|
10445
11345
|
return 1;
|
|
@@ -10550,6 +11450,7 @@ function reviewCommand(program2) {
|
|
|
10550
11450
|
process.exit(0);
|
|
10551
11451
|
}
|
|
10552
11452
|
const envVars = buildEnvVars2(config, options);
|
|
11453
|
+
applyProjectFeedbackPromptEnv2(envVars, projectDir, "reviewer", !options.dryRun);
|
|
10553
11454
|
const scriptPath = getScriptPath("night-watch-pr-reviewer-cron.sh");
|
|
10554
11455
|
if (options.dryRun) {
|
|
10555
11456
|
header("Dry Run: PR Reviewer");
|
|
@@ -10614,12 +11515,14 @@ function reviewCommand(program2) {
|
|
|
10614
11515
|
const spinner = createSpinner("Running PR reviewer...");
|
|
10615
11516
|
spinner.start();
|
|
10616
11517
|
try {
|
|
11518
|
+
const startedAt = Date.now();
|
|
10617
11519
|
await maybeApplyCronSchedulingDelay(config, "reviewer", projectDir);
|
|
10618
11520
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10619
11521
|
scriptPath,
|
|
10620
11522
|
[projectDir],
|
|
10621
11523
|
envVars
|
|
10622
11524
|
);
|
|
11525
|
+
const finishedAt = Date.now();
|
|
10623
11526
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10624
11527
|
${stderr}`);
|
|
10625
11528
|
if (exitCode === 0) {
|
|
@@ -10634,6 +11537,31 @@ ${stderr}`);
|
|
|
10634
11537
|
spinner.fail(`PR reviewer exited with code ${exitCode}`);
|
|
10635
11538
|
}
|
|
10636
11539
|
if (!options.dryRun) {
|
|
11540
|
+
try {
|
|
11541
|
+
const repository = getRepositories().sessionOutcomes;
|
|
11542
|
+
const storedOutcome = repository.insertOutcome(
|
|
11543
|
+
buildSessionOutcomeInput({
|
|
11544
|
+
exitCode,
|
|
11545
|
+
finishedAt,
|
|
11546
|
+
jobType: "reviewer",
|
|
11547
|
+
metadata: {
|
|
11548
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11549
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
11550
|
+
},
|
|
11551
|
+
minReviewScore: config.minReviewScore,
|
|
11552
|
+
projectPath: projectDir,
|
|
11553
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "reviewer"),
|
|
11554
|
+
scriptResult,
|
|
11555
|
+
startedAt,
|
|
11556
|
+
stderr,
|
|
11557
|
+
stdout
|
|
11558
|
+
})
|
|
11559
|
+
);
|
|
11560
|
+
if (isFeedbackEnabled(config)) {
|
|
11561
|
+
analyzeFeedbackOutcome(repository, storedOutcome, getFeedbackAnalysisOptions(config));
|
|
11562
|
+
}
|
|
11563
|
+
} catch {
|
|
11564
|
+
}
|
|
10637
11565
|
const shouldNotifyCompletion = shouldSendReviewCompletionNotification(
|
|
10638
11566
|
exitCode,
|
|
10639
11567
|
scriptResult?.status
|
|
@@ -10847,12 +11775,14 @@ function qaCommand(program2) {
|
|
|
10847
11775
|
const spinner = createSpinner("Running QA process...");
|
|
10848
11776
|
spinner.start();
|
|
10849
11777
|
try {
|
|
11778
|
+
const startedAt = Date.now();
|
|
10850
11779
|
await maybeApplyCronSchedulingDelay(config, "qa", projectDir);
|
|
10851
11780
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
10852
11781
|
scriptPath,
|
|
10853
11782
|
[projectDir],
|
|
10854
11783
|
envVars
|
|
10855
11784
|
);
|
|
11785
|
+
const finishedAt = Date.now();
|
|
10856
11786
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10857
11787
|
${stderr}`);
|
|
10858
11788
|
if (exitCode === 0) {
|
|
@@ -10869,6 +11799,25 @@ ${stderr}`);
|
|
|
10869
11799
|
spinner.fail(`QA process exited with code ${exitCode}`);
|
|
10870
11800
|
}
|
|
10871
11801
|
if (!options.dryRun) {
|
|
11802
|
+
try {
|
|
11803
|
+
recordJobOutcome({
|
|
11804
|
+
config,
|
|
11805
|
+
exitCode,
|
|
11806
|
+
finishedAt,
|
|
11807
|
+
jobType: "qa",
|
|
11808
|
+
metadata: {
|
|
11809
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11810
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
11811
|
+
},
|
|
11812
|
+
projectDir,
|
|
11813
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "qa"),
|
|
11814
|
+
scriptResult,
|
|
11815
|
+
startedAt,
|
|
11816
|
+
stderr,
|
|
11817
|
+
stdout
|
|
11818
|
+
});
|
|
11819
|
+
} catch {
|
|
11820
|
+
}
|
|
10872
11821
|
const skipNotification = !shouldSendQaNotification(scriptResult?.status);
|
|
10873
11822
|
if (skipNotification) {
|
|
10874
11823
|
info("Skipping QA notification (no actionable QA result)");
|
|
@@ -10971,6 +11920,7 @@ function auditCommand(program2) {
|
|
|
10971
11920
|
}
|
|
10972
11921
|
const spinner = createSpinner("Running code audit...");
|
|
10973
11922
|
spinner.start();
|
|
11923
|
+
const startedAt = Date.now();
|
|
10974
11924
|
try {
|
|
10975
11925
|
await maybeApplyCronSchedulingDelay(config, "audit", projectDir);
|
|
10976
11926
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
@@ -10978,8 +11928,30 @@ function auditCommand(program2) {
|
|
|
10978
11928
|
[projectDir],
|
|
10979
11929
|
envVars
|
|
10980
11930
|
);
|
|
11931
|
+
const finishedAt = Date.now();
|
|
10981
11932
|
const scriptResult = parseScriptResult(`${stdout}
|
|
10982
11933
|
${stderr}`);
|
|
11934
|
+
if (!options.dryRun) {
|
|
11935
|
+
try {
|
|
11936
|
+
recordJobOutcome({
|
|
11937
|
+
config,
|
|
11938
|
+
exitCode,
|
|
11939
|
+
finishedAt,
|
|
11940
|
+
jobType: "audit",
|
|
11941
|
+
metadata: {
|
|
11942
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11943
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
11944
|
+
},
|
|
11945
|
+
projectDir,
|
|
11946
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "audit"),
|
|
11947
|
+
scriptResult,
|
|
11948
|
+
startedAt,
|
|
11949
|
+
stderr,
|
|
11950
|
+
stdout
|
|
11951
|
+
});
|
|
11952
|
+
} catch {
|
|
11953
|
+
}
|
|
11954
|
+
}
|
|
10983
11955
|
if (exitCode === 0) {
|
|
10984
11956
|
if (scriptResult?.status === "queued") {
|
|
10985
11957
|
spinner.succeed("Code audit queued \u2014 another job is currently running");
|
|
@@ -11016,6 +11988,23 @@ ${stderr}`);
|
|
|
11016
11988
|
process.exit(exitCode || 1);
|
|
11017
11989
|
}
|
|
11018
11990
|
} catch (err) {
|
|
11991
|
+
try {
|
|
11992
|
+
recordJobOutcome({
|
|
11993
|
+
config,
|
|
11994
|
+
exitCode: 1,
|
|
11995
|
+
finishedAt: Date.now(),
|
|
11996
|
+
jobType: "audit",
|
|
11997
|
+
metadata: {
|
|
11998
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
11999
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
12000
|
+
},
|
|
12001
|
+
projectDir,
|
|
12002
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "audit"),
|
|
12003
|
+
startedAt,
|
|
12004
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
12005
|
+
});
|
|
12006
|
+
} catch {
|
|
12007
|
+
}
|
|
11019
12008
|
spinner.fail(`Code audit failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11020
12009
|
process.exit(1);
|
|
11021
12010
|
}
|
|
@@ -11047,6 +12036,25 @@ function analyticsCommand(program2) {
|
|
|
11047
12036
|
const apiKey = config.providerEnv?.AMPLITUDE_API_KEY;
|
|
11048
12037
|
const secretKey = config.providerEnv?.AMPLITUDE_SECRET_KEY;
|
|
11049
12038
|
if (!apiKey || !secretKey) {
|
|
12039
|
+
const now = Date.now();
|
|
12040
|
+
if (!options.dryRun) {
|
|
12041
|
+
try {
|
|
12042
|
+
recordJobOutcome({
|
|
12043
|
+
config,
|
|
12044
|
+
exitCode: 1,
|
|
12045
|
+
finishedAt: now,
|
|
12046
|
+
jobType: "analytics",
|
|
12047
|
+
metadata: {
|
|
12048
|
+
missingAmplitudeCredentials: true
|
|
12049
|
+
},
|
|
12050
|
+
projectDir,
|
|
12051
|
+
providerKey: resolveJobProvider(config, "analytics"),
|
|
12052
|
+
startedAt: now,
|
|
12053
|
+
stderr: "AMPLITUDE_API_KEY and AMPLITUDE_SECRET_KEY must be set in providerEnv to run analytics."
|
|
12054
|
+
});
|
|
12055
|
+
} catch {
|
|
12056
|
+
}
|
|
12057
|
+
}
|
|
11050
12058
|
info(
|
|
11051
12059
|
"AMPLITUDE_API_KEY and AMPLITUDE_SECRET_KEY must be set in providerEnv to run analytics."
|
|
11052
12060
|
);
|
|
@@ -11068,11 +12076,45 @@ function analyticsCommand(program2) {
|
|
|
11068
12076
|
}
|
|
11069
12077
|
const spinner = createSpinner("Running analytics job...");
|
|
11070
12078
|
spinner.start();
|
|
12079
|
+
const startedAt = Date.now();
|
|
11071
12080
|
try {
|
|
11072
12081
|
await maybeApplyCronSchedulingDelay(config, "analytics", projectDir);
|
|
11073
12082
|
const result = await runAnalytics(config, projectDir);
|
|
12083
|
+
try {
|
|
12084
|
+
recordJobOutcome({
|
|
12085
|
+
config,
|
|
12086
|
+
exitCode: 0,
|
|
12087
|
+
finishedAt: Date.now(),
|
|
12088
|
+
jobType: "analytics",
|
|
12089
|
+
metadata: {
|
|
12090
|
+
lookbackDays: config.analytics.lookbackDays,
|
|
12091
|
+
summary: result.summary
|
|
12092
|
+
},
|
|
12093
|
+
projectDir,
|
|
12094
|
+
providerKey: resolveJobProvider(config, "analytics"),
|
|
12095
|
+
startedAt,
|
|
12096
|
+
stdout: result.summary
|
|
12097
|
+
});
|
|
12098
|
+
} catch {
|
|
12099
|
+
}
|
|
11074
12100
|
spinner.succeed(`Analytics complete \u2014 ${result.summary}`);
|
|
11075
12101
|
} catch (err) {
|
|
12102
|
+
try {
|
|
12103
|
+
recordJobOutcome({
|
|
12104
|
+
config,
|
|
12105
|
+
exitCode: 1,
|
|
12106
|
+
finishedAt: Date.now(),
|
|
12107
|
+
jobType: "analytics",
|
|
12108
|
+
metadata: {
|
|
12109
|
+
lookbackDays: config.analytics.lookbackDays
|
|
12110
|
+
},
|
|
12111
|
+
projectDir,
|
|
12112
|
+
providerKey: resolveJobProvider(config, "analytics"),
|
|
12113
|
+
startedAt,
|
|
12114
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
12115
|
+
});
|
|
12116
|
+
} catch {
|
|
12117
|
+
}
|
|
11076
12118
|
spinner.fail(`Analytics failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
11077
12119
|
process.exit(1);
|
|
11078
12120
|
}
|
|
@@ -15787,11 +16829,217 @@ function createProjectDoctorRoutes() {
|
|
|
15787
16829
|
return router;
|
|
15788
16830
|
}
|
|
15789
16831
|
|
|
16832
|
+
// ../server/dist/routes/feedback.routes.js
|
|
16833
|
+
init_dist();
|
|
16834
|
+
import { Router as Router5 } from "express";
|
|
16835
|
+
var DAY_MS = 24 * 60 * 60 * 1e3;
|
|
16836
|
+
var WINDOW_DAYS = [7, 30];
|
|
16837
|
+
var VALID_AUGMENTATION_STATUSES = [
|
|
16838
|
+
"active",
|
|
16839
|
+
"paused",
|
|
16840
|
+
"expired",
|
|
16841
|
+
"archived"
|
|
16842
|
+
];
|
|
16843
|
+
function emptyBreakdown() {
|
|
16844
|
+
return {
|
|
16845
|
+
totalCount: 0,
|
|
16846
|
+
successCount: 0,
|
|
16847
|
+
failureCount: 0,
|
|
16848
|
+
timeoutCount: 0,
|
|
16849
|
+
rateLimitedCount: 0,
|
|
16850
|
+
skippedCount: 0,
|
|
16851
|
+
successRate: null
|
|
16852
|
+
};
|
|
16853
|
+
}
|
|
16854
|
+
function applyOutcome(summary, outcome) {
|
|
16855
|
+
summary.totalCount += 1;
|
|
16856
|
+
if (outcome === "success")
|
|
16857
|
+
summary.successCount += 1;
|
|
16858
|
+
if (outcome === "failure")
|
|
16859
|
+
summary.failureCount += 1;
|
|
16860
|
+
if (outcome === "timeout")
|
|
16861
|
+
summary.timeoutCount += 1;
|
|
16862
|
+
if (outcome === "rate_limited")
|
|
16863
|
+
summary.rateLimitedCount += 1;
|
|
16864
|
+
if (outcome === "skipped")
|
|
16865
|
+
summary.skippedCount += 1;
|
|
16866
|
+
}
|
|
16867
|
+
function finalizeBreakdown(summary) {
|
|
16868
|
+
return {
|
|
16869
|
+
...summary,
|
|
16870
|
+
successRate: summary.totalCount > 0 ? summary.successCount / summary.totalCount : null
|
|
16871
|
+
};
|
|
16872
|
+
}
|
|
16873
|
+
function summarizeOutcomesBy(outcomes, getKey) {
|
|
16874
|
+
const grouped = {};
|
|
16875
|
+
for (const outcome of outcomes) {
|
|
16876
|
+
const key = getKey(outcome);
|
|
16877
|
+
grouped[key] ??= emptyBreakdown();
|
|
16878
|
+
applyOutcome(grouped[key], outcome.outcome);
|
|
16879
|
+
}
|
|
16880
|
+
return Object.fromEntries(Object.entries(grouped).map(([key, summary]) => [key, finalizeBreakdown(summary)]));
|
|
16881
|
+
}
|
|
16882
|
+
function buildWindowSummary(projectPath, days) {
|
|
16883
|
+
const repo = getRepositories().sessionOutcomes;
|
|
16884
|
+
const toFinishedAt = Date.now();
|
|
16885
|
+
const fromFinishedAt = toFinishedAt - days * DAY_MS;
|
|
16886
|
+
const base = repo.querySummary({ projectPath, fromFinishedAt, toFinishedAt });
|
|
16887
|
+
const outcomes = repo.queryOutcomes({ projectPath, fromFinishedAt, toFinishedAt, limit: 500 });
|
|
16888
|
+
const byJobType = Object.fromEntries(getValidJobTypes().map((jobType) => {
|
|
16889
|
+
const summary = repo.querySummary({ projectPath, jobType, fromFinishedAt, toFinishedAt });
|
|
16890
|
+
return [
|
|
16891
|
+
jobType,
|
|
16892
|
+
finalizeBreakdown({
|
|
16893
|
+
totalCount: summary.totalCount,
|
|
16894
|
+
successCount: summary.successCount,
|
|
16895
|
+
failureCount: summary.failureCount,
|
|
16896
|
+
timeoutCount: summary.timeoutCount,
|
|
16897
|
+
rateLimitedCount: summary.rateLimitedCount,
|
|
16898
|
+
skippedCount: summary.skippedCount,
|
|
16899
|
+
successRate: null
|
|
16900
|
+
})
|
|
16901
|
+
];
|
|
16902
|
+
}).filter(([, summary]) => summary.totalCount > 0));
|
|
16903
|
+
return {
|
|
16904
|
+
...base,
|
|
16905
|
+
days,
|
|
16906
|
+
fromFinishedAt,
|
|
16907
|
+
toFinishedAt,
|
|
16908
|
+
successRate: base.totalCount > 0 ? base.successCount / base.totalCount : null,
|
|
16909
|
+
byJobType,
|
|
16910
|
+
byProvider: summarizeOutcomesBy(outcomes, (outcome) => outcome.providerKey)
|
|
16911
|
+
};
|
|
16912
|
+
}
|
|
16913
|
+
function getActiveAugmentations(projectPath) {
|
|
16914
|
+
return getRepositories().sessionOutcomes.listAugmentations({
|
|
16915
|
+
projectPath,
|
|
16916
|
+
status: "active",
|
|
16917
|
+
includeExpired: false,
|
|
16918
|
+
limit: 250
|
|
16919
|
+
});
|
|
16920
|
+
}
|
|
16921
|
+
function buildFailurePatterns(projectPath) {
|
|
16922
|
+
const outcomes = getRepositories().sessionOutcomes.queryOutcomes({
|
|
16923
|
+
projectPath,
|
|
16924
|
+
outcome: "failure",
|
|
16925
|
+
limit: 500
|
|
16926
|
+
});
|
|
16927
|
+
const patterns = /* @__PURE__ */ new Map();
|
|
16928
|
+
for (const outcome of outcomes) {
|
|
16929
|
+
const key = [
|
|
16930
|
+
outcome.jobType,
|
|
16931
|
+
outcome.providerKey,
|
|
16932
|
+
outcome.failureCategory ?? "uncategorized",
|
|
16933
|
+
outcome.failureSignature ?? "unknown"
|
|
16934
|
+
].join(":");
|
|
16935
|
+
const current = patterns.get(key);
|
|
16936
|
+
if (current) {
|
|
16937
|
+
current.sampleCount += 1;
|
|
16938
|
+
current.lastSeenAt = Math.max(current.lastSeenAt, outcome.finishedAt);
|
|
16939
|
+
continue;
|
|
16940
|
+
}
|
|
16941
|
+
patterns.set(key, {
|
|
16942
|
+
key,
|
|
16943
|
+
jobType: outcome.jobType,
|
|
16944
|
+
providerKey: outcome.providerKey,
|
|
16945
|
+
category: outcome.failureCategory,
|
|
16946
|
+
signature: outcome.failureSignature,
|
|
16947
|
+
sampleCount: 1,
|
|
16948
|
+
lastSeenAt: outcome.finishedAt
|
|
16949
|
+
});
|
|
16950
|
+
}
|
|
16951
|
+
return [...patterns.values()].sort((a, b) => b.sampleCount - a.sampleCount || b.lastSeenAt - a.lastSeenAt).slice(0, 10);
|
|
16952
|
+
}
|
|
16953
|
+
function resolveAugmentationStatus(body) {
|
|
16954
|
+
if (body.action === "enable")
|
|
16955
|
+
return "active";
|
|
16956
|
+
if (body.action === "disable")
|
|
16957
|
+
return "paused";
|
|
16958
|
+
if (body.action === "expire")
|
|
16959
|
+
return "expired";
|
|
16960
|
+
if (body.enabled === true)
|
|
16961
|
+
return "active";
|
|
16962
|
+
if (body.enabled === false)
|
|
16963
|
+
return "paused";
|
|
16964
|
+
if (body.status && VALID_AUGMENTATION_STATUSES.includes(body.status))
|
|
16965
|
+
return body.status;
|
|
16966
|
+
return null;
|
|
16967
|
+
}
|
|
16968
|
+
function createFeedbackRouteHandlers(ctx) {
|
|
16969
|
+
const router = Router5({ mergeParams: true });
|
|
16970
|
+
const p = ctx.pathPrefix;
|
|
16971
|
+
router.get(`/${p}summary`, (req, res) => {
|
|
16972
|
+
try {
|
|
16973
|
+
const projectPath = ctx.getProjectDir(req);
|
|
16974
|
+
const response = {
|
|
16975
|
+
projectPath,
|
|
16976
|
+
windows: {
|
|
16977
|
+
last7Days: buildWindowSummary(projectPath, WINDOW_DAYS[0]),
|
|
16978
|
+
last30Days: buildWindowSummary(projectPath, WINDOW_DAYS[1])
|
|
16979
|
+
},
|
|
16980
|
+
activeAugmentations: getActiveAugmentations(projectPath)
|
|
16981
|
+
};
|
|
16982
|
+
res.json(response);
|
|
16983
|
+
} catch (error2) {
|
|
16984
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
16985
|
+
}
|
|
16986
|
+
});
|
|
16987
|
+
router.get(`/${p}patterns`, (req, res) => {
|
|
16988
|
+
try {
|
|
16989
|
+
const projectPath = ctx.getProjectDir(req);
|
|
16990
|
+
const response = {
|
|
16991
|
+
projectPath,
|
|
16992
|
+
patterns: getRepositories().sessionOutcomes.listPatterns({ projectPath, limit: 25 }),
|
|
16993
|
+
topFailurePatterns: buildFailurePatterns(projectPath)
|
|
16994
|
+
};
|
|
16995
|
+
res.json(response);
|
|
16996
|
+
} catch (error2) {
|
|
16997
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
16998
|
+
}
|
|
16999
|
+
});
|
|
17000
|
+
router.patch(`/${p}augmentations/:id`, (req, res) => {
|
|
17001
|
+
try {
|
|
17002
|
+
const id = parseInt(req.params.id, 10);
|
|
17003
|
+
if (!Number.isInteger(id) || id <= 0) {
|
|
17004
|
+
res.status(400).json({ error: "Invalid augmentation id" });
|
|
17005
|
+
return;
|
|
17006
|
+
}
|
|
17007
|
+
const status = resolveAugmentationStatus(req.body);
|
|
17008
|
+
if (!status) {
|
|
17009
|
+
res.status(400).json({ error: "Expected action, enabled, or status update" });
|
|
17010
|
+
return;
|
|
17011
|
+
}
|
|
17012
|
+
const projectPath = ctx.getProjectDir(req);
|
|
17013
|
+
const augmentation = getRepositories().sessionOutcomes.updateAugmentationStatus(id, status, projectPath);
|
|
17014
|
+
if (!augmentation) {
|
|
17015
|
+
res.status(404).json({ error: "Augmentation not found" });
|
|
17016
|
+
return;
|
|
17017
|
+
}
|
|
17018
|
+
res.json({ augmentation });
|
|
17019
|
+
} catch (error2) {
|
|
17020
|
+
res.status(500).json({ error: error2 instanceof Error ? error2.message : String(error2) });
|
|
17021
|
+
}
|
|
17022
|
+
});
|
|
17023
|
+
return router;
|
|
17024
|
+
}
|
|
17025
|
+
function createFeedbackRoutes(deps) {
|
|
17026
|
+
return createFeedbackRouteHandlers({
|
|
17027
|
+
getProjectDir: () => deps.projectDir,
|
|
17028
|
+
pathPrefix: ""
|
|
17029
|
+
});
|
|
17030
|
+
}
|
|
17031
|
+
function createProjectFeedbackRoutes() {
|
|
17032
|
+
return createFeedbackRouteHandlers({
|
|
17033
|
+
getProjectDir: (req) => req.projectDir,
|
|
17034
|
+
pathPrefix: "feedback/"
|
|
17035
|
+
});
|
|
17036
|
+
}
|
|
17037
|
+
|
|
15790
17038
|
// ../server/dist/routes/job.routes.js
|
|
15791
17039
|
init_dist();
|
|
15792
17040
|
import { spawn as spawn7 } from "child_process";
|
|
15793
17041
|
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
15794
|
-
import { Router as
|
|
17042
|
+
import { Router as Router6 } from "express";
|
|
15795
17043
|
var SUPPORTED_GITHUB_EVENTS = [
|
|
15796
17044
|
"workflow_run",
|
|
15797
17045
|
"check_suite",
|
|
@@ -15984,7 +17232,7 @@ function getLockPathForJob2(projectDir, jobId) {
|
|
|
15984
17232
|
}
|
|
15985
17233
|
}
|
|
15986
17234
|
function createJobRouteHandlers(ctx) {
|
|
15987
|
-
const router =
|
|
17235
|
+
const router = Router6({ mergeParams: true });
|
|
15988
17236
|
const p = ctx.pathPrefix;
|
|
15989
17237
|
router.post(`/${p}:id/run`, (req, res) => {
|
|
15990
17238
|
try {
|
|
@@ -16108,10 +17356,10 @@ function createProjectJobRoutes() {
|
|
|
16108
17356
|
// ../server/dist/routes/log.routes.js
|
|
16109
17357
|
init_dist();
|
|
16110
17358
|
import * as path35 from "path";
|
|
16111
|
-
import { Router as
|
|
17359
|
+
import { Router as Router7 } from "express";
|
|
16112
17360
|
function createLogRoutes(deps) {
|
|
16113
17361
|
const { projectDir } = deps;
|
|
16114
|
-
const router =
|
|
17362
|
+
const router = Router7();
|
|
16115
17363
|
router.get("/:name", (req, res) => {
|
|
16116
17364
|
try {
|
|
16117
17365
|
const { name } = req.params;
|
|
@@ -16136,7 +17384,7 @@ function createLogRoutes(deps) {
|
|
|
16136
17384
|
return router;
|
|
16137
17385
|
}
|
|
16138
17386
|
function createProjectLogRoutes() {
|
|
16139
|
-
const router =
|
|
17387
|
+
const router = Router7({ mergeParams: true });
|
|
16140
17388
|
router.get("/logs/:name", (req, res) => {
|
|
16141
17389
|
try {
|
|
16142
17390
|
const projectDir = req.projectDir;
|
|
@@ -16163,9 +17411,9 @@ function createProjectLogRoutes() {
|
|
|
16163
17411
|
}
|
|
16164
17412
|
|
|
16165
17413
|
// ../server/dist/routes/prd.routes.js
|
|
16166
|
-
import { Router as
|
|
17414
|
+
import { Router as Router8 } from "express";
|
|
16167
17415
|
function createPrdRoutes(_deps) {
|
|
16168
|
-
const router =
|
|
17416
|
+
const router = Router8();
|
|
16169
17417
|
router.get("/", (_req, res) => {
|
|
16170
17418
|
res.status(410).json({ error: "PRDs endpoint deprecated - use GitHub Board instead" });
|
|
16171
17419
|
});
|
|
@@ -16175,7 +17423,7 @@ function createPrdRoutes(_deps) {
|
|
|
16175
17423
|
return router;
|
|
16176
17424
|
}
|
|
16177
17425
|
function createProjectPrdRoutes() {
|
|
16178
|
-
const router =
|
|
17426
|
+
const router = Router8({ mergeParams: true });
|
|
16179
17427
|
router.get("/prds", (_req, res) => {
|
|
16180
17428
|
res.status(410).json({ error: "PRDs endpoint deprecated - use GitHub Board instead" });
|
|
16181
17429
|
});
|
|
@@ -16188,9 +17436,9 @@ function createProjectPrdRoutes() {
|
|
|
16188
17436
|
// ../server/dist/routes/roadmap.routes.js
|
|
16189
17437
|
init_dist();
|
|
16190
17438
|
import * as path36 from "path";
|
|
16191
|
-
import { Router as
|
|
17439
|
+
import { Router as Router9 } from "express";
|
|
16192
17440
|
function createRoadmapRouteHandlers(ctx) {
|
|
16193
|
-
const router =
|
|
17441
|
+
const router = Router9({ mergeParams: true });
|
|
16194
17442
|
const p = ctx.pathPrefix;
|
|
16195
17443
|
router.get(`/${p}`, (req, res) => {
|
|
16196
17444
|
try {
|
|
@@ -16271,11 +17519,11 @@ function createProjectRoadmapRoutes() {
|
|
|
16271
17519
|
|
|
16272
17520
|
// ../server/dist/routes/status.routes.js
|
|
16273
17521
|
init_dist();
|
|
16274
|
-
import { Router as
|
|
17522
|
+
import { Router as Router10 } from "express";
|
|
16275
17523
|
import { CronExpressionParser as CronExpressionParser2 } from "cron-parser";
|
|
16276
17524
|
function createStatusRoutes(deps) {
|
|
16277
17525
|
const { projectDir, getConfig, sseClients } = deps;
|
|
16278
|
-
const router =
|
|
17526
|
+
const router = Router10();
|
|
16279
17527
|
router.get("/events", (req, res) => {
|
|
16280
17528
|
res.setHeader("Content-Type", "text/event-stream");
|
|
16281
17529
|
res.setHeader("Cache-Control", "no-cache");
|
|
@@ -16404,7 +17652,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
16404
17652
|
}
|
|
16405
17653
|
function createScheduleInfoRoutes(deps) {
|
|
16406
17654
|
const { projectDir, getConfig } = deps;
|
|
16407
|
-
const router =
|
|
17655
|
+
const router = Router10();
|
|
16408
17656
|
router.get("/", async (_req, res) => {
|
|
16409
17657
|
try {
|
|
16410
17658
|
const config = getConfig();
|
|
@@ -16418,7 +17666,7 @@ function createScheduleInfoRoutes(deps) {
|
|
|
16418
17666
|
}
|
|
16419
17667
|
function createProjectSseRoutes(deps) {
|
|
16420
17668
|
const { projectSseClients, projectSseWatchers } = deps;
|
|
16421
|
-
const router =
|
|
17669
|
+
const router = Router10({ mergeParams: true });
|
|
16422
17670
|
router.get("/status/events", (req, res) => {
|
|
16423
17671
|
const projectDir = req.projectDir;
|
|
16424
17672
|
if (!projectSseClients.has(projectDir)) {
|
|
@@ -16475,9 +17723,9 @@ data: ${JSON.stringify(snapshot)}
|
|
|
16475
17723
|
|
|
16476
17724
|
// ../server/dist/routes/queue.routes.js
|
|
16477
17725
|
init_dist();
|
|
16478
|
-
import { Router as
|
|
17726
|
+
import { Router as Router11 } from "express";
|
|
16479
17727
|
function createGlobalQueueRoutes() {
|
|
16480
|
-
const router =
|
|
17728
|
+
const router = Router11();
|
|
16481
17729
|
router.get("/status", async (_req, res) => {
|
|
16482
17730
|
try {
|
|
16483
17731
|
const status = getQueueStatus();
|
|
@@ -16512,7 +17760,7 @@ function createGlobalQueueRoutes() {
|
|
|
16512
17760
|
}
|
|
16513
17761
|
function createQueueRoutes(deps) {
|
|
16514
17762
|
const { getConfig } = deps;
|
|
16515
|
-
const router =
|
|
17763
|
+
const router = Router11();
|
|
16516
17764
|
router.get("/status", async (_req, res) => {
|
|
16517
17765
|
try {
|
|
16518
17766
|
const config = getConfig();
|
|
@@ -16622,6 +17870,7 @@ function createApp(projectDir) {
|
|
|
16622
17870
|
app.use("/api/logs", createLogRoutes({ projectDir }));
|
|
16623
17871
|
app.use("/api/doctor", createDoctorRoutes({ projectDir, getConfig: () => config }));
|
|
16624
17872
|
app.use("/api/queue", createQueueRoutes({ getConfig: () => config }));
|
|
17873
|
+
app.use("/api/feedback", createFeedbackRoutes({ projectDir }));
|
|
16625
17874
|
app.use("/api/global-notifications", createGlobalNotificationsRoutes());
|
|
16626
17875
|
app.get("/api/prs", async (_req, res) => {
|
|
16627
17876
|
try {
|
|
@@ -16669,6 +17918,7 @@ function createProjectRouter() {
|
|
|
16669
17918
|
router.use(createProjectActionRoutes({ projectSseClients }));
|
|
16670
17919
|
router.use(createProjectJobRoutes());
|
|
16671
17920
|
router.use(createProjectRoadmapRoutes());
|
|
17921
|
+
router.use(createProjectFeedbackRoutes());
|
|
16672
17922
|
router.get("/prs", async (req, res) => {
|
|
16673
17923
|
try {
|
|
16674
17924
|
res.json(await collectPrInfo(req.projectDir, req.projectConfig.branchPatterns));
|
|
@@ -17698,6 +18948,7 @@ function sliceCommand(program2) {
|
|
|
17698
18948
|
}
|
|
17699
18949
|
const spinner = createSpinner("Running Planner...");
|
|
17700
18950
|
spinner.start();
|
|
18951
|
+
const startedAt = Date.now();
|
|
17701
18952
|
try {
|
|
17702
18953
|
await maybeApplyCronSchedulingDelay(config, "slicer", projectDir);
|
|
17703
18954
|
const result = await sliceNextItem(projectDir, config);
|
|
@@ -17727,6 +18978,28 @@ function sliceCommand(program2) {
|
|
|
17727
18978
|
}
|
|
17728
18979
|
const nothingPending = result.error === "No pending items to process";
|
|
17729
18980
|
const exitCode = result.sliced || nothingPending ? 0 : 1;
|
|
18981
|
+
if (!options.dryRun) {
|
|
18982
|
+
try {
|
|
18983
|
+
recordJobOutcome({
|
|
18984
|
+
config,
|
|
18985
|
+
exitCode,
|
|
18986
|
+
finishedAt: Date.now(),
|
|
18987
|
+
jobType: "planner",
|
|
18988
|
+
metadata: {
|
|
18989
|
+
error: result.error ?? null,
|
|
18990
|
+
file: result.file ?? null,
|
|
18991
|
+
itemTitle: result.item?.title ?? null,
|
|
18992
|
+
sliced: result.sliced
|
|
18993
|
+
},
|
|
18994
|
+
projectDir,
|
|
18995
|
+
providerKey: resolveJobProvider(config, "slicer"),
|
|
18996
|
+
startedAt,
|
|
18997
|
+
stderr: result.error,
|
|
18998
|
+
stdout: result.file ? `Created ${result.file}` : void 0
|
|
18999
|
+
});
|
|
19000
|
+
} catch {
|
|
19001
|
+
}
|
|
19002
|
+
}
|
|
17730
19003
|
if (!options.dryRun && result.sliced) {
|
|
17731
19004
|
await sendNotifications(config, {
|
|
17732
19005
|
event: "run_succeeded",
|
|
@@ -17745,6 +19018,22 @@ function sliceCommand(program2) {
|
|
|
17745
19018
|
}
|
|
17746
19019
|
process.exit(exitCode);
|
|
17747
19020
|
} catch (err) {
|
|
19021
|
+
try {
|
|
19022
|
+
recordJobOutcome({
|
|
19023
|
+
config,
|
|
19024
|
+
exitCode: 1,
|
|
19025
|
+
finishedAt: Date.now(),
|
|
19026
|
+
jobType: "planner",
|
|
19027
|
+
metadata: {
|
|
19028
|
+
error: err instanceof Error ? err.message : String(err)
|
|
19029
|
+
},
|
|
19030
|
+
projectDir,
|
|
19031
|
+
providerKey: resolveJobProvider(config, "slicer"),
|
|
19032
|
+
startedAt,
|
|
19033
|
+
stderr: err instanceof Error ? err.message : String(err)
|
|
19034
|
+
});
|
|
19035
|
+
} catch {
|
|
19036
|
+
}
|
|
17748
19037
|
spinner.fail("Failed to execute planner command");
|
|
17749
19038
|
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
17750
19039
|
process.exit(1);
|
|
@@ -18407,7 +19696,7 @@ import { spawn as spawn8 } from "child_process";
|
|
|
18407
19696
|
import chalk7 from "chalk";
|
|
18408
19697
|
import { Command as Command2 } from "commander";
|
|
18409
19698
|
var logger6 = createLogger("queue");
|
|
18410
|
-
var
|
|
19699
|
+
var VALID_JOB_TYPES2 = [
|
|
18411
19700
|
"executor",
|
|
18412
19701
|
"reviewer",
|
|
18413
19702
|
"qa",
|
|
@@ -18515,18 +19804,18 @@ function createQueueCommand() {
|
|
|
18515
19804
|
}
|
|
18516
19805
|
});
|
|
18517
19806
|
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 && !
|
|
19807
|
+
if (opts.type && !VALID_JOB_TYPES2.includes(opts.type)) {
|
|
18519
19808
|
console.error(chalk7.red(`Invalid job type: ${opts.type}`));
|
|
18520
|
-
console.error(chalk7.dim(`Valid types: ${
|
|
19809
|
+
console.error(chalk7.dim(`Valid types: ${VALID_JOB_TYPES2.join(", ")}`));
|
|
18521
19810
|
process.exit(1);
|
|
18522
19811
|
}
|
|
18523
19812
|
const count = clearQueue(opts.type);
|
|
18524
19813
|
console.log(chalk7.green(`Cleared ${count} pending job(s) from the queue.`));
|
|
18525
19814
|
});
|
|
18526
19815
|
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 (!
|
|
19816
|
+
if (!VALID_JOB_TYPES2.includes(jobType)) {
|
|
18528
19817
|
console.error(chalk7.red(`Invalid job type: ${jobType}`));
|
|
18529
|
-
console.error(chalk7.dim(`Valid types: ${
|
|
19818
|
+
console.error(chalk7.dim(`Valid types: ${VALID_JOB_TYPES2.join(", ")}`));
|
|
18530
19819
|
process.exit(1);
|
|
18531
19820
|
}
|
|
18532
19821
|
let envVars = {};
|
|
@@ -18630,9 +19919,9 @@ function createQueueCommand() {
|
|
|
18630
19919
|
queue.command("claim <job-type> <project-dir>").description(
|
|
18631
19920
|
"Atomically claim a concurrency slot and insert a running entry (used by cron scripts)"
|
|
18632
19921
|
).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 (!
|
|
19922
|
+
if (!VALID_JOB_TYPES2.includes(jobType)) {
|
|
18634
19923
|
console.error(`Invalid job type: ${jobType}`);
|
|
18635
|
-
console.error(`Valid types: ${
|
|
19924
|
+
console.error(`Valid types: ${VALID_JOB_TYPES2.join(", ")}`);
|
|
18636
19925
|
process.exit(1);
|
|
18637
19926
|
}
|
|
18638
19927
|
const queueConfig = loadConfig(projectDir).queue;
|
|
@@ -19039,12 +20328,14 @@ function resolveCommand(program2) {
|
|
|
19039
20328
|
const spinner = createSpinner("Running PR resolver...");
|
|
19040
20329
|
spinner.start();
|
|
19041
20330
|
try {
|
|
20331
|
+
const startedAt = Date.now();
|
|
19042
20332
|
await maybeApplyCronSchedulingDelay(config, "pr-resolver", projectDir);
|
|
19043
20333
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
19044
20334
|
scriptPath,
|
|
19045
20335
|
[projectDir],
|
|
19046
20336
|
envVars
|
|
19047
20337
|
);
|
|
20338
|
+
const finishedAt = Date.now();
|
|
19048
20339
|
const scriptResult = parseScriptResult(`${stdout}
|
|
19049
20340
|
${stderr}`);
|
|
19050
20341
|
if (exitCode === 0) {
|
|
@@ -19059,6 +20350,27 @@ ${stderr}`);
|
|
|
19059
20350
|
spinner.fail(`PR resolver exited with code ${exitCode}`);
|
|
19060
20351
|
}
|
|
19061
20352
|
const notificationEvent = exitCode === 0 ? "pr_resolver_completed" : "pr_resolver_failed";
|
|
20353
|
+
if (!options.dryRun) {
|
|
20354
|
+
try {
|
|
20355
|
+
recordJobOutcome({
|
|
20356
|
+
config,
|
|
20357
|
+
exitCode,
|
|
20358
|
+
finishedAt,
|
|
20359
|
+
jobType: "pr-resolver",
|
|
20360
|
+
metadata: {
|
|
20361
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
20362
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
20363
|
+
},
|
|
20364
|
+
projectDir,
|
|
20365
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "pr-resolver"),
|
|
20366
|
+
scriptResult,
|
|
20367
|
+
startedAt,
|
|
20368
|
+
stderr,
|
|
20369
|
+
stdout
|
|
20370
|
+
});
|
|
20371
|
+
} catch {
|
|
20372
|
+
}
|
|
20373
|
+
}
|
|
19062
20374
|
await sendNotifications(config, {
|
|
19063
20375
|
event: notificationEvent,
|
|
19064
20376
|
projectName: path45.basename(projectDir),
|
|
@@ -19157,12 +20469,14 @@ function mergeCommand(program2) {
|
|
|
19157
20469
|
const spinner = createSpinner("Running merge orchestrator...");
|
|
19158
20470
|
spinner.start();
|
|
19159
20471
|
try {
|
|
20472
|
+
const startedAt = Date.now();
|
|
19160
20473
|
await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
|
|
19161
20474
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
19162
20475
|
scriptPath,
|
|
19163
20476
|
[projectDir],
|
|
19164
20477
|
envVars
|
|
19165
20478
|
);
|
|
20479
|
+
const finishedAt = Date.now();
|
|
19166
20480
|
const scriptResult = parseScriptResult(`${stdout}
|
|
19167
20481
|
${stderr}`);
|
|
19168
20482
|
if (exitCode === 0) {
|
|
@@ -19179,6 +20493,30 @@ ${stderr}`);
|
|
|
19179
20493
|
const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
|
|
19180
20494
|
const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
|
|
19181
20495
|
const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
|
|
20496
|
+
if (!options.dryRun) {
|
|
20497
|
+
try {
|
|
20498
|
+
recordJobOutcome({
|
|
20499
|
+
config,
|
|
20500
|
+
exitCode,
|
|
20501
|
+
finishedAt,
|
|
20502
|
+
jobType: "merger",
|
|
20503
|
+
metadata: {
|
|
20504
|
+
failedCount,
|
|
20505
|
+
mergedCount,
|
|
20506
|
+
providerCommand: envVars.NW_PROVIDER_CMD,
|
|
20507
|
+
providerLabel: envVars.NW_PROVIDER_LABEL
|
|
20508
|
+
},
|
|
20509
|
+
minReviewScore: config.merger.minReviewScore,
|
|
20510
|
+
projectDir,
|
|
20511
|
+
providerKey: envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "merger"),
|
|
20512
|
+
scriptResult,
|
|
20513
|
+
startedAt,
|
|
20514
|
+
stderr,
|
|
20515
|
+
stdout
|
|
20516
|
+
});
|
|
20517
|
+
} catch {
|
|
20518
|
+
}
|
|
20519
|
+
}
|
|
19182
20520
|
if (notificationEvent) {
|
|
19183
20521
|
await sendNotifications(config, {
|
|
19184
20522
|
event: notificationEvent,
|