@jonit-dev/night-watch-cli 1.8.14-beta.7 → 1.8.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +1611 -409
- package/dist/cli.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +16 -0
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +13 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.d.ts.map +1 -1
- package/dist/commands/install.js +26 -4
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/logs.d.ts.map +1 -1
- package/dist/commands/logs.js +14 -4
- package/dist/commands/logs.js.map +1 -1
- package/dist/commands/merge.d.ts.map +1 -1
- package/dist/commands/merge.js +20 -0
- package/dist/commands/merge.js.map +1 -1
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +11 -7
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/review.d.ts.map +1 -1
- package/dist/commands/review.js +26 -0
- package/dist/commands/review.js.map +1 -1
- package/dist/commands/run.d.ts.map +1 -1
- package/dist/commands/run.js +24 -0
- package/dist/commands/run.js.map +1 -1
- package/dist/commands/shared/telemetry.d.ts +27 -0
- package/dist/commands/shared/telemetry.d.ts.map +1 -0
- package/dist/commands/shared/telemetry.js +73 -0
- package/dist/commands/shared/telemetry.js.map +1 -0
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +24 -0
- package/dist/commands/status.js.map +1 -1
- package/dist/commands/telemetry.d.ts +3 -0
- package/dist/commands/telemetry.d.ts.map +1 -0
- package/dist/commands/telemetry.js +52 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/ux.d.ts +14 -0
- package/dist/commands/ux.d.ts.map +1 -0
- package/dist/commands/ux.js +169 -0
- package/dist/commands/ux.js.map +1 -0
- package/dist/scripts/night-watch-cron.sh +36 -12
- package/dist/telemetry-bootstrap.d.ts +2 -0
- package/dist/telemetry-bootstrap.d.ts.map +1 -0
- package/dist/telemetry-bootstrap.js +32 -0
- package/dist/telemetry-bootstrap.js.map +1 -0
- package/dist/templates/night-watch.config.json +14 -0
- package/dist/web/assets/index-BheLL2O2.css +1 -0
- package/dist/web/assets/index-BsTuwxzi.js +447 -0
- package/dist/web/index.html +2 -2
- package/package.json +2 -2
- package/dist/web/assets/index-6Yf-Q6Di.js +0 -442
- package/dist/web/assets/index-B6E6kOoR.js +0 -406
- package/dist/web/assets/index-BbiKFOgi.css +0 -1
- package/dist/web/assets/index-C-xpWpS8.css +0 -1
- package/dist/web/assets/index-C3BV1Hoa.js +0 -406
- package/dist/web/assets/index-CEYe-290.js +0 -412
- package/dist/web/assets/index-CL3Q-KB4.css +0 -1
- package/dist/web/assets/index-DIMUXIP8.css +0 -1
- package/dist/web/assets/index-DatF4suf.css +0 -1
- package/dist/web/assets/index-DpvzoXEv.js +0 -442
- package/dist/web/assets/index-Ds8OqaCa.css +0 -1
- package/dist/web/assets/index-DyME41HV.css +0 -1
- package/dist/web/assets/index-FDOCfjkP.js +0 -442
- package/dist/web/assets/index-NR27JE3b.js +0 -406
- package/dist/web/assets/index-Q3IYCcdZ.js +0 -447
- package/dist/web/assets/index-uBao8iYf.js +0 -447
package/dist/cli.js
CHANGED
|
@@ -379,6 +379,42 @@ var init_job_registry = __esm({
|
|
|
379
379
|
targetColumn: "Draft"
|
|
380
380
|
}
|
|
381
381
|
},
|
|
382
|
+
{
|
|
383
|
+
id: "ux",
|
|
384
|
+
name: "UX",
|
|
385
|
+
description: "Inspects user flows with browser automation and drafts prioritized UX reports",
|
|
386
|
+
cliCommand: "ux",
|
|
387
|
+
logName: "ux",
|
|
388
|
+
lockSuffix: "-ux.lock",
|
|
389
|
+
queuePriority: 10,
|
|
390
|
+
envPrefix: "NW_UX",
|
|
391
|
+
extraFields: [
|
|
392
|
+
{
|
|
393
|
+
name: "targetColumn",
|
|
394
|
+
type: "enum",
|
|
395
|
+
enumValues: [...BOARD_COLUMNS],
|
|
396
|
+
defaultValue: "Draft"
|
|
397
|
+
},
|
|
398
|
+
{ name: "baseUrl", type: "string", defaultValue: "" },
|
|
399
|
+
{ name: "startUrl", type: "string", defaultValue: "" },
|
|
400
|
+
{ name: "flows", type: "string[]", defaultValue: [] },
|
|
401
|
+
{ name: "autoInstallPlaywright", type: "boolean", defaultValue: true },
|
|
402
|
+
{ name: "maxIssues", type: "number", defaultValue: 10 },
|
|
403
|
+
{ name: "reportPrompt", type: "string", defaultValue: "" }
|
|
404
|
+
],
|
|
405
|
+
defaultConfig: {
|
|
406
|
+
enabled: false,
|
|
407
|
+
schedule: "0 7 * * 1",
|
|
408
|
+
maxRuntime: 0,
|
|
409
|
+
targetColumn: "Draft",
|
|
410
|
+
baseUrl: "",
|
|
411
|
+
startUrl: "",
|
|
412
|
+
flows: [],
|
|
413
|
+
autoInstallPlaywright: true,
|
|
414
|
+
maxIssues: 10,
|
|
415
|
+
reportPrompt: ""
|
|
416
|
+
}
|
|
417
|
+
},
|
|
382
418
|
{
|
|
383
419
|
id: "analytics",
|
|
384
420
|
name: "Analytics",
|
|
@@ -471,7 +507,7 @@ function resolveProviderBucketKey(provider, providerEnv) {
|
|
|
471
507
|
return `claude-proxy:${baseUrl}`;
|
|
472
508
|
}
|
|
473
509
|
}
|
|
474
|
-
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_CREATE_ISSUES, 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_MANAGER_ENABLED, DEFAULT_MANAGER_SCHEDULE, DEFAULT_MANAGER_MAX_RUNTIME, DEFAULT_MANAGER_AUTHORITY, DEFAULT_MANAGER_OUTPUT_MODE, DEFAULT_MANAGER_TARGET_COLUMN, DEFAULT_MANAGER_MEMORY_PATH, DEFAULT_MANAGER_DOCS_DIR, DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED, DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY, DEFAULT_MANAGER, 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_CI_POLICY, DEFAULT_MERGER_LOCAL_CHECK_COMMAND, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, MANAGER_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;
|
|
510
|
+
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_MODEL_ATTRIBUTION, DEFAULT_NEW_PR_LABEL, 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_CREATE_ISSUES, DEFAULT_AUDIT_TARGET_COLUMN, DEFAULT_AUDIT, DEFAULT_UX_ENABLED, DEFAULT_UX_SCHEDULE, DEFAULT_UX_MAX_RUNTIME, DEFAULT_UX_TARGET_COLUMN, DEFAULT_UX_BASE_URL, DEFAULT_UX_START_URL, DEFAULT_UX_AUTO_INSTALL_PLAYWRIGHT, DEFAULT_UX_MAX_ISSUES, DEFAULT_UX_REPORT_PROMPT, DEFAULT_UX, DEFAULT_ANALYTICS_ENABLED, DEFAULT_ANALYTICS_SCHEDULE, DEFAULT_ANALYTICS_MAX_RUNTIME, DEFAULT_ANALYTICS_LOOKBACK_DAYS, DEFAULT_ANALYTICS_TARGET_COLUMN, DEFAULT_ANALYTICS_PROMPT, DEFAULT_ANALYTICS, DEFAULT_MANAGER_ENABLED, DEFAULT_MANAGER_SCHEDULE, DEFAULT_MANAGER_MAX_RUNTIME, DEFAULT_MANAGER_AUTHORITY, DEFAULT_MANAGER_OUTPUT_MODE, DEFAULT_MANAGER_TARGET_COLUMN, DEFAULT_MANAGER_MEMORY_PATH, DEFAULT_MANAGER_DOCS_DIR, DEFAULT_MANAGER_WEEKLY_SUMMARY_ENABLED, DEFAULT_MANAGER_WEEKLY_SUMMARY_DAY, DEFAULT_MANAGER, 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_CI_POLICY, DEFAULT_MERGER_LOCAL_CHECK_COMMAND, DEFAULT_MERGER, MERGER_LOG_NAME, AUDIT_LOG_NAME, UX_LOG_NAME, PLANNER_LOG_NAME, ANALYTICS_LOG_NAME, PR_RESOLVER_LOG_NAME, MANAGER_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, TELEMETRY_FILE_NAME, DEFAULT_AMPLITUDE_API_KEY, DEFAULT_AMPLITUDE_INGEST_ENDPOINT, 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;
|
|
475
511
|
var init_constants = __esm({
|
|
476
512
|
"../core/dist/constants.js"() {
|
|
477
513
|
"use strict";
|
|
@@ -503,6 +539,8 @@ var init_constants = __esm({
|
|
|
503
539
|
DEFAULT_EXECUTOR_ENABLED = true;
|
|
504
540
|
DEFAULT_REVIEWER_ENABLED = true;
|
|
505
541
|
DEFAULT_PROVIDER_ENV = {};
|
|
542
|
+
DEFAULT_MODEL_ATTRIBUTION = false;
|
|
543
|
+
DEFAULT_NEW_PR_LABEL = "draft";
|
|
506
544
|
DEFAULT_FALLBACK_ON_RATE_LIMIT = true;
|
|
507
545
|
DEFAULT_CLAUDE_MODEL = void 0;
|
|
508
546
|
DEFAULT_PRIMARY_FALLBACK_MODEL = void 0;
|
|
@@ -564,6 +602,27 @@ var init_constants = __esm({
|
|
|
564
602
|
createIssues: DEFAULT_AUDIT_CREATE_ISSUES,
|
|
565
603
|
targetColumn: DEFAULT_AUDIT_TARGET_COLUMN
|
|
566
604
|
};
|
|
605
|
+
DEFAULT_UX_ENABLED = false;
|
|
606
|
+
DEFAULT_UX_SCHEDULE = "0 7 * * 1";
|
|
607
|
+
DEFAULT_UX_MAX_RUNTIME = 0;
|
|
608
|
+
DEFAULT_UX_TARGET_COLUMN = "Draft";
|
|
609
|
+
DEFAULT_UX_BASE_URL = "";
|
|
610
|
+
DEFAULT_UX_START_URL = "";
|
|
611
|
+
DEFAULT_UX_AUTO_INSTALL_PLAYWRIGHT = true;
|
|
612
|
+
DEFAULT_UX_MAX_ISSUES = 10;
|
|
613
|
+
DEFAULT_UX_REPORT_PROMPT = "";
|
|
614
|
+
DEFAULT_UX = {
|
|
615
|
+
enabled: DEFAULT_UX_ENABLED,
|
|
616
|
+
schedule: DEFAULT_UX_SCHEDULE,
|
|
617
|
+
maxRuntime: DEFAULT_UX_MAX_RUNTIME,
|
|
618
|
+
targetColumn: DEFAULT_UX_TARGET_COLUMN,
|
|
619
|
+
baseUrl: DEFAULT_UX_BASE_URL,
|
|
620
|
+
startUrl: DEFAULT_UX_START_URL,
|
|
621
|
+
flows: [],
|
|
622
|
+
autoInstallPlaywright: DEFAULT_UX_AUTO_INSTALL_PLAYWRIGHT,
|
|
623
|
+
maxIssues: DEFAULT_UX_MAX_ISSUES,
|
|
624
|
+
reportPrompt: DEFAULT_UX_REPORT_PROMPT
|
|
625
|
+
};
|
|
567
626
|
DEFAULT_ANALYTICS_ENABLED = false;
|
|
568
627
|
DEFAULT_ANALYTICS_SCHEDULE = "0 6 * * 1";
|
|
569
628
|
DEFAULT_ANALYTICS_MAX_RUNTIME = 0;
|
|
@@ -646,6 +705,7 @@ If no issues are warranted, output an empty array: []`;
|
|
|
646
705
|
};
|
|
647
706
|
MERGER_LOG_NAME = "merger";
|
|
648
707
|
AUDIT_LOG_NAME = "audit";
|
|
708
|
+
UX_LOG_NAME = "ux";
|
|
649
709
|
PLANNER_LOG_NAME = "slicer";
|
|
650
710
|
ANALYTICS_LOG_NAME = "analytics";
|
|
651
711
|
PR_RESOLVER_LOG_NAME = "pr-resolver";
|
|
@@ -747,6 +807,9 @@ If no issues are warranted, output an empty array: []`;
|
|
|
747
807
|
PRD_STATES_FILE_NAME = "prd-states.json";
|
|
748
808
|
STATE_DB_FILE_NAME = "state.db";
|
|
749
809
|
GLOBAL_NOTIFICATIONS_FILE_NAME = "global-notifications.json";
|
|
810
|
+
TELEMETRY_FILE_NAME = "telemetry.json";
|
|
811
|
+
DEFAULT_AMPLITUDE_API_KEY = "5289e9a61e10e059d25f8eb846bceaa8";
|
|
812
|
+
DEFAULT_AMPLITUDE_INGEST_ENDPOINT = "https://api2.amplitude.com/2/httpapi";
|
|
750
813
|
MAX_HISTORY_RECORDS_PER_PRD = 10;
|
|
751
814
|
DEFAULT_QUEUE_ENABLED = true;
|
|
752
815
|
DEFAULT_QUEUE_MODE = "auto";
|
|
@@ -826,6 +889,11 @@ function normalizeConfig(rawConfig) {
|
|
|
826
889
|
if (providerLabelVal) {
|
|
827
890
|
normalized.providerLabel = providerLabelVal;
|
|
828
891
|
}
|
|
892
|
+
normalized.modelAttribution = readBoolean(rawConfig.modelAttribution);
|
|
893
|
+
const newPrLabelVal = readString2(rawConfig.newPrLabel);
|
|
894
|
+
if (newPrLabelVal !== void 0) {
|
|
895
|
+
normalized.newPrLabel = newPrLabelVal.trim();
|
|
896
|
+
}
|
|
829
897
|
const rawProviderEnv = readObject2(rawConfig.providerEnv);
|
|
830
898
|
if (rawProviderEnv) {
|
|
831
899
|
const env = {};
|
|
@@ -974,7 +1042,7 @@ function normalizeConfig(rawConfig) {
|
|
|
974
1042
|
if (mergeMethod && VALID_MERGE_METHODS.includes(mergeMethod)) {
|
|
975
1043
|
normalized.autoMergeMethod = mergeMethod;
|
|
976
1044
|
}
|
|
977
|
-
for (const jobId of ["qa", "audit", "analytics", "merger", "manager"]) {
|
|
1045
|
+
for (const jobId of ["qa", "audit", "ux", "analytics", "merger", "manager"]) {
|
|
978
1046
|
const jobDef = getJobDef(jobId);
|
|
979
1047
|
if (!jobDef)
|
|
980
1048
|
continue;
|
|
@@ -1252,6 +1320,14 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
1252
1320
|
if (v !== null)
|
|
1253
1321
|
env.executorEnabled = v;
|
|
1254
1322
|
}
|
|
1323
|
+
if (process.env.NW_MODEL_ATTRIBUTION_ENABLED !== void 0) {
|
|
1324
|
+
const v = parseBoolean(process.env.NW_MODEL_ATTRIBUTION_ENABLED);
|
|
1325
|
+
if (v !== null)
|
|
1326
|
+
env.modelAttribution = v;
|
|
1327
|
+
}
|
|
1328
|
+
if (process.env.NW_NEW_PR_LABEL !== void 0) {
|
|
1329
|
+
env.newPrLabel = process.env.NW_NEW_PR_LABEL.trim();
|
|
1330
|
+
}
|
|
1255
1331
|
if (process.env.NW_NOTIFICATIONS) {
|
|
1256
1332
|
try {
|
|
1257
1333
|
const parsed = JSON.parse(process.env.NW_NOTIFICATIONS);
|
|
@@ -1325,7 +1401,7 @@ function buildEnvOverrideConfig(fileConfig) {
|
|
|
1325
1401
|
env.claudeModel = model;
|
|
1326
1402
|
}
|
|
1327
1403
|
}
|
|
1328
|
-
for (const jobId of ["qa", "audit", "analytics", "manager"]) {
|
|
1404
|
+
for (const jobId of ["qa", "audit", "ux", "analytics", "manager"]) {
|
|
1329
1405
|
const jobDef = getJobDef(jobId);
|
|
1330
1406
|
if (!jobDef)
|
|
1331
1407
|
continue;
|
|
@@ -1438,6 +1514,8 @@ function getDefaultConfig() {
|
|
|
1438
1514
|
executorEnabled: DEFAULT_EXECUTOR_ENABLED,
|
|
1439
1515
|
reviewerEnabled: DEFAULT_REVIEWER_ENABLED,
|
|
1440
1516
|
providerEnv: { ...DEFAULT_PROVIDER_ENV },
|
|
1517
|
+
modelAttribution: DEFAULT_MODEL_ATTRIBUTION,
|
|
1518
|
+
newPrLabel: DEFAULT_NEW_PR_LABEL,
|
|
1441
1519
|
notifications: { ...DEFAULT_NOTIFICATIONS, webhooks: [] },
|
|
1442
1520
|
prdPriority: [...DEFAULT_PRD_PRIORITY],
|
|
1443
1521
|
roadmapScanner: { ...DEFAULT_ROADMAP_SCANNER },
|
|
@@ -1451,6 +1529,7 @@ function getDefaultConfig() {
|
|
|
1451
1529
|
claudeModel: DEFAULT_CLAUDE_MODEL,
|
|
1452
1530
|
qa: { ...DEFAULT_QA },
|
|
1453
1531
|
audit: { ...DEFAULT_AUDIT },
|
|
1532
|
+
ux: { ...DEFAULT_UX, flows: [...DEFAULT_UX.flows] },
|
|
1454
1533
|
analytics: { ...DEFAULT_ANALYTICS },
|
|
1455
1534
|
manager: { ...DEFAULT_MANAGER },
|
|
1456
1535
|
feedback: { ...DEFAULT_FEEDBACK },
|
|
@@ -1652,7 +1731,7 @@ function mergeConfigLayer(base, layer) {
|
|
|
1652
1731
|
})
|
|
1653
1732
|
}
|
|
1654
1733
|
};
|
|
1655
|
-
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "analytics" || _key === "manager" || _key === "feedback" || _key === "prResolver" || _key === "merger") {
|
|
1734
|
+
} else if (_key === "providerEnv" || _key === "boardProvider" || _key === "qa" || _key === "audit" || _key === "ux" || _key === "analytics" || _key === "manager" || _key === "feedback" || _key === "prResolver" || _key === "merger") {
|
|
1656
1735
|
base[_key] = {
|
|
1657
1736
|
...base[_key],
|
|
1658
1737
|
...value
|
|
@@ -4549,6 +4628,9 @@ function qaLockPath(projectDir) {
|
|
|
4549
4628
|
function auditLockPath(projectDir) {
|
|
4550
4629
|
return `${LOCK_FILE_PREFIX}audit-${projectRuntimeKey(projectDir)}.lock`;
|
|
4551
4630
|
}
|
|
4631
|
+
function uxLockPath(projectDir) {
|
|
4632
|
+
return `${LOCK_FILE_PREFIX}ux-${projectRuntimeKey(projectDir)}.lock`;
|
|
4633
|
+
}
|
|
4552
4634
|
function plannerLockPath(projectDir) {
|
|
4553
4635
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
4554
4636
|
}
|
|
@@ -4951,6 +5033,7 @@ function collectLogInfo(projectDir) {
|
|
|
4951
5033
|
{ name: "reviewer", fileName: "reviewer.log" },
|
|
4952
5034
|
{ name: "qa", fileName: `${QA_LOG_NAME}.log` },
|
|
4953
5035
|
{ name: "audit", fileName: `${AUDIT_LOG_NAME}.log` },
|
|
5036
|
+
{ name: "ux", fileName: `${UX_LOG_NAME}.log` },
|
|
4954
5037
|
{ name: "planner", fileName: `${PLANNER_LOG_NAME}.log` },
|
|
4955
5038
|
{ name: "analytics", fileName: `${ANALYTICS_LOG_NAME}.log` },
|
|
4956
5039
|
{ name: "pr-resolver", fileName: `${PR_RESOLVER_LOG_NAME}.log` },
|
|
@@ -4983,6 +5066,7 @@ async function fetchStatusSnapshot(projectDir, config) {
|
|
|
4983
5066
|
const reviewerLock = checkLockFile(reviewerLockPath(projectDir));
|
|
4984
5067
|
const qaLock = checkLockFile(qaLockPath(projectDir));
|
|
4985
5068
|
const auditLock = checkLockFile(auditLockPath(projectDir));
|
|
5069
|
+
const uxLock = checkLockFile(uxLockPath(projectDir));
|
|
4986
5070
|
const plannerLock = checkLockFile(plannerLockPath(projectDir));
|
|
4987
5071
|
const analyticsLock = checkLockFile(analyticsLockPath(projectDir));
|
|
4988
5072
|
const prResolverLock = checkLockFile(prResolverLockPath(projectDir));
|
|
@@ -4993,6 +5077,7 @@ async function fetchStatusSnapshot(projectDir, config) {
|
|
|
4993
5077
|
{ name: "reviewer", running: reviewerLock.running, pid: reviewerLock.pid },
|
|
4994
5078
|
{ name: "qa", running: qaLock.running, pid: qaLock.pid },
|
|
4995
5079
|
{ name: "audit", running: auditLock.running, pid: auditLock.pid },
|
|
5080
|
+
{ name: "ux", running: uxLock.running, pid: uxLock.pid },
|
|
4996
5081
|
{ name: "planner", running: plannerLock.running, pid: plannerLock.pid },
|
|
4997
5082
|
{ name: "analytics", running: analyticsLock.running, pid: analyticsLock.pid },
|
|
4998
5083
|
{ name: "pr-resolver", running: prResolverLock.running, pid: prResolverLock.pid },
|
|
@@ -7999,6 +8084,8 @@ function getLockPathForJob(projectPath, jobType) {
|
|
|
7999
8084
|
return qaLockPath(projectPath);
|
|
8000
8085
|
case "audit":
|
|
8001
8086
|
return auditLockPath(projectPath);
|
|
8087
|
+
case "ux":
|
|
8088
|
+
return uxLockPath(projectPath);
|
|
8002
8089
|
case "slicer":
|
|
8003
8090
|
case "planner":
|
|
8004
8091
|
return plannerLockPath(projectPath);
|
|
@@ -8866,6 +8953,351 @@ var init_analytics = __esm({
|
|
|
8866
8953
|
}
|
|
8867
8954
|
});
|
|
8868
8955
|
|
|
8956
|
+
// ../core/dist/telemetry/amplitude-ingest-client.js
|
|
8957
|
+
var AmplitudeIngestClient;
|
|
8958
|
+
var init_amplitude_ingest_client = __esm({
|
|
8959
|
+
"../core/dist/telemetry/amplitude-ingest-client.js"() {
|
|
8960
|
+
"use strict";
|
|
8961
|
+
init_constants();
|
|
8962
|
+
AmplitudeIngestClient = class {
|
|
8963
|
+
apiKey;
|
|
8964
|
+
endpoint;
|
|
8965
|
+
fetchImpl;
|
|
8966
|
+
timeoutMs;
|
|
8967
|
+
constructor(options) {
|
|
8968
|
+
this.apiKey = options.apiKey;
|
|
8969
|
+
this.endpoint = options.endpoint ?? DEFAULT_AMPLITUDE_INGEST_ENDPOINT;
|
|
8970
|
+
this.fetchImpl = options.fetchImpl ?? fetch;
|
|
8971
|
+
this.timeoutMs = options.timeoutMs ?? 1500;
|
|
8972
|
+
}
|
|
8973
|
+
async send(event) {
|
|
8974
|
+
const controller = new AbortController();
|
|
8975
|
+
const timeout = setTimeout(() => controller.abort(), this.timeoutMs);
|
|
8976
|
+
try {
|
|
8977
|
+
const response = await this.fetchImpl(this.endpoint, {
|
|
8978
|
+
method: "POST",
|
|
8979
|
+
headers: { "content-type": "application/json" },
|
|
8980
|
+
body: JSON.stringify({
|
|
8981
|
+
api_key: this.apiKey,
|
|
8982
|
+
events: [
|
|
8983
|
+
{
|
|
8984
|
+
device_id: event.installId,
|
|
8985
|
+
event_type: event.eventName,
|
|
8986
|
+
event_properties: event.properties,
|
|
8987
|
+
time: event.time ?? Date.now()
|
|
8988
|
+
}
|
|
8989
|
+
]
|
|
8990
|
+
}),
|
|
8991
|
+
signal: controller.signal
|
|
8992
|
+
});
|
|
8993
|
+
if (!response.ok) {
|
|
8994
|
+
throw new Error(`Amplitude ingestion failed with ${response.status}`);
|
|
8995
|
+
}
|
|
8996
|
+
} finally {
|
|
8997
|
+
clearTimeout(timeout);
|
|
8998
|
+
}
|
|
8999
|
+
}
|
|
9000
|
+
};
|
|
9001
|
+
}
|
|
9002
|
+
});
|
|
9003
|
+
|
|
9004
|
+
// ../core/dist/telemetry/config.js
|
|
9005
|
+
import * as crypto2 from "crypto";
|
|
9006
|
+
import * as fs23 from "fs";
|
|
9007
|
+
import * as os9 from "os";
|
|
9008
|
+
import * as path22 from "path";
|
|
9009
|
+
function getTelemetryConfigPath(env = process.env) {
|
|
9010
|
+
const base = env.NIGHT_WATCH_HOME || path22.join(os9.homedir(), GLOBAL_CONFIG_DIR);
|
|
9011
|
+
return path22.join(base, TELEMETRY_FILE_NAME);
|
|
9012
|
+
}
|
|
9013
|
+
function isTelemetryEnvDisabled(env = process.env) {
|
|
9014
|
+
if (env.NW_TELEMETRY_DISABLED === "1") {
|
|
9015
|
+
return "env:NW_TELEMETRY_DISABLED";
|
|
9016
|
+
}
|
|
9017
|
+
if (env.DO_NOT_TRACK === "1") {
|
|
9018
|
+
return "env:DO_NOT_TRACK";
|
|
9019
|
+
}
|
|
9020
|
+
return null;
|
|
9021
|
+
}
|
|
9022
|
+
function createTelemetryConfig(now) {
|
|
9023
|
+
const timestamp = now.toISOString();
|
|
9024
|
+
return {
|
|
9025
|
+
schemaVersion: 1,
|
|
9026
|
+
installId: crypto2.randomUUID(),
|
|
9027
|
+
enabled: true,
|
|
9028
|
+
createdAt: timestamp,
|
|
9029
|
+
updatedAt: timestamp
|
|
9030
|
+
};
|
|
9031
|
+
}
|
|
9032
|
+
function parseTelemetryConfig(raw) {
|
|
9033
|
+
const parsed = JSON.parse(raw);
|
|
9034
|
+
if (parsed.schemaVersion !== 1 || typeof parsed.installId !== "string" || parsed.installId.length === 0 || typeof parsed.enabled !== "boolean" || typeof parsed.createdAt !== "string" || typeof parsed.updatedAt !== "string") {
|
|
9035
|
+
return null;
|
|
9036
|
+
}
|
|
9037
|
+
return {
|
|
9038
|
+
schemaVersion: 1,
|
|
9039
|
+
installId: parsed.installId,
|
|
9040
|
+
enabled: parsed.enabled,
|
|
9041
|
+
noticeShownAt: typeof parsed.noticeShownAt === "string" && parsed.noticeShownAt.length > 0 ? parsed.noticeShownAt : void 0,
|
|
9042
|
+
createdAt: parsed.createdAt,
|
|
9043
|
+
updatedAt: parsed.updatedAt
|
|
9044
|
+
};
|
|
9045
|
+
}
|
|
9046
|
+
function saveTelemetryConfig(config, env = process.env) {
|
|
9047
|
+
const filePath = getTelemetryConfigPath(env);
|
|
9048
|
+
fs23.mkdirSync(path22.dirname(filePath), { recursive: true });
|
|
9049
|
+
fs23.writeFileSync(filePath, JSON.stringify(config, null, 2) + "\n", "utf-8");
|
|
9050
|
+
}
|
|
9051
|
+
function loadOrCreateTelemetryConfig(options = {}) {
|
|
9052
|
+
const env = options.env ?? process.env;
|
|
9053
|
+
const now = options.now?.() ?? /* @__PURE__ */ new Date();
|
|
9054
|
+
const filePath = getTelemetryConfigPath(env);
|
|
9055
|
+
try {
|
|
9056
|
+
if (fs23.existsSync(filePath)) {
|
|
9057
|
+
const parsed = parseTelemetryConfig(fs23.readFileSync(filePath, "utf-8"));
|
|
9058
|
+
if (parsed) {
|
|
9059
|
+
return parsed;
|
|
9060
|
+
}
|
|
9061
|
+
}
|
|
9062
|
+
} catch {
|
|
9063
|
+
}
|
|
9064
|
+
const fresh = createTelemetryConfig(now);
|
|
9065
|
+
try {
|
|
9066
|
+
saveTelemetryConfig(fresh, env);
|
|
9067
|
+
} catch {
|
|
9068
|
+
}
|
|
9069
|
+
return fresh;
|
|
9070
|
+
}
|
|
9071
|
+
function getTelemetryEffectiveState(options = {}) {
|
|
9072
|
+
const env = options.env ?? process.env;
|
|
9073
|
+
const config = loadOrCreateTelemetryConfig(options);
|
|
9074
|
+
const envDisabled = isTelemetryEnvDisabled(env);
|
|
9075
|
+
const pathValue = getTelemetryConfigPath(env);
|
|
9076
|
+
if (envDisabled) {
|
|
9077
|
+
return { config, enabled: false, path: pathValue, reason: envDisabled };
|
|
9078
|
+
}
|
|
9079
|
+
if (!config.enabled) {
|
|
9080
|
+
return { config, enabled: false, path: pathValue, reason: "config" };
|
|
9081
|
+
}
|
|
9082
|
+
if (options.apiKey !== void 0 && options.apiKey.trim().length === 0) {
|
|
9083
|
+
return { config, enabled: false, path: pathValue, reason: "missing-api-key" };
|
|
9084
|
+
}
|
|
9085
|
+
return { config, enabled: true, path: pathValue, reason: "config" };
|
|
9086
|
+
}
|
|
9087
|
+
function setTelemetryEnabled(enabled, options = {}) {
|
|
9088
|
+
const env = options.env ?? process.env;
|
|
9089
|
+
const config = loadOrCreateTelemetryConfig(options);
|
|
9090
|
+
const updated = {
|
|
9091
|
+
...config,
|
|
9092
|
+
enabled,
|
|
9093
|
+
updatedAt: (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString()
|
|
9094
|
+
};
|
|
9095
|
+
saveTelemetryConfig(updated, env);
|
|
9096
|
+
return updated;
|
|
9097
|
+
}
|
|
9098
|
+
function markTelemetryNoticeShown(options = {}) {
|
|
9099
|
+
const env = options.env ?? process.env;
|
|
9100
|
+
const config = loadOrCreateTelemetryConfig(options);
|
|
9101
|
+
const timestamp = (options.now?.() ?? /* @__PURE__ */ new Date()).toISOString();
|
|
9102
|
+
const updated = {
|
|
9103
|
+
...config,
|
|
9104
|
+
noticeShownAt: config.noticeShownAt ?? timestamp,
|
|
9105
|
+
updatedAt: timestamp
|
|
9106
|
+
};
|
|
9107
|
+
saveTelemetryConfig(updated, env);
|
|
9108
|
+
return updated;
|
|
9109
|
+
}
|
|
9110
|
+
var init_config2 = __esm({
|
|
9111
|
+
"../core/dist/telemetry/config.js"() {
|
|
9112
|
+
"use strict";
|
|
9113
|
+
init_constants();
|
|
9114
|
+
}
|
|
9115
|
+
});
|
|
9116
|
+
|
|
9117
|
+
// ../core/dist/telemetry/schema.js
|
|
9118
|
+
var TELEMETRY_EVENT_NAMES, TELEMETRY_ERROR_CATEGORIES;
|
|
9119
|
+
var init_schema = __esm({
|
|
9120
|
+
"../core/dist/telemetry/schema.js"() {
|
|
9121
|
+
"use strict";
|
|
9122
|
+
TELEMETRY_EVENT_NAMES = [
|
|
9123
|
+
"cli_first_run",
|
|
9124
|
+
"cli_init_completed",
|
|
9125
|
+
"command_started",
|
|
9126
|
+
"command_completed",
|
|
9127
|
+
"job_started",
|
|
9128
|
+
"job_completed",
|
|
9129
|
+
"job_failed",
|
|
9130
|
+
"pr_opened",
|
|
9131
|
+
"review_completed",
|
|
9132
|
+
"auto_merge_completed",
|
|
9133
|
+
"doctor_failed",
|
|
9134
|
+
"telemetry_enabled",
|
|
9135
|
+
"telemetry_disabled"
|
|
9136
|
+
];
|
|
9137
|
+
TELEMETRY_ERROR_CATEGORIES = [
|
|
9138
|
+
"config",
|
|
9139
|
+
"provider",
|
|
9140
|
+
"github",
|
|
9141
|
+
"network",
|
|
9142
|
+
"rate_limit",
|
|
9143
|
+
"timeout",
|
|
9144
|
+
"validation",
|
|
9145
|
+
"unknown"
|
|
9146
|
+
];
|
|
9147
|
+
}
|
|
9148
|
+
});
|
|
9149
|
+
|
|
9150
|
+
// ../core/dist/telemetry/sanitizer.js
|
|
9151
|
+
function isAllowedTelemetryEventName(eventName) {
|
|
9152
|
+
return TELEMETRY_EVENT_NAMES.includes(eventName);
|
|
9153
|
+
}
|
|
9154
|
+
function isSuspiciousString(value) {
|
|
9155
|
+
if (!SAFE_STRING_PATTERN.test(value)) {
|
|
9156
|
+
return true;
|
|
9157
|
+
}
|
|
9158
|
+
return value.includes("/") || value.includes("\\") || /^[a-z][a-z0-9+.-]*:/i.test(value);
|
|
9159
|
+
}
|
|
9160
|
+
function sanitizeString(value) {
|
|
9161
|
+
if (typeof value !== "string") {
|
|
9162
|
+
return void 0;
|
|
9163
|
+
}
|
|
9164
|
+
const trimmed = value.trim();
|
|
9165
|
+
if (trimmed.length === 0 || isSuspiciousString(trimmed)) {
|
|
9166
|
+
return void 0;
|
|
9167
|
+
}
|
|
9168
|
+
return trimmed;
|
|
9169
|
+
}
|
|
9170
|
+
function sanitizeBoolean(value) {
|
|
9171
|
+
return typeof value === "boolean" ? value : void 0;
|
|
9172
|
+
}
|
|
9173
|
+
function sanitizeInteger(value) {
|
|
9174
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
9175
|
+
return void 0;
|
|
9176
|
+
}
|
|
9177
|
+
return value;
|
|
9178
|
+
}
|
|
9179
|
+
function mapTelemetryErrorCategory(value) {
|
|
9180
|
+
if (typeof value === "string") {
|
|
9181
|
+
const normalized = value.toLowerCase();
|
|
9182
|
+
if (TELEMETRY_ERROR_CATEGORIES.includes(normalized)) {
|
|
9183
|
+
return normalized;
|
|
9184
|
+
}
|
|
9185
|
+
if (normalized.includes("timeout") || normalized.includes("timed out"))
|
|
9186
|
+
return "timeout";
|
|
9187
|
+
if (normalized.includes("rate"))
|
|
9188
|
+
return "rate_limit";
|
|
9189
|
+
if (normalized.includes("github") || normalized.includes("gh "))
|
|
9190
|
+
return "github";
|
|
9191
|
+
if (normalized.includes("network") || normalized.includes("fetch"))
|
|
9192
|
+
return "network";
|
|
9193
|
+
if (normalized.includes("provider") || normalized.includes("claude") || normalized.includes("codex")) {
|
|
9194
|
+
return "provider";
|
|
9195
|
+
}
|
|
9196
|
+
if (normalized.includes("config"))
|
|
9197
|
+
return "config";
|
|
9198
|
+
if (normalized.includes("valid"))
|
|
9199
|
+
return "validation";
|
|
9200
|
+
}
|
|
9201
|
+
return "unknown";
|
|
9202
|
+
}
|
|
9203
|
+
function sanitizeTelemetryEvent(eventName, properties = {}) {
|
|
9204
|
+
if (!isAllowedTelemetryEventName(eventName)) {
|
|
9205
|
+
return null;
|
|
9206
|
+
}
|
|
9207
|
+
const sanitized = {};
|
|
9208
|
+
for (const key of STRING_PROPERTIES) {
|
|
9209
|
+
const value = sanitizeString(properties[key]);
|
|
9210
|
+
if (value !== void 0) {
|
|
9211
|
+
sanitized[key] = value;
|
|
9212
|
+
}
|
|
9213
|
+
}
|
|
9214
|
+
for (const key of BOOLEAN_PROPERTIES) {
|
|
9215
|
+
const value = sanitizeBoolean(properties[key]);
|
|
9216
|
+
if (value !== void 0) {
|
|
9217
|
+
sanitized[key] = value;
|
|
9218
|
+
}
|
|
9219
|
+
}
|
|
9220
|
+
for (const key of INTEGER_PROPERTIES) {
|
|
9221
|
+
const value = sanitizeInteger(properties[key]);
|
|
9222
|
+
if (value !== void 0) {
|
|
9223
|
+
sanitized[key] = value;
|
|
9224
|
+
}
|
|
9225
|
+
}
|
|
9226
|
+
if (properties.errorCategory !== void 0) {
|
|
9227
|
+
sanitized.errorCategory = mapTelemetryErrorCategory(properties.errorCategory);
|
|
9228
|
+
}
|
|
9229
|
+
return { eventName, properties: sanitized };
|
|
9230
|
+
}
|
|
9231
|
+
var STRING_PROPERTIES, BOOLEAN_PROPERTIES, INTEGER_PROPERTIES, SAFE_STRING_PATTERN;
|
|
9232
|
+
var init_sanitizer = __esm({
|
|
9233
|
+
"../core/dist/telemetry/sanitizer.js"() {
|
|
9234
|
+
"use strict";
|
|
9235
|
+
init_schema();
|
|
9236
|
+
STRING_PROPERTIES = ["cliVersion", "command", "jobType", "provider", "platform"];
|
|
9237
|
+
BOOLEAN_PROPERTIES = ["success", "failure", "boardMode"];
|
|
9238
|
+
INTEGER_PROPERTIES = [
|
|
9239
|
+
"durationMs",
|
|
9240
|
+
"exitCode",
|
|
9241
|
+
"nodeMajorVersion",
|
|
9242
|
+
"registeredProjectCount"
|
|
9243
|
+
];
|
|
9244
|
+
SAFE_STRING_PATTERN = /^[a-zA-Z0-9_.:-]{1,80}$/;
|
|
9245
|
+
}
|
|
9246
|
+
});
|
|
9247
|
+
|
|
9248
|
+
// ../core/dist/telemetry/reporter.js
|
|
9249
|
+
function resolveAmplitudeApiKey(env, explicit) {
|
|
9250
|
+
return explicit ?? env.NW_AMPLITUDE_API_KEY ?? DEFAULT_AMPLITUDE_API_KEY;
|
|
9251
|
+
}
|
|
9252
|
+
async function trackTelemetryEvent(eventName, properties = {}, options = {}) {
|
|
9253
|
+
const env = options.env ?? process.env;
|
|
9254
|
+
const apiKey = resolveAmplitudeApiKey(env, options.apiKey);
|
|
9255
|
+
const state = getTelemetryEffectiveState({ env, apiKey, now: options.now });
|
|
9256
|
+
if (!state.enabled) {
|
|
9257
|
+
return { sent: false, reason: `disabled:${state.reason}` };
|
|
9258
|
+
}
|
|
9259
|
+
if (apiKey.trim().length === 0) {
|
|
9260
|
+
return { sent: false, reason: "missing-api-key" };
|
|
9261
|
+
}
|
|
9262
|
+
const sanitized = sanitizeTelemetryEvent(eventName, properties);
|
|
9263
|
+
if (!sanitized) {
|
|
9264
|
+
return { sent: false, reason: "dropped:event-name" };
|
|
9265
|
+
}
|
|
9266
|
+
try {
|
|
9267
|
+
const client = options.client ?? new AmplitudeIngestClient({ apiKey });
|
|
9268
|
+
await client.send({
|
|
9269
|
+
eventName: sanitized.eventName,
|
|
9270
|
+
installId: state.config.installId,
|
|
9271
|
+
properties: sanitized.properties,
|
|
9272
|
+
time: options.now?.().getTime()
|
|
9273
|
+
});
|
|
9274
|
+
return { sent: true };
|
|
9275
|
+
} catch {
|
|
9276
|
+
return { sent: false, reason: "network-error" };
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9279
|
+
var init_reporter = __esm({
|
|
9280
|
+
"../core/dist/telemetry/reporter.js"() {
|
|
9281
|
+
"use strict";
|
|
9282
|
+
init_constants();
|
|
9283
|
+
init_amplitude_ingest_client();
|
|
9284
|
+
init_config2();
|
|
9285
|
+
init_sanitizer();
|
|
9286
|
+
}
|
|
9287
|
+
});
|
|
9288
|
+
|
|
9289
|
+
// ../core/dist/telemetry/index.js
|
|
9290
|
+
var init_telemetry = __esm({
|
|
9291
|
+
"../core/dist/telemetry/index.js"() {
|
|
9292
|
+
"use strict";
|
|
9293
|
+
init_amplitude_ingest_client();
|
|
9294
|
+
init_config2();
|
|
9295
|
+
init_reporter();
|
|
9296
|
+
init_sanitizer();
|
|
9297
|
+
init_schema();
|
|
9298
|
+
}
|
|
9299
|
+
});
|
|
9300
|
+
|
|
8869
9301
|
// ../core/dist/audit/board-sync.js
|
|
8870
9302
|
function humanizeCategory(category) {
|
|
8871
9303
|
return category.trim().replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
@@ -9030,19 +9462,272 @@ var init_audit = __esm({
|
|
|
9030
9462
|
}
|
|
9031
9463
|
});
|
|
9032
9464
|
|
|
9465
|
+
// ../core/dist/ux/ux-runner.js
|
|
9466
|
+
import * as fs24 from "fs";
|
|
9467
|
+
import * as os10 from "os";
|
|
9468
|
+
import * as path23 from "path";
|
|
9469
|
+
function asString(value, fallback = "") {
|
|
9470
|
+
return typeof value === "string" ? value.trim() : fallback;
|
|
9471
|
+
}
|
|
9472
|
+
function asStringArray(value) {
|
|
9473
|
+
if (Array.isArray(value)) {
|
|
9474
|
+
return value.map((item) => asString(item)).filter(Boolean);
|
|
9475
|
+
}
|
|
9476
|
+
const single = asString(value);
|
|
9477
|
+
return single ? [single] : [];
|
|
9478
|
+
}
|
|
9479
|
+
function normalizeUxPriority(value) {
|
|
9480
|
+
const raw = asString(value).toUpperCase();
|
|
9481
|
+
if (raw === "P0" || raw === "CRITICAL" || raw === "BLOCKER")
|
|
9482
|
+
return "P0";
|
|
9483
|
+
if (raw === "P1" || raw === "HIGH" || raw === "MAJOR")
|
|
9484
|
+
return "P1";
|
|
9485
|
+
if (raw === "P2" || raw === "MEDIUM" || raw === "MODERATE")
|
|
9486
|
+
return "P2";
|
|
9487
|
+
return "P3";
|
|
9488
|
+
}
|
|
9489
|
+
function sortUxFindings(findings) {
|
|
9490
|
+
return [...findings].sort((left, right) => PRIORITY_ORDER.indexOf(left.priority) - PRIORITY_ORDER.indexOf(right.priority) || left.title.localeCompare(right.title));
|
|
9491
|
+
}
|
|
9492
|
+
function rawFindingToFinding(raw) {
|
|
9493
|
+
const title = asString(raw.title);
|
|
9494
|
+
if (!title)
|
|
9495
|
+
return null;
|
|
9496
|
+
return {
|
|
9497
|
+
title,
|
|
9498
|
+
priority: normalizeUxPriority(raw.priority ?? raw.severity),
|
|
9499
|
+
impact: asString(raw.impact, "Not specified"),
|
|
9500
|
+
affectedFlows: asStringArray(raw.affectedFlows ?? raw.affectedFlow ?? raw.flows),
|
|
9501
|
+
affectedPages: asStringArray(raw.affectedPages ?? raw.affectedPage ?? raw.pages),
|
|
9502
|
+
evidence: asStringArray(raw.evidence),
|
|
9503
|
+
reproductionSteps: asStringArray(raw.reproductionSteps ?? raw.steps),
|
|
9504
|
+
recommendedFix: asString(raw.recommendedFix ?? raw.recommendation ?? raw.fix, "Investigate and improve the affected user experience.")
|
|
9505
|
+
};
|
|
9506
|
+
}
|
|
9507
|
+
function extractJsonCandidate(text) {
|
|
9508
|
+
const trimmed = text.trim();
|
|
9509
|
+
try {
|
|
9510
|
+
return JSON.parse(trimmed);
|
|
9511
|
+
} catch {
|
|
9512
|
+
const arrayStart = text.indexOf("[");
|
|
9513
|
+
const arrayEnd = text.lastIndexOf("]");
|
|
9514
|
+
if (arrayStart !== -1 && arrayEnd > arrayStart) {
|
|
9515
|
+
return JSON.parse(text.slice(arrayStart, arrayEnd + 1));
|
|
9516
|
+
}
|
|
9517
|
+
const objectStart = text.indexOf("{");
|
|
9518
|
+
const objectEnd = text.lastIndexOf("}");
|
|
9519
|
+
if (objectStart !== -1 && objectEnd > objectStart) {
|
|
9520
|
+
return JSON.parse(text.slice(objectStart, objectEnd + 1));
|
|
9521
|
+
}
|
|
9522
|
+
throw new Error("No JSON object or array found in UX provider output");
|
|
9523
|
+
}
|
|
9524
|
+
}
|
|
9525
|
+
function parseUxFindings(text) {
|
|
9526
|
+
let parsed;
|
|
9527
|
+
try {
|
|
9528
|
+
parsed = extractJsonCandidate(text);
|
|
9529
|
+
} catch {
|
|
9530
|
+
logger6.warn("Failed to parse UX provider output as JSON");
|
|
9531
|
+
return [];
|
|
9532
|
+
}
|
|
9533
|
+
let rawItems = [];
|
|
9534
|
+
if (Array.isArray(parsed)) {
|
|
9535
|
+
rawItems = parsed;
|
|
9536
|
+
} else if (Array.isArray(parsed?.findings)) {
|
|
9537
|
+
rawItems = parsed.findings;
|
|
9538
|
+
}
|
|
9539
|
+
const findings = rawItems.filter((item) => item !== null && typeof item === "object").map(rawFindingToFinding).filter((item) => item !== null);
|
|
9540
|
+
return sortUxFindings(findings);
|
|
9541
|
+
}
|
|
9542
|
+
function formatList(items, fallback = "Not specified") {
|
|
9543
|
+
if (items.length === 0)
|
|
9544
|
+
return fallback;
|
|
9545
|
+
return items.map((item) => `- ${item}`).join("\n");
|
|
9546
|
+
}
|
|
9547
|
+
function buildUxReportBody(input) {
|
|
9548
|
+
const lines = [
|
|
9549
|
+
"# UX Report",
|
|
9550
|
+
"",
|
|
9551
|
+
`Generated by Night Watch UX agent on ${(/* @__PURE__ */ new Date()).toISOString()}.`,
|
|
9552
|
+
"",
|
|
9553
|
+
"## Scope",
|
|
9554
|
+
"",
|
|
9555
|
+
`- Base URL: ${input.baseUrl || "Not configured"}`,
|
|
9556
|
+
`- Start URL: ${input.startUrl || "Not configured"}`,
|
|
9557
|
+
"- Configured flows:",
|
|
9558
|
+
input.flows.length > 0 ? formatList(input.flows) : "- Not configured",
|
|
9559
|
+
"",
|
|
9560
|
+
"## Findings"
|
|
9561
|
+
];
|
|
9562
|
+
input.findings.forEach((finding, index) => {
|
|
9563
|
+
lines.push("", `### ${index + 1}. [${finding.priority}] ${finding.title}`, "", "**Impact**", "", finding.impact, "", "**Affected Flows**", "", formatList(finding.affectedFlows), "", "**Affected Pages**", "", formatList(finding.affectedPages), "", "**Evidence**", "", formatList(finding.evidence), "", "**Reproduction Steps**", "", formatList(finding.reproductionSteps), "", "**Recommended Fix**", "", finding.recommendedFix);
|
|
9564
|
+
});
|
|
9565
|
+
return lines.join("\n");
|
|
9566
|
+
}
|
|
9567
|
+
function buildUxAuditPrompt(config, projectDir) {
|
|
9568
|
+
const ux = config.ux;
|
|
9569
|
+
const configuredUrl = ux.startUrl || ux.baseUrl;
|
|
9570
|
+
const custom = ux.reportPrompt.trim();
|
|
9571
|
+
return [
|
|
9572
|
+
custom || "You are a senior UX reviewer creating an actionable UX report.",
|
|
9573
|
+
"",
|
|
9574
|
+
"Use Playwright/browser automation for the inspection. If Playwright is missing and auto-install is enabled, use a lightweight local check/install such as `npx playwright --version` and `npx playwright install chromium` as needed.",
|
|
9575
|
+
"Inspect the configured app, traverse the listed flows, capture screenshots or other evidence when useful, and focus on issues that materially affect user comprehension, completion, accessibility, responsiveness, or trust.",
|
|
9576
|
+
"",
|
|
9577
|
+
"Return only JSON. Use this exact shape:",
|
|
9578
|
+
'[{"title":"...","priority":"P0|P1|P2|P3","impact":"...","affectedFlows":["..."],"affectedPages":["..."],"evidence":["screenshot path or observation"],"reproductionSteps":["..."],"recommendedFix":"..."}]',
|
|
9579
|
+
"If no actionable UX issues are found, return [] only.",
|
|
9580
|
+
"",
|
|
9581
|
+
"Priority definitions:",
|
|
9582
|
+
"- P0: blocks core user flow or causes serious data/action risk",
|
|
9583
|
+
"- P1: major friction, broken responsive state, or accessibility failure in a key flow",
|
|
9584
|
+
"- P2: moderate confusion or inefficient interaction",
|
|
9585
|
+
"- P3: polish or minor usability concern",
|
|
9586
|
+
"",
|
|
9587
|
+
`Project directory: ${projectDir}`,
|
|
9588
|
+
`Base URL: ${ux.baseUrl || "not configured"}`,
|
|
9589
|
+
`Start URL: ${configuredUrl || "not configured"}`,
|
|
9590
|
+
`Auto-install Playwright: ${ux.autoInstallPlaywright ? "yes" : "no"}`,
|
|
9591
|
+
`Maximum findings: ${Math.max(1, ux.maxIssues)}`,
|
|
9592
|
+
"",
|
|
9593
|
+
"Configured flows:",
|
|
9594
|
+
ux.flows.length > 0 ? ux.flows.map((flow) => `- ${flow}`).join("\n") : "- Discover the primary user flows from the app/repo."
|
|
9595
|
+
].join("\n");
|
|
9596
|
+
}
|
|
9597
|
+
function shellQuote(value) {
|
|
9598
|
+
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
9599
|
+
}
|
|
9600
|
+
function buildPresetCommand(preset, promptFile, projectDir) {
|
|
9601
|
+
const args = [preset.command];
|
|
9602
|
+
if (preset.subcommand)
|
|
9603
|
+
args.push(preset.subcommand);
|
|
9604
|
+
if (preset.workdirFlag)
|
|
9605
|
+
args.push(preset.workdirFlag, projectDir);
|
|
9606
|
+
if (preset.modelFlag && preset.model)
|
|
9607
|
+
args.push(preset.modelFlag, preset.model);
|
|
9608
|
+
if (preset.autoApproveFlag)
|
|
9609
|
+
args.push(preset.autoApproveFlag);
|
|
9610
|
+
const quotedPrefix = args.map(shellQuote).join(" ");
|
|
9611
|
+
const promptArg = `"$(cat ${shellQuote(promptFile)})"`;
|
|
9612
|
+
if (preset.promptFlag) {
|
|
9613
|
+
return `${quotedPrefix} ${shellQuote(preset.promptFlag)} ${promptArg}`;
|
|
9614
|
+
}
|
|
9615
|
+
return `${quotedPrefix} ${promptArg}`;
|
|
9616
|
+
}
|
|
9617
|
+
async function invokeProvider(prompt, config, projectDir) {
|
|
9618
|
+
const tmpDir = fs24.mkdtempSync(path23.join(os10.tmpdir(), "nw-ux-"));
|
|
9619
|
+
const promptFile = path23.join(tmpDir, "ux-prompt.md");
|
|
9620
|
+
const scriptFile = path23.join(tmpDir, "run-ux.sh");
|
|
9621
|
+
try {
|
|
9622
|
+
fs24.writeFileSync(promptFile, prompt, "utf-8");
|
|
9623
|
+
const provider = resolveJobProvider(config, "ux");
|
|
9624
|
+
const preset = resolvePreset(config, provider);
|
|
9625
|
+
const command = buildPresetCommand(preset, promptFile, projectDir);
|
|
9626
|
+
fs24.writeFileSync(scriptFile, `#!/usr/bin/env bash
|
|
9627
|
+
set -euo pipefail
|
|
9628
|
+
${command} 2>&1
|
|
9629
|
+
`, {
|
|
9630
|
+
mode: 493
|
|
9631
|
+
});
|
|
9632
|
+
const env = { ...preset.envVars ?? {}, ...config.providerEnv ?? {} };
|
|
9633
|
+
const { exitCode, stdout, stderr } = await executeScriptWithOutput(scriptFile, [], env, {
|
|
9634
|
+
cwd: projectDir
|
|
9635
|
+
});
|
|
9636
|
+
if (exitCode !== 0) {
|
|
9637
|
+
throw new Error(`UX provider exited with code ${exitCode}: ${stderr || stdout}`);
|
|
9638
|
+
}
|
|
9639
|
+
return `${stdout}
|
|
9640
|
+
${stderr}`;
|
|
9641
|
+
} finally {
|
|
9642
|
+
try {
|
|
9643
|
+
fs24.rmSync(tmpDir, { recursive: true, force: true });
|
|
9644
|
+
} catch {
|
|
9645
|
+
}
|
|
9646
|
+
}
|
|
9647
|
+
}
|
|
9648
|
+
async function resolveBoardProvider(config, projectDir, injected) {
|
|
9649
|
+
if (injected !== void 0)
|
|
9650
|
+
return injected;
|
|
9651
|
+
if (!config.boardProvider?.enabled)
|
|
9652
|
+
return null;
|
|
9653
|
+
return createBoardProvider(config.boardProvider, projectDir);
|
|
9654
|
+
}
|
|
9655
|
+
async function runUx(config, projectDir, options = {}) {
|
|
9656
|
+
const prompt = buildUxAuditPrompt(config, projectDir);
|
|
9657
|
+
const providerOutput = options.providerOutput ?? await (options.providerInvoker ?? invokeProvider)(prompt, config, projectDir);
|
|
9658
|
+
const findings = parseUxFindings(providerOutput).slice(0, Math.max(1, config.ux.maxIssues));
|
|
9659
|
+
if (findings.length === 0) {
|
|
9660
|
+
return { findings, issuesCreated: 0, summary: "No actionable UX issues found" };
|
|
9661
|
+
}
|
|
9662
|
+
const body = buildUxReportBody({
|
|
9663
|
+
findings,
|
|
9664
|
+
baseUrl: config.ux.baseUrl,
|
|
9665
|
+
startUrl: config.ux.startUrl,
|
|
9666
|
+
flows: config.ux.flows
|
|
9667
|
+
});
|
|
9668
|
+
if (options.dryRun) {
|
|
9669
|
+
return {
|
|
9670
|
+
findings,
|
|
9671
|
+
issuesCreated: 0,
|
|
9672
|
+
summary: `Dry run found ${findings.length} UX finding(s); no board report created`
|
|
9673
|
+
};
|
|
9674
|
+
}
|
|
9675
|
+
const boardProvider = await resolveBoardProvider(config, projectDir, options.boardProvider);
|
|
9676
|
+
if (!boardProvider) {
|
|
9677
|
+
return {
|
|
9678
|
+
findings,
|
|
9679
|
+
issuesCreated: 0,
|
|
9680
|
+
summary: `Found ${findings.length} UX finding(s); board provider is disabled`
|
|
9681
|
+
};
|
|
9682
|
+
}
|
|
9683
|
+
const targetColumn = config.ux.targetColumn;
|
|
9684
|
+
const issue = await boardProvider.createIssue({
|
|
9685
|
+
title: `UX Report: ${findings.length} prioritized finding(s)`,
|
|
9686
|
+
body,
|
|
9687
|
+
column: targetColumn,
|
|
9688
|
+
labels: ["ux", "night-watch"]
|
|
9689
|
+
});
|
|
9690
|
+
return {
|
|
9691
|
+
findings,
|
|
9692
|
+
issuesCreated: 1,
|
|
9693
|
+
reportUrl: issue.url,
|
|
9694
|
+
summary: `Created UX report with ${findings.length} finding(s) in ${targetColumn}`
|
|
9695
|
+
};
|
|
9696
|
+
}
|
|
9697
|
+
var logger6, PRIORITY_ORDER;
|
|
9698
|
+
var init_ux_runner = __esm({
|
|
9699
|
+
"../core/dist/ux/ux-runner.js"() {
|
|
9700
|
+
"use strict";
|
|
9701
|
+
init_factory();
|
|
9702
|
+
init_config();
|
|
9703
|
+
init_shell();
|
|
9704
|
+
init_logger();
|
|
9705
|
+
logger6 = createLogger("ux");
|
|
9706
|
+
PRIORITY_ORDER = ["P0", "P1", "P2", "P3"];
|
|
9707
|
+
}
|
|
9708
|
+
});
|
|
9709
|
+
|
|
9710
|
+
// ../core/dist/ux/index.js
|
|
9711
|
+
var init_ux = __esm({
|
|
9712
|
+
"../core/dist/ux/index.js"() {
|
|
9713
|
+
"use strict";
|
|
9714
|
+
init_ux_runner();
|
|
9715
|
+
}
|
|
9716
|
+
});
|
|
9717
|
+
|
|
9033
9718
|
// ../core/dist/manager/manager-memory.js
|
|
9034
|
-
import * as
|
|
9035
|
-
import * as
|
|
9719
|
+
import * as fs25 from "fs";
|
|
9720
|
+
import * as path24 from "path";
|
|
9036
9721
|
import { createHash as createHash4 } from "crypto";
|
|
9037
9722
|
function createFindingFingerprint(parts) {
|
|
9038
9723
|
const normalized = parts.map((part) => part.toLowerCase().trim().replace(/\s+/g, " ")).join("|");
|
|
9039
9724
|
return createHash4("sha256").update(normalized).digest("hex").slice(0, 16);
|
|
9040
9725
|
}
|
|
9041
9726
|
function loadManagerMemory(memoryPath) {
|
|
9042
|
-
if (!
|
|
9727
|
+
if (!fs25.existsSync(memoryPath)) {
|
|
9043
9728
|
return { fingerprints: /* @__PURE__ */ new Set(), lastWeeklySummaryAt: null, raw: "" };
|
|
9044
9729
|
}
|
|
9045
|
-
const raw =
|
|
9730
|
+
const raw = fs25.readFileSync(memoryPath, "utf-8");
|
|
9046
9731
|
const fingerprints = /* @__PURE__ */ new Set();
|
|
9047
9732
|
let match;
|
|
9048
9733
|
while ((match = FINGERPRINT_PATTERN.exec(raw)) !== null) {
|
|
@@ -9100,8 +9785,8 @@ function renderManagerMemory(result, previous) {
|
|
|
9100
9785
|
`;
|
|
9101
9786
|
}
|
|
9102
9787
|
function writeManagerMemory(memoryPath, result, previous) {
|
|
9103
|
-
|
|
9104
|
-
|
|
9788
|
+
fs25.mkdirSync(path24.dirname(memoryPath), { recursive: true });
|
|
9789
|
+
fs25.writeFileSync(memoryPath, renderManagerMemory(result, previous), "utf-8");
|
|
9105
9790
|
}
|
|
9106
9791
|
function renderFinding(finding) {
|
|
9107
9792
|
return [
|
|
@@ -9149,13 +9834,13 @@ var init_manager_memory = __esm({
|
|
|
9149
9834
|
});
|
|
9150
9835
|
|
|
9151
9836
|
// ../core/dist/manager/manager-analysis.js
|
|
9152
|
-
import * as
|
|
9153
|
-
import * as
|
|
9837
|
+
import * as fs26 from "fs";
|
|
9838
|
+
import * as path25 from "path";
|
|
9154
9839
|
function analyzeManagerInputs(context) {
|
|
9155
|
-
const roadmapPath =
|
|
9156
|
-
const roadmapContent =
|
|
9840
|
+
const roadmapPath = path25.resolve(context.projectDir, context.config.roadmapScanner?.roadmapPath || "ROADMAP.md");
|
|
9841
|
+
const roadmapContent = fs26.existsSync(roadmapPath) ? fs26.readFileSync(roadmapPath, "utf-8") : "";
|
|
9157
9842
|
const roadmapItems = roadmapContent ? parseRoadmap(roadmapContent) : [];
|
|
9158
|
-
const prds = collectPrdFiles(
|
|
9843
|
+
const prds = collectPrdFiles(path25.resolve(context.projectDir, context.config.prdDir || "docs/prds"));
|
|
9159
9844
|
const findings = [];
|
|
9160
9845
|
const searchableWork = buildSearchableWork(context.boardIssues.map((issue) => issue.title), prds);
|
|
9161
9846
|
for (const item of roadmapItems.filter((roadmapItem) => !roadmapItem.checked)) {
|
|
@@ -9203,7 +9888,7 @@ function analyzeManagerInputs(context) {
|
|
|
9203
9888
|
labels: ["manager", "queue", "blocked"]
|
|
9204
9889
|
});
|
|
9205
9890
|
}
|
|
9206
|
-
if (!
|
|
9891
|
+
if (!fs26.existsSync(path25.join(context.managerConfig.docsDirectory, "overview.md"))) {
|
|
9207
9892
|
const fingerprint = createFindingFingerprint(["missing_manager_doc", context.managerConfig.docsDirectory]);
|
|
9208
9893
|
findings.push({
|
|
9209
9894
|
kind: "missing_manager_doc",
|
|
@@ -9219,19 +9904,19 @@ function analyzeManagerInputs(context) {
|
|
|
9219
9904
|
return { findings, roadmapItems: roadmapItems.length, prds };
|
|
9220
9905
|
}
|
|
9221
9906
|
function collectPrdFiles(prdDir) {
|
|
9222
|
-
if (!
|
|
9907
|
+
if (!fs26.existsSync(prdDir))
|
|
9223
9908
|
return [];
|
|
9224
9909
|
const files = [];
|
|
9225
9910
|
const visit = (dir) => {
|
|
9226
|
-
for (const entry of
|
|
9227
|
-
const fullPath =
|
|
9911
|
+
for (const entry of fs26.readdirSync(dir, { withFileTypes: true })) {
|
|
9912
|
+
const fullPath = path25.join(dir, entry.name);
|
|
9228
9913
|
if (entry.isDirectory()) {
|
|
9229
9914
|
visit(fullPath);
|
|
9230
9915
|
} else if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
9231
9916
|
files.push({
|
|
9232
9917
|
name: entry.name.replace(/\.md$/, ""),
|
|
9233
9918
|
path: fullPath,
|
|
9234
|
-
content:
|
|
9919
|
+
content: fs26.readFileSync(fullPath, "utf-8")
|
|
9235
9920
|
});
|
|
9236
9921
|
}
|
|
9237
9922
|
}
|
|
@@ -9406,14 +10091,14 @@ var init_manager_notifications = __esm({
|
|
|
9406
10091
|
});
|
|
9407
10092
|
|
|
9408
10093
|
// ../core/dist/manager/manager-runner.js
|
|
9409
|
-
import * as
|
|
9410
|
-
import * as
|
|
10094
|
+
import * as fs27 from "fs";
|
|
10095
|
+
import * as path26 from "path";
|
|
9411
10096
|
async function runManager(projectDir, config, options = {}) {
|
|
9412
10097
|
const dryRun = options.dryRun ?? false;
|
|
9413
10098
|
const now = options.now ?? /* @__PURE__ */ new Date();
|
|
9414
10099
|
const managerConfig = resolveManagerConfig(config, projectDir);
|
|
9415
10100
|
const memory = loadManagerMemory(managerConfig.memoryFile);
|
|
9416
|
-
const boardProvider = await
|
|
10101
|
+
const boardProvider = await resolveBoardProvider2(config, projectDir, options.boardProvider);
|
|
9417
10102
|
const boardIssues = await readBoardIssues(boardProvider);
|
|
9418
10103
|
const queueStatus = resolveQueueStatus(options.queueStatus);
|
|
9419
10104
|
const statusSnapshot = await resolveStatusSnapshot(projectDir, config, options.statusSnapshot);
|
|
@@ -9490,11 +10175,11 @@ function resolveManagerConfig(config, projectDir) {
|
|
|
9490
10175
|
return {
|
|
9491
10176
|
...merged,
|
|
9492
10177
|
weeklySummaryDay,
|
|
9493
|
-
memoryFile:
|
|
9494
|
-
docsDirectory:
|
|
10178
|
+
memoryFile: path26.resolve(projectDir, merged.memoryPath),
|
|
10179
|
+
docsDirectory: path26.resolve(projectDir, merged.docsDir)
|
|
9495
10180
|
};
|
|
9496
10181
|
}
|
|
9497
|
-
async function
|
|
10182
|
+
async function resolveBoardProvider2(config, projectDir, injected) {
|
|
9498
10183
|
if (injected !== void 0) {
|
|
9499
10184
|
return injected;
|
|
9500
10185
|
}
|
|
@@ -9533,13 +10218,13 @@ async function resolveStatusSnapshot(projectDir, config, injected) {
|
|
|
9533
10218
|
}
|
|
9534
10219
|
}
|
|
9535
10220
|
function writeManagerDocs(docsDir, result, now) {
|
|
9536
|
-
const overviewPath =
|
|
9537
|
-
const relative3 =
|
|
9538
|
-
if (relative3.startsWith("..") ||
|
|
10221
|
+
const overviewPath = path26.resolve(docsDir, "overview.md");
|
|
10222
|
+
const relative3 = path26.relative(docsDir, overviewPath);
|
|
10223
|
+
if (relative3.startsWith("..") || path26.isAbsolute(relative3)) {
|
|
9539
10224
|
throw new Error(`Refusing to write Manager docs outside docsDir: ${overviewPath}`);
|
|
9540
10225
|
}
|
|
9541
|
-
|
|
9542
|
-
|
|
10226
|
+
fs27.mkdirSync(docsDir, { recursive: true });
|
|
10227
|
+
fs27.writeFileSync(overviewPath, [
|
|
9543
10228
|
"# Manager Overview",
|
|
9544
10229
|
"",
|
|
9545
10230
|
`Generated: ${now.toISOString()}`,
|
|
@@ -10411,6 +11096,7 @@ var dist_exports = {};
|
|
|
10411
11096
|
__export(dist_exports, {
|
|
10412
11097
|
ANALYTICS_LOG_NAME: () => ANALYTICS_LOG_NAME,
|
|
10413
11098
|
AUDIT_LOG_NAME: () => AUDIT_LOG_NAME,
|
|
11099
|
+
AmplitudeIngestClient: () => AmplitudeIngestClient,
|
|
10414
11100
|
BOARD_COLUMNS: () => BOARD_COLUMNS,
|
|
10415
11101
|
BUILT_IN_PRESETS: () => BUILT_IN_PRESETS,
|
|
10416
11102
|
BUILT_IN_PRESET_IDS: () => BUILT_IN_PRESET_IDS,
|
|
@@ -10421,6 +11107,8 @@ __export(dist_exports, {
|
|
|
10421
11107
|
CONFIG_FILE_NAME: () => CONFIG_FILE_NAME,
|
|
10422
11108
|
CRONTAB_MARKER_PREFIX: () => CRONTAB_MARKER_PREFIX,
|
|
10423
11109
|
DATABASE_TOKEN: () => DATABASE_TOKEN,
|
|
11110
|
+
DEFAULT_AMPLITUDE_API_KEY: () => DEFAULT_AMPLITUDE_API_KEY,
|
|
11111
|
+
DEFAULT_AMPLITUDE_INGEST_ENDPOINT: () => DEFAULT_AMPLITUDE_INGEST_ENDPOINT,
|
|
10424
11112
|
DEFAULT_ANALYTICS: () => DEFAULT_ANALYTICS,
|
|
10425
11113
|
DEFAULT_ANALYTICS_ENABLED: () => DEFAULT_ANALYTICS_ENABLED,
|
|
10426
11114
|
DEFAULT_ANALYTICS_LOOKBACK_DAYS: () => DEFAULT_ANALYTICS_LOOKBACK_DAYS,
|
|
@@ -10473,6 +11161,8 @@ __export(dist_exports, {
|
|
|
10473
11161
|
DEFAULT_MERGER_REBASE_BEFORE_MERGE: () => DEFAULT_MERGER_REBASE_BEFORE_MERGE,
|
|
10474
11162
|
DEFAULT_MERGER_SCHEDULE: () => DEFAULT_MERGER_SCHEDULE,
|
|
10475
11163
|
DEFAULT_MIN_REVIEW_SCORE: () => DEFAULT_MIN_REVIEW_SCORE,
|
|
11164
|
+
DEFAULT_MODEL_ATTRIBUTION: () => DEFAULT_MODEL_ATTRIBUTION,
|
|
11165
|
+
DEFAULT_NEW_PR_LABEL: () => DEFAULT_NEW_PR_LABEL,
|
|
10476
11166
|
DEFAULT_NOTIFICATIONS: () => DEFAULT_NOTIFICATIONS,
|
|
10477
11167
|
DEFAULT_PRD_DIR: () => DEFAULT_PRD_DIR,
|
|
10478
11168
|
DEFAULT_PRD_PRIORITY: () => DEFAULT_PRD_PRIORITY,
|
|
@@ -10516,6 +11206,16 @@ __export(dist_exports, {
|
|
|
10516
11206
|
DEFAULT_SLICER_SCHEDULE: () => DEFAULT_SLICER_SCHEDULE,
|
|
10517
11207
|
DEFAULT_SUMMARY_WINDOW_HOURS: () => DEFAULT_SUMMARY_WINDOW_HOURS,
|
|
10518
11208
|
DEFAULT_TEMPLATES_DIR: () => DEFAULT_TEMPLATES_DIR,
|
|
11209
|
+
DEFAULT_UX: () => DEFAULT_UX,
|
|
11210
|
+
DEFAULT_UX_AUTO_INSTALL_PLAYWRIGHT: () => DEFAULT_UX_AUTO_INSTALL_PLAYWRIGHT,
|
|
11211
|
+
DEFAULT_UX_BASE_URL: () => DEFAULT_UX_BASE_URL,
|
|
11212
|
+
DEFAULT_UX_ENABLED: () => DEFAULT_UX_ENABLED,
|
|
11213
|
+
DEFAULT_UX_MAX_ISSUES: () => DEFAULT_UX_MAX_ISSUES,
|
|
11214
|
+
DEFAULT_UX_MAX_RUNTIME: () => DEFAULT_UX_MAX_RUNTIME,
|
|
11215
|
+
DEFAULT_UX_REPORT_PROMPT: () => DEFAULT_UX_REPORT_PROMPT,
|
|
11216
|
+
DEFAULT_UX_SCHEDULE: () => DEFAULT_UX_SCHEDULE,
|
|
11217
|
+
DEFAULT_UX_START_URL: () => DEFAULT_UX_START_URL,
|
|
11218
|
+
DEFAULT_UX_TARGET_COLUMN: () => DEFAULT_UX_TARGET_COLUMN,
|
|
10519
11219
|
DEFAULT_WEBHOOK_TRIGGERS: () => DEFAULT_WEBHOOK_TRIGGERS,
|
|
10520
11220
|
DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS: () => DEFAULT_WEBHOOK_TRIGGER_MAX_SKEW_SECONDS,
|
|
10521
11221
|
DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV: () => DEFAULT_WEBHOOK_TRIGGER_SECRET_ENV,
|
|
@@ -10556,6 +11256,10 @@ __export(dist_exports, {
|
|
|
10556
11256
|
STATE_DB_FILE_NAME: () => STATE_DB_FILE_NAME,
|
|
10557
11257
|
SqliteKanbanIssueRepository: () => SqliteKanbanIssueRepository,
|
|
10558
11258
|
SqliteSessionOutcomeRepository: () => SqliteSessionOutcomeRepository,
|
|
11259
|
+
TELEMETRY_ERROR_CATEGORIES: () => TELEMETRY_ERROR_CATEGORIES,
|
|
11260
|
+
TELEMETRY_EVENT_NAMES: () => TELEMETRY_EVENT_NAMES,
|
|
11261
|
+
TELEMETRY_FILE_NAME: () => TELEMETRY_FILE_NAME,
|
|
11262
|
+
UX_LOG_NAME: () => UX_LOG_NAME,
|
|
10559
11263
|
VALID_CLAUDE_MODELS: () => VALID_CLAUDE_MODELS,
|
|
10560
11264
|
VALID_JOB_TYPES: () => VALID_JOB_TYPES,
|
|
10561
11265
|
VALID_MERGE_METHODS: () => VALID_MERGE_METHODS,
|
|
@@ -10573,6 +11277,8 @@ __export(dist_exports, {
|
|
|
10573
11277
|
buildManagerDraftTitle: () => buildManagerDraftTitle,
|
|
10574
11278
|
buildProjectFeedbackPromptBlock: () => buildProjectFeedbackPromptBlock,
|
|
10575
11279
|
buildSessionOutcomeInput: () => buildSessionOutcomeInput,
|
|
11280
|
+
buildUxAuditPrompt: () => buildUxAuditPrompt,
|
|
11281
|
+
buildUxReportBody: () => buildUxReportBody,
|
|
10576
11282
|
calculateStringSimilarity: () => calculateStringSimilarity,
|
|
10577
11283
|
camelToUpperSnake: () => camelToUpperSnake,
|
|
10578
11284
|
canStartJob: () => canStartJob,
|
|
@@ -10688,6 +11394,8 @@ __export(dist_exports, {
|
|
|
10688
11394
|
getStateFilePath: () => getStateFilePath,
|
|
10689
11395
|
getStateItem: () => getStateItem,
|
|
10690
11396
|
getSummaryData: () => getSummaryData,
|
|
11397
|
+
getTelemetryConfigPath: () => getTelemetryConfigPath,
|
|
11398
|
+
getTelemetryEffectiveState: () => getTelemetryEffectiveState,
|
|
10691
11399
|
getUncheckedItems: () => getUncheckedItems,
|
|
10692
11400
|
getValidJobTypes: () => getValidJobTypes,
|
|
10693
11401
|
groupBySection: () => groupBySection2,
|
|
@@ -10696,6 +11404,7 @@ __export(dist_exports, {
|
|
|
10696
11404
|
header: () => header,
|
|
10697
11405
|
info: () => info,
|
|
10698
11406
|
initContainer: () => initContainer,
|
|
11407
|
+
isAllowedTelemetryEventName: () => isAllowedTelemetryEventName,
|
|
10699
11408
|
isContainerInitialized: () => isContainerInitialized,
|
|
10700
11409
|
isFeedbackPromptEnabled: () => isFeedbackPromptEnabled,
|
|
10701
11410
|
isInCooldown: () => isInCooldown,
|
|
@@ -10704,6 +11413,7 @@ __export(dist_exports, {
|
|
|
10704
11413
|
isKnownFinding: () => isKnownFinding,
|
|
10705
11414
|
isProcessAliveSince: () => isProcessAliveSince,
|
|
10706
11415
|
isProcessRunning: () => isProcessRunning,
|
|
11416
|
+
isTelemetryEnvDisabled: () => isTelemetryEnvDisabled,
|
|
10707
11417
|
isValidCategory: () => isValidCategory,
|
|
10708
11418
|
isValidHorizon: () => isValidHorizon,
|
|
10709
11419
|
isValidPriority: () => isValidPriority,
|
|
@@ -10715,24 +11425,29 @@ __export(dist_exports, {
|
|
|
10715
11425
|
loadGlobalNotificationsConfig: () => loadGlobalNotificationsConfig,
|
|
10716
11426
|
loadHistory: () => loadHistory,
|
|
10717
11427
|
loadManagerMemory: () => loadManagerMemory,
|
|
11428
|
+
loadOrCreateTelemetryConfig: () => loadOrCreateTelemetryConfig,
|
|
10718
11429
|
loadRegistry: () => loadRegistry,
|
|
10719
11430
|
loadRoadmapState: () => loadRoadmapState,
|
|
10720
11431
|
loadSlicerTemplate: () => loadSlicerTemplate,
|
|
10721
11432
|
managerLockPath: () => managerLockPath,
|
|
11433
|
+
mapTelemetryErrorCategory: () => mapTelemetryErrorCategory,
|
|
10722
11434
|
markItemProcessed: () => markItemProcessed,
|
|
10723
11435
|
markJobRunning: () => markJobRunning,
|
|
10724
11436
|
markPrdDone: () => markPrdDone,
|
|
11437
|
+
markTelemetryNoticeShown: () => markTelemetryNoticeShown,
|
|
10725
11438
|
mergerLockPath: () => mergerLockPath,
|
|
10726
11439
|
migrateJsonToSqlite: () => migrateJsonToSqlite,
|
|
10727
11440
|
normalizeAuditSeverity: () => normalizeAuditSeverity,
|
|
10728
11441
|
normalizeJobConfig: () => normalizeJobConfig,
|
|
10729
11442
|
normalizeSchedulingPriority: () => normalizeSchedulingPriority,
|
|
11443
|
+
normalizeUxPriority: () => normalizeUxPriority,
|
|
10730
11444
|
parseAuditFindings: () => parseAuditFindings,
|
|
10731
11445
|
parseConfigValue: () => parseConfigValue,
|
|
10732
11446
|
parsePrdDependencies: () => parsePrdDependencies,
|
|
10733
11447
|
parseRoadmap: () => parseRoadmap,
|
|
10734
11448
|
parseScriptResult: () => parseScriptResult,
|
|
10735
11449
|
parseTimeToMinutes: () => parseTimeToMinutes,
|
|
11450
|
+
parseUxFindings: () => parseUxFindings,
|
|
10736
11451
|
performCancel: () => performCancel,
|
|
10737
11452
|
plannerLockPath: () => plannerLockPath,
|
|
10738
11453
|
prResolverLockPath: () => prResolverLockPath,
|
|
@@ -10770,6 +11485,8 @@ __export(dist_exports, {
|
|
|
10770
11485
|
runAnalytics: () => runAnalytics,
|
|
10771
11486
|
runManager: () => runManager,
|
|
10772
11487
|
runMigrations: () => runMigrations,
|
|
11488
|
+
runUx: () => runUx,
|
|
11489
|
+
sanitizeTelemetryEvent: () => sanitizeTelemetryEvent,
|
|
10773
11490
|
saveConfig: () => saveConfig,
|
|
10774
11491
|
saveGlobalNotificationsConfig: () => saveGlobalNotificationsConfig,
|
|
10775
11492
|
saveHistory: () => saveHistory,
|
|
@@ -10780,19 +11497,23 @@ __export(dist_exports, {
|
|
|
10780
11497
|
sendNotifications: () => sendNotifications,
|
|
10781
11498
|
sendWebhook: () => sendWebhook,
|
|
10782
11499
|
setConfigValue: () => setConfigValue,
|
|
11500
|
+
setTelemetryEnabled: () => setTelemetryEnabled,
|
|
10783
11501
|
sleep: () => sleep,
|
|
10784
11502
|
sliceNextItem: () => sliceNextItem,
|
|
10785
11503
|
sliceRoadmapItem: () => sliceRoadmapItem,
|
|
10786
11504
|
slugify: () => slugify,
|
|
10787
11505
|
sortByPriority: () => sortByPriority,
|
|
11506
|
+
sortUxFindings: () => sortUxFindings,
|
|
10788
11507
|
step: () => step,
|
|
10789
11508
|
success: () => success,
|
|
10790
11509
|
summarizeCreatedDrafts: () => summarizeCreatedDrafts,
|
|
10791
11510
|
summarizeSkippedFindings: () => summarizeSkippedFindings,
|
|
10792
11511
|
syncAuditFindingsToBoard: () => syncAuditFindingsToBoard,
|
|
11512
|
+
trackTelemetryEvent: () => trackTelemetryEvent,
|
|
10793
11513
|
unmarkItemProcessed: () => unmarkItemProcessed,
|
|
10794
11514
|
unregisterProject: () => unregisterProject,
|
|
10795
11515
|
updateJobStatus: () => updateJobStatus,
|
|
11516
|
+
uxLockPath: () => uxLockPath,
|
|
10796
11517
|
validateProvider: () => validateProvider,
|
|
10797
11518
|
validateRegistry: () => validateRegistry,
|
|
10798
11519
|
validateWebhook: () => validateWebhook,
|
|
@@ -10848,7 +11569,9 @@ var init_dist = __esm({
|
|
|
10848
11569
|
init_job_queue();
|
|
10849
11570
|
init_summary();
|
|
10850
11571
|
init_analytics();
|
|
11572
|
+
init_telemetry();
|
|
10851
11573
|
init_audit();
|
|
11574
|
+
init_ux();
|
|
10852
11575
|
init_manager();
|
|
10853
11576
|
init_outcome_parser();
|
|
10854
11577
|
init_pattern_analyzer();
|
|
@@ -10862,30 +11585,99 @@ var init_dist = __esm({
|
|
|
10862
11585
|
// src/cli.ts
|
|
10863
11586
|
import "reflect-metadata";
|
|
10864
11587
|
import { Command as Command3 } from "commander";
|
|
10865
|
-
import { existsSync as
|
|
11588
|
+
import { existsSync as existsSync37, readFileSync as readFileSync24 } from "fs";
|
|
10866
11589
|
import { fileURLToPath as fileURLToPath6 } from "url";
|
|
10867
|
-
import { dirname as
|
|
11590
|
+
import { dirname as dirname14, join as join41 } from "path";
|
|
10868
11591
|
|
|
10869
11592
|
// src/commands/init.ts
|
|
10870
11593
|
init_dist();
|
|
10871
|
-
import
|
|
10872
|
-
import
|
|
11594
|
+
import fs28 from "fs";
|
|
11595
|
+
import path27 from "path";
|
|
10873
11596
|
import { execSync as execSync3 } from "child_process";
|
|
10874
11597
|
import { fileURLToPath as fileURLToPath3 } from "url";
|
|
10875
|
-
import { dirname as
|
|
11598
|
+
import { dirname as dirname8, join as join23 } from "path";
|
|
10876
11599
|
import * as readline from "readline";
|
|
11600
|
+
|
|
11601
|
+
// src/commands/shared/telemetry.ts
|
|
11602
|
+
init_dist();
|
|
11603
|
+
var cliVersion = "unknown";
|
|
11604
|
+
var telemetryReporter = (eventName, properties) => trackTelemetryEvent(eventName, properties);
|
|
11605
|
+
function setCliTelemetryVersion(version) {
|
|
11606
|
+
cliVersion = version;
|
|
11607
|
+
}
|
|
11608
|
+
function getNodeMajorVersion() {
|
|
11609
|
+
const major = parseInt(process.versions.node.split(".")[0] ?? "0", 10);
|
|
11610
|
+
return Number.isNaN(major) ? 0 : major;
|
|
11611
|
+
}
|
|
11612
|
+
function getRegisteredProjectCount() {
|
|
11613
|
+
try {
|
|
11614
|
+
return validateRegistry().valid.length;
|
|
11615
|
+
} catch {
|
|
11616
|
+
return 0;
|
|
11617
|
+
}
|
|
11618
|
+
}
|
|
11619
|
+
function buildTelemetryBaseProperties(config) {
|
|
11620
|
+
return {
|
|
11621
|
+
cliVersion,
|
|
11622
|
+
platform: process.platform,
|
|
11623
|
+
nodeMajorVersion: getNodeMajorVersion(),
|
|
11624
|
+
boardMode: config ? config.boardProvider?.enabled !== false : void 0,
|
|
11625
|
+
registeredProjectCount: getRegisteredProjectCount()
|
|
11626
|
+
};
|
|
11627
|
+
}
|
|
11628
|
+
function fireTelemetryEvent(eventName, properties = {}) {
|
|
11629
|
+
void telemetryReporter(eventName, properties).catch(() => void 0);
|
|
11630
|
+
}
|
|
11631
|
+
async function trackCommandStarted(command, config) {
|
|
11632
|
+
await telemetryReporter("command_started", {
|
|
11633
|
+
...buildTelemetryBaseProperties(config),
|
|
11634
|
+
command
|
|
11635
|
+
}).catch(() => void 0);
|
|
11636
|
+
}
|
|
11637
|
+
async function trackCommandCompleted(command, startedAt, exitCode, config, extraProps = {}) {
|
|
11638
|
+
await telemetryReporter("command_completed", {
|
|
11639
|
+
...buildTelemetryBaseProperties(config),
|
|
11640
|
+
...extraProps,
|
|
11641
|
+
command,
|
|
11642
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
11643
|
+
exitCode,
|
|
11644
|
+
success: exitCode === 0,
|
|
11645
|
+
failure: exitCode !== 0
|
|
11646
|
+
}).catch(() => void 0);
|
|
11647
|
+
}
|
|
11648
|
+
async function trackJobStarted(jobType, provider, config) {
|
|
11649
|
+
await telemetryReporter("job_started", {
|
|
11650
|
+
...buildTelemetryBaseProperties(config),
|
|
11651
|
+
jobType,
|
|
11652
|
+
provider
|
|
11653
|
+
}).catch(() => void 0);
|
|
11654
|
+
}
|
|
11655
|
+
async function trackJobCompletedOrFailed(jobType, provider, startedAt, exitCode, config, errorCategory) {
|
|
11656
|
+
await telemetryReporter(exitCode === 0 ? "job_completed" : "job_failed", {
|
|
11657
|
+
...buildTelemetryBaseProperties(config),
|
|
11658
|
+
jobType,
|
|
11659
|
+
provider,
|
|
11660
|
+
durationMs: Math.max(0, Date.now() - startedAt),
|
|
11661
|
+
exitCode,
|
|
11662
|
+
success: exitCode === 0,
|
|
11663
|
+
failure: exitCode !== 0,
|
|
11664
|
+
errorCategory: exitCode === 0 ? void 0 : mapTelemetryErrorCategory(errorCategory)
|
|
11665
|
+
}).catch(() => void 0);
|
|
11666
|
+
}
|
|
11667
|
+
|
|
11668
|
+
// src/commands/init.ts
|
|
10877
11669
|
var __filename2 = fileURLToPath3(import.meta.url);
|
|
10878
|
-
var __dirname2 =
|
|
11670
|
+
var __dirname2 = dirname8(__filename2);
|
|
10879
11671
|
function findTemplatesDir(startDir) {
|
|
10880
11672
|
let d = startDir;
|
|
10881
11673
|
for (let i = 0; i < 8; i++) {
|
|
10882
|
-
const candidate =
|
|
10883
|
-
if (
|
|
11674
|
+
const candidate = join23(d, "templates");
|
|
11675
|
+
if (fs28.existsSync(candidate) && fs28.statSync(candidate).isDirectory()) {
|
|
10884
11676
|
return candidate;
|
|
10885
11677
|
}
|
|
10886
|
-
d =
|
|
11678
|
+
d = dirname8(d);
|
|
10887
11679
|
}
|
|
10888
|
-
return
|
|
11680
|
+
return join23(startDir, "templates");
|
|
10889
11681
|
}
|
|
10890
11682
|
var TEMPLATES_DIR = findTemplatesDir(__dirname2);
|
|
10891
11683
|
var NW_SKILLS = [
|
|
@@ -10897,12 +11689,12 @@ var NW_SKILLS = [
|
|
|
10897
11689
|
"nw-review"
|
|
10898
11690
|
];
|
|
10899
11691
|
function hasPlaywrightDependency(cwd) {
|
|
10900
|
-
const packageJsonPath =
|
|
10901
|
-
if (!
|
|
11692
|
+
const packageJsonPath = path27.join(cwd, "package.json");
|
|
11693
|
+
if (!fs28.existsSync(packageJsonPath)) {
|
|
10902
11694
|
return false;
|
|
10903
11695
|
}
|
|
10904
11696
|
try {
|
|
10905
|
-
const packageJson2 = JSON.parse(
|
|
11697
|
+
const packageJson2 = JSON.parse(fs28.readFileSync(packageJsonPath, "utf-8"));
|
|
10906
11698
|
return Boolean(
|
|
10907
11699
|
packageJson2.dependencies?.["@playwright/test"] || packageJson2.dependencies?.playwright || packageJson2.devDependencies?.["@playwright/test"] || packageJson2.devDependencies?.playwright
|
|
10908
11700
|
);
|
|
@@ -10914,7 +11706,7 @@ function detectPlaywright(cwd) {
|
|
|
10914
11706
|
if (hasPlaywrightDependency(cwd)) {
|
|
10915
11707
|
return true;
|
|
10916
11708
|
}
|
|
10917
|
-
if (
|
|
11709
|
+
if (fs28.existsSync(path27.join(cwd, "node_modules", ".bin", "playwright"))) {
|
|
10918
11710
|
return true;
|
|
10919
11711
|
}
|
|
10920
11712
|
try {
|
|
@@ -10930,10 +11722,10 @@ function detectPlaywright(cwd) {
|
|
|
10930
11722
|
}
|
|
10931
11723
|
}
|
|
10932
11724
|
function resolvePlaywrightInstallCommand(cwd) {
|
|
10933
|
-
if (
|
|
11725
|
+
if (fs28.existsSync(path27.join(cwd, "pnpm-lock.yaml"))) {
|
|
10934
11726
|
return "pnpm add -D @playwright/test";
|
|
10935
11727
|
}
|
|
10936
|
-
if (
|
|
11728
|
+
if (fs28.existsSync(path27.join(cwd, "yarn.lock"))) {
|
|
10937
11729
|
return "yarn add -D @playwright/test";
|
|
10938
11730
|
}
|
|
10939
11731
|
return "npm install -D @playwright/test";
|
|
@@ -11079,8 +11871,8 @@ function promptProviderSelection(providers) {
|
|
|
11079
11871
|
});
|
|
11080
11872
|
}
|
|
11081
11873
|
function ensureDir(dirPath) {
|
|
11082
|
-
if (!
|
|
11083
|
-
|
|
11874
|
+
if (!fs28.existsSync(dirPath)) {
|
|
11875
|
+
fs28.mkdirSync(dirPath, { recursive: true });
|
|
11084
11876
|
}
|
|
11085
11877
|
}
|
|
11086
11878
|
function buildInitConfig(params) {
|
|
@@ -11107,6 +11899,8 @@ function buildInitConfig(params) {
|
|
|
11107
11899
|
reviewerRetryDelay: defaults.reviewerRetryDelay,
|
|
11108
11900
|
provider: params.provider,
|
|
11109
11901
|
providerLabel: "",
|
|
11902
|
+
modelAttribution: defaults.modelAttribution,
|
|
11903
|
+
newPrLabel: defaults.newPrLabel,
|
|
11110
11904
|
executorEnabled: defaults.executorEnabled ?? true,
|
|
11111
11905
|
reviewerEnabled: params.reviewerEnabled,
|
|
11112
11906
|
providerEnv: { ...defaults.providerEnv },
|
|
@@ -11129,6 +11923,10 @@ function buildInitConfig(params) {
|
|
|
11129
11923
|
branchPatterns: [...defaults.qa.branchPatterns]
|
|
11130
11924
|
},
|
|
11131
11925
|
audit: { ...defaults.audit },
|
|
11926
|
+
ux: {
|
|
11927
|
+
...defaults.ux,
|
|
11928
|
+
flows: [...defaults.ux.flows]
|
|
11929
|
+
},
|
|
11132
11930
|
analytics: { ...defaults.analytics },
|
|
11133
11931
|
manager: { ...defaults.manager },
|
|
11134
11932
|
feedback: { ...defaults.feedback },
|
|
@@ -11155,30 +11953,30 @@ function buildInitConfig(params) {
|
|
|
11155
11953
|
}
|
|
11156
11954
|
function resolveTemplatePath(templateName, customTemplatesDir, bundledTemplatesDir) {
|
|
11157
11955
|
if (customTemplatesDir !== null) {
|
|
11158
|
-
const customPath =
|
|
11159
|
-
if (
|
|
11956
|
+
const customPath = join23(customTemplatesDir, templateName);
|
|
11957
|
+
if (fs28.existsSync(customPath)) {
|
|
11160
11958
|
return { path: customPath, source: "custom" };
|
|
11161
11959
|
}
|
|
11162
11960
|
}
|
|
11163
|
-
return { path:
|
|
11961
|
+
return { path: join23(bundledTemplatesDir, templateName), source: "bundled" };
|
|
11164
11962
|
}
|
|
11165
11963
|
function processTemplate(templateName, targetPath, replacements, force, sourcePath, source) {
|
|
11166
|
-
if (
|
|
11964
|
+
if (fs28.existsSync(targetPath) && !force) {
|
|
11167
11965
|
console.log(` Skipped (exists): ${targetPath}`);
|
|
11168
11966
|
return { created: false, source: source ?? "bundled" };
|
|
11169
11967
|
}
|
|
11170
|
-
const templatePath = sourcePath ??
|
|
11968
|
+
const templatePath = sourcePath ?? join23(TEMPLATES_DIR, templateName);
|
|
11171
11969
|
const resolvedSource = source ?? "bundled";
|
|
11172
|
-
let content =
|
|
11970
|
+
let content = fs28.readFileSync(templatePath, "utf-8");
|
|
11173
11971
|
for (const [key, value] of Object.entries(replacements)) {
|
|
11174
11972
|
content = content.replaceAll(key, value);
|
|
11175
11973
|
}
|
|
11176
|
-
|
|
11974
|
+
fs28.writeFileSync(targetPath, content);
|
|
11177
11975
|
console.log(` Created: ${targetPath} (${resolvedSource})`);
|
|
11178
11976
|
return { created: true, source: resolvedSource };
|
|
11179
11977
|
}
|
|
11180
11978
|
function addToGitignore(cwd) {
|
|
11181
|
-
const gitignorePath =
|
|
11979
|
+
const gitignorePath = path27.join(cwd, ".gitignore");
|
|
11182
11980
|
const entries = [
|
|
11183
11981
|
{
|
|
11184
11982
|
pattern: "/logs/",
|
|
@@ -11192,13 +11990,13 @@ function addToGitignore(cwd) {
|
|
|
11192
11990
|
},
|
|
11193
11991
|
{ pattern: "*.claim", label: "*.claim", check: (c) => c.includes("*.claim") }
|
|
11194
11992
|
];
|
|
11195
|
-
if (!
|
|
11993
|
+
if (!fs28.existsSync(gitignorePath)) {
|
|
11196
11994
|
const lines = ["# Night Watch", ...entries.map((e) => e.pattern), ""];
|
|
11197
|
-
|
|
11995
|
+
fs28.writeFileSync(gitignorePath, lines.join("\n"));
|
|
11198
11996
|
console.log(` Created: ${gitignorePath} (with Night Watch entries)`);
|
|
11199
11997
|
return;
|
|
11200
11998
|
}
|
|
11201
|
-
const content =
|
|
11999
|
+
const content = fs28.readFileSync(gitignorePath, "utf-8");
|
|
11202
12000
|
const missing = entries.filter((e) => !e.check(content));
|
|
11203
12001
|
if (missing.length === 0) {
|
|
11204
12002
|
console.log(` Skipped (exists): Night Watch entries in .gitignore`);
|
|
@@ -11206,59 +12004,59 @@ function addToGitignore(cwd) {
|
|
|
11206
12004
|
}
|
|
11207
12005
|
const additions = missing.map((e) => e.pattern).join("\n");
|
|
11208
12006
|
const newContent = content.trimEnd() + "\n\n# Night Watch\n" + additions + "\n";
|
|
11209
|
-
|
|
12007
|
+
fs28.writeFileSync(gitignorePath, newContent);
|
|
11210
12008
|
console.log(` Updated: ${gitignorePath} (added ${missing.map((e) => e.label).join(", ")})`);
|
|
11211
12009
|
}
|
|
11212
12010
|
function installSkills(cwd, provider, force, templatesDir) {
|
|
11213
|
-
const skillsTemplatesDir =
|
|
11214
|
-
if (!
|
|
12011
|
+
const skillsTemplatesDir = path27.join(templatesDir, "skills");
|
|
12012
|
+
if (!fs28.existsSync(skillsTemplatesDir)) {
|
|
11215
12013
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
11216
12014
|
}
|
|
11217
12015
|
const isClaudeProvider = provider === "claude" || provider.startsWith("claude");
|
|
11218
12016
|
const isCodexProvider = provider === "codex";
|
|
11219
|
-
const claudeDir =
|
|
11220
|
-
if (isClaudeProvider ||
|
|
12017
|
+
const claudeDir = path27.join(cwd, ".claude");
|
|
12018
|
+
if (isClaudeProvider || fs28.existsSync(claudeDir)) {
|
|
11221
12019
|
ensureDir(claudeDir);
|
|
11222
|
-
const skillsDir =
|
|
12020
|
+
const skillsDir = path27.join(claudeDir, "skills");
|
|
11223
12021
|
ensureDir(skillsDir);
|
|
11224
12022
|
let installed = 0;
|
|
11225
12023
|
let skipped = 0;
|
|
11226
12024
|
for (const skillName of NW_SKILLS) {
|
|
11227
|
-
const templateFile =
|
|
11228
|
-
if (!
|
|
11229
|
-
const skillDir =
|
|
12025
|
+
const templateFile = path27.join(skillsTemplatesDir, `${skillName}.md`);
|
|
12026
|
+
if (!fs28.existsSync(templateFile)) continue;
|
|
12027
|
+
const skillDir = path27.join(skillsDir, skillName);
|
|
11230
12028
|
ensureDir(skillDir);
|
|
11231
|
-
const target =
|
|
11232
|
-
if (
|
|
12029
|
+
const target = path27.join(skillDir, "SKILL.md");
|
|
12030
|
+
if (fs28.existsSync(target) && !force) {
|
|
11233
12031
|
skipped++;
|
|
11234
12032
|
continue;
|
|
11235
12033
|
}
|
|
11236
|
-
|
|
12034
|
+
fs28.copyFileSync(templateFile, target);
|
|
11237
12035
|
installed++;
|
|
11238
12036
|
}
|
|
11239
12037
|
return { location: ".claude/skills/", installed, skipped, type: "claude" };
|
|
11240
12038
|
}
|
|
11241
12039
|
if (isCodexProvider) {
|
|
11242
|
-
const agentsFile =
|
|
11243
|
-
const blockFile =
|
|
11244
|
-
if (!
|
|
12040
|
+
const agentsFile = path27.join(cwd, "AGENTS.md");
|
|
12041
|
+
const blockFile = path27.join(skillsTemplatesDir, "_codex-block.md");
|
|
12042
|
+
if (!fs28.existsSync(blockFile)) {
|
|
11245
12043
|
return { location: "", installed: 0, skipped: 0, type: "none" };
|
|
11246
12044
|
}
|
|
11247
|
-
const block =
|
|
12045
|
+
const block = fs28.readFileSync(blockFile, "utf-8");
|
|
11248
12046
|
const marker = "## Night Watch Skills";
|
|
11249
|
-
if (!
|
|
11250
|
-
|
|
12047
|
+
if (!fs28.existsSync(agentsFile)) {
|
|
12048
|
+
fs28.writeFileSync(agentsFile, block);
|
|
11251
12049
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
11252
12050
|
}
|
|
11253
|
-
const existing =
|
|
12051
|
+
const existing = fs28.readFileSync(agentsFile, "utf-8");
|
|
11254
12052
|
if (existing.includes(marker)) {
|
|
11255
12053
|
if (!force) {
|
|
11256
12054
|
return { location: "AGENTS.md", installed: 0, skipped: NW_SKILLS.length, type: "codex" };
|
|
11257
12055
|
}
|
|
11258
12056
|
const withoutSection = existing.replace(/\n\n## Night Watch Skills[\s\S]*$/, "");
|
|
11259
|
-
|
|
12057
|
+
fs28.writeFileSync(agentsFile, withoutSection + "\n\n" + block);
|
|
11260
12058
|
} else {
|
|
11261
|
-
|
|
12059
|
+
fs28.appendFileSync(agentsFile, "\n\n" + block);
|
|
11262
12060
|
}
|
|
11263
12061
|
return { location: "AGENTS.md", installed: NW_SKILLS.length, skipped: 0, type: "codex" };
|
|
11264
12062
|
}
|
|
@@ -11382,28 +12180,28 @@ function initCommand(program2) {
|
|
|
11382
12180
|
"${DEFAULT_BRANCH}": defaultBranch
|
|
11383
12181
|
};
|
|
11384
12182
|
step(6, totalSteps, "Creating PRD directory structure...");
|
|
11385
|
-
const prdDirPath =
|
|
11386
|
-
const doneDirPath =
|
|
12183
|
+
const prdDirPath = path27.join(cwd, prdDir);
|
|
12184
|
+
const doneDirPath = path27.join(prdDirPath, "done");
|
|
11387
12185
|
ensureDir(doneDirPath);
|
|
11388
12186
|
success(`Created ${prdDirPath}/`);
|
|
11389
12187
|
success(`Created ${doneDirPath}/`);
|
|
11390
12188
|
step(7, totalSteps, "Creating logs directory...");
|
|
11391
|
-
const logsPath =
|
|
12189
|
+
const logsPath = path27.join(cwd, LOG_DIR);
|
|
11392
12190
|
ensureDir(logsPath);
|
|
11393
12191
|
success(`Created ${logsPath}/`);
|
|
11394
12192
|
addToGitignore(cwd);
|
|
11395
12193
|
step(8, totalSteps, "Creating instructions directory...");
|
|
11396
|
-
const instructionsDir =
|
|
12194
|
+
const instructionsDir = path27.join(cwd, "instructions");
|
|
11397
12195
|
ensureDir(instructionsDir);
|
|
11398
12196
|
success(`Created ${instructionsDir}/`);
|
|
11399
12197
|
const existingConfig = loadConfig(cwd);
|
|
11400
|
-
const customTemplatesDirPath =
|
|
11401
|
-
const customTemplatesDir =
|
|
12198
|
+
const customTemplatesDirPath = path27.join(cwd, existingConfig.templatesDir);
|
|
12199
|
+
const customTemplatesDir = fs28.existsSync(customTemplatesDirPath) ? customTemplatesDirPath : null;
|
|
11402
12200
|
const templateSources = [];
|
|
11403
12201
|
const nwResolution = resolveTemplatePath("executor.md", customTemplatesDir, TEMPLATES_DIR);
|
|
11404
12202
|
const nwResult = processTemplate(
|
|
11405
12203
|
"executor.md",
|
|
11406
|
-
|
|
12204
|
+
path27.join(instructionsDir, "executor.md"),
|
|
11407
12205
|
replacements,
|
|
11408
12206
|
force,
|
|
11409
12207
|
nwResolution.path,
|
|
@@ -11417,7 +12215,7 @@ function initCommand(program2) {
|
|
|
11417
12215
|
);
|
|
11418
12216
|
const peResult = processTemplate(
|
|
11419
12217
|
"prd-executor.md",
|
|
11420
|
-
|
|
12218
|
+
path27.join(instructionsDir, "prd-executor.md"),
|
|
11421
12219
|
replacements,
|
|
11422
12220
|
force,
|
|
11423
12221
|
peResolution.path,
|
|
@@ -11427,7 +12225,7 @@ function initCommand(program2) {
|
|
|
11427
12225
|
const prResolution = resolveTemplatePath("pr-reviewer.md", customTemplatesDir, TEMPLATES_DIR);
|
|
11428
12226
|
const prResult = processTemplate(
|
|
11429
12227
|
"pr-reviewer.md",
|
|
11430
|
-
|
|
12228
|
+
path27.join(instructionsDir, "pr-reviewer.md"),
|
|
11431
12229
|
replacements,
|
|
11432
12230
|
force,
|
|
11433
12231
|
prResolution.path,
|
|
@@ -11437,7 +12235,7 @@ function initCommand(program2) {
|
|
|
11437
12235
|
const qaResolution = resolveTemplatePath("qa.md", customTemplatesDir, TEMPLATES_DIR);
|
|
11438
12236
|
const qaResult = processTemplate(
|
|
11439
12237
|
"qa.md",
|
|
11440
|
-
|
|
12238
|
+
path27.join(instructionsDir, "qa.md"),
|
|
11441
12239
|
replacements,
|
|
11442
12240
|
force,
|
|
11443
12241
|
qaResolution.path,
|
|
@@ -11447,7 +12245,7 @@ function initCommand(program2) {
|
|
|
11447
12245
|
const auditResolution = resolveTemplatePath("audit.md", customTemplatesDir, TEMPLATES_DIR);
|
|
11448
12246
|
const auditResult = processTemplate(
|
|
11449
12247
|
"audit.md",
|
|
11450
|
-
|
|
12248
|
+
path27.join(instructionsDir, "audit.md"),
|
|
11451
12249
|
replacements,
|
|
11452
12250
|
force,
|
|
11453
12251
|
auditResolution.path,
|
|
@@ -11461,7 +12259,7 @@ function initCommand(program2) {
|
|
|
11461
12259
|
);
|
|
11462
12260
|
const plannerResult = processTemplate(
|
|
11463
12261
|
"prd-creator.md",
|
|
11464
|
-
|
|
12262
|
+
path27.join(instructionsDir, "prd-creator.md"),
|
|
11465
12263
|
replacements,
|
|
11466
12264
|
force,
|
|
11467
12265
|
plannerResolution.path,
|
|
@@ -11469,8 +12267,8 @@ function initCommand(program2) {
|
|
|
11469
12267
|
);
|
|
11470
12268
|
templateSources.push({ name: "prd-creator.md", source: plannerResult.source });
|
|
11471
12269
|
step(9, totalSteps, "Creating configuration file...");
|
|
11472
|
-
const configPath =
|
|
11473
|
-
if (
|
|
12270
|
+
const configPath = path27.join(cwd, CONFIG_FILE_NAME);
|
|
12271
|
+
if (fs28.existsSync(configPath) && !force) {
|
|
11474
12272
|
console.log(` Skipped (exists): ${configPath}`);
|
|
11475
12273
|
} else {
|
|
11476
12274
|
const config = buildInitConfig({
|
|
@@ -11480,11 +12278,11 @@ function initCommand(program2) {
|
|
|
11480
12278
|
reviewerEnabled,
|
|
11481
12279
|
prdDir
|
|
11482
12280
|
});
|
|
11483
|
-
|
|
12281
|
+
fs28.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n");
|
|
11484
12282
|
success(`Created ${configPath}`);
|
|
11485
12283
|
}
|
|
11486
12284
|
step(10, totalSteps, "Setting up GitHub Project board...");
|
|
11487
|
-
const existingRaw = JSON.parse(
|
|
12285
|
+
const existingRaw = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
|
|
11488
12286
|
const existingBoard = existingRaw.boardProvider;
|
|
11489
12287
|
let boardSetupStatus = "Skipped";
|
|
11490
12288
|
if (existingBoard?.projectNumber && !force) {
|
|
@@ -11506,14 +12304,14 @@ function initCommand(program2) {
|
|
|
11506
12304
|
const provider = createBoardProvider({ enabled: true, provider: "github" }, cwd);
|
|
11507
12305
|
const boardTitle = `${projectName} Night Watch`;
|
|
11508
12306
|
const board = await provider.setupBoard(boardTitle);
|
|
11509
|
-
const rawConfig = JSON.parse(
|
|
12307
|
+
const rawConfig = JSON.parse(fs28.readFileSync(configPath, "utf-8"));
|
|
11510
12308
|
rawConfig.boardProvider = {
|
|
11511
12309
|
enabled: true,
|
|
11512
12310
|
provider: "github",
|
|
11513
12311
|
projectNumber: board.number,
|
|
11514
12312
|
projectTitle: board.title
|
|
11515
12313
|
};
|
|
11516
|
-
|
|
12314
|
+
fs28.writeFileSync(configPath, JSON.stringify(rawConfig, null, 2) + "\n");
|
|
11517
12315
|
boardSetupStatus = `Created (#${board.number})`;
|
|
11518
12316
|
success(`GitHub Project board "${boardTitle}" ready (#${board.number})`);
|
|
11519
12317
|
} catch (boardErr) {
|
|
@@ -11605,6 +12403,12 @@ function initCommand(program2) {
|
|
|
11605
12403
|
label("Reviewer", reviewerEnabled ? "Enabled" : "Disabled");
|
|
11606
12404
|
label("Playwright", playwrightStatus);
|
|
11607
12405
|
console.log();
|
|
12406
|
+
fireTelemetryEvent("cli_init_completed", {
|
|
12407
|
+
command: "init",
|
|
12408
|
+
provider: selectedProvider,
|
|
12409
|
+
boardMode: existingRaw.boardProvider?.enabled !== false,
|
|
12410
|
+
success: true
|
|
12411
|
+
});
|
|
11608
12412
|
header("Next Steps");
|
|
11609
12413
|
info(`1. Add your PRD files to ${prdDir}/`);
|
|
11610
12414
|
info("2. Run `night-watch install` to set up cron jobs");
|
|
@@ -11739,8 +12543,8 @@ function recordJobOutcome(input) {
|
|
|
11739
12543
|
}
|
|
11740
12544
|
|
|
11741
12545
|
// src/commands/run.ts
|
|
11742
|
-
import * as
|
|
11743
|
-
import * as
|
|
12546
|
+
import * as fs29 from "fs";
|
|
12547
|
+
import * as path28 from "path";
|
|
11744
12548
|
function resolveRunNotificationEvent(exitCode, scriptStatus) {
|
|
11745
12549
|
if (exitCode === 124) {
|
|
11746
12550
|
return "run_timeout";
|
|
@@ -11838,7 +12642,7 @@ function buildRunNotificationContext(config, projectDir, event, exitCode, script
|
|
|
11838
12642
|
const checkpointStatus = checkpointValue === "created" || checkpointValue === "available" || checkpointValue === "none" ? checkpointValue : void 0;
|
|
11839
12643
|
return {
|
|
11840
12644
|
event,
|
|
11841
|
-
projectName:
|
|
12645
|
+
projectName: path28.basename(projectDir),
|
|
11842
12646
|
exitCode,
|
|
11843
12647
|
provider: config.provider,
|
|
11844
12648
|
prdName: scriptResult?.data.prd ?? extractResultValueFromOutput(rawOutput, "prd"),
|
|
@@ -11858,12 +12662,12 @@ function buildRunNotificationContext(config, projectDir, event, exitCode, script
|
|
|
11858
12662
|
};
|
|
11859
12663
|
}
|
|
11860
12664
|
function getCrossProjectFallbackCandidates(currentProjectDir) {
|
|
11861
|
-
const current =
|
|
12665
|
+
const current = path28.resolve(currentProjectDir);
|
|
11862
12666
|
const { valid, invalid } = validateRegistry();
|
|
11863
12667
|
for (const entry of invalid) {
|
|
11864
12668
|
warn(`Skipping invalid registry entry: ${entry.path}`);
|
|
11865
12669
|
}
|
|
11866
|
-
return valid.filter((entry) =>
|
|
12670
|
+
return valid.filter((entry) => path28.resolve(entry.path) !== current);
|
|
11867
12671
|
}
|
|
11868
12672
|
async function sendRunCompletionNotifications(config, projectDir, options, exitCode, scriptResult, rawOutput) {
|
|
11869
12673
|
if (isRateLimitFallbackTriggered(scriptResult?.data)) {
|
|
@@ -11873,7 +12677,7 @@ async function sendRunCompletionNotifications(config, projectDir, options, exitC
|
|
|
11873
12677
|
if (nonTelegramWebhooks.length > 0) {
|
|
11874
12678
|
const _rateLimitCtx = {
|
|
11875
12679
|
event: "rate_limit_fallback",
|
|
11876
|
-
projectName:
|
|
12680
|
+
projectName: path28.basename(projectDir),
|
|
11877
12681
|
exitCode,
|
|
11878
12682
|
provider: config.provider
|
|
11879
12683
|
};
|
|
@@ -12033,6 +12837,8 @@ function buildEnvVars(config, options) {
|
|
|
12033
12837
|
}
|
|
12034
12838
|
env.NW_PRD_DIR = config.prdDir;
|
|
12035
12839
|
env.NW_BRANCH_PREFIX = config.branchPrefix;
|
|
12840
|
+
env.NW_MODEL_ATTRIBUTION_ENABLED = config.modelAttribution ? "1" : "0";
|
|
12841
|
+
env.NW_NEW_PR_LABEL = config.newPrLabel ?? "draft";
|
|
12036
12842
|
if (config.prdPriority && config.prdPriority.length > 0) {
|
|
12037
12843
|
env.NW_PRD_PRIORITY = config.prdPriority.join(":");
|
|
12038
12844
|
}
|
|
@@ -12111,20 +12917,20 @@ function applyCliOverrides(config, options) {
|
|
|
12111
12917
|
}
|
|
12112
12918
|
function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
12113
12919
|
const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
|
|
12114
|
-
const absolutePrdDir =
|
|
12115
|
-
const doneDir =
|
|
12920
|
+
const absolutePrdDir = path28.join(projectDir, prdDir);
|
|
12921
|
+
const doneDir = path28.join(absolutePrdDir, "done");
|
|
12116
12922
|
const pending = [];
|
|
12117
12923
|
const completed = [];
|
|
12118
|
-
if (
|
|
12119
|
-
const entries =
|
|
12924
|
+
if (fs29.existsSync(absolutePrdDir)) {
|
|
12925
|
+
const entries = fs29.readdirSync(absolutePrdDir, { withFileTypes: true });
|
|
12120
12926
|
for (const entry of entries) {
|
|
12121
12927
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
12122
|
-
const claimPath =
|
|
12928
|
+
const claimPath = path28.join(absolutePrdDir, entry.name + CLAIM_FILE_EXTENSION);
|
|
12123
12929
|
let claimed = false;
|
|
12124
12930
|
let claimInfo = null;
|
|
12125
|
-
if (
|
|
12931
|
+
if (fs29.existsSync(claimPath)) {
|
|
12126
12932
|
try {
|
|
12127
|
-
const content =
|
|
12933
|
+
const content = fs29.readFileSync(claimPath, "utf-8");
|
|
12128
12934
|
const data = JSON.parse(content);
|
|
12129
12935
|
const age = Math.floor(Date.now() / 1e3) - data.timestamp;
|
|
12130
12936
|
if (age < claimStaleAfter) {
|
|
@@ -12138,8 +12944,8 @@ function scanPrdDirectory(projectDir, prdDir, maxRuntime) {
|
|
|
12138
12944
|
}
|
|
12139
12945
|
}
|
|
12140
12946
|
}
|
|
12141
|
-
if (
|
|
12142
|
-
const entries =
|
|
12947
|
+
if (fs29.existsSync(doneDir)) {
|
|
12948
|
+
const entries = fs29.readdirSync(doneDir, { withFileTypes: true });
|
|
12143
12949
|
for (const entry of entries) {
|
|
12144
12950
|
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
12145
12951
|
completed.push(entry.name);
|
|
@@ -12159,8 +12965,11 @@ function runCommand(program2) {
|
|
|
12159
12965
|
const projectDir = process.cwd();
|
|
12160
12966
|
let config = loadConfig(projectDir);
|
|
12161
12967
|
config = applyCliOverrides(config, options);
|
|
12968
|
+
const commandStartedAt = Date.now();
|
|
12969
|
+
await trackCommandStarted("run", config);
|
|
12162
12970
|
if (config.executorEnabled === false && !options.dryRun) {
|
|
12163
12971
|
info("Executor is disabled in config; skipping run.");
|
|
12972
|
+
await trackCommandCompleted("run", commandStartedAt, 0, config);
|
|
12164
12973
|
process.exit(0);
|
|
12165
12974
|
}
|
|
12166
12975
|
const envVars = buildEnvVars(config, options);
|
|
@@ -12252,12 +13061,15 @@ function runCommand(program2) {
|
|
|
12252
13061
|
header("Command");
|
|
12253
13062
|
dim(` bash ${scriptPath} ${projectDir}`);
|
|
12254
13063
|
console.log();
|
|
13064
|
+
await trackCommandCompleted("run", commandStartedAt, 0, config);
|
|
12255
13065
|
process.exit(0);
|
|
12256
13066
|
}
|
|
12257
13067
|
const spinner = createSpinner("Running PRD executor...");
|
|
12258
13068
|
spinner.start();
|
|
12259
13069
|
try {
|
|
12260
13070
|
const startedAt = Date.now();
|
|
13071
|
+
const provider = envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "executor");
|
|
13072
|
+
await trackJobStarted("executor", provider, config);
|
|
12261
13073
|
await maybeApplyCronSchedulingDelay(config, "executor", projectDir);
|
|
12262
13074
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
12263
13075
|
scriptPath,
|
|
@@ -12268,6 +13080,14 @@ function runCommand(program2) {
|
|
|
12268
13080
|
const finishedAt = Date.now();
|
|
12269
13081
|
const scriptResult = parseScriptResult(`${stdout}
|
|
12270
13082
|
${stderr}`);
|
|
13083
|
+
await trackJobCompletedOrFailed(
|
|
13084
|
+
"executor",
|
|
13085
|
+
provider,
|
|
13086
|
+
startedAt,
|
|
13087
|
+
exitCode,
|
|
13088
|
+
config,
|
|
13089
|
+
scriptResult?.status
|
|
13090
|
+
);
|
|
12271
13091
|
if (exitCode === 0) {
|
|
12272
13092
|
if (scriptResult?.status === "queued") {
|
|
12273
13093
|
spinner.succeed("PRD executor queued \u2014 another job is currently running");
|
|
@@ -12306,16 +13126,29 @@ ${stderr}`);
|
|
|
12306
13126
|
${stderr}`
|
|
12307
13127
|
);
|
|
12308
13128
|
}
|
|
13129
|
+
if (exitCode === 0 && (scriptResult?.status === "success_open_pr" || getRunPrMetadata(scriptResult, `${stdout}
|
|
13130
|
+
${stderr}`).prUrl)) {
|
|
13131
|
+
fireTelemetryEvent("pr_opened", {
|
|
13132
|
+
...buildTelemetryBaseProperties(config),
|
|
13133
|
+
jobType: "executor",
|
|
13134
|
+
provider,
|
|
13135
|
+
success: true
|
|
13136
|
+
});
|
|
13137
|
+
}
|
|
12309
13138
|
if (shouldAttemptCrossProjectFallback(options, scriptResult?.status)) {
|
|
12310
13139
|
const executedFallback = await runCrossProjectFallback(projectDir, options);
|
|
12311
13140
|
if (!executedFallback) {
|
|
12312
13141
|
info("Cross-project fallback: no eligible work found in other registered projects");
|
|
12313
13142
|
}
|
|
12314
13143
|
}
|
|
13144
|
+
await trackCommandCompleted("run", commandStartedAt, exitCode, config);
|
|
12315
13145
|
process.exit(exitCode);
|
|
12316
13146
|
} catch (err) {
|
|
12317
13147
|
spinner.fail("Failed to execute run command");
|
|
12318
13148
|
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
13149
|
+
await trackCommandCompleted("run", commandStartedAt, 1, config, {
|
|
13150
|
+
errorCategory: err instanceof Error ? err.message : String(err)
|
|
13151
|
+
});
|
|
12319
13152
|
process.exit(1);
|
|
12320
13153
|
}
|
|
12321
13154
|
});
|
|
@@ -12324,7 +13157,7 @@ ${stderr}`
|
|
|
12324
13157
|
// src/commands/review.ts
|
|
12325
13158
|
init_dist();
|
|
12326
13159
|
import { execFileSync as execFileSync5 } from "child_process";
|
|
12327
|
-
import * as
|
|
13160
|
+
import * as path29 from "path";
|
|
12328
13161
|
function shouldSendReviewNotification(scriptStatus) {
|
|
12329
13162
|
if (!scriptStatus) {
|
|
12330
13163
|
return true;
|
|
@@ -12497,8 +13330,11 @@ function reviewCommand(program2) {
|
|
|
12497
13330
|
const projectDir = process.cwd();
|
|
12498
13331
|
let config = loadConfig(projectDir);
|
|
12499
13332
|
config = applyCliOverrides2(config, options);
|
|
13333
|
+
const commandStartedAt = Date.now();
|
|
13334
|
+
await trackCommandStarted("review", config);
|
|
12500
13335
|
if (!config.reviewerEnabled && !options.dryRun) {
|
|
12501
13336
|
info("Reviewer is disabled in config; skipping review.");
|
|
13337
|
+
await trackCommandCompleted("review", commandStartedAt, 0, config);
|
|
12502
13338
|
process.exit(0);
|
|
12503
13339
|
}
|
|
12504
13340
|
const envVars = buildEnvVars2(config, options);
|
|
@@ -12547,6 +13383,7 @@ function reviewCommand(program2) {
|
|
|
12547
13383
|
header("Command");
|
|
12548
13384
|
dim(` bash ${scriptPath} ${projectDir}`);
|
|
12549
13385
|
console.log();
|
|
13386
|
+
await trackCommandCompleted("review", commandStartedAt, 0, config);
|
|
12550
13387
|
process.exit(0);
|
|
12551
13388
|
}
|
|
12552
13389
|
const preflightOpenPrs = getOpenPrsNeedingWork(config.branchPatterns);
|
|
@@ -12568,6 +13405,8 @@ function reviewCommand(program2) {
|
|
|
12568
13405
|
spinner.start();
|
|
12569
13406
|
try {
|
|
12570
13407
|
const startedAt = Date.now();
|
|
13408
|
+
const provider = envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "reviewer");
|
|
13409
|
+
await trackJobStarted("reviewer", provider, config);
|
|
12571
13410
|
await maybeApplyCronSchedulingDelay(config, "reviewer", projectDir);
|
|
12572
13411
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
12573
13412
|
scriptPath,
|
|
@@ -12577,6 +13416,14 @@ function reviewCommand(program2) {
|
|
|
12577
13416
|
const finishedAt = Date.now();
|
|
12578
13417
|
const scriptResult = parseScriptResult(`${stdout}
|
|
12579
13418
|
${stderr}`);
|
|
13419
|
+
await trackJobCompletedOrFailed(
|
|
13420
|
+
"reviewer",
|
|
13421
|
+
provider,
|
|
13422
|
+
startedAt,
|
|
13423
|
+
exitCode,
|
|
13424
|
+
config,
|
|
13425
|
+
scriptResult?.status
|
|
13426
|
+
);
|
|
12580
13427
|
if (exitCode === 0) {
|
|
12581
13428
|
if (scriptResult?.status === "queued") {
|
|
12582
13429
|
spinner.succeed("PR reviewer queued \u2014 another job is currently running");
|
|
@@ -12620,6 +13467,13 @@ ${stderr}`);
|
|
|
12620
13467
|
);
|
|
12621
13468
|
if (!shouldNotifyCompletion) {
|
|
12622
13469
|
info("Skipping review completion notification (review did not complete successfully)");
|
|
13470
|
+
} else {
|
|
13471
|
+
fireTelemetryEvent("review_completed", {
|
|
13472
|
+
...buildTelemetryBaseProperties(config),
|
|
13473
|
+
jobType: "reviewer",
|
|
13474
|
+
provider,
|
|
13475
|
+
success: true
|
|
13476
|
+
});
|
|
12623
13477
|
}
|
|
12624
13478
|
let fallbackPrDetails = null;
|
|
12625
13479
|
if (shouldNotifyCompletion) {
|
|
@@ -12652,7 +13506,7 @@ ${stderr}`);
|
|
|
12652
13506
|
const reviewEvent = legacyNoChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
12653
13507
|
await sendNotifications(config, {
|
|
12654
13508
|
event: reviewEvent,
|
|
12655
|
-
projectName:
|
|
13509
|
+
projectName: path29.basename(projectDir),
|
|
12656
13510
|
exitCode,
|
|
12657
13511
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
12658
13512
|
prUrl: fallbackPrDetails?.url,
|
|
@@ -12671,7 +13525,7 @@ ${stderr}`);
|
|
|
12671
13525
|
const reviewEvent = target.noChangesNeeded ? "review_ready_for_human" : "review_completed";
|
|
12672
13526
|
await sendNotifications(config, {
|
|
12673
13527
|
event: reviewEvent,
|
|
12674
|
-
projectName:
|
|
13528
|
+
projectName: path29.basename(projectDir),
|
|
12675
13529
|
exitCode,
|
|
12676
13530
|
provider: formatProviderDisplay(
|
|
12677
13531
|
envVars.NW_PROVIDER_CMD,
|
|
@@ -12692,11 +13546,17 @@ ${stderr}`);
|
|
|
12692
13546
|
}
|
|
12693
13547
|
const autoMergedPrNumbers = parseAutoMergedPrNumbers(scriptResult?.data.auto_merged);
|
|
12694
13548
|
if (autoMergedPrNumbers.length > 0) {
|
|
13549
|
+
fireTelemetryEvent("auto_merge_completed", {
|
|
13550
|
+
...buildTelemetryBaseProperties(config),
|
|
13551
|
+
jobType: "reviewer",
|
|
13552
|
+
provider,
|
|
13553
|
+
success: true
|
|
13554
|
+
});
|
|
12695
13555
|
const autoMergedPrNumber = autoMergedPrNumbers[0];
|
|
12696
13556
|
const autoMergedPrDetails = fetchPrDetailsByNumber(autoMergedPrNumber, projectDir);
|
|
12697
13557
|
const _mergeCtx = {
|
|
12698
13558
|
event: "pr_auto_merged",
|
|
12699
|
-
projectName:
|
|
13559
|
+
projectName: path29.basename(projectDir),
|
|
12700
13560
|
exitCode,
|
|
12701
13561
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
12702
13562
|
prNumber: autoMergedPrDetails?.number ?? autoMergedPrNumber,
|
|
@@ -12710,10 +13570,14 @@ ${stderr}`);
|
|
|
12710
13570
|
await sendNotifications(config, _mergeCtx);
|
|
12711
13571
|
}
|
|
12712
13572
|
}
|
|
13573
|
+
await trackCommandCompleted("review", commandStartedAt, exitCode, config);
|
|
12713
13574
|
process.exit(exitCode);
|
|
12714
13575
|
} catch (err) {
|
|
12715
13576
|
spinner.fail("Failed to execute review command");
|
|
12716
13577
|
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
13578
|
+
await trackCommandCompleted("review", commandStartedAt, 1, config, {
|
|
13579
|
+
errorCategory: err instanceof Error ? err.message : String(err)
|
|
13580
|
+
});
|
|
12717
13581
|
process.exit(1);
|
|
12718
13582
|
}
|
|
12719
13583
|
});
|
|
@@ -12721,7 +13585,7 @@ ${stderr}`);
|
|
|
12721
13585
|
|
|
12722
13586
|
// src/commands/qa.ts
|
|
12723
13587
|
init_dist();
|
|
12724
|
-
import * as
|
|
13588
|
+
import * as path30 from "path";
|
|
12725
13589
|
function shouldSendQaNotification(scriptStatus) {
|
|
12726
13590
|
if (!scriptStatus) {
|
|
12727
13591
|
return true;
|
|
@@ -12883,7 +13747,7 @@ ${stderr}`);
|
|
|
12883
13747
|
const qaScreenshotUrls = primaryQaPr !== void 0 ? fetchQaScreenshotUrlsForPr(primaryQaPr, projectDir, repo) : [];
|
|
12884
13748
|
const _qaCtx = {
|
|
12885
13749
|
event: "qa_completed",
|
|
12886
|
-
projectName:
|
|
13750
|
+
projectName: path30.basename(projectDir),
|
|
12887
13751
|
exitCode,
|
|
12888
13752
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL),
|
|
12889
13753
|
prNumber: prDetails?.number ?? primaryQaPr,
|
|
@@ -12909,8 +13773,8 @@ ${stderr}`);
|
|
|
12909
13773
|
|
|
12910
13774
|
// src/commands/audit.ts
|
|
12911
13775
|
init_dist();
|
|
12912
|
-
import * as
|
|
12913
|
-
import * as
|
|
13776
|
+
import * as fs30 from "fs";
|
|
13777
|
+
import * as path31 from "path";
|
|
12914
13778
|
function buildEnvVars4(config, options) {
|
|
12915
13779
|
const env = buildBaseEnvVars(config, "audit", options.dryRun);
|
|
12916
13780
|
env.NW_AUDIT_MAX_RUNTIME = String(config.audit.maxRuntime);
|
|
@@ -12958,7 +13822,7 @@ function auditCommand(program2) {
|
|
|
12958
13822
|
if (config.audit.createIssues) {
|
|
12959
13823
|
configTable.push(["Target Column", config.audit.targetColumn]);
|
|
12960
13824
|
}
|
|
12961
|
-
configTable.push(["Report File",
|
|
13825
|
+
configTable.push(["Report File", path31.join(projectDir, "logs", "audit-report.md")]);
|
|
12962
13826
|
console.log(configTable.toString());
|
|
12963
13827
|
header("Provider Invocation");
|
|
12964
13828
|
const providerCmd = PROVIDER_COMMANDS[auditProvider];
|
|
@@ -13016,8 +13880,8 @@ ${stderr}`);
|
|
|
13016
13880
|
} else if (scriptResult?.status?.startsWith("skip_")) {
|
|
13017
13881
|
spinner.succeed("Code audit skipped");
|
|
13018
13882
|
} else {
|
|
13019
|
-
const reportPath =
|
|
13020
|
-
if (!
|
|
13883
|
+
const reportPath = path31.join(projectDir, "logs", "audit-report.md");
|
|
13884
|
+
if (!fs30.existsSync(reportPath)) {
|
|
13021
13885
|
spinner.fail("Code audit finished without a report file");
|
|
13022
13886
|
process.exit(1);
|
|
13023
13887
|
}
|
|
@@ -13034,9 +13898,9 @@ ${stderr}`);
|
|
|
13034
13898
|
const providerExit = scriptResult?.data?.provider_exit;
|
|
13035
13899
|
const exitDetail = providerExit && providerExit !== String(exitCode) ? `, provider exit ${providerExit}` : "";
|
|
13036
13900
|
spinner.fail(`Code audit exited with code ${exitCode}${statusSuffix}${exitDetail}`);
|
|
13037
|
-
const logPath =
|
|
13038
|
-
if (
|
|
13039
|
-
const logLines =
|
|
13901
|
+
const logPath = path31.join(projectDir, "logs", "audit.log");
|
|
13902
|
+
if (fs30.existsSync(logPath)) {
|
|
13903
|
+
const logLines = fs30.readFileSync(logPath, "utf-8").split("\n").filter((l) => l.trim()).slice(-8);
|
|
13040
13904
|
if (logLines.length > 0) {
|
|
13041
13905
|
process.stderr.write(logLines.join("\n") + "\n");
|
|
13042
13906
|
}
|
|
@@ -13067,6 +13931,153 @@ ${stderr}`);
|
|
|
13067
13931
|
});
|
|
13068
13932
|
}
|
|
13069
13933
|
|
|
13934
|
+
// src/commands/ux.ts
|
|
13935
|
+
init_dist();
|
|
13936
|
+
function parseTimeout(timeout) {
|
|
13937
|
+
if (!timeout) return void 0;
|
|
13938
|
+
const parsed = parseInt(timeout, 10);
|
|
13939
|
+
return Number.isNaN(parsed) || parsed < 0 ? void 0 : parsed;
|
|
13940
|
+
}
|
|
13941
|
+
function applyUxCliOverrides(config, options) {
|
|
13942
|
+
let overridden = config;
|
|
13943
|
+
const timeout = parseTimeout(options.timeout);
|
|
13944
|
+
if (timeout !== void 0) {
|
|
13945
|
+
overridden = { ...overridden, ux: { ...overridden.ux, maxRuntime: timeout } };
|
|
13946
|
+
}
|
|
13947
|
+
if (options.provider) {
|
|
13948
|
+
overridden = {
|
|
13949
|
+
...overridden,
|
|
13950
|
+
_cliProviderOverride: options.provider
|
|
13951
|
+
};
|
|
13952
|
+
}
|
|
13953
|
+
return overridden;
|
|
13954
|
+
}
|
|
13955
|
+
function writeJson(value) {
|
|
13956
|
+
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
13957
|
+
`);
|
|
13958
|
+
}
|
|
13959
|
+
function cleanupQueueEntry() {
|
|
13960
|
+
const queueId = process.env.NW_QUEUE_ENTRY_ID ? parseInt(process.env.NW_QUEUE_ENTRY_ID, 10) : NaN;
|
|
13961
|
+
if (Number.isNaN(queueId) || queueId < 1) return;
|
|
13962
|
+
removeJob(queueId);
|
|
13963
|
+
}
|
|
13964
|
+
function printDryRun(config) {
|
|
13965
|
+
header("Dry Run: UX Agent");
|
|
13966
|
+
const table = createTable({ head: ["Setting", "Value"] });
|
|
13967
|
+
table.push(["Provider", resolveJobProvider(config, "ux")]);
|
|
13968
|
+
table.push(["Enabled", config.ux.enabled ? "yes" : "no"]);
|
|
13969
|
+
table.push(["Max Runtime", `${config.ux.maxRuntime}s`]);
|
|
13970
|
+
table.push(["Target Column", config.ux.targetColumn]);
|
|
13971
|
+
table.push(["Base URL", config.ux.baseUrl || "(not configured)"]);
|
|
13972
|
+
table.push(["Start URL", config.ux.startUrl || "(not configured)"]);
|
|
13973
|
+
table.push(["Flows", config.ux.flows.length > 0 ? config.ux.flows.join(", ") : "(discover)"]);
|
|
13974
|
+
table.push(["Auto-install Playwright", config.ux.autoInstallPlaywright ? "yes" : "no"]);
|
|
13975
|
+
table.push(["Max Issues", String(config.ux.maxIssues)]);
|
|
13976
|
+
console.log(table.toString());
|
|
13977
|
+
}
|
|
13978
|
+
function uxCommand(program2) {
|
|
13979
|
+
program2.command("ux").description("Run UX agent to inspect user flows and draft a prioritized report").option("--dry-run", "Show what would be executed without running").option("--json", "Output structured JSON").option("--timeout <seconds>", "Override max runtime in seconds").option("--provider <string>", "AI provider to use").action(async (options) => {
|
|
13980
|
+
const projectDir = process.cwd();
|
|
13981
|
+
let config = loadConfig(projectDir);
|
|
13982
|
+
config = applyUxCliOverrides(config, options);
|
|
13983
|
+
if (!config.ux.enabled && !options.dryRun) {
|
|
13984
|
+
cleanupQueueEntry();
|
|
13985
|
+
if (options.json) {
|
|
13986
|
+
writeJson({ skipped: true, reason: "ux-disabled" });
|
|
13987
|
+
} else {
|
|
13988
|
+
info("UX agent is disabled in config; skipping run.");
|
|
13989
|
+
}
|
|
13990
|
+
process.exit(0);
|
|
13991
|
+
}
|
|
13992
|
+
if (options.dryRun) {
|
|
13993
|
+
if (options.json) {
|
|
13994
|
+
writeJson({
|
|
13995
|
+
dryRun: true,
|
|
13996
|
+
provider: resolveJobProvider(config, "ux"),
|
|
13997
|
+
config: config.ux
|
|
13998
|
+
});
|
|
13999
|
+
} else {
|
|
14000
|
+
printDryRun(config);
|
|
14001
|
+
}
|
|
14002
|
+
process.exit(0);
|
|
14003
|
+
}
|
|
14004
|
+
const spinner = options.json ? null : createSpinner("Running UX agent...");
|
|
14005
|
+
spinner?.start();
|
|
14006
|
+
const startedAt = Date.now();
|
|
14007
|
+
let exitCode = 0;
|
|
14008
|
+
const lockPath = uxLockPath(projectDir);
|
|
14009
|
+
let lockAcquired = false;
|
|
14010
|
+
try {
|
|
14011
|
+
await maybeApplyCronSchedulingDelay(config, "ux", projectDir);
|
|
14012
|
+
lockAcquired = acquireLock(lockPath);
|
|
14013
|
+
if (!lockAcquired) {
|
|
14014
|
+
cleanupQueueEntry();
|
|
14015
|
+
if (options.json) {
|
|
14016
|
+
writeJson({ skipped: true, reason: "ux-locked" });
|
|
14017
|
+
} else {
|
|
14018
|
+
spinner?.succeed("UX agent skipped: already running.");
|
|
14019
|
+
}
|
|
14020
|
+
process.exit(0);
|
|
14021
|
+
}
|
|
14022
|
+
const result = await runUx(config, projectDir);
|
|
14023
|
+
exitCode = result.issuesCreated >= 0 ? 0 : 1;
|
|
14024
|
+
try {
|
|
14025
|
+
recordJobOutcome({
|
|
14026
|
+
config,
|
|
14027
|
+
exitCode,
|
|
14028
|
+
finishedAt: Date.now(),
|
|
14029
|
+
jobType: "ux",
|
|
14030
|
+
metadata: {
|
|
14031
|
+
findings: result.findings.length,
|
|
14032
|
+
issuesCreated: result.issuesCreated,
|
|
14033
|
+
reportUrl: result.reportUrl,
|
|
14034
|
+
summary: result.summary
|
|
14035
|
+
},
|
|
14036
|
+
projectDir,
|
|
14037
|
+
providerKey: resolveJobProvider(config, "ux"),
|
|
14038
|
+
startedAt,
|
|
14039
|
+
stdout: result.summary
|
|
14040
|
+
});
|
|
14041
|
+
} catch {
|
|
14042
|
+
}
|
|
14043
|
+
if (options.json) {
|
|
14044
|
+
writeJson(result);
|
|
14045
|
+
} else {
|
|
14046
|
+
spinner?.succeed(`UX agent complete \u2014 ${result.summary}`);
|
|
14047
|
+
}
|
|
14048
|
+
} catch (err) {
|
|
14049
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
14050
|
+
try {
|
|
14051
|
+
recordJobOutcome({
|
|
14052
|
+
config,
|
|
14053
|
+
exitCode: 1,
|
|
14054
|
+
finishedAt: Date.now(),
|
|
14055
|
+
jobType: "ux",
|
|
14056
|
+
metadata: { error: message },
|
|
14057
|
+
projectDir,
|
|
14058
|
+
providerKey: resolveJobProvider(config, "ux"),
|
|
14059
|
+
startedAt,
|
|
14060
|
+
stderr: message
|
|
14061
|
+
});
|
|
14062
|
+
} catch {
|
|
14063
|
+
}
|
|
14064
|
+
if (options.json) {
|
|
14065
|
+
process.stderr.write(`${JSON.stringify({ ok: false, error: message }, null, 2)}
|
|
14066
|
+
`);
|
|
14067
|
+
} else {
|
|
14068
|
+
spinner?.fail(`UX agent failed: ${message}`);
|
|
14069
|
+
}
|
|
14070
|
+
process.exit(1);
|
|
14071
|
+
} finally {
|
|
14072
|
+
if (lockAcquired) {
|
|
14073
|
+
releaseLock(lockPath);
|
|
14074
|
+
}
|
|
14075
|
+
cleanupQueueEntry();
|
|
14076
|
+
}
|
|
14077
|
+
process.exit(exitCode);
|
|
14078
|
+
});
|
|
14079
|
+
}
|
|
14080
|
+
|
|
13070
14081
|
// src/commands/analytics.ts
|
|
13071
14082
|
init_dist();
|
|
13072
14083
|
function analyticsCommand(program2) {
|
|
@@ -13180,16 +14191,16 @@ function analyticsCommand(program2) {
|
|
|
13180
14191
|
// src/commands/install.ts
|
|
13181
14192
|
init_dist();
|
|
13182
14193
|
import { execSync as execSync4 } from "child_process";
|
|
13183
|
-
import * as
|
|
13184
|
-
import * as
|
|
13185
|
-
function
|
|
14194
|
+
import * as path32 from "path";
|
|
14195
|
+
import * as fs31 from "fs";
|
|
14196
|
+
function shellQuote2(value) {
|
|
13186
14197
|
return `'${value.replace(/'/g, `'"'"'`)}'`;
|
|
13187
14198
|
}
|
|
13188
14199
|
function getNightWatchBinPath() {
|
|
13189
14200
|
try {
|
|
13190
14201
|
const npmBin = execSync4("npm bin -g", { encoding: "utf-8" }).trim();
|
|
13191
|
-
const binPath =
|
|
13192
|
-
if (
|
|
14202
|
+
const binPath = path32.join(npmBin, "night-watch");
|
|
14203
|
+
if (fs31.existsSync(binPath)) {
|
|
13193
14204
|
return binPath;
|
|
13194
14205
|
}
|
|
13195
14206
|
} catch {
|
|
@@ -13202,17 +14213,17 @@ function getNightWatchBinPath() {
|
|
|
13202
14213
|
}
|
|
13203
14214
|
function getNodeBinDir() {
|
|
13204
14215
|
if (process.execPath && process.execPath !== "node") {
|
|
13205
|
-
return
|
|
14216
|
+
return path32.dirname(process.execPath);
|
|
13206
14217
|
}
|
|
13207
14218
|
try {
|
|
13208
14219
|
const nodePath = execSync4("which node", { encoding: "utf-8" }).trim();
|
|
13209
|
-
return
|
|
14220
|
+
return path32.dirname(nodePath);
|
|
13210
14221
|
} catch {
|
|
13211
14222
|
return "";
|
|
13212
14223
|
}
|
|
13213
14224
|
}
|
|
13214
14225
|
function buildCronPathPrefix(nodeBinDir, nightWatchBin) {
|
|
13215
|
-
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ?
|
|
14226
|
+
const nightWatchBinDir = nightWatchBin.includes("/") || nightWatchBin.includes("\\") ? path32.dirname(nightWatchBin) : "";
|
|
13216
14227
|
const pathParts = Array.from(
|
|
13217
14228
|
new Set([nodeBinDir, nightWatchBinDir].filter((part) => part.length > 0))
|
|
13218
14229
|
);
|
|
@@ -13228,12 +14239,12 @@ function performInstall(projectDir, config, options) {
|
|
|
13228
14239
|
const nightWatchBin = getNightWatchBinPath();
|
|
13229
14240
|
const projectName = getProjectName(projectDir);
|
|
13230
14241
|
const marker = generateMarker(projectName);
|
|
13231
|
-
const logDir =
|
|
13232
|
-
if (!
|
|
13233
|
-
|
|
14242
|
+
const logDir = path32.join(projectDir, LOG_DIR);
|
|
14243
|
+
if (!fs31.existsSync(logDir)) {
|
|
14244
|
+
fs31.mkdirSync(logDir, { recursive: true });
|
|
13234
14245
|
}
|
|
13235
|
-
const executorLog =
|
|
13236
|
-
const reviewerLog =
|
|
14246
|
+
const executorLog = path32.join(logDir, "executor.log");
|
|
14247
|
+
const reviewerLog = path32.join(logDir, "reviewer.log");
|
|
13237
14248
|
if (!options?.force) {
|
|
13238
14249
|
const existingEntries2 = Array.from(
|
|
13239
14250
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
@@ -13250,76 +14261,84 @@ function performInstall(projectDir, config, options) {
|
|
|
13250
14261
|
const entries = [];
|
|
13251
14262
|
const nodeBinDir = getNodeBinDir();
|
|
13252
14263
|
const pathPrefix = buildCronPathPrefix(nodeBinDir, nightWatchBin);
|
|
13253
|
-
const cliBinPrefix = `export NW_CLI_BIN=${
|
|
14264
|
+
const cliBinPrefix = `export NW_CLI_BIN=${shellQuote2(nightWatchBin)} && `;
|
|
13254
14265
|
const cronTriggerPrefix = "export NW_CRON_TRIGGER=1 && ";
|
|
13255
14266
|
let providerEnvPrefix = "";
|
|
13256
14267
|
if (config.providerEnv && Object.keys(config.providerEnv).length > 0) {
|
|
13257
|
-
const exports = Object.entries(config.providerEnv).map(([key, value]) => `export ${key}=${
|
|
14268
|
+
const exports = Object.entries(config.providerEnv).map(([key, value]) => `export ${key}=${shellQuote2(value)}`).join(" && ");
|
|
13258
14269
|
providerEnvPrefix = exports + " && ";
|
|
13259
14270
|
}
|
|
13260
14271
|
const installExecutor = config.executorEnabled !== false;
|
|
13261
14272
|
if (installExecutor) {
|
|
13262
|
-
const executorEntry = `${executorSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14273
|
+
const executorEntry = `${executorSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} run >> ${shellQuote2(executorLog)} 2>&1 ${marker}`;
|
|
13263
14274
|
entries.push(executorEntry);
|
|
13264
14275
|
}
|
|
13265
14276
|
const installReviewer = options?.noReviewer === true ? false : config.reviewerEnabled;
|
|
13266
14277
|
if (installReviewer) {
|
|
13267
|
-
const reviewerEntry = `${reviewerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14278
|
+
const reviewerEntry = `${reviewerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} review >> ${shellQuote2(reviewerLog)} 2>&1 ${marker}`;
|
|
13268
14279
|
entries.push(reviewerEntry);
|
|
13269
14280
|
}
|
|
13270
14281
|
const installSlicer = options?.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
13271
14282
|
if (installSlicer) {
|
|
13272
14283
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
13273
|
-
const slicerLog =
|
|
13274
|
-
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14284
|
+
const slicerLog = path32.join(logDir, "slicer.log");
|
|
14285
|
+
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} planner >> ${shellQuote2(slicerLog)} 2>&1 ${marker}`;
|
|
13275
14286
|
entries.push(slicerEntry);
|
|
13276
14287
|
}
|
|
13277
14288
|
const disableQa = options?.noQa === true || options?.qa === false;
|
|
13278
14289
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
13279
14290
|
if (installQa) {
|
|
13280
14291
|
const qaSchedule = config.qa.schedule;
|
|
13281
|
-
const qaLog =
|
|
13282
|
-
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14292
|
+
const qaLog = path32.join(logDir, "qa.log");
|
|
14293
|
+
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} qa >> ${shellQuote2(qaLog)} 2>&1 ${marker}`;
|
|
13283
14294
|
entries.push(qaEntry);
|
|
13284
14295
|
}
|
|
13285
14296
|
const disableAudit = options?.noAudit === true || options?.audit === false;
|
|
13286
14297
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
13287
14298
|
if (installAudit) {
|
|
13288
14299
|
const auditSchedule = config.audit.schedule;
|
|
13289
|
-
const auditLog =
|
|
13290
|
-
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14300
|
+
const auditLog = path32.join(logDir, "audit.log");
|
|
14301
|
+
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} audit >> ${shellQuote2(auditLog)} 2>&1 ${marker}`;
|
|
13291
14302
|
entries.push(auditEntry);
|
|
13292
14303
|
}
|
|
14304
|
+
const disableUx = options?.noUx === true || options?.ux === false;
|
|
14305
|
+
const installUx = disableUx ? false : config.ux.enabled;
|
|
14306
|
+
if (installUx) {
|
|
14307
|
+
const uxSchedule = config.ux.schedule;
|
|
14308
|
+
const uxLog = path32.join(logDir, "ux.log");
|
|
14309
|
+
const uxEntry = `${uxSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} ux >> ${shellQuote2(uxLog)} 2>&1 ${marker}`;
|
|
14310
|
+
entries.push(uxEntry);
|
|
14311
|
+
}
|
|
13293
14312
|
const disableAnalytics = options?.noAnalytics === true || options?.analytics === false;
|
|
13294
14313
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
13295
14314
|
if (installAnalytics) {
|
|
13296
14315
|
const analyticsSchedule = config.analytics.schedule;
|
|
13297
|
-
const analyticsLog =
|
|
13298
|
-
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14316
|
+
const analyticsLog = path32.join(logDir, "analytics.log");
|
|
14317
|
+
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} analytics >> ${shellQuote2(analyticsLog)} 2>&1 ${marker}`;
|
|
13299
14318
|
entries.push(analyticsEntry);
|
|
13300
14319
|
}
|
|
13301
14320
|
const disablePrResolver = options?.noPrResolver === true || options?.prResolver === false;
|
|
13302
14321
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
13303
14322
|
if (installPrResolver) {
|
|
13304
14323
|
const prResolverSchedule = config.prResolver.schedule;
|
|
13305
|
-
const prResolverLog =
|
|
13306
|
-
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14324
|
+
const prResolverLog = path32.join(logDir, "pr-resolver.log");
|
|
14325
|
+
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} resolve >> ${shellQuote2(prResolverLog)} 2>&1 ${marker}`;
|
|
13307
14326
|
entries.push(prResolverEntry);
|
|
13308
14327
|
}
|
|
13309
14328
|
const disableMerger = options?.noMerger === true || options?.merger === false;
|
|
13310
14329
|
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
13311
14330
|
if (installMerger) {
|
|
13312
14331
|
const mergerSchedule = config.merger.schedule;
|
|
13313
|
-
const mergerLog =
|
|
13314
|
-
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14332
|
+
const mergerLog = path32.join(logDir, "merger.log");
|
|
14333
|
+
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} merge >> ${shellQuote2(mergerLog)} 2>&1 ${marker}`;
|
|
13315
14334
|
entries.push(mergerEntry);
|
|
13316
14335
|
}
|
|
13317
14336
|
const disableManager = options?.noManager === true || options?.manager === false;
|
|
13318
14337
|
const installManager = disableManager ? false : config.manager?.enabled ?? false;
|
|
13319
14338
|
if (installManager) {
|
|
13320
14339
|
const managerSchedule = config.manager.schedule;
|
|
13321
|
-
const managerLog =
|
|
13322
|
-
const managerEntry = `${managerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14340
|
+
const managerLog = path32.join(logDir, `${MANAGER_LOG_NAME}.log`);
|
|
14341
|
+
const managerEntry = `${managerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} manager >> ${shellQuote2(managerLog)} 2>&1 ${marker}`;
|
|
13323
14342
|
entries.push(managerEntry);
|
|
13324
14343
|
}
|
|
13325
14344
|
const existingEntries = new Set(
|
|
@@ -13340,7 +14359,7 @@ function performInstall(projectDir, config, options) {
|
|
|
13340
14359
|
}
|
|
13341
14360
|
}
|
|
13342
14361
|
function installCommand(program2) {
|
|
13343
|
-
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("--no-analytics", "Skip installing analytics cron").option("--no-pr-resolver", "Skip installing PR resolver cron").option("--no-merger", "Skip installing merger cron").option("--no-manager", "Skip installing manager cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
|
|
14362
|
+
program2.command("install").description("Add crontab entries for automated execution").option("-s, --schedule <cron>", "Cron schedule for PRD executor").option("--reviewer-schedule <cron>", "Cron schedule for reviewer").option("--no-reviewer", "Skip installing reviewer cron").option("--no-slicer", "Skip installing slicer cron").option("--no-qa", "Skip installing QA cron").option("--no-audit", "Skip installing audit cron").option("--no-ux", "Skip installing UX cron").option("--no-analytics", "Skip installing analytics cron").option("--no-pr-resolver", "Skip installing PR resolver cron").option("--no-merger", "Skip installing merger cron").option("--no-manager", "Skip installing manager cron").option("-f, --force", "Replace existing cron entries for this project").action(async (options) => {
|
|
13344
14363
|
try {
|
|
13345
14364
|
const projectDir = process.cwd();
|
|
13346
14365
|
const config = loadConfig(projectDir);
|
|
@@ -13349,12 +14368,12 @@ function installCommand(program2) {
|
|
|
13349
14368
|
const nightWatchBin = getNightWatchBinPath();
|
|
13350
14369
|
const projectName = getProjectName(projectDir);
|
|
13351
14370
|
const marker = generateMarker(projectName);
|
|
13352
|
-
const logDir =
|
|
13353
|
-
if (!
|
|
13354
|
-
|
|
14371
|
+
const logDir = path32.join(projectDir, LOG_DIR);
|
|
14372
|
+
if (!fs31.existsSync(logDir)) {
|
|
14373
|
+
fs31.mkdirSync(logDir, { recursive: true });
|
|
13355
14374
|
}
|
|
13356
|
-
const executorLog =
|
|
13357
|
-
const reviewerLog =
|
|
14375
|
+
const executorLog = path32.join(logDir, "executor.log");
|
|
14376
|
+
const reviewerLog = path32.join(logDir, "reviewer.log");
|
|
13358
14377
|
const existingEntries = Array.from(
|
|
13359
14378
|
/* @__PURE__ */ new Set([...getEntries(marker), ...getProjectEntries(projectDir)])
|
|
13360
14379
|
);
|
|
@@ -13370,83 +14389,92 @@ function installCommand(program2) {
|
|
|
13370
14389
|
const entries = [];
|
|
13371
14390
|
const nodeBinDir = getNodeBinDir();
|
|
13372
14391
|
const pathPrefix = buildCronPathPrefix(nodeBinDir, nightWatchBin);
|
|
13373
|
-
const cliBinPrefix = `export NW_CLI_BIN=${
|
|
14392
|
+
const cliBinPrefix = `export NW_CLI_BIN=${shellQuote2(nightWatchBin)} && `;
|
|
13374
14393
|
const cronTriggerPrefix = "export NW_CRON_TRIGGER=1 && ";
|
|
13375
14394
|
let providerEnvPrefix = "";
|
|
13376
14395
|
if (config.providerEnv && Object.keys(config.providerEnv).length > 0) {
|
|
13377
|
-
const exports = Object.entries(config.providerEnv).map(([key, value]) => `export ${key}=${
|
|
14396
|
+
const exports = Object.entries(config.providerEnv).map(([key, value]) => `export ${key}=${shellQuote2(value)}`).join(" && ");
|
|
13378
14397
|
providerEnvPrefix = exports + " && ";
|
|
13379
14398
|
}
|
|
13380
14399
|
const installExecutor = config.executorEnabled !== false;
|
|
13381
14400
|
if (installExecutor) {
|
|
13382
|
-
const executorEntry = `${executorSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14401
|
+
const executorEntry = `${executorSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} run >> ${shellQuote2(executorLog)} 2>&1 ${marker}`;
|
|
13383
14402
|
entries.push(executorEntry);
|
|
13384
14403
|
}
|
|
13385
14404
|
const installReviewer = options.noReviewer === true ? false : config.reviewerEnabled;
|
|
13386
14405
|
if (installReviewer) {
|
|
13387
|
-
const reviewerEntry = `${reviewerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14406
|
+
const reviewerEntry = `${reviewerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} review >> ${shellQuote2(reviewerLog)} 2>&1 ${marker}`;
|
|
13388
14407
|
entries.push(reviewerEntry);
|
|
13389
14408
|
}
|
|
13390
14409
|
const installSlicer = options.noSlicer === true ? false : config.roadmapScanner.enabled;
|
|
13391
14410
|
let slicerLog;
|
|
13392
14411
|
if (installSlicer) {
|
|
13393
|
-
slicerLog =
|
|
14412
|
+
slicerLog = path32.join(logDir, "slicer.log");
|
|
13394
14413
|
const slicerSchedule = config.roadmapScanner.slicerSchedule;
|
|
13395
|
-
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14414
|
+
const slicerEntry = `${slicerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} planner >> ${shellQuote2(slicerLog)} 2>&1 ${marker}`;
|
|
13396
14415
|
entries.push(slicerEntry);
|
|
13397
14416
|
}
|
|
13398
14417
|
const disableQa = options.noQa === true || options.qa === false;
|
|
13399
14418
|
const installQa = disableQa ? false : config.qa.enabled;
|
|
13400
14419
|
let qaLog;
|
|
13401
14420
|
if (installQa) {
|
|
13402
|
-
qaLog =
|
|
14421
|
+
qaLog = path32.join(logDir, "qa.log");
|
|
13403
14422
|
const qaSchedule = config.qa.schedule;
|
|
13404
|
-
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14423
|
+
const qaEntry = `${qaSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} qa >> ${shellQuote2(qaLog)} 2>&1 ${marker}`;
|
|
13405
14424
|
entries.push(qaEntry);
|
|
13406
14425
|
}
|
|
13407
14426
|
const disableAudit = options.noAudit === true || options.audit === false;
|
|
13408
14427
|
const installAudit = disableAudit ? false : config.audit.enabled;
|
|
13409
14428
|
let auditLog;
|
|
13410
14429
|
if (installAudit) {
|
|
13411
|
-
auditLog =
|
|
14430
|
+
auditLog = path32.join(logDir, "audit.log");
|
|
13412
14431
|
const auditSchedule = config.audit.schedule;
|
|
13413
|
-
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14432
|
+
const auditEntry = `${auditSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} audit >> ${shellQuote2(auditLog)} 2>&1 ${marker}`;
|
|
13414
14433
|
entries.push(auditEntry);
|
|
13415
14434
|
}
|
|
14435
|
+
const disableUx = options.noUx === true || options.ux === false;
|
|
14436
|
+
const installUx = disableUx ? false : config.ux.enabled;
|
|
14437
|
+
let uxLog;
|
|
14438
|
+
if (installUx) {
|
|
14439
|
+
uxLog = path32.join(logDir, "ux.log");
|
|
14440
|
+
const uxSchedule = config.ux.schedule;
|
|
14441
|
+
const uxEntry = `${uxSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} ux >> ${shellQuote2(uxLog)} 2>&1 ${marker}`;
|
|
14442
|
+
entries.push(uxEntry);
|
|
14443
|
+
}
|
|
13416
14444
|
const disableAnalytics = options.noAnalytics === true || options.analytics === false;
|
|
13417
14445
|
const installAnalytics = disableAnalytics ? false : config.analytics.enabled;
|
|
13418
14446
|
let analyticsLog;
|
|
13419
14447
|
if (installAnalytics) {
|
|
13420
|
-
analyticsLog =
|
|
14448
|
+
analyticsLog = path32.join(logDir, "analytics.log");
|
|
13421
14449
|
const analyticsSchedule = config.analytics.schedule;
|
|
13422
|
-
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14450
|
+
const analyticsEntry = `${analyticsSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} analytics >> ${shellQuote2(analyticsLog)} 2>&1 ${marker}`;
|
|
13423
14451
|
entries.push(analyticsEntry);
|
|
13424
14452
|
}
|
|
13425
14453
|
const disablePrResolver = options.noPrResolver === true || options.prResolver === false;
|
|
13426
14454
|
const installPrResolver = disablePrResolver ? false : config.prResolver.enabled;
|
|
13427
14455
|
let prResolverLog;
|
|
13428
14456
|
if (installPrResolver) {
|
|
13429
|
-
prResolverLog =
|
|
14457
|
+
prResolverLog = path32.join(logDir, "pr-resolver.log");
|
|
13430
14458
|
const prResolverSchedule = config.prResolver.schedule;
|
|
13431
|
-
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14459
|
+
const prResolverEntry = `${prResolverSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} resolve >> ${shellQuote2(prResolverLog)} 2>&1 ${marker}`;
|
|
13432
14460
|
entries.push(prResolverEntry);
|
|
13433
14461
|
}
|
|
13434
14462
|
const disableMerger = options.noMerger === true || options.merger === false;
|
|
13435
14463
|
const installMerger = disableMerger ? false : config.merger?.enabled ?? false;
|
|
13436
14464
|
let mergerLog;
|
|
13437
14465
|
if (installMerger) {
|
|
13438
|
-
mergerLog =
|
|
14466
|
+
mergerLog = path32.join(logDir, "merger.log");
|
|
13439
14467
|
const mergerSchedule = config.merger.schedule;
|
|
13440
|
-
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14468
|
+
const mergerEntry = `${mergerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} merge >> ${shellQuote2(mergerLog)} 2>&1 ${marker}`;
|
|
13441
14469
|
entries.push(mergerEntry);
|
|
13442
14470
|
}
|
|
13443
14471
|
const disableManager = options.noManager === true || options.manager === false;
|
|
13444
14472
|
const installManager = disableManager ? false : config.manager?.enabled ?? false;
|
|
13445
14473
|
let managerLog;
|
|
13446
14474
|
if (installManager) {
|
|
13447
|
-
managerLog =
|
|
14475
|
+
managerLog = path32.join(logDir, `${MANAGER_LOG_NAME}.log`);
|
|
13448
14476
|
const managerSchedule = config.manager.schedule;
|
|
13449
|
-
const managerEntry = `${managerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${
|
|
14477
|
+
const managerEntry = `${managerSchedule} ${pathPrefix}${providerEnvPrefix}${cliBinPrefix}${cronTriggerPrefix}cd ${shellQuote2(projectDir)} && ${shellQuote2(nightWatchBin)} manager >> ${shellQuote2(managerLog)} 2>&1 ${marker}`;
|
|
13450
14478
|
entries.push(managerEntry);
|
|
13451
14479
|
}
|
|
13452
14480
|
const existingEntrySet = new Set(existingEntries);
|
|
@@ -13475,6 +14503,9 @@ function installCommand(program2) {
|
|
|
13475
14503
|
if (installAudit && auditLog) {
|
|
13476
14504
|
dim(` Audit: ${auditLog}`);
|
|
13477
14505
|
}
|
|
14506
|
+
if (installUx && uxLog) {
|
|
14507
|
+
dim(` UX: ${uxLog}`);
|
|
14508
|
+
}
|
|
13478
14509
|
if (installAnalytics && analyticsLog) {
|
|
13479
14510
|
dim(` Analytics: ${analyticsLog}`);
|
|
13480
14511
|
}
|
|
@@ -13501,8 +14532,8 @@ function installCommand(program2) {
|
|
|
13501
14532
|
|
|
13502
14533
|
// src/commands/uninstall.ts
|
|
13503
14534
|
init_dist();
|
|
13504
|
-
import * as
|
|
13505
|
-
import * as
|
|
14535
|
+
import * as path33 from "path";
|
|
14536
|
+
import * as fs32 from "fs";
|
|
13506
14537
|
function performUninstall(projectDir, options) {
|
|
13507
14538
|
try {
|
|
13508
14539
|
const projectName = getProjectName(projectDir);
|
|
@@ -13517,8 +14548,8 @@ function performUninstall(projectDir, options) {
|
|
|
13517
14548
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
13518
14549
|
unregisterProject(projectDir);
|
|
13519
14550
|
if (!options?.keepLogs) {
|
|
13520
|
-
const logDir =
|
|
13521
|
-
if (
|
|
14551
|
+
const logDir = path33.join(projectDir, "logs");
|
|
14552
|
+
if (fs32.existsSync(logDir)) {
|
|
13522
14553
|
const logFiles = [
|
|
13523
14554
|
"executor.log",
|
|
13524
14555
|
"reviewer.log",
|
|
@@ -13528,15 +14559,15 @@ function performUninstall(projectDir, options) {
|
|
|
13528
14559
|
"manager.log"
|
|
13529
14560
|
];
|
|
13530
14561
|
logFiles.forEach((logFile) => {
|
|
13531
|
-
const logPath =
|
|
13532
|
-
if (
|
|
13533
|
-
|
|
14562
|
+
const logPath = path33.join(logDir, logFile);
|
|
14563
|
+
if (fs32.existsSync(logPath)) {
|
|
14564
|
+
fs32.unlinkSync(logPath);
|
|
13534
14565
|
}
|
|
13535
14566
|
});
|
|
13536
14567
|
try {
|
|
13537
|
-
const remainingFiles =
|
|
14568
|
+
const remainingFiles = fs32.readdirSync(logDir);
|
|
13538
14569
|
if (remainingFiles.length === 0) {
|
|
13539
|
-
|
|
14570
|
+
fs32.rmdirSync(logDir);
|
|
13540
14571
|
}
|
|
13541
14572
|
} catch {
|
|
13542
14573
|
}
|
|
@@ -13569,8 +14600,8 @@ function uninstallCommand(program2) {
|
|
|
13569
14600
|
existingEntries.forEach((entry) => dim(` ${entry}`));
|
|
13570
14601
|
const removedCount = removeEntriesForProject(projectDir, marker);
|
|
13571
14602
|
if (!options.keepLogs) {
|
|
13572
|
-
const logDir =
|
|
13573
|
-
if (
|
|
14603
|
+
const logDir = path33.join(projectDir, "logs");
|
|
14604
|
+
if (fs32.existsSync(logDir)) {
|
|
13574
14605
|
const logFiles = [
|
|
13575
14606
|
"executor.log",
|
|
13576
14607
|
"reviewer.log",
|
|
@@ -13581,16 +14612,16 @@ function uninstallCommand(program2) {
|
|
|
13581
14612
|
];
|
|
13582
14613
|
let logsRemoved = 0;
|
|
13583
14614
|
logFiles.forEach((logFile) => {
|
|
13584
|
-
const logPath =
|
|
13585
|
-
if (
|
|
13586
|
-
|
|
14615
|
+
const logPath = path33.join(logDir, logFile);
|
|
14616
|
+
if (fs32.existsSync(logPath)) {
|
|
14617
|
+
fs32.unlinkSync(logPath);
|
|
13587
14618
|
logsRemoved++;
|
|
13588
14619
|
}
|
|
13589
14620
|
});
|
|
13590
14621
|
try {
|
|
13591
|
-
const remainingFiles =
|
|
14622
|
+
const remainingFiles = fs32.readdirSync(logDir);
|
|
13592
14623
|
if (remainingFiles.length === 0) {
|
|
13593
|
-
|
|
14624
|
+
fs32.rmdirSync(logDir);
|
|
13594
14625
|
}
|
|
13595
14626
|
} catch {
|
|
13596
14627
|
}
|
|
@@ -13633,6 +14664,7 @@ function statusCommand(program2) {
|
|
|
13633
14664
|
const reviewerProc = snapshot.processes.find((p) => p.name === "reviewer");
|
|
13634
14665
|
const qaProc = snapshot.processes.find((p) => p.name === "qa");
|
|
13635
14666
|
const auditProc = snapshot.processes.find((p) => p.name === "audit");
|
|
14667
|
+
const uxProc = snapshot.processes.find((p) => p.name === "ux");
|
|
13636
14668
|
const plannerProc = snapshot.processes.find((p) => p.name === "planner");
|
|
13637
14669
|
const analyticsProc = snapshot.processes.find((p) => p.name === "analytics");
|
|
13638
14670
|
const mergerProc = snapshot.processes.find((p) => p.name === "merger");
|
|
@@ -13640,6 +14672,7 @@ function statusCommand(program2) {
|
|
|
13640
14672
|
const reviewerLog = snapshot.logs.find((l) => l.name === "reviewer");
|
|
13641
14673
|
const qaLog = snapshot.logs.find((l) => l.name === "qa");
|
|
13642
14674
|
const auditLog = snapshot.logs.find((l) => l.name === "audit");
|
|
14675
|
+
const uxLog = snapshot.logs.find((l) => l.name === "ux");
|
|
13643
14676
|
const plannerLog = snapshot.logs.find((l) => l.name === "planner");
|
|
13644
14677
|
const analyticsLog = snapshot.logs.find((l) => l.name === "analytics");
|
|
13645
14678
|
const mergerLog = snapshot.logs.find((l) => l.name === "merger");
|
|
@@ -13659,6 +14692,7 @@ function statusCommand(program2) {
|
|
|
13659
14692
|
reviewer: { running: reviewerProc?.running ?? false, pid: reviewerProc?.pid ?? null },
|
|
13660
14693
|
qa: { running: qaProc?.running ?? false, pid: qaProc?.pid ?? null },
|
|
13661
14694
|
audit: { running: auditProc?.running ?? false, pid: auditProc?.pid ?? null },
|
|
14695
|
+
ux: { running: uxProc?.running ?? false, pid: uxProc?.pid ?? null },
|
|
13662
14696
|
planner: { running: plannerProc?.running ?? false, pid: plannerProc?.pid ?? null },
|
|
13663
14697
|
analytics: { running: analyticsProc?.running ?? false, pid: analyticsProc?.pid ?? null },
|
|
13664
14698
|
merger: { running: mergerProc?.running ?? false, pid: mergerProc?.pid ?? null },
|
|
@@ -13690,6 +14724,12 @@ function statusCommand(program2) {
|
|
|
13690
14724
|
exists: auditLog.exists,
|
|
13691
14725
|
size: auditLog.size
|
|
13692
14726
|
} : void 0,
|
|
14727
|
+
ux: uxLog ? {
|
|
14728
|
+
path: uxLog.path,
|
|
14729
|
+
lastLines: uxLog.lastLines,
|
|
14730
|
+
exists: uxLog.exists,
|
|
14731
|
+
size: uxLog.size
|
|
14732
|
+
} : void 0,
|
|
13693
14733
|
planner: plannerLog ? {
|
|
13694
14734
|
path: plannerLog.path,
|
|
13695
14735
|
lastLines: plannerLog.lastLines,
|
|
@@ -13740,6 +14780,7 @@ function statusCommand(program2) {
|
|
|
13740
14780
|
]);
|
|
13741
14781
|
processTable.push(["QA", formatRunningStatus(status.qa.running, status.qa.pid)]);
|
|
13742
14782
|
processTable.push(["Audit", formatRunningStatus(status.audit.running, status.audit.pid)]);
|
|
14783
|
+
processTable.push(["UX", formatRunningStatus(status.ux.running, status.ux.pid)]);
|
|
13743
14784
|
processTable.push([
|
|
13744
14785
|
"Planner",
|
|
13745
14786
|
formatRunningStatus(status.planner.running, status.planner.pid)
|
|
@@ -13801,6 +14842,13 @@ function statusCommand(program2) {
|
|
|
13801
14842
|
status.logs.audit.exists ? "Exists" : "Not found"
|
|
13802
14843
|
]);
|
|
13803
14844
|
}
|
|
14845
|
+
if (status.logs.ux) {
|
|
14846
|
+
logTable.push([
|
|
14847
|
+
"UX",
|
|
14848
|
+
status.logs.ux.exists ? formatBytes(status.logs.ux.size) : "-",
|
|
14849
|
+
status.logs.ux.exists ? "Exists" : "Not found"
|
|
14850
|
+
]);
|
|
14851
|
+
}
|
|
13804
14852
|
if (status.logs.planner) {
|
|
13805
14853
|
logTable.push([
|
|
13806
14854
|
"Planner",
|
|
@@ -13840,6 +14888,10 @@ function statusCommand(program2) {
|
|
|
13840
14888
|
dim(" Audit last 5 lines:");
|
|
13841
14889
|
status.logs.audit.lastLines.forEach((line) => dim(` ${line}`));
|
|
13842
14890
|
}
|
|
14891
|
+
if (status.logs.ux?.exists && status.logs.ux.lastLines.length > 0) {
|
|
14892
|
+
dim(" UX last 5 lines:");
|
|
14893
|
+
status.logs.ux.lastLines.forEach((line) => dim(` ${line}`));
|
|
14894
|
+
}
|
|
13843
14895
|
if (status.logs.planner?.exists && status.logs.planner.lastLines.length > 0) {
|
|
13844
14896
|
dim(" Planner last 5 lines:");
|
|
13845
14897
|
status.logs.planner.lastLines.forEach((line) => dim(` ${line}`));
|
|
@@ -13860,6 +14912,7 @@ function statusCommand(program2) {
|
|
|
13860
14912
|
dim(" night-watch review - Run reviewer now");
|
|
13861
14913
|
dim(" night-watch qa - Run QA now");
|
|
13862
14914
|
dim(" night-watch audit - Run audit now");
|
|
14915
|
+
dim(" night-watch ux - Run UX agent now");
|
|
13863
14916
|
dim(" night-watch planner - Run planner now");
|
|
13864
14917
|
dim(" night-watch analytics - Run analytics now");
|
|
13865
14918
|
dim(" night-watch merge - Run merger now");
|
|
@@ -13876,14 +14929,14 @@ function statusCommand(program2) {
|
|
|
13876
14929
|
// src/commands/logs.ts
|
|
13877
14930
|
init_dist();
|
|
13878
14931
|
import { spawn as spawn3 } from "child_process";
|
|
13879
|
-
import * as
|
|
13880
|
-
import * as
|
|
14932
|
+
import * as path34 from "path";
|
|
14933
|
+
import * as fs33 from "fs";
|
|
13881
14934
|
function getLastLines(filePath, lineCount) {
|
|
13882
|
-
if (!
|
|
14935
|
+
if (!fs33.existsSync(filePath)) {
|
|
13883
14936
|
return `Log file not found: ${filePath}`;
|
|
13884
14937
|
}
|
|
13885
14938
|
try {
|
|
13886
|
-
const content =
|
|
14939
|
+
const content = fs33.readFileSync(filePath, "utf-8");
|
|
13887
14940
|
const lines = content.trim().split("\n");
|
|
13888
14941
|
return lines.slice(-lineCount).join("\n");
|
|
13889
14942
|
} catch (error2) {
|
|
@@ -13891,7 +14944,7 @@ function getLastLines(filePath, lineCount) {
|
|
|
13891
14944
|
}
|
|
13892
14945
|
}
|
|
13893
14946
|
function followLog(filePath) {
|
|
13894
|
-
if (!
|
|
14947
|
+
if (!fs33.existsSync(filePath)) {
|
|
13895
14948
|
console.log(`Log file not found: ${filePath}`);
|
|
13896
14949
|
console.log("The log file will be created when the first execution runs.");
|
|
13897
14950
|
return;
|
|
@@ -13910,26 +14963,28 @@ function followLog(filePath) {
|
|
|
13910
14963
|
function logsCommand(program2) {
|
|
13911
14964
|
program2.command("logs").description("View night-watch log output").option("-n, --lines <count>", "Number of lines to show", "50").option("-f, --follow", "Follow log output (tail -f)").option(
|
|
13912
14965
|
"-t, --type <type>",
|
|
13913
|
-
"Log type to view (executor|reviewer|qa|audit|planner|analytics|merger|manager|all)",
|
|
14966
|
+
"Log type to view (executor|reviewer|qa|audit|ux|planner|analytics|merger|manager|all)",
|
|
13914
14967
|
"all"
|
|
13915
14968
|
).action(async (options) => {
|
|
13916
14969
|
try {
|
|
13917
14970
|
const projectDir = process.cwd();
|
|
13918
|
-
const logDir =
|
|
14971
|
+
const logDir = path34.join(projectDir, LOG_DIR);
|
|
13919
14972
|
const lineCount = parseInt(options.lines || "50", 10);
|
|
13920
|
-
const executorLog =
|
|
13921
|
-
const reviewerLog =
|
|
13922
|
-
const qaLog =
|
|
13923
|
-
const auditLog =
|
|
13924
|
-
const
|
|
13925
|
-
const
|
|
13926
|
-
const
|
|
13927
|
-
const
|
|
14973
|
+
const executorLog = path34.join(logDir, EXECUTOR_LOG_FILE);
|
|
14974
|
+
const reviewerLog = path34.join(logDir, REVIEWER_LOG_FILE);
|
|
14975
|
+
const qaLog = path34.join(logDir, `${QA_LOG_NAME}.log`);
|
|
14976
|
+
const auditLog = path34.join(logDir, `${AUDIT_LOG_NAME}.log`);
|
|
14977
|
+
const uxLog = path34.join(logDir, `${UX_LOG_NAME}.log`);
|
|
14978
|
+
const plannerLog = path34.join(logDir, `${PLANNER_LOG_NAME}.log`);
|
|
14979
|
+
const analyticsLog = path34.join(logDir, `${ANALYTICS_LOG_NAME}.log`);
|
|
14980
|
+
const mergerLog = path34.join(logDir, `${MERGER_LOG_NAME}.log`);
|
|
14981
|
+
const managerLog = path34.join(logDir, `${MANAGER_LOG_NAME}.log`);
|
|
13928
14982
|
const logType = options.type?.toLowerCase() || "all";
|
|
13929
14983
|
const showExecutor = logType === "all" || logType === "run" || logType === "executor";
|
|
13930
14984
|
const showReviewer = logType === "all" || logType === "review" || logType === "reviewer";
|
|
13931
14985
|
const showQa = logType === "all" || logType === "qa";
|
|
13932
14986
|
const showAudit = logType === "all" || logType === "audit";
|
|
14987
|
+
const showUx = logType === "all" || logType === "ux";
|
|
13933
14988
|
const showPlanner = logType === "all" || logType === "planner" || logType === "slice" || logType === "slicer";
|
|
13934
14989
|
const showAnalytics = logType === "all" || logType === "analytics";
|
|
13935
14990
|
const showMerger = logType === "all" || logType === "merge" || logType === "merger";
|
|
@@ -13937,12 +14992,15 @@ function logsCommand(program2) {
|
|
|
13937
14992
|
if (options.follow) {
|
|
13938
14993
|
if (logType === "all") {
|
|
13939
14994
|
dim("Note: Following all logs is not supported. Showing executor log.");
|
|
13940
|
-
dim(
|
|
14995
|
+
dim(
|
|
14996
|
+
"Use --type reviewer|qa|audit|ux|planner|analytics|merger|manager for other logs.\n"
|
|
14997
|
+
);
|
|
13941
14998
|
}
|
|
13942
14999
|
let targetLog = executorLog;
|
|
13943
15000
|
if (showReviewer) targetLog = reviewerLog;
|
|
13944
15001
|
else if (showQa) targetLog = qaLog;
|
|
13945
15002
|
else if (showAudit) targetLog = auditLog;
|
|
15003
|
+
else if (showUx) targetLog = uxLog;
|
|
13946
15004
|
else if (showPlanner) targetLog = plannerLog;
|
|
13947
15005
|
else if (showAnalytics) targetLog = analyticsLog;
|
|
13948
15006
|
else if (showMerger) targetLog = mergerLog;
|
|
@@ -13975,6 +15033,12 @@ function logsCommand(program2) {
|
|
|
13975
15033
|
console.log();
|
|
13976
15034
|
console.log(getLastLines(auditLog, lineCount));
|
|
13977
15035
|
}
|
|
15036
|
+
if (showUx) {
|
|
15037
|
+
header("UX Log");
|
|
15038
|
+
dim(`File: ${uxLog}`);
|
|
15039
|
+
console.log();
|
|
15040
|
+
console.log(getLastLines(uxLog, lineCount));
|
|
15041
|
+
}
|
|
13978
15042
|
if (showPlanner) {
|
|
13979
15043
|
header("Planner Log");
|
|
13980
15044
|
dim(`File: ${plannerLog}`);
|
|
@@ -14003,7 +15067,7 @@ function logsCommand(program2) {
|
|
|
14003
15067
|
dim("---");
|
|
14004
15068
|
dim("Tip: Use -f to follow logs in real-time");
|
|
14005
15069
|
dim(
|
|
14006
|
-
" Use --type executor|reviewer|qa|audit|planner|analytics|merger|manager to view specific logs"
|
|
15070
|
+
" Use --type executor|reviewer|qa|audit|ux|planner|analytics|merger|manager to view specific logs"
|
|
14007
15071
|
);
|
|
14008
15072
|
} catch (err) {
|
|
14009
15073
|
console.error(`Error reading logs: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -14015,30 +15079,30 @@ function logsCommand(program2) {
|
|
|
14015
15079
|
// src/commands/prd.ts
|
|
14016
15080
|
init_dist();
|
|
14017
15081
|
import { execSync as execSync5, spawn as spawn4, spawnSync } from "child_process";
|
|
14018
|
-
import * as
|
|
14019
|
-
import * as
|
|
15082
|
+
import * as fs34 from "fs";
|
|
15083
|
+
import * as path35 from "path";
|
|
14020
15084
|
import { fileURLToPath as fileURLToPath4 } from "url";
|
|
14021
|
-
import { dirname as
|
|
15085
|
+
import { dirname as dirname11 } from "path";
|
|
14022
15086
|
var __filename3 = fileURLToPath4(import.meta.url);
|
|
14023
|
-
var __dirname3 =
|
|
15087
|
+
var __dirname3 = dirname11(__filename3);
|
|
14024
15088
|
function findTemplatesDir2(startDir) {
|
|
14025
15089
|
let current = startDir;
|
|
14026
15090
|
for (let i = 0; i < 8; i++) {
|
|
14027
|
-
const candidate =
|
|
14028
|
-
if (
|
|
15091
|
+
const candidate = path35.join(current, "templates");
|
|
15092
|
+
if (fs34.existsSync(candidate) && fs34.statSync(candidate).isDirectory()) {
|
|
14029
15093
|
return candidate;
|
|
14030
15094
|
}
|
|
14031
|
-
current =
|
|
15095
|
+
current = path35.dirname(current);
|
|
14032
15096
|
}
|
|
14033
|
-
return
|
|
15097
|
+
return path35.join(startDir, "templates");
|
|
14034
15098
|
}
|
|
14035
15099
|
var TEMPLATES_DIR2 = findTemplatesDir2(__dirname3);
|
|
14036
15100
|
function slugify2(name) {
|
|
14037
15101
|
return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "");
|
|
14038
15102
|
}
|
|
14039
15103
|
function getNextPrdNumber2(prdDir) {
|
|
14040
|
-
if (!
|
|
14041
|
-
const files =
|
|
15104
|
+
if (!fs34.existsSync(prdDir)) return 1;
|
|
15105
|
+
const files = fs34.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
14042
15106
|
const numbers = files.map((f) => {
|
|
14043
15107
|
const match = f.match(/^(\d+)-/);
|
|
14044
15108
|
return match ? parseInt(match[1], 10) : 0;
|
|
@@ -14119,13 +15183,13 @@ function resolveGitHubBlobUrl(projectDir, relPath) {
|
|
|
14119
15183
|
return null;
|
|
14120
15184
|
}
|
|
14121
15185
|
const ref = branch && branch !== "HEAD" ? branch : "main";
|
|
14122
|
-
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(
|
|
15186
|
+
return `${httpsBase}/blob/${encodeURIComponent(ref).replace(/%2F/g, "/")}/${relPath.split(path35.sep).map((segment) => encodeURIComponent(segment)).join("/")}`;
|
|
14123
15187
|
} catch {
|
|
14124
15188
|
return null;
|
|
14125
15189
|
}
|
|
14126
15190
|
}
|
|
14127
15191
|
function buildGithubIssueBody(prdPath, projectDir, prdContent) {
|
|
14128
|
-
const relPath =
|
|
15192
|
+
const relPath = path35.relative(projectDir, prdPath);
|
|
14129
15193
|
const blobUrl = resolveGitHubBlobUrl(projectDir, relPath);
|
|
14130
15194
|
const fileLine = blobUrl ? `PRD file: [\`${relPath}\`](${blobUrl})` : `PRD file: \`${relPath}\``;
|
|
14131
15195
|
return `${fileLine}
|
|
@@ -14136,13 +15200,13 @@ ${prdContent}
|
|
|
14136
15200
|
Created via \`night-watch prd create\`.`;
|
|
14137
15201
|
}
|
|
14138
15202
|
async function generatePrdWithClaude(description, projectDir, model) {
|
|
14139
|
-
const bundledTemplatePath =
|
|
14140
|
-
const installedTemplatePath =
|
|
14141
|
-
const templatePath =
|
|
14142
|
-
if (!
|
|
15203
|
+
const bundledTemplatePath = path35.join(TEMPLATES_DIR2, "prd-creator.md");
|
|
15204
|
+
const installedTemplatePath = path35.join(projectDir, "instructions", "prd-creator.md");
|
|
15205
|
+
const templatePath = fs34.existsSync(installedTemplatePath) ? installedTemplatePath : bundledTemplatePath;
|
|
15206
|
+
if (!fs34.existsSync(templatePath)) {
|
|
14143
15207
|
return null;
|
|
14144
15208
|
}
|
|
14145
|
-
const planningPrinciples =
|
|
15209
|
+
const planningPrinciples = fs34.readFileSync(templatePath, "utf-8");
|
|
14146
15210
|
const prompt = buildPrdPrompt(description, projectDir, planningPrinciples);
|
|
14147
15211
|
const modelId = model ?? CLAUDE_MODEL_IDS.opus;
|
|
14148
15212
|
const env = buildNativeClaudeEnv(process.env);
|
|
@@ -14210,17 +15274,17 @@ function runGh(args, cwd) {
|
|
|
14210
15274
|
return null;
|
|
14211
15275
|
}
|
|
14212
15276
|
function createGithubIssue(title, prdPath, projectDir, prdContent) {
|
|
14213
|
-
const tmpFile =
|
|
15277
|
+
const tmpFile = path35.join(projectDir, `.prd-issue-body-${Date.now()}.tmp`);
|
|
14214
15278
|
try {
|
|
14215
15279
|
const body = buildGithubIssueBody(prdPath, projectDir, prdContent);
|
|
14216
|
-
|
|
15280
|
+
fs34.writeFileSync(tmpFile, body, "utf-8");
|
|
14217
15281
|
const baseArgs = ["issue", "create", "--title", `PRD: ${title}`, "--body-file", tmpFile];
|
|
14218
15282
|
return runGh([...baseArgs, "--label", "prd"], projectDir) ?? runGh(baseArgs, projectDir);
|
|
14219
15283
|
} catch {
|
|
14220
15284
|
return null;
|
|
14221
15285
|
} finally {
|
|
14222
15286
|
try {
|
|
14223
|
-
|
|
15287
|
+
fs34.unlinkSync(tmpFile);
|
|
14224
15288
|
} catch {
|
|
14225
15289
|
}
|
|
14226
15290
|
}
|
|
@@ -14233,10 +15297,10 @@ function parseDependencies(content) {
|
|
|
14233
15297
|
function isClaimActive(claimPath, maxRuntime) {
|
|
14234
15298
|
const claimStaleAfter = maxRuntime > 0 ? maxRuntime : 14400;
|
|
14235
15299
|
try {
|
|
14236
|
-
if (!
|
|
15300
|
+
if (!fs34.existsSync(claimPath)) {
|
|
14237
15301
|
return { active: false };
|
|
14238
15302
|
}
|
|
14239
|
-
const content =
|
|
15303
|
+
const content = fs34.readFileSync(claimPath, "utf-8");
|
|
14240
15304
|
const claim = JSON.parse(content);
|
|
14241
15305
|
const age = Math.floor(Date.now() / 1e3) - claim.timestamp;
|
|
14242
15306
|
if (age < claimStaleAfter) {
|
|
@@ -14251,9 +15315,9 @@ function prdCommand(program2) {
|
|
|
14251
15315
|
const prd = program2.command("prd").description("Manage PRD files");
|
|
14252
15316
|
prd.command("create").description("Generate a new PRD markdown file using Claude").argument("<name>", "PRD description").option("--number", "Add auto-numbering prefix to the filename", false).option("--model <model>", "Claude model to use (e.g. sonnet, opus, or a full model ID)").action(async (name, options) => {
|
|
14253
15317
|
const projectDir = process.cwd();
|
|
14254
|
-
const prdDir =
|
|
14255
|
-
if (!
|
|
14256
|
-
|
|
15318
|
+
const prdDir = path35.join(projectDir, resolvePrdCreateDir());
|
|
15319
|
+
if (!fs34.existsSync(prdDir)) {
|
|
15320
|
+
fs34.mkdirSync(prdDir, { recursive: true });
|
|
14257
15321
|
}
|
|
14258
15322
|
const resolvedModel = options.model ? CLAUDE_MODEL_IDS[options.model] ?? options.model : void 0;
|
|
14259
15323
|
const modelLabel = resolvedModel ?? CLAUDE_MODEL_IDS.opus;
|
|
@@ -14269,13 +15333,13 @@ function prdCommand(program2) {
|
|
|
14269
15333
|
const prdTitle = extractPrdTitle(generated) ?? name;
|
|
14270
15334
|
const slug = slugify2(prdTitle);
|
|
14271
15335
|
const filename = options.number ? `${String(getNextPrdNumber2(prdDir)).padStart(2, "0")}-${slug}.md` : `${slug}.md`;
|
|
14272
|
-
const filePath =
|
|
14273
|
-
if (
|
|
15336
|
+
const filePath = path35.join(prdDir, filename);
|
|
15337
|
+
if (fs34.existsSync(filePath)) {
|
|
14274
15338
|
error(`File already exists: ${filePath}`);
|
|
14275
15339
|
dim("Use a different name or remove the existing file.");
|
|
14276
15340
|
process.exit(1);
|
|
14277
15341
|
}
|
|
14278
|
-
|
|
15342
|
+
fs34.writeFileSync(filePath, generated, "utf-8");
|
|
14279
15343
|
header("PRD Created");
|
|
14280
15344
|
success(`Created: ${filePath}`);
|
|
14281
15345
|
const issueUrl = createGithubIssue(prdTitle, filePath, projectDir, generated);
|
|
@@ -14288,15 +15352,15 @@ function prdCommand(program2) {
|
|
|
14288
15352
|
prd.command("list").description("List all PRDs with status").option("--json", "Output as JSON").action(async (options) => {
|
|
14289
15353
|
const projectDir = process.cwd();
|
|
14290
15354
|
const config = loadConfig(projectDir);
|
|
14291
|
-
const absolutePrdDir =
|
|
14292
|
-
const doneDir =
|
|
15355
|
+
const absolutePrdDir = path35.join(projectDir, config.prdDir);
|
|
15356
|
+
const doneDir = path35.join(absolutePrdDir, "done");
|
|
14293
15357
|
const pending = [];
|
|
14294
|
-
if (
|
|
14295
|
-
const files =
|
|
15358
|
+
if (fs34.existsSync(absolutePrdDir)) {
|
|
15359
|
+
const files = fs34.readdirSync(absolutePrdDir).filter((f) => f.endsWith(".md"));
|
|
14296
15360
|
for (const file of files) {
|
|
14297
|
-
const content =
|
|
15361
|
+
const content = fs34.readFileSync(path35.join(absolutePrdDir, file), "utf-8");
|
|
14298
15362
|
const deps = parseDependencies(content);
|
|
14299
|
-
const claimPath =
|
|
15363
|
+
const claimPath = path35.join(absolutePrdDir, file + CLAIM_FILE_EXTENSION);
|
|
14300
15364
|
const claimStatus = isClaimActive(claimPath, config.maxRuntime);
|
|
14301
15365
|
pending.push({
|
|
14302
15366
|
name: file,
|
|
@@ -14307,10 +15371,10 @@ function prdCommand(program2) {
|
|
|
14307
15371
|
}
|
|
14308
15372
|
}
|
|
14309
15373
|
const done = [];
|
|
14310
|
-
if (
|
|
14311
|
-
const files =
|
|
15374
|
+
if (fs34.existsSync(doneDir)) {
|
|
15375
|
+
const files = fs34.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
14312
15376
|
for (const file of files) {
|
|
14313
|
-
const content =
|
|
15377
|
+
const content = fs34.readFileSync(path35.join(doneDir, file), "utf-8");
|
|
14314
15378
|
const deps = parseDependencies(content);
|
|
14315
15379
|
done.push({ name: file, dependencies: deps });
|
|
14316
15380
|
}
|
|
@@ -14347,7 +15411,7 @@ import blessed6 from "blessed";
|
|
|
14347
15411
|
// src/commands/dashboard/tab-status.ts
|
|
14348
15412
|
init_dist();
|
|
14349
15413
|
import blessed from "blessed";
|
|
14350
|
-
import * as
|
|
15414
|
+
import * as fs35 from "fs";
|
|
14351
15415
|
function sortPrdsByPriority(prds, priority) {
|
|
14352
15416
|
if (priority.length === 0) return prds;
|
|
14353
15417
|
const priorityMap = /* @__PURE__ */ new Map();
|
|
@@ -14443,7 +15507,7 @@ function renderLogPane(projectDir, logs) {
|
|
|
14443
15507
|
let newestMtime = 0;
|
|
14444
15508
|
for (const log of existingLogs) {
|
|
14445
15509
|
try {
|
|
14446
|
-
const stat =
|
|
15510
|
+
const stat = fs35.statSync(log.path);
|
|
14447
15511
|
if (stat.mtimeMs > newestMtime) {
|
|
14448
15512
|
newestMtime = stat.mtimeMs;
|
|
14449
15513
|
newestLog = log;
|
|
@@ -16096,8 +17160,8 @@ function createActionsTab() {
|
|
|
16096
17160
|
// src/commands/dashboard/tab-logs.ts
|
|
16097
17161
|
init_dist();
|
|
16098
17162
|
import blessed5 from "blessed";
|
|
16099
|
-
import * as
|
|
16100
|
-
import * as
|
|
17163
|
+
import * as fs36 from "fs";
|
|
17164
|
+
import * as path36 from "path";
|
|
16101
17165
|
var LOG_NAMES = ["executor", "reviewer"];
|
|
16102
17166
|
var LOG_LINES = 200;
|
|
16103
17167
|
function createLogsTab() {
|
|
@@ -16138,7 +17202,7 @@ function createLogsTab() {
|
|
|
16138
17202
|
let activeKeyHandlers = [];
|
|
16139
17203
|
let activeCtx = null;
|
|
16140
17204
|
function getLogPath(projectDir, logName) {
|
|
16141
|
-
return
|
|
17205
|
+
return path36.join(projectDir, "logs", `${logName}.log`);
|
|
16142
17206
|
}
|
|
16143
17207
|
function updateSelector() {
|
|
16144
17208
|
const tabs = LOG_NAMES.map((name, idx) => {
|
|
@@ -16152,7 +17216,7 @@ function createLogsTab() {
|
|
|
16152
17216
|
function loadLog(ctx) {
|
|
16153
17217
|
const logName = LOG_NAMES[selectedLogIndex];
|
|
16154
17218
|
const logPath = getLogPath(ctx.projectDir, logName);
|
|
16155
|
-
if (!
|
|
17219
|
+
if (!fs36.existsSync(logPath)) {
|
|
16156
17220
|
logContent.setContent(
|
|
16157
17221
|
`{yellow-fg}No ${logName}.log file found{/yellow-fg}
|
|
16158
17222
|
|
|
@@ -16162,7 +17226,7 @@ Log will appear here once the ${logName} runs.`
|
|
|
16162
17226
|
return;
|
|
16163
17227
|
}
|
|
16164
17228
|
try {
|
|
16165
|
-
const stat =
|
|
17229
|
+
const stat = fs36.statSync(logPath);
|
|
16166
17230
|
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
16167
17231
|
logContent.setLabel(`[ ${logName}.log - ${sizeKB} KB ]`);
|
|
16168
17232
|
} catch {
|
|
@@ -16487,6 +17551,15 @@ function dashboardCommand(program2) {
|
|
|
16487
17551
|
// src/commands/doctor.ts
|
|
16488
17552
|
init_dist();
|
|
16489
17553
|
init_dist();
|
|
17554
|
+
function resolveDoctorErrorCategory(configPassed, providerPassed) {
|
|
17555
|
+
if (!configPassed) {
|
|
17556
|
+
return "config";
|
|
17557
|
+
}
|
|
17558
|
+
if (!providerPassed) {
|
|
17559
|
+
return "provider";
|
|
17560
|
+
}
|
|
17561
|
+
return "unknown";
|
|
17562
|
+
}
|
|
16490
17563
|
function runCheck(checkNum, total, checkName, checkFn, options) {
|
|
16491
17564
|
step(checkNum, total, `Checking ${checkName}...`);
|
|
16492
17565
|
const result = checkFn();
|
|
@@ -16605,6 +17678,12 @@ function doctorCommand(program2) {
|
|
|
16605
17678
|
success("All checks passed");
|
|
16606
17679
|
} else {
|
|
16607
17680
|
error("Issues found \u2014 fix errors above before running Night Watch");
|
|
17681
|
+
fireTelemetryEvent("doctor_failed", {
|
|
17682
|
+
command: "doctor",
|
|
17683
|
+
errorCategory: resolveDoctorErrorCategory(configResult.passed, providerResult.passed),
|
|
17684
|
+
success: false,
|
|
17685
|
+
failure: true
|
|
17686
|
+
});
|
|
16608
17687
|
process.exit(1);
|
|
16609
17688
|
}
|
|
16610
17689
|
});
|
|
@@ -16612,13 +17691,13 @@ function doctorCommand(program2) {
|
|
|
16612
17691
|
|
|
16613
17692
|
// src/commands/serve.ts
|
|
16614
17693
|
init_dist();
|
|
16615
|
-
import * as
|
|
17694
|
+
import * as fs41 from "fs";
|
|
16616
17695
|
|
|
16617
17696
|
// ../server/dist/index.js
|
|
16618
17697
|
init_dist();
|
|
16619
|
-
import * as
|
|
16620
|
-
import * as
|
|
16621
|
-
import { dirname as
|
|
17698
|
+
import * as fs40 from "fs";
|
|
17699
|
+
import * as path42 from "path";
|
|
17700
|
+
import { dirname as dirname13 } from "path";
|
|
16622
17701
|
import { fileURLToPath as fileURLToPath5 } from "url";
|
|
16623
17702
|
import cors from "cors";
|
|
16624
17703
|
import express from "express";
|
|
@@ -16702,8 +17781,8 @@ function setupGracefulShutdown(server, beforeClose) {
|
|
|
16702
17781
|
|
|
16703
17782
|
// ../server/dist/middleware/project-resolver.middleware.js
|
|
16704
17783
|
init_dist();
|
|
16705
|
-
import * as
|
|
16706
|
-
import * as
|
|
17784
|
+
import * as fs37 from "fs";
|
|
17785
|
+
import * as path37 from "path";
|
|
16707
17786
|
function resolveProject(req, res, next) {
|
|
16708
17787
|
const projectId = req.params.projectId;
|
|
16709
17788
|
const decodedId = decodeURIComponent(projectId).replace(/~/g, "/");
|
|
@@ -16713,7 +17792,7 @@ function resolveProject(req, res, next) {
|
|
|
16713
17792
|
res.status(404).json({ error: `Project not found: ${decodedId}` });
|
|
16714
17793
|
return;
|
|
16715
17794
|
}
|
|
16716
|
-
if (!
|
|
17795
|
+
if (!fs37.existsSync(entry.path) || !fs37.existsSync(path37.join(entry.path, CONFIG_FILE_NAME))) {
|
|
16717
17796
|
res.status(404).json({ error: `Project path invalid or missing config: ${entry.path}` });
|
|
16718
17797
|
return;
|
|
16719
17798
|
}
|
|
@@ -16792,7 +17871,7 @@ function getExecutorStatus(config, snapshot) {
|
|
|
16792
17871
|
if (snapshot.processes.some((processInfo) => processInfo.running)) {
|
|
16793
17872
|
return "running";
|
|
16794
17873
|
}
|
|
16795
|
-
const executorActive = config.executorEnabled !== false && !config.pausedJobs?.executor;
|
|
17874
|
+
const executorActive = snapshot.crontab.installed && config.executorEnabled !== false && !config.pausedJobs?.executor;
|
|
16796
17875
|
return executorActive ? "active" : "paused";
|
|
16797
17876
|
}
|
|
16798
17877
|
function describeError(error2) {
|
|
@@ -16876,8 +17955,8 @@ function formatProjectStartupSummaryLine(summary, options = {}) {
|
|
|
16876
17955
|
|
|
16877
17956
|
// ../server/dist/routes/action.routes.js
|
|
16878
17957
|
init_dist();
|
|
16879
|
-
import * as
|
|
16880
|
-
import * as
|
|
17958
|
+
import * as fs38 from "fs";
|
|
17959
|
+
import * as path38 from "path";
|
|
16881
17960
|
import { execSync as execSync6, spawn as spawn6 } from "child_process";
|
|
16882
17961
|
import { Router } from "express";
|
|
16883
17962
|
|
|
@@ -16915,17 +17994,17 @@ function getBoardProvider(config, projectDir) {
|
|
|
16915
17994
|
function cleanOrphanedClaims(dir) {
|
|
16916
17995
|
let entries;
|
|
16917
17996
|
try {
|
|
16918
|
-
entries =
|
|
17997
|
+
entries = fs38.readdirSync(dir, { withFileTypes: true });
|
|
16919
17998
|
} catch {
|
|
16920
17999
|
return;
|
|
16921
18000
|
}
|
|
16922
18001
|
for (const entry of entries) {
|
|
16923
|
-
const fullPath =
|
|
18002
|
+
const fullPath = path38.join(dir, entry.name);
|
|
16924
18003
|
if (entry.isDirectory() && entry.name !== "done") {
|
|
16925
18004
|
cleanOrphanedClaims(fullPath);
|
|
16926
18005
|
} else if (entry.name.endsWith(CLAIM_FILE_EXTENSION)) {
|
|
16927
18006
|
try {
|
|
16928
|
-
|
|
18007
|
+
fs38.unlinkSync(fullPath);
|
|
16929
18008
|
} catch {
|
|
16930
18009
|
}
|
|
16931
18010
|
}
|
|
@@ -17080,19 +18159,19 @@ function createActionRouteHandlers(ctx) {
|
|
|
17080
18159
|
res.status(400).json({ error: "Invalid PRD name" });
|
|
17081
18160
|
return;
|
|
17082
18161
|
}
|
|
17083
|
-
const prdDir =
|
|
18162
|
+
const prdDir = path38.join(projectDir, config.prdDir);
|
|
17084
18163
|
const normalized = prdName.endsWith(".md") ? prdName : `${prdName}.md`;
|
|
17085
|
-
const pendingPath =
|
|
17086
|
-
const donePath =
|
|
17087
|
-
if (
|
|
18164
|
+
const pendingPath = path38.join(prdDir, normalized);
|
|
18165
|
+
const donePath = path38.join(prdDir, "done", normalized);
|
|
18166
|
+
if (fs38.existsSync(pendingPath)) {
|
|
17088
18167
|
res.json({ message: `"${normalized}" is already pending` });
|
|
17089
18168
|
return;
|
|
17090
18169
|
}
|
|
17091
|
-
if (!
|
|
18170
|
+
if (!fs38.existsSync(donePath)) {
|
|
17092
18171
|
res.status(404).json({ error: `PRD "${normalized}" not found in done/` });
|
|
17093
18172
|
return;
|
|
17094
18173
|
}
|
|
17095
|
-
|
|
18174
|
+
fs38.renameSync(donePath, pendingPath);
|
|
17096
18175
|
res.json({ message: `Moved "${normalized}" back to pending` });
|
|
17097
18176
|
} catch (error2) {
|
|
17098
18177
|
res.status(500).json({
|
|
@@ -17110,11 +18189,11 @@ function createActionRouteHandlers(ctx) {
|
|
|
17110
18189
|
res.status(409).json({ error: "Executor is actively running \u2014 use Stop instead" });
|
|
17111
18190
|
return;
|
|
17112
18191
|
}
|
|
17113
|
-
if (
|
|
17114
|
-
|
|
18192
|
+
if (fs38.existsSync(lockPath)) {
|
|
18193
|
+
fs38.unlinkSync(lockPath);
|
|
17115
18194
|
}
|
|
17116
|
-
const prdDir =
|
|
17117
|
-
if (
|
|
18195
|
+
const prdDir = path38.join(projectDir, config.prdDir);
|
|
18196
|
+
if (fs38.existsSync(prdDir)) {
|
|
17118
18197
|
cleanOrphanedClaims(prdDir);
|
|
17119
18198
|
}
|
|
17120
18199
|
broadcastSSE(ctx.getSseClients(req), "status_changed", await fetchStatusSnapshot(projectDir, config));
|
|
@@ -17485,6 +18564,12 @@ function validateConfigChanges(changes, currentConfig) {
|
|
|
17485
18564
|
if (changes.autoMerge !== void 0 && typeof changes.autoMerge !== "boolean") {
|
|
17486
18565
|
return "autoMerge must be a boolean";
|
|
17487
18566
|
}
|
|
18567
|
+
if (changes.modelAttribution !== void 0 && typeof changes.modelAttribution !== "boolean") {
|
|
18568
|
+
return "modelAttribution must be a boolean";
|
|
18569
|
+
}
|
|
18570
|
+
if (changes.newPrLabel !== void 0 && typeof changes.newPrLabel !== "string") {
|
|
18571
|
+
return "newPrLabel must be a string";
|
|
18572
|
+
}
|
|
17488
18573
|
if (changes.autoMergeMethod !== void 0) {
|
|
17489
18574
|
const validMethods = ["squash", "merge", "rebase"];
|
|
17490
18575
|
if (!validMethods.includes(changes.autoMergeMethod)) {
|
|
@@ -17882,8 +18967,8 @@ function createProjectConfigRoutes() {
|
|
|
17882
18967
|
|
|
17883
18968
|
// ../server/dist/routes/doctor.routes.js
|
|
17884
18969
|
init_dist();
|
|
17885
|
-
import * as
|
|
17886
|
-
import * as
|
|
18970
|
+
import * as fs39 from "fs";
|
|
18971
|
+
import * as path39 from "path";
|
|
17887
18972
|
import { execSync as execSync7 } from "child_process";
|
|
17888
18973
|
import { Router as Router4 } from "express";
|
|
17889
18974
|
function runDoctorChecks(projectDir, config) {
|
|
@@ -17916,7 +19001,7 @@ function runDoctorChecks(projectDir, config) {
|
|
|
17916
19001
|
});
|
|
17917
19002
|
}
|
|
17918
19003
|
try {
|
|
17919
|
-
const projectName =
|
|
19004
|
+
const projectName = path39.basename(projectDir);
|
|
17920
19005
|
const marker = generateMarker(projectName);
|
|
17921
19006
|
const crontabEntries = [...getEntries(marker), ...getProjectEntries(projectDir)];
|
|
17922
19007
|
if (crontabEntries.length > 0) {
|
|
@@ -17939,8 +19024,8 @@ function runDoctorChecks(projectDir, config) {
|
|
|
17939
19024
|
detail: "Failed to check crontab"
|
|
17940
19025
|
});
|
|
17941
19026
|
}
|
|
17942
|
-
const configPath =
|
|
17943
|
-
if (
|
|
19027
|
+
const configPath = path39.join(projectDir, CONFIG_FILE_NAME);
|
|
19028
|
+
if (fs39.existsSync(configPath)) {
|
|
17944
19029
|
checks.push({ name: "config", status: "pass", detail: "Config file exists" });
|
|
17945
19030
|
} else {
|
|
17946
19031
|
checks.push({
|
|
@@ -17949,9 +19034,9 @@ function runDoctorChecks(projectDir, config) {
|
|
|
17949
19034
|
detail: "Config file not found (using defaults)"
|
|
17950
19035
|
});
|
|
17951
19036
|
}
|
|
17952
|
-
const prdDir =
|
|
17953
|
-
if (
|
|
17954
|
-
const prds =
|
|
19037
|
+
const prdDir = path39.join(projectDir, config.prdDir);
|
|
19038
|
+
if (fs39.existsSync(prdDir)) {
|
|
19039
|
+
const prds = fs39.readdirSync(prdDir).filter((f) => f.endsWith(".md"));
|
|
17955
19040
|
checks.push({
|
|
17956
19041
|
name: "prdDir",
|
|
17957
19042
|
status: "pass",
|
|
@@ -18201,7 +19286,7 @@ function createProjectFeedbackRoutes() {
|
|
|
18201
19286
|
// ../server/dist/routes/job.routes.js
|
|
18202
19287
|
init_dist();
|
|
18203
19288
|
import { spawn as spawn7 } from "child_process";
|
|
18204
|
-
import { createHmac, randomUUID, timingSafeEqual } from "crypto";
|
|
19289
|
+
import { createHmac, randomUUID as randomUUID2, timingSafeEqual } from "crypto";
|
|
18205
19290
|
import { Router as Router6 } from "express";
|
|
18206
19291
|
var SUPPORTED_GITHUB_EVENTS = [
|
|
18207
19292
|
"workflow_run",
|
|
@@ -18383,6 +19468,8 @@ function getLockPathForJob2(projectDir, jobId) {
|
|
|
18383
19468
|
return qaLockPath(projectDir);
|
|
18384
19469
|
case "audit":
|
|
18385
19470
|
return auditLockPath(projectDir);
|
|
19471
|
+
case "ux":
|
|
19472
|
+
return uxLockPath(projectDir);
|
|
18386
19473
|
case "slicer":
|
|
18387
19474
|
case "planner":
|
|
18388
19475
|
return plannerLockPath(projectDir);
|
|
@@ -18471,7 +19558,7 @@ function createJobRouteHandlers(ctx) {
|
|
|
18471
19558
|
});
|
|
18472
19559
|
return;
|
|
18473
19560
|
}
|
|
18474
|
-
const dispatchId =
|
|
19561
|
+
const dispatchId = randomUUID2();
|
|
18475
19562
|
const child = spawn7("night-watch", [jobDef.cliCommand], {
|
|
18476
19563
|
detached: true,
|
|
18477
19564
|
stdio: "ignore",
|
|
@@ -18520,7 +19607,7 @@ function createProjectJobRoutes() {
|
|
|
18520
19607
|
|
|
18521
19608
|
// ../server/dist/routes/log.routes.js
|
|
18522
19609
|
init_dist();
|
|
18523
|
-
import * as
|
|
19610
|
+
import * as path40 from "path";
|
|
18524
19611
|
import { Router as Router7 } from "express";
|
|
18525
19612
|
function createLogRoutes(deps) {
|
|
18526
19613
|
const { projectDir } = deps;
|
|
@@ -18539,7 +19626,7 @@ function createLogRoutes(deps) {
|
|
|
18539
19626
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
18540
19627
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
18541
19628
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
18542
|
-
const logPath =
|
|
19629
|
+
const logPath = path40.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
18543
19630
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
18544
19631
|
res.json({ name, lines: logLines });
|
|
18545
19632
|
} catch (error2) {
|
|
@@ -18565,7 +19652,7 @@ function createProjectLogRoutes() {
|
|
|
18565
19652
|
const lines = typeof linesParam === "string" ? parseInt(linesParam, 10) : 200;
|
|
18566
19653
|
const linesToRead = isNaN(lines) || lines < 1 ? 200 : Math.min(lines, 1e4);
|
|
18567
19654
|
const fileName = LOG_FILE_NAMES[name] || name;
|
|
18568
|
-
const logPath =
|
|
19655
|
+
const logPath = path40.join(projectDir, LOG_DIR, `${fileName}.log`);
|
|
18569
19656
|
const logLines = getLastLogLines(logPath, linesToRead);
|
|
18570
19657
|
res.json({ name, lines: logLines });
|
|
18571
19658
|
} catch (error2) {
|
|
@@ -18600,7 +19687,7 @@ function createProjectPrdRoutes() {
|
|
|
18600
19687
|
|
|
18601
19688
|
// ../server/dist/routes/roadmap.routes.js
|
|
18602
19689
|
init_dist();
|
|
18603
|
-
import * as
|
|
19690
|
+
import * as path41 from "path";
|
|
18604
19691
|
import { Router as Router9 } from "express";
|
|
18605
19692
|
function createRoadmapRouteHandlers(ctx) {
|
|
18606
19693
|
const router = Router9({ mergeParams: true });
|
|
@@ -18610,7 +19697,7 @@ function createRoadmapRouteHandlers(ctx) {
|
|
|
18610
19697
|
const config = ctx.getConfig(req);
|
|
18611
19698
|
const projectDir = ctx.getProjectDir(req);
|
|
18612
19699
|
const status = getRoadmapStatus(projectDir, config);
|
|
18613
|
-
const prdDir =
|
|
19700
|
+
const prdDir = path41.join(projectDir, config.prdDir);
|
|
18614
19701
|
const state = loadRoadmapState(prdDir);
|
|
18615
19702
|
res.json({
|
|
18616
19703
|
...status,
|
|
@@ -18733,6 +19820,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
18733
19820
|
const reviewerPlan = getSchedulingPlan(projectDir, config, "reviewer");
|
|
18734
19821
|
const qaPlan = getSchedulingPlan(projectDir, config, "qa");
|
|
18735
19822
|
const auditPlan = getSchedulingPlan(projectDir, config, "audit");
|
|
19823
|
+
const uxPlan = getSchedulingPlan(projectDir, config, "ux");
|
|
18736
19824
|
const plannerPlan = getSchedulingPlan(projectDir, config, "slicer");
|
|
18737
19825
|
const analyticsPlan = getSchedulingPlan(projectDir, config, "analytics");
|
|
18738
19826
|
const prResolverPlan = getSchedulingPlan(projectDir, config, "pr-resolver");
|
|
@@ -18742,6 +19830,7 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
18742
19830
|
const reviewerInstalled = installed && config.reviewerEnabled && hasScheduledCommand(entries, "review");
|
|
18743
19831
|
const qaInstalled = installed && config.qa.enabled && hasScheduledCommand(entries, "qa");
|
|
18744
19832
|
const auditInstalled = installed && config.audit.enabled && hasScheduledCommand(entries, "audit");
|
|
19833
|
+
const uxInstalled = installed && config.ux.enabled && hasScheduledCommand(entries, "ux");
|
|
18745
19834
|
const plannerInstalled = installed && config.roadmapScanner.enabled && (hasScheduledCommand(entries, "planner") || hasScheduledCommand(entries, "slice"));
|
|
18746
19835
|
const analyticsInstalled = installed && config.analytics.enabled && hasScheduledCommand(entries, "analytics");
|
|
18747
19836
|
const prResolverInstalled = installed && (config.prResolver?.enabled ?? true) && hasScheduledCommand(entries, "resolve");
|
|
@@ -18780,6 +19869,14 @@ function buildScheduleInfoResponse(projectDir, config, entries, installed) {
|
|
|
18780
19869
|
manualDelayMinutes: auditPlan.manualDelayMinutes,
|
|
18781
19870
|
balancedDelayMinutes: auditPlan.balancedDelayMinutes
|
|
18782
19871
|
},
|
|
19872
|
+
ux: {
|
|
19873
|
+
schedule: config.ux.schedule,
|
|
19874
|
+
installed: uxInstalled,
|
|
19875
|
+
nextRun: uxInstalled ? addDelayToIsoString(computeNextRun(config.ux.schedule), uxPlan.totalDelayMinutes) : null,
|
|
19876
|
+
delayMinutes: uxPlan.totalDelayMinutes,
|
|
19877
|
+
manualDelayMinutes: uxPlan.manualDelayMinutes,
|
|
19878
|
+
balancedDelayMinutes: uxPlan.balancedDelayMinutes
|
|
19879
|
+
},
|
|
18783
19880
|
planner: {
|
|
18784
19881
|
schedule: config.roadmapScanner.slicerSchedule,
|
|
18785
19882
|
installed: plannerInstalled,
|
|
@@ -18972,31 +20069,31 @@ function createQueueRoutes(deps) {
|
|
|
18972
20069
|
|
|
18973
20070
|
// ../server/dist/index.js
|
|
18974
20071
|
var __filename4 = fileURLToPath5(import.meta.url);
|
|
18975
|
-
var __dirname4 =
|
|
20072
|
+
var __dirname4 = dirname13(__filename4);
|
|
18976
20073
|
var JOB_RAW_BODY_LIMIT = "1mb";
|
|
18977
20074
|
function setupJobRawBodyParsing(app) {
|
|
18978
20075
|
app.use("/api/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
|
|
18979
20076
|
app.use("/api/projects/:projectId/jobs", express.raw({ type: "*/*", limit: JOB_RAW_BODY_LIMIT }));
|
|
18980
20077
|
}
|
|
18981
20078
|
function resolveWebDistPath() {
|
|
18982
|
-
const bundled =
|
|
18983
|
-
if (
|
|
20079
|
+
const bundled = path42.join(__dirname4, "web");
|
|
20080
|
+
if (fs40.existsSync(path42.join(bundled, "index.html")))
|
|
18984
20081
|
return bundled;
|
|
18985
20082
|
let d = __dirname4;
|
|
18986
20083
|
for (let i = 0; i < 8; i++) {
|
|
18987
|
-
if (
|
|
18988
|
-
const dev =
|
|
18989
|
-
if (
|
|
20084
|
+
if (fs40.existsSync(path42.join(d, "turbo.json"))) {
|
|
20085
|
+
const dev = path42.join(d, "web/dist");
|
|
20086
|
+
if (fs40.existsSync(path42.join(dev, "index.html")))
|
|
18990
20087
|
return dev;
|
|
18991
20088
|
break;
|
|
18992
20089
|
}
|
|
18993
|
-
d =
|
|
20090
|
+
d = dirname13(d);
|
|
18994
20091
|
}
|
|
18995
20092
|
return bundled;
|
|
18996
20093
|
}
|
|
18997
20094
|
function setupStaticFiles(app) {
|
|
18998
20095
|
const webDistPath = resolveWebDistPath();
|
|
18999
|
-
if (
|
|
20096
|
+
if (fs40.existsSync(webDistPath)) {
|
|
19000
20097
|
app.use(express.static(webDistPath));
|
|
19001
20098
|
}
|
|
19002
20099
|
app.use((req, res, next) => {
|
|
@@ -19004,8 +20101,8 @@ function setupStaticFiles(app) {
|
|
|
19004
20101
|
next();
|
|
19005
20102
|
return;
|
|
19006
20103
|
}
|
|
19007
|
-
const indexPath =
|
|
19008
|
-
if (
|
|
20104
|
+
const indexPath = path42.resolve(webDistPath, "index.html");
|
|
20105
|
+
if (fs40.existsSync(indexPath)) {
|
|
19009
20106
|
res.sendFile(indexPath, (err) => {
|
|
19010
20107
|
if (err)
|
|
19011
20108
|
next();
|
|
@@ -19144,7 +20241,7 @@ function createGlobalApp() {
|
|
|
19144
20241
|
return app;
|
|
19145
20242
|
}
|
|
19146
20243
|
function bootContainer() {
|
|
19147
|
-
initContainer(
|
|
20244
|
+
initContainer(path42.dirname(getDbPath()));
|
|
19148
20245
|
}
|
|
19149
20246
|
function startServer(projectDir, port) {
|
|
19150
20247
|
bootContainer();
|
|
@@ -19199,8 +20296,8 @@ function isProcessRunning2(pid) {
|
|
|
19199
20296
|
}
|
|
19200
20297
|
function readPid(lockPath) {
|
|
19201
20298
|
try {
|
|
19202
|
-
if (!
|
|
19203
|
-
const raw =
|
|
20299
|
+
if (!fs41.existsSync(lockPath)) return null;
|
|
20300
|
+
const raw = fs41.readFileSync(lockPath, "utf-8").trim();
|
|
19204
20301
|
const pid = parseInt(raw, 10);
|
|
19205
20302
|
return Number.isFinite(pid) ? pid : null;
|
|
19206
20303
|
} catch {
|
|
@@ -19212,10 +20309,10 @@ function acquireServeLock(mode, port) {
|
|
|
19212
20309
|
let stalePidCleaned;
|
|
19213
20310
|
for (let attempt = 0; attempt < 2; attempt++) {
|
|
19214
20311
|
try {
|
|
19215
|
-
const fd =
|
|
19216
|
-
|
|
20312
|
+
const fd = fs41.openSync(lockPath, "wx");
|
|
20313
|
+
fs41.writeFileSync(fd, `${process.pid}
|
|
19217
20314
|
`);
|
|
19218
|
-
|
|
20315
|
+
fs41.closeSync(fd);
|
|
19219
20316
|
return { acquired: true, lockPath, stalePidCleaned };
|
|
19220
20317
|
} catch (error2) {
|
|
19221
20318
|
const err = error2;
|
|
@@ -19236,7 +20333,7 @@ function acquireServeLock(mode, port) {
|
|
|
19236
20333
|
};
|
|
19237
20334
|
}
|
|
19238
20335
|
try {
|
|
19239
|
-
|
|
20336
|
+
fs41.unlinkSync(lockPath);
|
|
19240
20337
|
if (existingPid) {
|
|
19241
20338
|
stalePidCleaned = existingPid;
|
|
19242
20339
|
}
|
|
@@ -19259,10 +20356,10 @@ function acquireServeLock(mode, port) {
|
|
|
19259
20356
|
}
|
|
19260
20357
|
function releaseServeLock(lockPath) {
|
|
19261
20358
|
try {
|
|
19262
|
-
if (!
|
|
20359
|
+
if (!fs41.existsSync(lockPath)) return;
|
|
19263
20360
|
const lockPid = readPid(lockPath);
|
|
19264
20361
|
if (lockPid !== null && lockPid !== process.pid) return;
|
|
19265
|
-
|
|
20362
|
+
fs41.unlinkSync(lockPath);
|
|
19266
20363
|
} catch {
|
|
19267
20364
|
}
|
|
19268
20365
|
}
|
|
@@ -19364,14 +20461,14 @@ function historyCommand(program2) {
|
|
|
19364
20461
|
// src/commands/update.ts
|
|
19365
20462
|
init_dist();
|
|
19366
20463
|
import { spawnSync as spawnSync2 } from "child_process";
|
|
19367
|
-
import * as
|
|
19368
|
-
import * as
|
|
20464
|
+
import * as fs42 from "fs";
|
|
20465
|
+
import * as path43 from "path";
|
|
19369
20466
|
var DEFAULT_GLOBAL_SPEC = "@jonit-dev/night-watch-cli@latest";
|
|
19370
20467
|
function parseProjectDirs(projects, cwd) {
|
|
19371
20468
|
if (!projects || projects.trim().length === 0) {
|
|
19372
20469
|
return [cwd];
|
|
19373
20470
|
}
|
|
19374
|
-
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) =>
|
|
20471
|
+
const dirs = projects.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0).map((entry) => path43.resolve(cwd, entry));
|
|
19375
20472
|
return Array.from(new Set(dirs));
|
|
19376
20473
|
}
|
|
19377
20474
|
function shouldInstallGlobal(options) {
|
|
@@ -19413,7 +20510,7 @@ function updateCommand(program2) {
|
|
|
19413
20510
|
}
|
|
19414
20511
|
const nightWatchBin = resolveNightWatchBin();
|
|
19415
20512
|
for (const projectDir of projectDirs) {
|
|
19416
|
-
if (!
|
|
20513
|
+
if (!fs42.existsSync(projectDir) || !fs42.statSync(projectDir).isDirectory()) {
|
|
19417
20514
|
warn(`Skipping invalid project directory: ${projectDir}`);
|
|
19418
20515
|
continue;
|
|
19419
20516
|
}
|
|
@@ -19457,8 +20554,8 @@ function prdStateCommand(program2) {
|
|
|
19457
20554
|
|
|
19458
20555
|
// src/commands/retry.ts
|
|
19459
20556
|
init_dist();
|
|
19460
|
-
import * as
|
|
19461
|
-
import * as
|
|
20557
|
+
import * as fs43 from "fs";
|
|
20558
|
+
import * as path44 from "path";
|
|
19462
20559
|
function normalizePrdName(name) {
|
|
19463
20560
|
if (!name.endsWith(".md")) {
|
|
19464
20561
|
return `${name}.md`;
|
|
@@ -19466,26 +20563,26 @@ function normalizePrdName(name) {
|
|
|
19466
20563
|
return name;
|
|
19467
20564
|
}
|
|
19468
20565
|
function getDonePrds(doneDir) {
|
|
19469
|
-
if (!
|
|
20566
|
+
if (!fs43.existsSync(doneDir)) {
|
|
19470
20567
|
return [];
|
|
19471
20568
|
}
|
|
19472
|
-
return
|
|
20569
|
+
return fs43.readdirSync(doneDir).filter((f) => f.endsWith(".md"));
|
|
19473
20570
|
}
|
|
19474
20571
|
function retryCommand(program2) {
|
|
19475
20572
|
program2.command("retry <prdName>").description("Move a completed PRD from done/ back to pending").action((prdName) => {
|
|
19476
20573
|
const projectDir = process.cwd();
|
|
19477
20574
|
const config = loadConfig(projectDir);
|
|
19478
|
-
const prdDir =
|
|
19479
|
-
const doneDir =
|
|
20575
|
+
const prdDir = path44.join(projectDir, config.prdDir);
|
|
20576
|
+
const doneDir = path44.join(prdDir, "done");
|
|
19480
20577
|
const normalizedPrdName = normalizePrdName(prdName);
|
|
19481
|
-
const pendingPath =
|
|
19482
|
-
if (
|
|
20578
|
+
const pendingPath = path44.join(prdDir, normalizedPrdName);
|
|
20579
|
+
if (fs43.existsSync(pendingPath)) {
|
|
19483
20580
|
info(`"${normalizedPrdName}" is already pending, nothing to retry.`);
|
|
19484
20581
|
return;
|
|
19485
20582
|
}
|
|
19486
|
-
const donePath =
|
|
19487
|
-
if (
|
|
19488
|
-
|
|
20583
|
+
const donePath = path44.join(doneDir, normalizedPrdName);
|
|
20584
|
+
if (fs43.existsSync(donePath)) {
|
|
20585
|
+
fs43.renameSync(donePath, pendingPath);
|
|
19489
20586
|
success(`Moved "${normalizedPrdName}" back to pending.`);
|
|
19490
20587
|
dim(`From: ${donePath}`);
|
|
19491
20588
|
dim(`To: ${pendingPath}`);
|
|
@@ -19737,7 +20834,7 @@ function prdsCommand(program2) {
|
|
|
19737
20834
|
|
|
19738
20835
|
// src/commands/cancel.ts
|
|
19739
20836
|
init_dist();
|
|
19740
|
-
import * as
|
|
20837
|
+
import * as fs44 from "fs";
|
|
19741
20838
|
import * as readline2 from "readline";
|
|
19742
20839
|
function getLockFilePaths2(projectDir) {
|
|
19743
20840
|
const runtimeKey = projectRuntimeKey(projectDir);
|
|
@@ -19784,7 +20881,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
19784
20881
|
const pid = lockStatus.pid;
|
|
19785
20882
|
if (!lockStatus.running) {
|
|
19786
20883
|
try {
|
|
19787
|
-
|
|
20884
|
+
fs44.unlinkSync(lockPath);
|
|
19788
20885
|
return {
|
|
19789
20886
|
success: true,
|
|
19790
20887
|
message: `${processType} is not running (cleaned up stale lock file for PID ${pid})`,
|
|
@@ -19822,7 +20919,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
19822
20919
|
await sleep2(3e3);
|
|
19823
20920
|
if (!isProcessRunning3(pid)) {
|
|
19824
20921
|
try {
|
|
19825
|
-
|
|
20922
|
+
fs44.unlinkSync(lockPath);
|
|
19826
20923
|
} catch {
|
|
19827
20924
|
}
|
|
19828
20925
|
return {
|
|
@@ -19857,7 +20954,7 @@ async function cancelProcess2(processType, lockPath, force = false) {
|
|
|
19857
20954
|
await sleep2(500);
|
|
19858
20955
|
if (!isProcessRunning3(pid)) {
|
|
19859
20956
|
try {
|
|
19860
|
-
|
|
20957
|
+
fs44.unlinkSync(lockPath);
|
|
19861
20958
|
} catch {
|
|
19862
20959
|
}
|
|
19863
20960
|
return {
|
|
@@ -19918,31 +21015,31 @@ function cancelCommand(program2) {
|
|
|
19918
21015
|
|
|
19919
21016
|
// src/commands/slice.ts
|
|
19920
21017
|
init_dist();
|
|
19921
|
-
import * as
|
|
19922
|
-
import * as
|
|
21018
|
+
import * as fs45 from "fs";
|
|
21019
|
+
import * as path45 from "path";
|
|
19923
21020
|
function plannerLockPath2(projectDir) {
|
|
19924
21021
|
return `${LOCK_FILE_PREFIX}slicer-${projectRuntimeKey(projectDir)}.lock`;
|
|
19925
21022
|
}
|
|
19926
21023
|
function acquirePlannerLock(projectDir) {
|
|
19927
21024
|
const lockFile = plannerLockPath2(projectDir);
|
|
19928
|
-
if (
|
|
19929
|
-
const pidRaw =
|
|
21025
|
+
if (fs45.existsSync(lockFile)) {
|
|
21026
|
+
const pidRaw = fs45.readFileSync(lockFile, "utf-8").trim();
|
|
19930
21027
|
const pid = parseInt(pidRaw, 10);
|
|
19931
21028
|
if (!Number.isNaN(pid) && isProcessRunning(pid)) {
|
|
19932
21029
|
return { acquired: false, lockFile, pid };
|
|
19933
21030
|
}
|
|
19934
21031
|
try {
|
|
19935
|
-
|
|
21032
|
+
fs45.unlinkSync(lockFile);
|
|
19936
21033
|
} catch {
|
|
19937
21034
|
}
|
|
19938
21035
|
}
|
|
19939
|
-
|
|
21036
|
+
fs45.writeFileSync(lockFile, String(process.pid));
|
|
19940
21037
|
return { acquired: true, lockFile };
|
|
19941
21038
|
}
|
|
19942
21039
|
function releasePlannerLock(lockFile) {
|
|
19943
21040
|
try {
|
|
19944
|
-
if (
|
|
19945
|
-
|
|
21041
|
+
if (fs45.existsSync(lockFile)) {
|
|
21042
|
+
fs45.unlinkSync(lockFile);
|
|
19946
21043
|
}
|
|
19947
21044
|
} catch {
|
|
19948
21045
|
}
|
|
@@ -19951,12 +21048,12 @@ function resolvePlannerIssueColumn(config) {
|
|
|
19951
21048
|
return config.roadmapScanner.issueColumn === "Draft" ? "Draft" : "Ready";
|
|
19952
21049
|
}
|
|
19953
21050
|
function buildPlannerIssueBody(projectDir, config, result) {
|
|
19954
|
-
const relativePrdPath =
|
|
19955
|
-
const absolutePrdPath =
|
|
21051
|
+
const relativePrdPath = path45.join(config.prdDir, result.file ?? "").replace(/\\/g, "/");
|
|
21052
|
+
const absolutePrdPath = path45.join(projectDir, config.prdDir, result.file ?? "");
|
|
19956
21053
|
const sourceItem = result.item;
|
|
19957
21054
|
let prdContent;
|
|
19958
21055
|
try {
|
|
19959
|
-
prdContent =
|
|
21056
|
+
prdContent = fs45.readFileSync(absolutePrdPath, "utf-8");
|
|
19960
21057
|
} catch {
|
|
19961
21058
|
prdContent = `Unable to read generated PRD file at \`${relativePrdPath}\`.`;
|
|
19962
21059
|
}
|
|
@@ -20186,7 +21283,7 @@ function sliceCommand(program2) {
|
|
|
20186
21283
|
if (!options.dryRun && result.sliced) {
|
|
20187
21284
|
await sendNotifications(config, {
|
|
20188
21285
|
event: "run_succeeded",
|
|
20189
|
-
projectName:
|
|
21286
|
+
projectName: path45.basename(projectDir),
|
|
20190
21287
|
exitCode,
|
|
20191
21288
|
provider: config.provider,
|
|
20192
21289
|
prTitle: result.item?.title
|
|
@@ -20194,7 +21291,7 @@ function sliceCommand(program2) {
|
|
|
20194
21291
|
} else if (!options.dryRun && !nothingPending) {
|
|
20195
21292
|
await sendNotifications(config, {
|
|
20196
21293
|
event: "run_failed",
|
|
20197
|
-
projectName:
|
|
21294
|
+
projectName: path45.basename(projectDir),
|
|
20198
21295
|
exitCode,
|
|
20199
21296
|
provider: config.provider
|
|
20200
21297
|
});
|
|
@@ -20226,21 +21323,21 @@ function sliceCommand(program2) {
|
|
|
20226
21323
|
|
|
20227
21324
|
// src/commands/state.ts
|
|
20228
21325
|
init_dist();
|
|
20229
|
-
import * as
|
|
20230
|
-
import * as
|
|
21326
|
+
import * as os11 from "os";
|
|
21327
|
+
import * as path46 from "path";
|
|
20231
21328
|
import chalk5 from "chalk";
|
|
20232
21329
|
import { Command } from "commander";
|
|
20233
21330
|
function createStateCommand() {
|
|
20234
21331
|
const state = new Command("state");
|
|
20235
21332
|
state.description("Manage Night Watch state");
|
|
20236
21333
|
state.command("migrate").description("Migrate legacy JSON state files to SQLite").option("--dry-run", "Show what would be migrated without making changes").action((opts) => {
|
|
20237
|
-
const nightWatchHome = process.env.NIGHT_WATCH_HOME ||
|
|
21334
|
+
const nightWatchHome = process.env.NIGHT_WATCH_HOME || path46.join(os11.homedir(), GLOBAL_CONFIG_DIR);
|
|
20238
21335
|
if (opts.dryRun) {
|
|
20239
21336
|
console.log(chalk5.cyan("Dry-run mode: no changes will be made.\n"));
|
|
20240
21337
|
console.log(`Legacy JSON files that would be migrated from: ${chalk5.bold(nightWatchHome)}`);
|
|
20241
|
-
console.log(` ${
|
|
20242
|
-
console.log(` ${
|
|
20243
|
-
console.log(` ${
|
|
21338
|
+
console.log(` ${path46.join(nightWatchHome, "projects.json")}`);
|
|
21339
|
+
console.log(` ${path46.join(nightWatchHome, "history.json")}`);
|
|
21340
|
+
console.log(` ${path46.join(nightWatchHome, "prd-states.json")}`);
|
|
20244
21341
|
console.log(` <project>/<prdDir>/.roadmap-state.json (per project)`);
|
|
20245
21342
|
console.log(chalk5.dim("\nRun without --dry-run to apply the migration."));
|
|
20246
21343
|
return;
|
|
@@ -20278,8 +21375,8 @@ function createStateCommand() {
|
|
|
20278
21375
|
init_dist();
|
|
20279
21376
|
init_dist();
|
|
20280
21377
|
import { execFileSync as execFileSync6 } from "child_process";
|
|
20281
|
-
import * as
|
|
20282
|
-
import * as
|
|
21378
|
+
import * as fs46 from "fs";
|
|
21379
|
+
import * as path47 from "path";
|
|
20283
21380
|
import * as readline3 from "readline";
|
|
20284
21381
|
import chalk6 from "chalk";
|
|
20285
21382
|
async function run(fn) {
|
|
@@ -20301,7 +21398,7 @@ function getProvider(config, cwd) {
|
|
|
20301
21398
|
return createBoardProvider(bp, cwd);
|
|
20302
21399
|
}
|
|
20303
21400
|
function defaultBoardTitle(cwd) {
|
|
20304
|
-
return `${
|
|
21401
|
+
return `${path47.basename(cwd)} Night Watch`;
|
|
20305
21402
|
}
|
|
20306
21403
|
async function ensureBoardConfigured(config, cwd, provider, options) {
|
|
20307
21404
|
if (config.boardProvider?.projectNumber) {
|
|
@@ -20502,11 +21599,11 @@ function boardCommand(program2) {
|
|
|
20502
21599
|
let body = options.body ?? "";
|
|
20503
21600
|
if (options.bodyFile) {
|
|
20504
21601
|
const filePath = options.bodyFile;
|
|
20505
|
-
if (!
|
|
21602
|
+
if (!fs46.existsSync(filePath)) {
|
|
20506
21603
|
console.error(`File not found: ${filePath}`);
|
|
20507
21604
|
process.exit(1);
|
|
20508
21605
|
}
|
|
20509
|
-
body =
|
|
21606
|
+
body = fs46.readFileSync(filePath, "utf-8");
|
|
20510
21607
|
}
|
|
20511
21608
|
const labels = [];
|
|
20512
21609
|
if (options.label) {
|
|
@@ -20747,12 +21844,12 @@ function boardCommand(program2) {
|
|
|
20747
21844
|
const config = loadConfig(cwd);
|
|
20748
21845
|
const provider = getProvider(config, cwd);
|
|
20749
21846
|
await ensureBoardConfigured(config, cwd, provider);
|
|
20750
|
-
const roadmapPath = options.roadmap ??
|
|
20751
|
-
if (!
|
|
21847
|
+
const roadmapPath = options.roadmap ?? path47.join(cwd, "ROADMAP.md");
|
|
21848
|
+
if (!fs46.existsSync(roadmapPath)) {
|
|
20752
21849
|
console.error(`Roadmap file not found: ${roadmapPath}`);
|
|
20753
21850
|
process.exit(1);
|
|
20754
21851
|
}
|
|
20755
|
-
const roadmapContent =
|
|
21852
|
+
const roadmapContent = fs46.readFileSync(roadmapPath, "utf-8");
|
|
20756
21853
|
const items = parseRoadmap(roadmapContent);
|
|
20757
21854
|
const uncheckedItems = getUncheckedItems(items);
|
|
20758
21855
|
if (uncheckedItems.length === 0) {
|
|
@@ -20876,16 +21973,17 @@ function boardCommand(program2) {
|
|
|
20876
21973
|
// src/commands/queue.ts
|
|
20877
21974
|
init_dist();
|
|
20878
21975
|
init_dist();
|
|
20879
|
-
import * as
|
|
21976
|
+
import * as path48 from "path";
|
|
20880
21977
|
import { spawn as spawn8 } from "child_process";
|
|
20881
21978
|
import chalk7 from "chalk";
|
|
20882
21979
|
import { Command as Command2 } from "commander";
|
|
20883
|
-
var
|
|
21980
|
+
var logger7 = createLogger("queue");
|
|
20884
21981
|
var VALID_JOB_TYPES2 = [
|
|
20885
21982
|
"executor",
|
|
20886
21983
|
"reviewer",
|
|
20887
21984
|
"qa",
|
|
20888
21985
|
"audit",
|
|
21986
|
+
"ux",
|
|
20889
21987
|
"slicer",
|
|
20890
21988
|
"planner",
|
|
20891
21989
|
"pr-resolver",
|
|
@@ -21013,10 +22111,10 @@ function createQueueCommand() {
|
|
|
21013
22111
|
process.exit(1);
|
|
21014
22112
|
}
|
|
21015
22113
|
}
|
|
21016
|
-
const projectName =
|
|
22114
|
+
const projectName = path48.basename(projectDir);
|
|
21017
22115
|
const queueConfig = loadConfig(projectDir).queue;
|
|
21018
22116
|
if (isJobPaused(projectDir, jobType)) {
|
|
21019
|
-
|
|
22117
|
+
logger7.info(`Skipping enqueue for paused job: ${jobType}`);
|
|
21020
22118
|
return;
|
|
21021
22119
|
}
|
|
21022
22120
|
const id = enqueueJob(
|
|
@@ -21031,7 +22129,7 @@ function createQueueCommand() {
|
|
|
21031
22129
|
});
|
|
21032
22130
|
queue.command("resolve-key").description("Resolve the provider bucket key for a given project and job type").requiredOption("--project <dir>", "Project directory").requiredOption(
|
|
21033
22131
|
"--job-type <type>",
|
|
21034
|
-
"Job type (executor, reviewer, qa, audit, slicer, planner, pr-resolver, merger, manager)"
|
|
22132
|
+
"Job type (executor, reviewer, qa, audit, ux, slicer, planner, pr-resolver, merger, manager)"
|
|
21035
22133
|
).action((opts) => {
|
|
21036
22134
|
try {
|
|
21037
22135
|
const config = loadConfig(opts.project);
|
|
@@ -21053,18 +22151,19 @@ function createQueueCommand() {
|
|
|
21053
22151
|
const configDir = _opts.projectDir ?? process.cwd();
|
|
21054
22152
|
const entry = dispatchNextJob(loadConfig(configDir).queue);
|
|
21055
22153
|
if (!entry) {
|
|
21056
|
-
|
|
22154
|
+
logger7.info("No pending jobs to dispatch");
|
|
21057
22155
|
return;
|
|
21058
22156
|
}
|
|
21059
22157
|
if (isJobPaused(entry.projectPath, entry.jobType)) {
|
|
21060
|
-
|
|
22158
|
+
logger7.info(`Skipping paused queued job: ${entry.jobType} for ${entry.projectName}`);
|
|
21061
22159
|
removeJob(entry.id);
|
|
21062
22160
|
return;
|
|
21063
22161
|
}
|
|
21064
|
-
|
|
22162
|
+
logger7.info(`Dispatching ${entry.jobType} for ${entry.projectName} (ID: ${entry.id})`);
|
|
21065
22163
|
const scriptName = getScriptNameForJobType(entry.jobType);
|
|
21066
|
-
|
|
21067
|
-
|
|
22164
|
+
const jobDef = getJobDef(entry.jobType);
|
|
22165
|
+
if (!scriptName && (!jobDef || entry.jobType !== "ux")) {
|
|
22166
|
+
logger7.error(`Unknown job type: ${entry.jobType}`);
|
|
21068
22167
|
return;
|
|
21069
22168
|
}
|
|
21070
22169
|
let projectEnv;
|
|
@@ -21082,21 +22181,22 @@ function createQueueCommand() {
|
|
|
21082
22181
|
NW_QUEUE_DISPATCHED: "1",
|
|
21083
22182
|
NW_QUEUE_ENTRY_ID: String(entry.id)
|
|
21084
22183
|
};
|
|
21085
|
-
const scriptPath = getScriptPath(scriptName);
|
|
21086
|
-
logger6.info(`Spawning: ${scriptPath} ${entry.projectPath}`);
|
|
21087
22184
|
try {
|
|
21088
|
-
const
|
|
22185
|
+
const command = scriptName ? "bash" : "night-watch";
|
|
22186
|
+
const args = scriptName ? [getScriptPath(scriptName), entry.projectPath] : [jobDef.cliCommand];
|
|
22187
|
+
logger7.info(`Spawning: ${command} ${args.join(" ")}`);
|
|
22188
|
+
const child = spawn8(command, args, {
|
|
21089
22189
|
detached: true,
|
|
21090
22190
|
stdio: "ignore",
|
|
21091
22191
|
env,
|
|
21092
22192
|
cwd: entry.projectPath
|
|
21093
22193
|
});
|
|
21094
22194
|
child.unref();
|
|
21095
|
-
|
|
22195
|
+
logger7.info(`Spawned PID: ${child.pid}`);
|
|
21096
22196
|
markJobRunning(entry.id, child.pid ?? void 0);
|
|
21097
22197
|
} catch (error2) {
|
|
21098
22198
|
updateJobStatus(entry.id, "pending");
|
|
21099
|
-
|
|
22199
|
+
logger7.error(
|
|
21100
22200
|
`Failed to dispatch ${entry.jobType} for ${entry.projectName}: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
21101
22201
|
);
|
|
21102
22202
|
process.exit(1);
|
|
@@ -21114,7 +22214,7 @@ function createQueueCommand() {
|
|
|
21114
22214
|
if (isJobPaused(projectDir, jobType)) {
|
|
21115
22215
|
process.exit(2);
|
|
21116
22216
|
}
|
|
21117
|
-
const projectName =
|
|
22217
|
+
const projectName = path48.basename(projectDir);
|
|
21118
22218
|
const callerPid = opts.pid ? parseInt(opts.pid, 10) : void 0;
|
|
21119
22219
|
const result = claimJobSlot(
|
|
21120
22220
|
projectDir,
|
|
@@ -21250,7 +22350,7 @@ function notifyCommand(program2) {
|
|
|
21250
22350
|
|
|
21251
22351
|
// src/commands/summary.ts
|
|
21252
22352
|
init_dist();
|
|
21253
|
-
import
|
|
22353
|
+
import path49 from "path";
|
|
21254
22354
|
import chalk8 from "chalk";
|
|
21255
22355
|
function formatDuration2(seconds) {
|
|
21256
22356
|
if (seconds === null) return "-";
|
|
@@ -21280,7 +22380,7 @@ function formatJobStatus(status) {
|
|
|
21280
22380
|
return chalk8.dim(status);
|
|
21281
22381
|
}
|
|
21282
22382
|
function getProjectName2(projectPath) {
|
|
21283
|
-
return
|
|
22383
|
+
return path49.basename(projectPath) || projectPath;
|
|
21284
22384
|
}
|
|
21285
22385
|
function formatProvider(providerKey) {
|
|
21286
22386
|
return providerKey.split(":")[0] || providerKey;
|
|
@@ -21399,7 +22499,7 @@ function summaryCommand(program2) {
|
|
|
21399
22499
|
// src/commands/resolve.ts
|
|
21400
22500
|
init_dist();
|
|
21401
22501
|
import { execFileSync as execFileSync7 } from "child_process";
|
|
21402
|
-
import * as
|
|
22502
|
+
import * as path50 from "path";
|
|
21403
22503
|
function buildEnvVars6(config, options) {
|
|
21404
22504
|
const env = buildBaseEnvVars(config, "pr-resolver", options.dryRun);
|
|
21405
22505
|
env.NW_PR_RESOLVER_MAX_RUNTIME = String(config.prResolver.maxRuntime);
|
|
@@ -21552,7 +22652,7 @@ ${stderr}`);
|
|
|
21552
22652
|
}
|
|
21553
22653
|
await sendNotifications(config, {
|
|
21554
22654
|
event: notificationEvent,
|
|
21555
|
-
projectName:
|
|
22655
|
+
projectName: path50.basename(projectDir),
|
|
21556
22656
|
exitCode,
|
|
21557
22657
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
21558
22658
|
});
|
|
@@ -21567,7 +22667,7 @@ ${stderr}`);
|
|
|
21567
22667
|
|
|
21568
22668
|
// src/commands/merge.ts
|
|
21569
22669
|
init_dist();
|
|
21570
|
-
import * as
|
|
22670
|
+
import * as path51 from "path";
|
|
21571
22671
|
function buildEnvVars7(config, options) {
|
|
21572
22672
|
const env = buildBaseEnvVars(config, "merger", options.dryRun);
|
|
21573
22673
|
env.NW_MERGER_MAX_RUNTIME = String(config.merger.maxRuntime);
|
|
@@ -21602,7 +22702,7 @@ function resolveMergeNotificationEvent(exitCode, mergedCount, failedCount) {
|
|
|
21602
22702
|
}
|
|
21603
22703
|
return null;
|
|
21604
22704
|
}
|
|
21605
|
-
function
|
|
22705
|
+
function printDryRun2(config, envVars, scriptPath, projectDir) {
|
|
21606
22706
|
header("Dry Run: Merge Orchestrator");
|
|
21607
22707
|
const mergerProvider = resolveJobProvider(config, "merger");
|
|
21608
22708
|
header("Configuration");
|
|
@@ -21639,20 +22739,26 @@ function mergeCommand(program2) {
|
|
|
21639
22739
|
const projectDir = process.cwd();
|
|
21640
22740
|
let config = loadConfig(projectDir);
|
|
21641
22741
|
config = applyCliOverrides6(config, options);
|
|
22742
|
+
const commandStartedAt = Date.now();
|
|
22743
|
+
await trackCommandStarted("merge", config);
|
|
21642
22744
|
if (!config.merger.enabled && !options.dryRun) {
|
|
21643
22745
|
info("Merge orchestrator is disabled in config; skipping.");
|
|
22746
|
+
await trackCommandCompleted("merge", commandStartedAt, 0, config);
|
|
21644
22747
|
process.exit(0);
|
|
21645
22748
|
}
|
|
21646
22749
|
const envVars = buildEnvVars7(config, options);
|
|
21647
22750
|
const scriptPath = getScriptPath("night-watch-merger-cron.sh");
|
|
21648
22751
|
if (options.dryRun) {
|
|
21649
|
-
|
|
22752
|
+
printDryRun2(config, envVars, scriptPath, projectDir);
|
|
22753
|
+
await trackCommandCompleted("merge", commandStartedAt, 0, config);
|
|
21650
22754
|
process.exit(0);
|
|
21651
22755
|
}
|
|
21652
22756
|
const spinner = createSpinner("Running merge orchestrator...");
|
|
21653
22757
|
spinner.start();
|
|
21654
22758
|
try {
|
|
21655
22759
|
const startedAt = Date.now();
|
|
22760
|
+
const provider = envVars.NW_PROVIDER_KEY ?? resolveJobProvider(config, "merger");
|
|
22761
|
+
await trackJobStarted("merger", provider, config);
|
|
21656
22762
|
await maybeApplyCronSchedulingDelay(config, "merger", projectDir);
|
|
21657
22763
|
const { exitCode, stdout, stderr } = await executeScriptWithOutput(
|
|
21658
22764
|
scriptPath,
|
|
@@ -21662,6 +22768,14 @@ function mergeCommand(program2) {
|
|
|
21662
22768
|
const finishedAt = Date.now();
|
|
21663
22769
|
const scriptResult = parseScriptResult(`${stdout}
|
|
21664
22770
|
${stderr}`);
|
|
22771
|
+
await trackJobCompletedOrFailed(
|
|
22772
|
+
"merger",
|
|
22773
|
+
provider,
|
|
22774
|
+
startedAt,
|
|
22775
|
+
exitCode,
|
|
22776
|
+
config,
|
|
22777
|
+
scriptResult?.status
|
|
22778
|
+
);
|
|
21665
22779
|
if (exitCode === 0) {
|
|
21666
22780
|
if (scriptResult?.status === "queued") {
|
|
21667
22781
|
spinner.succeed("Merge orchestrator queued \u2014 another job is currently running");
|
|
@@ -21676,6 +22790,14 @@ ${stderr}`);
|
|
|
21676
22790
|
const mergedCount = parseInt(scriptResult?.data?.merged ?? "0", 10);
|
|
21677
22791
|
const failedCount = parseInt(scriptResult?.data?.failed ?? "0", 10);
|
|
21678
22792
|
const notificationEvent = resolveMergeNotificationEvent(exitCode, mergedCount, failedCount);
|
|
22793
|
+
if (mergedCount > 0 && exitCode === 0) {
|
|
22794
|
+
fireTelemetryEvent("auto_merge_completed", {
|
|
22795
|
+
...buildTelemetryBaseProperties(config),
|
|
22796
|
+
jobType: "merger",
|
|
22797
|
+
provider,
|
|
22798
|
+
success: true
|
|
22799
|
+
});
|
|
22800
|
+
}
|
|
21679
22801
|
if (!options.dryRun) {
|
|
21680
22802
|
try {
|
|
21681
22803
|
recordJobOutcome({
|
|
@@ -21703,15 +22825,19 @@ ${stderr}`);
|
|
|
21703
22825
|
if (notificationEvent) {
|
|
21704
22826
|
await sendNotifications(config, {
|
|
21705
22827
|
event: notificationEvent,
|
|
21706
|
-
projectName:
|
|
22828
|
+
projectName: path51.basename(projectDir),
|
|
21707
22829
|
exitCode,
|
|
21708
22830
|
provider: formatProviderDisplay(envVars.NW_PROVIDER_CMD, envVars.NW_PROVIDER_LABEL)
|
|
21709
22831
|
});
|
|
21710
22832
|
}
|
|
22833
|
+
await trackCommandCompleted("merge", commandStartedAt, exitCode, config);
|
|
21711
22834
|
process.exit(exitCode);
|
|
21712
22835
|
} catch (err) {
|
|
21713
22836
|
spinner.fail("Failed to execute merge command");
|
|
21714
22837
|
error(`${err instanceof Error ? err.message : String(err)}`);
|
|
22838
|
+
await trackCommandCompleted("merge", commandStartedAt, 1, config, {
|
|
22839
|
+
errorCategory: err instanceof Error ? err.message : String(err)
|
|
22840
|
+
});
|
|
21715
22841
|
process.exit(1);
|
|
21716
22842
|
}
|
|
21717
22843
|
});
|
|
@@ -21719,7 +22845,7 @@ ${stderr}`);
|
|
|
21719
22845
|
|
|
21720
22846
|
// src/commands/manager.ts
|
|
21721
22847
|
init_dist();
|
|
21722
|
-
import * as
|
|
22848
|
+
import * as path52 from "path";
|
|
21723
22849
|
function resolveRunManager() {
|
|
21724
22850
|
const runManager2 = runManager;
|
|
21725
22851
|
if (typeof runManager2 !== "function") {
|
|
@@ -21729,17 +22855,17 @@ function resolveRunManager() {
|
|
|
21729
22855
|
}
|
|
21730
22856
|
return runManager2;
|
|
21731
22857
|
}
|
|
21732
|
-
function
|
|
22858
|
+
function writeJson2(value) {
|
|
21733
22859
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
21734
22860
|
`);
|
|
21735
22861
|
}
|
|
21736
|
-
function
|
|
22862
|
+
function parseTimeout2(timeout) {
|
|
21737
22863
|
if (!timeout) return void 0;
|
|
21738
22864
|
const parsed = parseInt(timeout, 10);
|
|
21739
22865
|
return Number.isNaN(parsed) || parsed < 0 ? void 0 : parsed;
|
|
21740
22866
|
}
|
|
21741
22867
|
function buildManagerRunOptions(options) {
|
|
21742
|
-
const timeout =
|
|
22868
|
+
const timeout = parseTimeout2(options.timeout);
|
|
21743
22869
|
return {
|
|
21744
22870
|
dryRun: options.dryRun === true,
|
|
21745
22871
|
...timeout !== void 0 ? { timeout } : {},
|
|
@@ -21747,7 +22873,7 @@ function buildManagerRunOptions(options) {
|
|
|
21747
22873
|
};
|
|
21748
22874
|
}
|
|
21749
22875
|
function applyManagerCliOverrides(config, options) {
|
|
21750
|
-
const timeout =
|
|
22876
|
+
const timeout = parseTimeout2(options.timeout);
|
|
21751
22877
|
let overridden = config;
|
|
21752
22878
|
if (timeout !== void 0) {
|
|
21753
22879
|
overridden = {
|
|
@@ -21799,7 +22925,7 @@ async function sendManagerNotifications(config, projectDir, result) {
|
|
|
21799
22925
|
if (!item.shouldNotify || !item.event) continue;
|
|
21800
22926
|
await sendNotifications(config, {
|
|
21801
22927
|
event: item.event,
|
|
21802
|
-
projectName:
|
|
22928
|
+
projectName: path52.basename(projectDir),
|
|
21803
22929
|
provider: resolveJobProvider(config, "manager"),
|
|
21804
22930
|
exitCode: 0,
|
|
21805
22931
|
failureReason: item.title,
|
|
@@ -21834,7 +22960,7 @@ function managerCommand(program2) {
|
|
|
21834
22960
|
const runOptions = buildManagerRunOptions(options);
|
|
21835
22961
|
if (!managerConfig.enabled && !runOptions.dryRun) {
|
|
21836
22962
|
if (options.json) {
|
|
21837
|
-
|
|
22963
|
+
writeJson2({ dryRun: false, skipped: true, reason: "manager-disabled" });
|
|
21838
22964
|
} else {
|
|
21839
22965
|
info("Manager is disabled in config; skipping run.");
|
|
21840
22966
|
}
|
|
@@ -21872,7 +22998,7 @@ function managerCommand(program2) {
|
|
|
21872
22998
|
}
|
|
21873
22999
|
}
|
|
21874
23000
|
if (options.json) {
|
|
21875
|
-
|
|
23001
|
+
writeJson2(buildJsonResult(result, runOptions));
|
|
21876
23002
|
} else {
|
|
21877
23003
|
if (exitCode === 0) {
|
|
21878
23004
|
spinner?.succeed("Manager completed successfully");
|
|
@@ -21913,12 +23039,55 @@ function managerCommand(program2) {
|
|
|
21913
23039
|
});
|
|
21914
23040
|
}
|
|
21915
23041
|
|
|
23042
|
+
// src/commands/telemetry.ts
|
|
23043
|
+
init_dist();
|
|
23044
|
+
function formatInstallId(installId) {
|
|
23045
|
+
return installId.slice(0, 8);
|
|
23046
|
+
}
|
|
23047
|
+
function printStatus() {
|
|
23048
|
+
const state = getTelemetryEffectiveState();
|
|
23049
|
+
const status = state.enabled ? "enabled" : "disabled";
|
|
23050
|
+
console.log(`Telemetry: ${status}`);
|
|
23051
|
+
console.log(`Reason: ${state.reason}`);
|
|
23052
|
+
console.log(`Config path: ${state.path}`);
|
|
23053
|
+
console.log(`Install ID: ${formatInstallId(state.config.installId)}`);
|
|
23054
|
+
console.log("Privacy docs: docs/privacy.md");
|
|
23055
|
+
console.log("Disable: night-watch telemetry disable");
|
|
23056
|
+
console.log("Env opt-outs: NW_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1");
|
|
23057
|
+
}
|
|
23058
|
+
function telemetryCommand(program2) {
|
|
23059
|
+
const command = program2.command("telemetry").description("Manage anonymous product telemetry");
|
|
23060
|
+
command.command("status").description("Show telemetry status and opt-out information").action(() => {
|
|
23061
|
+
printStatus();
|
|
23062
|
+
});
|
|
23063
|
+
command.command("disable").description("Disable anonymous product telemetry").action(async () => {
|
|
23064
|
+
const state = getTelemetryEffectiveState();
|
|
23065
|
+
if (state.enabled) {
|
|
23066
|
+
fireTelemetryEvent("telemetry_disabled");
|
|
23067
|
+
}
|
|
23068
|
+
setTelemetryEnabled(false);
|
|
23069
|
+
console.log("Telemetry disabled.");
|
|
23070
|
+
console.log("You can also set NW_TELEMETRY_DISABLED=1 or DO_NOT_TRACK=1.");
|
|
23071
|
+
});
|
|
23072
|
+
command.command("enable").description("Enable anonymous product telemetry").action(() => {
|
|
23073
|
+
setTelemetryEnabled(true);
|
|
23074
|
+
const envDisabled = isTelemetryEnvDisabled();
|
|
23075
|
+
if (!envDisabled) {
|
|
23076
|
+
fireTelemetryEvent("telemetry_enabled");
|
|
23077
|
+
}
|
|
23078
|
+
console.log("Telemetry enabled.");
|
|
23079
|
+
if (envDisabled) {
|
|
23080
|
+
console.log(`Currently overridden by ${envDisabled}.`);
|
|
23081
|
+
}
|
|
23082
|
+
});
|
|
23083
|
+
}
|
|
23084
|
+
|
|
21916
23085
|
// src/commands/agent.ts
|
|
21917
23086
|
init_dist();
|
|
21918
23087
|
var SCHEMA_VERSION2 = 1;
|
|
21919
23088
|
var JSON_OPTION = "--json";
|
|
21920
23089
|
var JSON_OPTION_DESCRIPTION = "Output as JSON";
|
|
21921
|
-
function
|
|
23090
|
+
function writeJson3(value) {
|
|
21922
23091
|
process.stdout.write(`${JSON.stringify(value, null, 2)}
|
|
21923
23092
|
`);
|
|
21924
23093
|
}
|
|
@@ -22075,7 +23244,7 @@ function normalizeJobType(job) {
|
|
|
22075
23244
|
function agentCommand(program2) {
|
|
22076
23245
|
const agent = program2.command("agent").description("Machine-readable agent operations");
|
|
22077
23246
|
agent.command("status").description("Print a stable machine-readable project snapshot").requiredOption(JSON_OPTION, "Output status as JSON").action(async () => {
|
|
22078
|
-
|
|
23247
|
+
writeJson3(await buildAgentStatus(process.cwd()));
|
|
22079
23248
|
});
|
|
22080
23249
|
}
|
|
22081
23250
|
function configCommand(program2) {
|
|
@@ -22083,18 +23252,18 @@ function configCommand(program2) {
|
|
|
22083
23252
|
config.command("list").description("Print resolved config").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((options) => {
|
|
22084
23253
|
const value = loadConfig(process.cwd());
|
|
22085
23254
|
if (options.json) {
|
|
22086
|
-
|
|
23255
|
+
writeJson3({ schemaVersion: SCHEMA_VERSION2, config: value });
|
|
22087
23256
|
} else {
|
|
22088
|
-
|
|
23257
|
+
writeJson3(value);
|
|
22089
23258
|
}
|
|
22090
23259
|
});
|
|
22091
23260
|
config.command("get <path>").description("Read a resolved config value by dot path").option(JSON_OPTION, JSON_OPTION_DESCRIPTION).action((dotPath, options) => {
|
|
22092
23261
|
try {
|
|
22093
23262
|
const result = getConfigValue(process.cwd(), dotPath);
|
|
22094
23263
|
if (options.json) {
|
|
22095
|
-
|
|
23264
|
+
writeJson3({ schemaVersion: SCHEMA_VERSION2, ...result });
|
|
22096
23265
|
} else {
|
|
22097
|
-
|
|
23266
|
+
writeJson3(result.value);
|
|
22098
23267
|
}
|
|
22099
23268
|
} catch (error2) {
|
|
22100
23269
|
fail(error2 instanceof Error ? error2.message : String(error2), options);
|
|
@@ -22104,7 +23273,7 @@ function configCommand(program2) {
|
|
|
22104
23273
|
try {
|
|
22105
23274
|
const result = setConfigValue(process.cwd(), dotPath, parseConfigValue(rawValue));
|
|
22106
23275
|
if (options.json) {
|
|
22107
|
-
|
|
23276
|
+
writeJson3({ schemaVersion: SCHEMA_VERSION2, ok: true, ...result });
|
|
22108
23277
|
} else {
|
|
22109
23278
|
process.stdout.write(`Updated ${result.path}
|
|
22110
23279
|
`);
|
|
@@ -22120,7 +23289,7 @@ function healthCommand(program2) {
|
|
|
22120
23289
|
const snapshot = await fetchStatusSnapshot(process.cwd(), config);
|
|
22121
23290
|
const health = buildHealth(snapshot, config);
|
|
22122
23291
|
if (options.json) {
|
|
22123
|
-
|
|
23292
|
+
writeJson3(health);
|
|
22124
23293
|
} else {
|
|
22125
23294
|
for (const check of health.checks) {
|
|
22126
23295
|
process.stdout.write(`${check.ok ? "ok" : "fail"} ${check.name}: ${check.message}
|
|
@@ -22139,7 +23308,7 @@ function jobCommand(program2) {
|
|
|
22139
23308
|
const jobType = normalizeJobType(jobName);
|
|
22140
23309
|
const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, true);
|
|
22141
23310
|
if (options.json) {
|
|
22142
|
-
|
|
23311
|
+
writeJson3({
|
|
22143
23312
|
schemaVersion: SCHEMA_VERSION2,
|
|
22144
23313
|
ok: true,
|
|
22145
23314
|
job: jobType,
|
|
@@ -22158,7 +23327,7 @@ function jobCommand(program2) {
|
|
|
22158
23327
|
const jobType = normalizeJobType(jobName);
|
|
22159
23328
|
const result = setConfigValue(process.cwd(), `pausedJobs.${jobType}`, false);
|
|
22160
23329
|
if (options.json) {
|
|
22161
|
-
|
|
23330
|
+
writeJson3({
|
|
22162
23331
|
schemaVersion: SCHEMA_VERSION2,
|
|
22163
23332
|
ok: true,
|
|
22164
23333
|
job: jobType,
|
|
@@ -22183,26 +23352,58 @@ function jobCommand(program2) {
|
|
|
22183
23352
|
});
|
|
22184
23353
|
}
|
|
22185
23354
|
|
|
23355
|
+
// src/telemetry-bootstrap.ts
|
|
23356
|
+
init_dist();
|
|
23357
|
+
var NOTICE_LINES = [
|
|
23358
|
+
"Night Watch collects anonymous product telemetry to understand usage and improve the CLI.",
|
|
23359
|
+
"Telemetry is enabled by default.",
|
|
23360
|
+
"Disable anytime with `night-watch telemetry disable`, `NW_TELEMETRY_DISABLED=1`, or `DO_NOT_TRACK=1`.",
|
|
23361
|
+
"Collected: CLI version, command/job type, provider, success/failure, duration, exit code, platform, Node major version, board mode, registered project count, and error category.",
|
|
23362
|
+
"Never collected: repo names, paths, remotes, branches, PR/issue titles/bodies/URLs/numbers, prompts, provider output, diffs, file paths, usernames/emails, hostnames, env vars, tokens/secrets, or raw stack traces.",
|
|
23363
|
+
"Privacy details: docs/privacy.md"
|
|
23364
|
+
];
|
|
23365
|
+
function shouldSuppressNotice(argv) {
|
|
23366
|
+
return argv.includes("--json") || argv.includes("-h") || argv.includes("--help");
|
|
23367
|
+
}
|
|
23368
|
+
function bootstrapTelemetry(cliVersion2, argv = process.argv) {
|
|
23369
|
+
setCliTelemetryVersion(cliVersion2);
|
|
23370
|
+
if (isTelemetryEnvDisabled() || shouldSuppressNotice(argv)) {
|
|
23371
|
+
return;
|
|
23372
|
+
}
|
|
23373
|
+
try {
|
|
23374
|
+
const state = getTelemetryEffectiveState();
|
|
23375
|
+
if (!state.enabled || state.config.noticeShownAt) {
|
|
23376
|
+
return;
|
|
23377
|
+
}
|
|
23378
|
+
console.error(NOTICE_LINES.join("\n"));
|
|
23379
|
+
markTelemetryNoticeShown();
|
|
23380
|
+
fireTelemetryEvent("cli_first_run");
|
|
23381
|
+
} catch {
|
|
23382
|
+
}
|
|
23383
|
+
}
|
|
23384
|
+
|
|
22186
23385
|
// src/cli.ts
|
|
22187
23386
|
var __filename5 = fileURLToPath6(import.meta.url);
|
|
22188
|
-
var __dirname5 =
|
|
23387
|
+
var __dirname5 = dirname14(__filename5);
|
|
22189
23388
|
function findPackageRoot(dir) {
|
|
22190
23389
|
let d = dir;
|
|
22191
23390
|
for (let i = 0; i < 5; i++) {
|
|
22192
|
-
if (
|
|
22193
|
-
d =
|
|
23391
|
+
if (existsSync37(join41(d, "package.json"))) return d;
|
|
23392
|
+
d = dirname14(d);
|
|
22194
23393
|
}
|
|
22195
23394
|
return dir;
|
|
22196
23395
|
}
|
|
22197
23396
|
var packageRoot = findPackageRoot(__dirname5);
|
|
22198
|
-
var packageJson = JSON.parse(
|
|
23397
|
+
var packageJson = JSON.parse(readFileSync24(join41(packageRoot, "package.json"), "utf-8"));
|
|
22199
23398
|
var program = new Command3();
|
|
22200
23399
|
program.name("night-watch").description("Autonomous PRD execution using Claude CLI + cron").version(packageJson.version);
|
|
23400
|
+
bootstrapTelemetry(packageJson.version);
|
|
22201
23401
|
initCommand(program);
|
|
22202
23402
|
runCommand(program);
|
|
22203
23403
|
reviewCommand(program);
|
|
22204
23404
|
qaCommand(program);
|
|
22205
23405
|
auditCommand(program);
|
|
23406
|
+
uxCommand(program);
|
|
22206
23407
|
analyticsCommand(program);
|
|
22207
23408
|
installCommand(program);
|
|
22208
23409
|
uninstallCommand(program);
|
|
@@ -22228,6 +23429,7 @@ summaryCommand(program);
|
|
|
22228
23429
|
resolveCommand(program);
|
|
22229
23430
|
mergeCommand(program);
|
|
22230
23431
|
managerCommand(program);
|
|
23432
|
+
telemetryCommand(program);
|
|
22231
23433
|
agentCommand(program);
|
|
22232
23434
|
configCommand(program);
|
|
22233
23435
|
healthCommand(program);
|