@jonit-dev/night-watch-cli 1.8.12-beta.3 → 1.8.12-beta.5

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