@ogygie/studio 0.31.0 → 0.32.0

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/server.js CHANGED
@@ -1187,7 +1187,7 @@ ${deps.originOf(path)}`
1187
1187
  }
1188
1188
 
1189
1189
  // src/server/storage/local-adapter.ts
1190
- import { mkdir, writeFile } from "node:fs/promises";
1190
+ import { mkdir, stat as stat2, writeFile } from "node:fs/promises";
1191
1191
  import { dirname, isAbsolute as isAbsolute2, resolve as resolve2, sep as sep2 } from "node:path";
1192
1192
  function resolveInsideRoot2(rootDir, relativePath) {
1193
1193
  const root = resolve2(rootDir);
@@ -1214,6 +1214,14 @@ function createLocalSpecStorage(rootDir) {
1214
1214
  const absolute = resolveInsideRoot2(rootDir, path);
1215
1215
  await mkdir(dirname(absolute), { recursive: true });
1216
1216
  await writeFile(absolute, content, "utf8");
1217
+ },
1218
+ async modifiedAt(path) {
1219
+ const absolute = resolveInsideRoot2(rootDir, path);
1220
+ try {
1221
+ return (await stat2(absolute)).mtime.toISOString();
1222
+ } catch {
1223
+ return null;
1224
+ }
1217
1225
  }
1218
1226
  };
1219
1227
  }
@@ -1345,7 +1353,8 @@ function intentOf(body) {
1345
1353
  const sentence = /^.*?[.!?](?:\s|$)/u.exec(paragraph)?.[0]?.trim() ?? paragraph;
1346
1354
  return sentence.length > 160 ? `${sentence.slice(0, 157).trimEnd()}\u2026` : sentence;
1347
1355
  }
1348
- function toReadableSummary(filePath, spec) {
1356
+ var modifiedAtOf = (deps, filePath) => deps.storage.modifiedAt?.(filePath) ?? Promise.resolve(null);
1357
+ function toReadableSummary(filePath, spec, updatedAt) {
1349
1358
  return {
1350
1359
  id: spec.frontmatter.id,
1351
1360
  status: spec.frontmatter.status,
@@ -1353,7 +1362,8 @@ function toReadableSummary(filePath, spec) {
1353
1362
  depends_on: spec.frontmatter.depends_on,
1354
1363
  filePath,
1355
1364
  presentSections: presentSectionsOf(spec.body),
1356
- intent: intentOf(spec.body)
1365
+ intent: intentOf(spec.body),
1366
+ updatedAt
1357
1367
  };
1358
1368
  }
1359
1369
  async function listSpecFileNames(deps) {
@@ -1369,7 +1379,7 @@ async function listSpecSummaries(deps) {
1369
1379
  const parsed = parseSpec(filePath, fileName, content);
1370
1380
  const id = fileName.replace(/\.spec\.md$/, "");
1371
1381
  summaries.push(
1372
- parsed.ok ? toReadableSummary(filePath, parsed.spec) : { id, filePath, error: parsed.error }
1382
+ parsed.ok ? toReadableSummary(filePath, parsed.spec, await modifiedAtOf(deps, filePath)) : { id, filePath, error: parsed.error }
1373
1383
  );
1374
1384
  }
1375
1385
  return summaries;
@@ -1484,7 +1494,7 @@ ${serialized}
1484
1494
  ---${body}`;
1485
1495
  const message = isValidation ? `spec: validate ${specId}` : `spec: save ${specId}`;
1486
1496
  await deps.storage.write(filePath, content, message);
1487
- return toReadableSummary(filePath, { frontmatter: input.frontmatter, body });
1497
+ return toReadableSummary(filePath, { frontmatter: input.frontmatter, body }, await modifiedAtOf(deps, filePath));
1488
1498
  }
1489
1499
  async function createSpec(deps, specId) {
1490
1500
  const fileName = `${specId}.spec.md`;
@@ -1526,7 +1536,7 @@ async function createSpec(deps, specId) {
1526
1536
  suggestedFix: "Fix the template's frontmatter to match SpecFrontmatterSchema."
1527
1537
  });
1528
1538
  }
1529
- return toReadableSummary(filePath, parsed.spec);
1539
+ return toReadableSummary(filePath, parsed.spec, await modifiedAtOf(deps, filePath));
1530
1540
  }
1531
1541
  async function getManifest(deps) {
1532
1542
  const content = await deps.storage.readFile(MANIFEST_PATH);
@@ -1920,7 +1930,9 @@ var ReadableSpecSummarySchema = z7.object({
1920
1930
  filePath: z7.string(),
1921
1931
  presentSections: z7.array(z7.string()),
1922
1932
  /** The first sentence of `# @intent`, for a list that reads (v1.20: the target-spec suggestions of the brief). */
1923
- intent: z7.string().default("")
1933
+ intent: z7.string().default(""),
1934
+ /** When the spec last changed (ISO 8601) — the board's order, newest first (v3.4); null when the storage does not know. */
1935
+ updatedAt: z7.string().nullable().default(null)
1924
1936
  });
1925
1937
  var UnreadableSpecSummarySchema = z7.object({
1926
1938
  id: z7.string(),
@@ -3687,6 +3699,12 @@ function createHostedSpecStorage(deps) {
3687
3699
  async readFile(path) {
3688
3700
  return (await readerOf(path)).readFile(path);
3689
3701
  },
3702
+ async modifiedAt(path) {
3703
+ if (deps.lastCommitDateOf === void 0) return null;
3704
+ const specId = specIdOfPath2(path);
3705
+ const ref = specId !== null && (await branches()).has(specBranchOf(specId)) ? specBranchOf(specId) : deps.github.defaultBranch;
3706
+ return deps.lastCommitDateOf(ref, path);
3707
+ },
3690
3708
  async exists(path) {
3691
3709
  return (await readerOf(path)).exists(path);
3692
3710
  },
@@ -4729,6 +4747,15 @@ function createGithubPullsClient(deps) {
4729
4747
  );
4730
4748
  return (json ?? []).map((entry) => ({ sha: entry.sha, message: entry.commit.message }));
4731
4749
  },
4750
+ async lastCommitDateOf(branch, path) {
4751
+ const { json } = await request(
4752
+ "GET",
4753
+ `${repoUrl}/commits?sha=${encodeURIComponent(branch)}&path=${encodeURIComponent(path)}&per_page=1`,
4754
+ void 0
4755
+ );
4756
+ const last = json?.[0];
4757
+ return last?.commit.committer?.date ?? last?.commit.author?.date ?? null;
4758
+ },
4732
4759
  async getPullRequest(number) {
4733
4760
  const { json } = await request(
4734
4761
  "GET",
@@ -5130,6 +5157,7 @@ async function startHostedStudio(options) {
5130
5157
  }
5131
5158
  }
5132
5159
  }
5160
+ const pulls = createGithubPullsClient({ github: config.repo, fetchImpl: githubApp.installationFetch });
5133
5161
  const app = await createStudioApp({
5134
5162
  uiDistDir,
5135
5163
  hosted: {
@@ -5157,7 +5185,7 @@ async function startHostedStudio(options) {
5157
5185
  }
5158
5186
  }
5159
5187
  },
5160
- pulls: createGithubPullsClient({ github: config.repo, fetchImpl: githubApp.installationFetch }),
5188
+ pulls,
5161
5189
  readerFor,
5162
5190
  usage: createGithubUsageClient({ github: config.repo, fetchImpl: githubApp.installationFetch }),
5163
5191
  ...deploy === void 0 ? {} : { deploy },
@@ -5167,6 +5195,7 @@ async function startHostedStudio(options) {
5167
5195
  github: config.repo,
5168
5196
  readerFor,
5169
5197
  listSpecBranches,
5198
+ lastCommitDateOf: pulls.lastCommitDateOf,
5170
5199
  client,
5171
5200
  user,
5172
5201
  now,