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.
@@ -1715,6 +1715,10 @@ var init_overviewCopy = __esm({
1715
1715
  // src/dashboard/api.ts
1716
1716
  import { readdir as readdir6, readFile as readFile8, writeFile as writeFile3 } from "fs/promises";
1717
1717
  import { resolve as resolve11, dirname as dirname2, basename } from "path";
1718
+ function accumulatePhase(traces, label, ms) {
1719
+ if (!traces) return;
1720
+ traces.subPhases.set(label, (traces.subPhases.get(label) ?? 0) + ms);
1721
+ }
1718
1722
  async function listStandaloneRecords(assignmentsDir) {
1719
1723
  if (!assignmentsDir) return [];
1720
1724
  if (!await fileExists(assignmentsDir)) return [];
@@ -2169,7 +2173,7 @@ async function buildStandaloneAssignmentDetail(resolved) {
2169
2173
  };
2170
2174
  return detail;
2171
2175
  }
2172
- async function listProjectRecords(projectsDir) {
2176
+ async function listProjectRecords(projectsDir, traces) {
2173
2177
  if (!await fileExists(projectsDir)) {
2174
2178
  return [];
2175
2179
  }
@@ -2179,61 +2183,75 @@ async function listProjectRecords(projectsDir) {
2179
2183
  }
2180
2184
  const entries = await readdir6(projectsDir, { withFileTypes: true });
2181
2185
  const projectDirs = entries.filter((entry) => entry.isDirectory() && !entry.name.startsWith("."));
2182
- const records = [];
2183
- for (const entry of projectDirs) {
2184
- const projectPath = resolve11(projectsDir, entry.name);
2185
- const projectMdPath = resolve11(projectPath, "project.md");
2186
- if (!await fileExists(projectMdPath)) {
2187
- continue;
2188
- }
2189
- const projectContent = await readFile8(projectMdPath, "utf-8");
2190
- const project = parseProject(projectContent);
2191
- const assignments = await listAssignmentRecords(projectPath);
2192
- const rollup = await buildProjectRollup(projectPath, project, assignments);
2193
- const updated = getProjectActivityTimestamp(project.updated, assignments);
2194
- records.push({
2195
- projectPath,
2196
- project,
2197
- assignments,
2198
- dependencyGraph: await loadDependencyGraph(projectPath, assignments),
2199
- summary: {
2200
- slug: project.slug || entry.name,
2201
- title: project.title,
2202
- status: rollup.status,
2203
- statusOverride: project.statusOverride,
2204
- archived: project.archived,
2205
- archivedAt: project.archivedAt,
2206
- archivedReason: project.archivedReason,
2207
- created: project.created,
2208
- updated,
2209
- tags: project.tags,
2210
- progress: rollup.progress,
2211
- needsAttention: rollup.needsAttention,
2212
- workspace: project.workspace
2186
+ const maybeRecords = await Promise.all(
2187
+ projectDirs.map(async (entry) => {
2188
+ const projectPath = resolve11(projectsDir, entry.name);
2189
+ const projectMdPath = resolve11(projectPath, "project.md");
2190
+ if (!await fileExists(projectMdPath)) {
2191
+ return null;
2213
2192
  }
2214
- });
2215
- }
2193
+ const t0 = traces ? performance.now() : 0;
2194
+ const projectContent = await readFile8(projectMdPath, "utf-8");
2195
+ const project = parseProject(projectContent);
2196
+ if (traces) accumulatePhase(traces, "parse-project-md", performance.now() - t0);
2197
+ const t1 = traces ? performance.now() : 0;
2198
+ const assignments = await listAssignmentRecords(projectPath, traces);
2199
+ if (traces) accumulatePhase(traces, "list-assignments", performance.now() - t1);
2200
+ const t2 = traces ? performance.now() : 0;
2201
+ const rollup = await buildProjectRollup(projectPath, project, assignments, traces);
2202
+ if (traces) accumulatePhase(traces, "build-rollup", performance.now() - t2);
2203
+ const updated = getProjectActivityTimestamp(project.updated, assignments);
2204
+ const t3 = traces ? performance.now() : 0;
2205
+ const dependencyGraph = await loadDependencyGraph(projectPath, assignments);
2206
+ if (traces) accumulatePhase(traces, "load-dep-graph", performance.now() - t3);
2207
+ return {
2208
+ projectPath,
2209
+ project,
2210
+ assignments,
2211
+ dependencyGraph,
2212
+ summary: {
2213
+ slug: project.slug || entry.name,
2214
+ title: project.title,
2215
+ status: rollup.status,
2216
+ statusOverride: project.statusOverride,
2217
+ archived: project.archived,
2218
+ archivedAt: project.archivedAt,
2219
+ archivedReason: project.archivedReason,
2220
+ created: project.created,
2221
+ updated,
2222
+ tags: project.tags,
2223
+ progress: rollup.progress,
2224
+ needsAttention: rollup.needsAttention,
2225
+ workspace: project.workspace
2226
+ }
2227
+ };
2228
+ })
2229
+ );
2230
+ const records = maybeRecords.filter((r) => r !== null);
2216
2231
  records.sort((left, right) => compareTimestamps(right.summary.updated, left.summary.updated));
2217
2232
  return records;
2218
2233
  }
2219
- async function listAssignmentRecords(projectPath) {
2234
+ async function listAssignmentRecords(projectPath, traces) {
2220
2235
  const assignmentsDir = resolve11(projectPath, "assignments");
2221
2236
  if (!await fileExists(assignmentsDir)) {
2222
2237
  return [];
2223
2238
  }
2224
2239
  const entries = await readdir6(assignmentsDir, { withFileTypes: true });
2225
- const records = [];
2226
- for (const entry of entries) {
2227
- if (!entry.isDirectory()) {
2228
- continue;
2229
- }
2230
- const assignmentMd = resolve11(assignmentsDir, entry.name, "assignment.md");
2231
- if (!await fileExists(assignmentMd)) {
2232
- continue;
2233
- }
2234
- const content = await readFile8(assignmentMd, "utf-8");
2235
- records.push(parseAssignmentFull(content));
2236
- }
2240
+ const dirEntries = entries.filter((entry) => entry.isDirectory());
2241
+ const maybeRecords = await Promise.all(
2242
+ dirEntries.map(async (entry) => {
2243
+ const assignmentMd = resolve11(assignmentsDir, entry.name, "assignment.md");
2244
+ if (!await fileExists(assignmentMd)) {
2245
+ return null;
2246
+ }
2247
+ const t0 = traces ? performance.now() : 0;
2248
+ const content = await readFile8(assignmentMd, "utf-8");
2249
+ const parsed = parseAssignmentFull(content);
2250
+ if (traces) accumulatePhase(traces, "read-assignment-md", performance.now() - t0);
2251
+ return parsed;
2252
+ })
2253
+ );
2254
+ const records = maybeRecords.filter((r) => r !== null);
2237
2255
  records.sort((left, right) => compareTimestamps(right.updated, left.updated));
2238
2256
  return records;
2239
2257
  }
@@ -2249,13 +2267,20 @@ async function loadDependencyGraph(projectPath, assignments) {
2249
2267
  }
2250
2268
  return buildDependencyGraph(assignments);
2251
2269
  }
2252
- async function buildProjectRollup(projectPath, project, assignments) {
2270
+ async function buildProjectRollup(projectPath, project, assignments, traces) {
2253
2271
  const progress = { total: assignments.length };
2272
+ const perAssignment = await Promise.all(
2273
+ assignments.map(async (assignment) => {
2274
+ const t0 = traces ? performance.now() : 0;
2275
+ const openQuestions2 = await countOpenQuestions(projectPath, assignment.slug);
2276
+ if (traces) accumulatePhase(traces, "count-open-questions", performance.now() - t0);
2277
+ return { status: assignment.status, openQuestions: openQuestions2 };
2278
+ })
2279
+ );
2254
2280
  let openQuestions = 0;
2255
- for (const assignment of assignments) {
2256
- const s = assignment.status;
2257
- progress[s] = (progress[s] ?? 0) + 1;
2258
- openQuestions += await countOpenQuestions(projectPath, assignment.slug);
2281
+ for (const entry of perAssignment) {
2282
+ progress[entry.status] = (progress[entry.status] ?? 0) + 1;
2283
+ openQuestions += entry.openQuestions;
2259
2284
  }
2260
2285
  const needsAttention = {
2261
2286
  blockedCount: progress["blocked"] ?? 0,
@@ -2308,18 +2333,26 @@ function buildDependencyGraph(assignments) {
2308
2333
  function findAssignmentStatus(assignments, slug) {
2309
2334
  return assignments.find((assignment) => assignment.slug === slug)?.status ?? "pending";
2310
2335
  }
2311
- async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug, assignment) {
2336
+ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug, assignment, options) {
2312
2337
  const config = await getStatusConfig();
2313
2338
  const transitionDefs = getTransitionDefinitions(config);
2314
2339
  const actions = [];
2315
2340
  const projectPath = resolve11(projectsDir, projectSlug);
2341
+ const traces = options?.traces;
2316
2342
  for (const definition of transitionDefs) {
2317
2343
  let warning = null;
2318
2344
  if (definition.command === "start" && !assignment.assignee) {
2319
2345
  warning = "No assignee set \u2014 consider assigning before starting.";
2320
2346
  }
2321
2347
  if (definition.command === "start" && assignment.dependsOn.length > 0) {
2322
- const unmetDependencies = await getUnmetDependencies(projectPath, assignment.dependsOn, config.terminalStatuses);
2348
+ const t0 = traces ? performance.now() : 0;
2349
+ const unmetDependencies = await getUnmetDependencies(
2350
+ projectPath,
2351
+ assignment.dependsOn,
2352
+ config.terminalStatuses,
2353
+ options?.dependencyStatusMap
2354
+ );
2355
+ if (traces) accumulatePhase(traces, "get-unmet-dependencies", performance.now() - t0);
2323
2356
  if (unmetDependencies.length > 0) {
2324
2357
  warning = `Unmet dependencies: ${unmetDependencies.join(", ")}.`;
2325
2358
  }
@@ -2338,10 +2371,19 @@ async function getAvailableTransitions(projectsDir, projectSlug, assignmentSlug,
2338
2371
  }
2339
2372
  return actions;
2340
2373
  }
2341
- async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses) {
2374
+ async function getUnmetDependencies(projectPath, dependsOn, terminalStatuses, dependencyStatusMap) {
2342
2375
  const terminals = terminalStatuses ?? /* @__PURE__ */ new Set(["completed"]);
2343
2376
  const unmet = [];
2344
2377
  for (const dependency of dependsOn) {
2378
+ if (dependencyStatusMap) {
2379
+ const mappedStatus = dependencyStatusMap.get(dependency);
2380
+ if (mappedStatus !== void 0) {
2381
+ if (!terminals.has(mappedStatus)) {
2382
+ unmet.push(`${dependency} (${mappedStatus})`);
2383
+ }
2384
+ continue;
2385
+ }
2386
+ }
2345
2387
  const dependencyPath = resolve11(projectPath, "assignments", dependency, "assignment.md");
2346
2388
  if (!await fileExists(dependencyPath)) {
2347
2389
  unmet.push(`${dependency} (missing)`);