syntaur 0.16.2 → 0.16.3
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/dashboard/server.js +178 -70
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +178 -70
- package/dist/index.js.map +1 -1
- package/dist/launch/index.js +97 -55
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5829,6 +5829,30 @@ function setUpdatedField(content, value) {
|
|
|
5829
5829
|
function escapeRegExp2(value) {
|
|
5830
5830
|
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
5831
5831
|
}
|
|
5832
|
+
function createTraces() {
|
|
5833
|
+
return { entries: [], subPhases: /* @__PURE__ */ new Map() };
|
|
5834
|
+
}
|
|
5835
|
+
async function timed(traces, label, fn) {
|
|
5836
|
+
if (!traces) return fn();
|
|
5837
|
+
const start = performance.now();
|
|
5838
|
+
try {
|
|
5839
|
+
return await fn();
|
|
5840
|
+
} finally {
|
|
5841
|
+
traces.entries.push({ label, ms: performance.now() - start });
|
|
5842
|
+
}
|
|
5843
|
+
}
|
|
5844
|
+
function accumulatePhase(traces, label, ms) {
|
|
5845
|
+
if (!traces) return;
|
|
5846
|
+
traces.subPhases.set(label, (traces.subPhases.get(label) ?? 0) + ms);
|
|
5847
|
+
}
|
|
5848
|
+
function emitTrace(traces, meta) {
|
|
5849
|
+
if (process.env.SYNTAUR_PERF_TRACE !== "1") return;
|
|
5850
|
+
const totalMs = traces.entries.reduce((sum, entry) => sum + entry.ms, 0);
|
|
5851
|
+
const subPhases = Object.fromEntries(traces.subPhases);
|
|
5852
|
+
console.log(
|
|
5853
|
+
JSON.stringify({ kind: "overview-trace", totalMs, phases: traces.entries, subPhases, ...meta })
|
|
5854
|
+
);
|
|
5855
|
+
}
|
|
5832
5856
|
async function listStandaloneRecords(assignmentsDir2) {
|
|
5833
5857
|
if (!assignmentsDir2) return [];
|
|
5834
5858
|
if (!await fileExists(assignmentsDir2)) return [];
|
|
@@ -5994,8 +6018,19 @@ async function deleteWorkspace(projectsDir2, name, opts = {}) {
|
|
|
5994
6018
|
return { rewroteFiles };
|
|
5995
6019
|
}
|
|
5996
6020
|
async function getOverview(projectsDir2, serversDir2, assignmentsDir2, options = {}) {
|
|
5997
|
-
const
|
|
5998
|
-
const
|
|
6021
|
+
const traceEnabled = process.env.SYNTAUR_PERF_TRACE === "1";
|
|
6022
|
+
const traces = traceEnabled ? createTraces() : void 0;
|
|
6023
|
+
const overallStart = traceEnabled ? performance.now() : 0;
|
|
6024
|
+
const projectRecords = await timed(
|
|
6025
|
+
traces,
|
|
6026
|
+
"list-project-records",
|
|
6027
|
+
() => listProjectRecords(projectsDir2, traces)
|
|
6028
|
+
);
|
|
6029
|
+
const standaloneRecords = await timed(
|
|
6030
|
+
traces,
|
|
6031
|
+
"list-standalone-records",
|
|
6032
|
+
() => listStandaloneRecords(assignmentsDir2)
|
|
6033
|
+
);
|
|
5999
6034
|
const recentActivity = buildRecentActivity(projectRecords, standaloneRecords);
|
|
6000
6035
|
const staleLimit = clamp(
|
|
6001
6036
|
Number.isFinite(options.staleLimit) ? Number(options.staleLimit) : STALE_LIMIT_DEFAULT,
|
|
@@ -6003,12 +6038,16 @@ async function getOverview(projectsDir2, serversDir2, assignmentsDir2, options =
|
|
|
6003
6038
|
STALE_LIMIT_MAX
|
|
6004
6039
|
);
|
|
6005
6040
|
const staleOffset = Math.max(0, Number.isFinite(options.staleOffset) ? Number(options.staleOffset) : 0);
|
|
6006
|
-
const buckets = await
|
|
6041
|
+
const buckets = await timed(
|
|
6042
|
+
traces,
|
|
6043
|
+
"build-segment-buckets",
|
|
6044
|
+
() => buildOverviewSegmentBuckets(projectsDir2, projectRecords, standaloneRecords, traces)
|
|
6045
|
+
);
|
|
6007
6046
|
const segments = toOverviewSegments(buckets, { staleLimit, staleOffset });
|
|
6008
6047
|
const hero = pickOverviewHero(buckets);
|
|
6009
6048
|
let recentSessions = [];
|
|
6010
6049
|
try {
|
|
6011
|
-
const all = await listAllSessions(projectsDir2);
|
|
6050
|
+
const all = await timed(traces, "list-recent-sessions", () => listAllSessions(projectsDir2));
|
|
6012
6051
|
recentSessions = all.slice(0, RECENT_SESSIONS_LIMIT);
|
|
6013
6052
|
} catch {
|
|
6014
6053
|
}
|
|
@@ -6016,7 +6055,11 @@ async function getOverview(projectsDir2, serversDir2, assignmentsDir2, options =
|
|
|
6016
6055
|
if (serversDir2) {
|
|
6017
6056
|
try {
|
|
6018
6057
|
const { scanAllSessions: scanAllSessions2 } = await Promise.resolve().then(() => (init_scanner(), scanner_exports));
|
|
6019
|
-
const servers = await
|
|
6058
|
+
const servers = await timed(
|
|
6059
|
+
traces,
|
|
6060
|
+
"scan-tmux-sessions",
|
|
6061
|
+
() => scanAllSessions2(serversDir2, projectsDir2, { assignmentsDir: assignmentsDir2 })
|
|
6062
|
+
);
|
|
6020
6063
|
if (servers.tmuxAvailable) {
|
|
6021
6064
|
const alive = servers.sessions.filter((s) => s.alive).length;
|
|
6022
6065
|
const totalPorts = servers.sessions.reduce((sum, s) => sum + s.windows.reduce((ws, w) => ws + w.panes.reduce((ps, p) => ps + p.ports.length, 0), 0), 0);
|
|
@@ -6030,6 +6073,14 @@ async function getOverview(projectsDir2, serversDir2, assignmentsDir2, options =
|
|
|
6030
6073
|
} catch {
|
|
6031
6074
|
}
|
|
6032
6075
|
}
|
|
6076
|
+
if (traces) {
|
|
6077
|
+
const wallMs = performance.now() - overallStart;
|
|
6078
|
+
const totalAssignments = projectRecords.reduce((sum, r) => sum + r.assignments.length, 0) + standaloneRecords.length;
|
|
6079
|
+
emitTrace(traces, {
|
|
6080
|
+
wallMs,
|
|
6081
|
+
fixture: { projects: projectRecords.length, assignments: totalAssignments }
|
|
6082
|
+
});
|
|
6083
|
+
}
|
|
6033
6084
|
return {
|
|
6034
6085
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6035
6086
|
firstRun: projectRecords.length === 0 && standaloneRecords.length === 0,
|
|
@@ -6560,7 +6611,7 @@ async function buildStandaloneAssignmentDetail(resolved) {
|
|
|
6560
6611
|
};
|
|
6561
6612
|
return detail;
|
|
6562
6613
|
}
|
|
6563
|
-
async function listProjectRecords(projectsDir2) {
|
|
6614
|
+
async function listProjectRecords(projectsDir2, traces) {
|
|
6564
6615
|
if (!await fileExists(projectsDir2)) {
|
|
6565
6616
|
return [];
|
|
6566
6617
|
}
|
|
@@ -6570,61 +6621,75 @@ async function listProjectRecords(projectsDir2) {
|
|
|
6570
6621
|
}
|
|
6571
6622
|
const entries = await readdir9(projectsDir2, { withFileTypes: true });
|
|
6572
6623
|
const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
|
|
6573
|
-
const
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
}
|
|
6580
|
-
const projectContent = await readFile11(projectMdPath, "utf-8");
|
|
6581
|
-
const project = parseProject(projectContent);
|
|
6582
|
-
const assignments = await listAssignmentRecords(projectPath);
|
|
6583
|
-
const rollup = await buildProjectRollup(projectPath, project, assignments);
|
|
6584
|
-
const updated = getProjectActivityTimestamp(project.updated, assignments);
|
|
6585
|
-
records.push({
|
|
6586
|
-
projectPath,
|
|
6587
|
-
project,
|
|
6588
|
-
assignments,
|
|
6589
|
-
dependencyGraph: await loadDependencyGraph(projectPath, assignments),
|
|
6590
|
-
summary: {
|
|
6591
|
-
slug: project.slug || entry.name,
|
|
6592
|
-
title: project.title,
|
|
6593
|
-
status: rollup.status,
|
|
6594
|
-
statusOverride: project.statusOverride,
|
|
6595
|
-
archived: project.archived,
|
|
6596
|
-
archivedAt: project.archivedAt,
|
|
6597
|
-
archivedReason: project.archivedReason,
|
|
6598
|
-
created: project.created,
|
|
6599
|
-
updated,
|
|
6600
|
-
tags: project.tags,
|
|
6601
|
-
progress: rollup.progress,
|
|
6602
|
-
needsAttention: rollup.needsAttention,
|
|
6603
|
-
workspace: project.workspace
|
|
6624
|
+
const maybeRecords = await Promise.all(
|
|
6625
|
+
projectDirs.map(async (entry) => {
|
|
6626
|
+
const projectPath = resolve16(projectsDir2, entry.name);
|
|
6627
|
+
const projectMdPath = resolve16(projectPath, "project.md");
|
|
6628
|
+
if (!await fileExists(projectMdPath)) {
|
|
6629
|
+
return null;
|
|
6604
6630
|
}
|
|
6605
|
-
|
|
6606
|
-
|
|
6631
|
+
const t0 = traces ? performance.now() : 0;
|
|
6632
|
+
const projectContent = await readFile11(projectMdPath, "utf-8");
|
|
6633
|
+
const project = parseProject(projectContent);
|
|
6634
|
+
if (traces) accumulatePhase(traces, "parse-project-md", performance.now() - t0);
|
|
6635
|
+
const t1 = traces ? performance.now() : 0;
|
|
6636
|
+
const assignments = await listAssignmentRecords(projectPath, traces);
|
|
6637
|
+
if (traces) accumulatePhase(traces, "list-assignments", performance.now() - t1);
|
|
6638
|
+
const t2 = traces ? performance.now() : 0;
|
|
6639
|
+
const rollup = await buildProjectRollup(projectPath, project, assignments, traces);
|
|
6640
|
+
if (traces) accumulatePhase(traces, "build-rollup", performance.now() - t2);
|
|
6641
|
+
const updated = getProjectActivityTimestamp(project.updated, assignments);
|
|
6642
|
+
const t3 = traces ? performance.now() : 0;
|
|
6643
|
+
const dependencyGraph = await loadDependencyGraph(projectPath, assignments);
|
|
6644
|
+
if (traces) accumulatePhase(traces, "load-dep-graph", performance.now() - t3);
|
|
6645
|
+
return {
|
|
6646
|
+
projectPath,
|
|
6647
|
+
project,
|
|
6648
|
+
assignments,
|
|
6649
|
+
dependencyGraph,
|
|
6650
|
+
summary: {
|
|
6651
|
+
slug: project.slug || entry.name,
|
|
6652
|
+
title: project.title,
|
|
6653
|
+
status: rollup.status,
|
|
6654
|
+
statusOverride: project.statusOverride,
|
|
6655
|
+
archived: project.archived,
|
|
6656
|
+
archivedAt: project.archivedAt,
|
|
6657
|
+
archivedReason: project.archivedReason,
|
|
6658
|
+
created: project.created,
|
|
6659
|
+
updated,
|
|
6660
|
+
tags: project.tags,
|
|
6661
|
+
progress: rollup.progress,
|
|
6662
|
+
needsAttention: rollup.needsAttention,
|
|
6663
|
+
workspace: project.workspace
|
|
6664
|
+
}
|
|
6665
|
+
};
|
|
6666
|
+
})
|
|
6667
|
+
);
|
|
6668
|
+
const records = maybeRecords.filter((r) => r !== null);
|
|
6607
6669
|
records.sort((left, right) => compareTimestamps(right.summary.updated, left.summary.updated));
|
|
6608
6670
|
return records;
|
|
6609
6671
|
}
|
|
6610
|
-
async function listAssignmentRecords(projectPath) {
|
|
6672
|
+
async function listAssignmentRecords(projectPath, traces) {
|
|
6611
6673
|
const assignmentsDir2 = resolve16(projectPath, "assignments");
|
|
6612
6674
|
if (!await fileExists(assignmentsDir2)) {
|
|
6613
6675
|
return [];
|
|
6614
6676
|
}
|
|
6615
6677
|
const entries = await readdir9(assignmentsDir2, { withFileTypes: true });
|
|
6616
|
-
const
|
|
6617
|
-
|
|
6618
|
-
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
|
|
6622
|
-
|
|
6623
|
-
|
|
6624
|
-
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
|
|
6678
|
+
const dirEntries = entries.filter((entry) => entry.isDirectory());
|
|
6679
|
+
const maybeRecords = await Promise.all(
|
|
6680
|
+
dirEntries.map(async (entry) => {
|
|
6681
|
+
const assignmentMd = resolve16(assignmentsDir2, entry.name, "assignment.md");
|
|
6682
|
+
if (!await fileExists(assignmentMd)) {
|
|
6683
|
+
return null;
|
|
6684
|
+
}
|
|
6685
|
+
const t0 = traces ? performance.now() : 0;
|
|
6686
|
+
const content = await readFile11(assignmentMd, "utf-8");
|
|
6687
|
+
const parsed = parseAssignmentFull(content);
|
|
6688
|
+
if (traces) accumulatePhase(traces, "read-assignment-md", performance.now() - t0);
|
|
6689
|
+
return parsed;
|
|
6690
|
+
})
|
|
6691
|
+
);
|
|
6692
|
+
const records = maybeRecords.filter((r) => r !== null);
|
|
6628
6693
|
records.sort((left, right) => compareTimestamps(right.updated, left.updated));
|
|
6629
6694
|
return records;
|
|
6630
6695
|
}
|
|
@@ -6782,13 +6847,20 @@ async function loadDependencyGraph(projectPath, assignments) {
|
|
|
6782
6847
|
}
|
|
6783
6848
|
return buildDependencyGraph(assignments);
|
|
6784
6849
|
}
|
|
6785
|
-
async function buildProjectRollup(projectPath, project, assignments) {
|
|
6850
|
+
async function buildProjectRollup(projectPath, project, assignments, traces) {
|
|
6786
6851
|
const progress = { total: assignments.length };
|
|
6852
|
+
const perAssignment = await Promise.all(
|
|
6853
|
+
assignments.map(async (assignment) => {
|
|
6854
|
+
const t0 = traces ? performance.now() : 0;
|
|
6855
|
+
const openQuestions2 = await countOpenQuestions(projectPath, assignment.slug);
|
|
6856
|
+
if (traces) accumulatePhase(traces, "count-open-questions", performance.now() - t0);
|
|
6857
|
+
return { status: assignment.status, openQuestions: openQuestions2 };
|
|
6858
|
+
})
|
|
6859
|
+
);
|
|
6787
6860
|
let openQuestions = 0;
|
|
6788
|
-
for (const
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
openQuestions += await countOpenQuestions(projectPath, assignment.slug);
|
|
6861
|
+
for (const entry of perAssignment) {
|
|
6862
|
+
progress[entry.status] = (progress[entry.status] ?? 0) + 1;
|
|
6863
|
+
openQuestions += entry.openQuestions;
|
|
6792
6864
|
}
|
|
6793
6865
|
const needsAttention = {
|
|
6794
6866
|
blockedCount: progress["blocked"] ?? 0,
|
|
@@ -6869,18 +6941,26 @@ function buildDependencyGraph(assignments) {
|
|
|
6869
6941
|
function findAssignmentStatus(assignments, slug) {
|
|
6870
6942
|
return assignments.find((assignment) => assignment.slug === slug)?.status ?? "pending";
|
|
6871
6943
|
}
|
|
6872
|
-
async function getAvailableTransitions(projectsDir2, projectSlug, assignmentSlug, assignment) {
|
|
6944
|
+
async function getAvailableTransitions(projectsDir2, projectSlug, assignmentSlug, assignment, options) {
|
|
6873
6945
|
const config = await getStatusConfig();
|
|
6874
6946
|
const transitionDefs = getTransitionDefinitions(config);
|
|
6875
6947
|
const actions = [];
|
|
6876
6948
|
const projectPath = resolve16(projectsDir2, projectSlug);
|
|
6949
|
+
const traces = options?.traces;
|
|
6877
6950
|
for (const definition of transitionDefs) {
|
|
6878
6951
|
let warning = null;
|
|
6879
6952
|
if (definition.command === "start" && !assignment.assignee) {
|
|
6880
6953
|
warning = "No assignee set \u2014 consider assigning before starting.";
|
|
6881
6954
|
}
|
|
6882
6955
|
if (definition.command === "start" && assignment.dependsOn.length > 0) {
|
|
6883
|
-
const
|
|
6956
|
+
const t0 = traces ? performance.now() : 0;
|
|
6957
|
+
const unmetDependencies = await getUnmetDependencies(
|
|
6958
|
+
projectPath,
|
|
6959
|
+
assignment.dependsOn,
|
|
6960
|
+
config.terminalStatuses,
|
|
6961
|
+
options?.dependencyStatusMap
|
|
6962
|
+
);
|
|
6963
|
+
if (traces) accumulatePhase(traces, "get-unmet-dependencies", performance.now() - t0);
|
|
6884
6964
|
if (unmetDependencies.length > 0) {
|
|
6885
6965
|
warning = `Unmet dependencies: ${unmetDependencies.join(", ")}.`;
|
|
6886
6966
|
}
|
|
@@ -6899,10 +6979,19 @@ async function getAvailableTransitions(projectsDir2, projectSlug, assignmentSlug
|
|
|
6899
6979
|
}
|
|
6900
6980
|
return actions;
|
|
6901
6981
|
}
|
|
6902
|
-
async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses3) {
|
|
6982
|
+
async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses3, dependencyStatusMap) {
|
|
6903
6983
|
const terminals = terminalStatuses3 ?? /* @__PURE__ */ new Set(["completed"]);
|
|
6904
6984
|
const unmet = [];
|
|
6905
6985
|
for (const dependency of dependsOn) {
|
|
6986
|
+
if (dependencyStatusMap) {
|
|
6987
|
+
const mappedStatus = dependencyStatusMap.get(dependency);
|
|
6988
|
+
if (mappedStatus !== void 0) {
|
|
6989
|
+
if (!terminals.has(mappedStatus)) {
|
|
6990
|
+
unmet.push(`${dependency} (${mappedStatus})`);
|
|
6991
|
+
}
|
|
6992
|
+
continue;
|
|
6993
|
+
}
|
|
6994
|
+
}
|
|
6906
6995
|
const dependencyPath = resolve16(projectPath, "assignments", dependency, "assignment.md");
|
|
6907
6996
|
if (!await fileExists(dependencyPath)) {
|
|
6908
6997
|
unmet.push(`${dependency} (missing)`);
|
|
@@ -6940,23 +7029,35 @@ function segmentSeverity(segment) {
|
|
|
6940
7029
|
return "medium";
|
|
6941
7030
|
}
|
|
6942
7031
|
}
|
|
6943
|
-
async function buildOverviewSegmentBuckets(projectsDir2, projectRecords, standaloneRecords) {
|
|
7032
|
+
async function buildOverviewSegmentBuckets(projectsDir2, projectRecords, standaloneRecords, traces) {
|
|
6944
7033
|
const now = Date.now();
|
|
6945
7034
|
const buckets = emptyBuckets();
|
|
6946
7035
|
const newestPool = [];
|
|
6947
7036
|
for (const record of projectRecords) {
|
|
6948
|
-
|
|
7037
|
+
const depMap = /* @__PURE__ */ new Map();
|
|
7038
|
+
for (const a of record.assignments) {
|
|
7039
|
+
depMap.set(a.slug, a.status);
|
|
7040
|
+
}
|
|
7041
|
+
const resolvedTransitions = await Promise.all(
|
|
7042
|
+
record.assignments.map(async (assignment) => {
|
|
7043
|
+
const t0 = traces ? performance.now() : 0;
|
|
7044
|
+
const availableTransitions = await getAvailableTransitions(
|
|
7045
|
+
projectsDir2,
|
|
7046
|
+
record.summary.slug,
|
|
7047
|
+
assignment.slug,
|
|
7048
|
+
assignment,
|
|
7049
|
+
{ traces, dependencyStatusMap: depMap }
|
|
7050
|
+
);
|
|
7051
|
+
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
7052
|
+
return { assignment, availableTransitions };
|
|
7053
|
+
})
|
|
7054
|
+
);
|
|
7055
|
+
for (const { assignment, availableTransitions } of resolvedTransitions) {
|
|
6949
7056
|
const segmentId = STATUS_TO_SEGMENT[assignment.status];
|
|
6950
7057
|
const stale = isStale(assignment.updated);
|
|
6951
7058
|
const isTerminal = TERMINAL_STATUSES2.has(assignment.status);
|
|
6952
7059
|
const agingMs = Math.max(0, now - parseTimestamp(assignment.updated));
|
|
6953
7060
|
const baseId = `${record.summary.slug}:${assignment.slug}`;
|
|
6954
|
-
const availableTransitions = await getAvailableTransitions(
|
|
6955
|
-
projectsDir2,
|
|
6956
|
-
record.summary.slug,
|
|
6957
|
-
assignment.slug,
|
|
6958
|
-
assignment
|
|
6959
|
-
);
|
|
6960
7061
|
const shared = {
|
|
6961
7062
|
projectSlug: record.summary.slug,
|
|
6962
7063
|
projectTitle: record.summary.title,
|
|
@@ -7006,14 +7107,21 @@ async function buildOverviewSegmentBuckets(projectsDir2, projectRecords, standal
|
|
|
7006
7107
|
}
|
|
7007
7108
|
}
|
|
7008
7109
|
}
|
|
7009
|
-
|
|
7110
|
+
const resolvedStandaloneTransitions = await Promise.all(
|
|
7111
|
+
standaloneRecords.map(async (sr) => {
|
|
7112
|
+
const t0 = traces ? performance.now() : 0;
|
|
7113
|
+
const availableTransitions = await getStandaloneAvailableTransitions(sr.record);
|
|
7114
|
+
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
7115
|
+
return { sr, availableTransitions };
|
|
7116
|
+
})
|
|
7117
|
+
);
|
|
7118
|
+
for (const { sr, availableTransitions } of resolvedStandaloneTransitions) {
|
|
7010
7119
|
const assignment = sr.record;
|
|
7011
7120
|
const segmentId = STATUS_TO_SEGMENT[assignment.status];
|
|
7012
7121
|
const stale = isStale(assignment.updated);
|
|
7013
7122
|
const isTerminal = TERMINAL_STATUSES2.has(assignment.status);
|
|
7014
7123
|
const agingMs = Math.max(0, now - parseTimestamp(assignment.updated));
|
|
7015
7124
|
const baseId = `standalone:${sr.id}`;
|
|
7016
|
-
const availableTransitions = await getStandaloneAvailableTransitions(assignment);
|
|
7017
7125
|
const shared = {
|
|
7018
7126
|
projectSlug: null,
|
|
7019
7127
|
projectTitle: null,
|