gitifact 0.8.0 → 0.8.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/dist/browser/assets/Banner-DQ1G6Jkv.js +1 -0
  2. package/dist/browser/assets/{BottomSheet-7C5eiOji.js → BottomSheet-BFoM6syr.js} +1 -1
  3. package/dist/browser/assets/Grid-DCCm_m5g.js +1 -0
  4. package/dist/browser/assets/HoverCard-Bpt8LVFT.js +1 -0
  5. package/dist/browser/assets/{Icon-EsJf4C7A.js → Icon-DPaLmsmh.js} +3 -3
  6. package/dist/browser/assets/{IconButton-CgOx1Jog.js → IconButton-Br-4XhQr.js} +1 -1
  7. package/dist/browser/assets/{Markdown-K4Mne1wf.js → Markdown-B9QJ2Ao-.js} +4 -4
  8. package/dist/browser/assets/{MenuBottomSheet-BOGxMa05.js → MenuBottomSheet-Dgz4h1S7.js} +1 -1
  9. package/dist/browser/assets/{MetadataListItem-C3SqHyk0.js → MetadataListItem-BEwPg27H.js} +1 -1
  10. package/dist/browser/assets/ProgressBarMarkTooltip-BxGSd9KS.js +1 -0
  11. package/dist/browser/assets/{Section-BEbhTpxd.js → Section-Z2PuhE6c.js} +1 -1
  12. package/dist/browser/assets/Selector-BlDYK5Fb.js +2 -0
  13. package/dist/browser/assets/Tab-BoCyIuQw.js +1 -0
  14. package/dist/browser/assets/{Table-8qCDrsKI.js → Table-LdzcI4YS.js} +3 -3
  15. package/dist/browser/assets/TimestampHoverCard-B5JBaQDZ.js +1 -0
  16. package/dist/browser/assets/{Token-Dmavo-NE.js → Token-CYBSUyAu.js} +1 -1
  17. package/dist/browser/assets/{Tooltip-QZg2pB5o.js → Tooltip-MoU3alsv.js} +1 -1
  18. package/dist/browser/assets/{about-B8tYN8_7.js → about-ClxCgIMf.js} +2 -2
  19. package/dist/browser/assets/activity-timeline-BEeyw2GA.css +1 -0
  20. package/dist/browser/assets/activity-timeline-i0lmNZ1o.js +2 -0
  21. package/dist/browser/assets/{appearance-CDQ4fLdE.js → appearance-DgDbwXAm.js} +1 -1
  22. package/dist/browser/assets/changelog-bGtJx397.js +2 -0
  23. package/dist/browser/assets/commit-5TIiQrYh.js +4 -0
  24. package/dist/browser/assets/{commit-45NumYDT.css → commit-DVyOH45R.css} +1 -1
  25. package/dist/browser/assets/{container.stylex-6OsSOpKw.js → container.stylex-rwI38dw9.js} +1 -1
  26. package/dist/browser/assets/contributor-2mkEa2Wo.css +1 -0
  27. package/dist/browser/assets/contributor-C1R6Q471.js +1 -0
  28. package/dist/browser/assets/contributors-DjNETDIr.js +1 -0
  29. package/dist/browser/assets/{contributors._email-CW4qlA9y.js → contributors._email-DXxCGNZx.js} +1 -1
  30. package/dist/browser/assets/contributors.index-ByVvGuYC.js +1 -0
  31. package/dist/browser/assets/dashboard.index-RoAwRn4H.js +1 -0
  32. package/dist/browser/assets/document-BwoITy_-.js +2 -0
  33. package/dist/browser/assets/{document-D4osdtS6.js → document-CwA2dxtP.js} +5 -5
  34. package/dist/browser/assets/document-DEGaf2yT.css +1 -0
  35. package/dist/browser/assets/features-CttwIYBf.js +2 -0
  36. package/dist/browser/assets/features-ZYCEDoiE.css +1 -0
  37. package/dist/browser/assets/features._featureId-DPctVYNz.js +1 -0
  38. package/dist/browser/assets/features.index-lcNtlMn8.js +1 -0
  39. package/dist/browser/assets/getting-started-Chwezama.js +1 -0
  40. package/dist/browser/assets/git-CtsTwkaq.js +1 -0
  41. package/dist/browser/assets/{index-BER0M7UG.css → index-CmU7dwFQ.css} +1 -1
  42. package/dist/browser/assets/index-iSt1IRzY.js +48 -0
  43. package/dist/browser/assets/instructions-2OUMP-vh.css +1 -0
  44. package/dist/browser/assets/instructions-DULmclHB.js +1 -0
  45. package/dist/browser/assets/instructions._instructionId-CBz40hpF.js +1 -0
  46. package/dist/browser/assets/instructions.agents-CDpXylSu.js +1 -0
  47. package/dist/browser/assets/instructions.index-BZ5tCMZJ.js +1 -0
  48. package/dist/browser/assets/{lazyRouteComponent-Dbwmw-_u.js → lazyRouteComponent-DhTs_UEV.js} +1 -1
  49. package/dist/browser/assets/load-more-DxscRoh6.js +1 -0
  50. package/dist/browser/assets/{page-header-PYFVxi9j.js → page-header-B9LQWl7J.js} +1 -1
  51. package/dist/browser/assets/records-Dk3shbYl.css +1 -0
  52. package/dist/browser/assets/records-page-B4tDip2H.css +1 -0
  53. package/dist/browser/assets/records-page-BjyFUmMr.js +1 -0
  54. package/dist/browser/assets/records._recordId-vwjWdPRX.js +1 -0
  55. package/dist/browser/assets/records.commits._commit-CYYgrOxv.js +1 -0
  56. package/dist/browser/assets/records.index-N49ep6ay.js +1 -0
  57. package/dist/browser/assets/records.working-CcYY8eyI.js +1 -0
  58. package/dist/browser/assets/request-state-BeiYxf95.js +1 -0
  59. package/dist/browser/assets/search-CSSuO-jR.js +1 -0
  60. package/dist/browser/assets/search-palette-BCEUlRVv.js +561 -0
  61. package/dist/browser/assets/search-palette-CDmYAo36.css +1 -0
  62. package/dist/browser/assets/{settings-BdZ_rM9V.js → settings-I5Mn86wq.js} +1 -1
  63. package/dist/browser/assets/{themeProps-CfjLB1h4.js → themeProps-CkLe5zXY.js} +1 -1
  64. package/dist/browser/assets/{useClipboard-CTfBnLlg.js → useClipboard-Cqx6Upy7.js} +1 -1
  65. package/dist/browser/assets/useDevWarning-Dudfg3qF.js +1 -0
  66. package/dist/browser/assets/{useInfiniteQuery-Cs8d91AB.js → useInfiniteQuery-tqfloUvn.js} +1 -1
  67. package/dist/browser/assets/{useIsomorphicLayoutEffect-DByj3svz.js → useIsomorphicLayoutEffect-BiEJxDg_.js} +1 -1
  68. package/dist/browser/assets/{useKeyboardHint-CuvkDYsZ.js → useKeyboardHint-HHpn4XUs.js} +1 -1
  69. package/dist/browser/assets/{useMediaQuery-CqJr5z44.js → useMediaQuery-twbHF0pO.js} +1 -1
  70. package/dist/browser/assets/{useScrollLock-CmZKJxOo.js → useScrollLock-D-Y-zHSV.js} +1 -1
  71. package/dist/browser/index.html +25 -24
  72. package/dist/i18n/en/block.md +23 -23
  73. package/dist/i18n/en/changelog.md +30 -0
  74. package/dist/i18n/en/docs/commit.md +1 -1
  75. package/dist/i18n/en/docs/instructions.md +16 -3
  76. package/dist/i18n/en/docs/migrate.md +3 -2
  77. package/dist/i18n/en/docs/spec.md +1 -1
  78. package/dist/i18n/en/docs/workflow.md +1 -1
  79. package/dist/i18n/ko/block.md +25 -25
  80. package/dist/i18n/ko/changelog.md +30 -0
  81. package/dist/i18n/ko/docs/commit.md +1 -1
  82. package/dist/i18n/ko/docs/instructions.md +16 -3
  83. package/dist/i18n/ko/docs/migrate.md +3 -2
  84. package/dist/i18n/ko/docs/spec.md +1 -1
  85. package/dist/i18n/ko/docs/workflow.md +1 -1
  86. package/dist/main.js +1660 -699
  87. package/package.json +1 -1
  88. package/dist/browser/assets/Grid-BrUBBmhu.js +0 -1
  89. package/dist/browser/assets/HgiRefresh-DVzwwzGM.js +0 -1
  90. package/dist/browser/assets/ProgressBarMarkTooltip-C5Moep_o.js +0 -1
  91. package/dist/browser/assets/Selector-DuT8AsMB.js +0 -2
  92. package/dist/browser/assets/TimestampHoverCard-_VKbn1Py.js +0 -1
  93. package/dist/browser/assets/activity-timeline-CiFzANgu.css +0 -1
  94. package/dist/browser/assets/activity-timeline-USDjdupU.js +0 -2
  95. package/dist/browser/assets/changelog-BwEMGKor.js +0 -2
  96. package/dist/browser/assets/commit-C0G_pkoH.js +0 -4
  97. package/dist/browser/assets/contributors-FNt-eYzY.js +0 -1
  98. package/dist/browser/assets/contributors.index-BMT_fvWV.js +0 -1
  99. package/dist/browser/assets/dashboard.index-IeAMhDp4.js +0 -1
  100. package/dist/browser/assets/document-BbpOg7j8.js +0 -1
  101. package/dist/browser/assets/document-DpkFXhPm.css +0 -1
  102. package/dist/browser/assets/features-D91CUmI3.css +0 -1
  103. package/dist/browser/assets/features-DUe_HTbz.js +0 -4
  104. package/dist/browser/assets/features._featureId-CPuJxCrI.js +0 -1
  105. package/dist/browser/assets/features.index-BqKMq6DZ.js +0 -1
  106. package/dist/browser/assets/getting-started-DG-Skl34.js +0 -1
  107. package/dist/browser/assets/git-B2XLYoEb.js +0 -1
  108. package/dist/browser/assets/index-BoqBsl1i.js +0 -48
  109. package/dist/browser/assets/instructions-CX6doP-p.css +0 -1
  110. package/dist/browser/assets/instructions-CmMSgO5v.js +0 -1
  111. package/dist/browser/assets/instructions._instructionId-Biq4h3Db.js +0 -1
  112. package/dist/browser/assets/instructions.agents-C1LXUm7K.js +0 -1
  113. package/dist/browser/assets/instructions.index-DCiLsKxg.js +0 -1
  114. package/dist/browser/assets/records-CiGcLzEi.css +0 -1
  115. package/dist/browser/assets/records-page-BSx-Bxmm.css +0 -1
  116. package/dist/browser/assets/records-page-D8qxMJNY.js +0 -1
  117. package/dist/browser/assets/records._recordId-B6sdT5ro.js +0 -1
  118. package/dist/browser/assets/records.commits._commit-goSqW9tl.js +0 -1
  119. package/dist/browser/assets/records.index-CxubA4G5.js +0 -1
  120. package/dist/browser/assets/related-list-CU-kUhYj.js +0 -2
  121. package/dist/browser/assets/related-list-Rp3yvN_G.css +0 -1
  122. package/dist/browser/assets/request-state-rD3dhp0I.js +0 -1
  123. package/dist/browser/assets/search-BCYi0CBz.js +0 -1
  124. package/dist/browser/assets/search-palette-C-XxJJ6I.js +0 -561
  125. package/dist/browser/assets/search-palette-DpmGOAIg.css +0 -1
  126. package/dist/browser/assets/useCollapsible-D7UZAUy-.js +0 -1
  127. package/dist/browser/assets/useDevWarning-gW8_bNZ_.js +0 -1
package/dist/main.js CHANGED
@@ -2427,11 +2427,11 @@ Expecting one of '${allowedValues.join("', '")}'`);
2427
2427
  * @private
2428
2428
  */
2429
2429
  _parseCommand(operands, unknown2) {
2430
- const parsed = this.parseOptions(unknown2);
2430
+ const parsed2 = this.parseOptions(unknown2);
2431
2431
  this._parseOptionsEnv();
2432
2432
  this._parseOptionsImplied();
2433
- operands = operands.concat(parsed.operands);
2434
- unknown2 = parsed.unknown;
2433
+ operands = operands.concat(parsed2.operands);
2434
+ unknown2 = parsed2.unknown;
2435
2435
  this.args = operands.concat(unknown2);
2436
2436
  if (operands && this._findCommand(operands[0])) {
2437
2437
  return this._dispatchSubcommand(operands[0], operands.slice(1), unknown2);
@@ -2450,12 +2450,12 @@ Expecting one of '${allowedValues.join("', '")}'`);
2450
2450
  if (this.commands.length && this.args.length === 0 && !this._actionHandler && !this._defaultCommandName) {
2451
2451
  this.help({ error: true });
2452
2452
  }
2453
- this._outputHelpIfRequested(parsed.unknown);
2453
+ this._outputHelpIfRequested(parsed2.unknown);
2454
2454
  this._checkForMissingMandatoryOptions();
2455
2455
  this._checkForConflictingOptions();
2456
2456
  const checkForUnknownOptions = () => {
2457
- if (parsed.unknown.length > 0) {
2458
- this.unknownOption(parsed.unknown[0]);
2457
+ if (parsed2.unknown.length > 0) {
2458
+ this.unknownOption(parsed2.unknown[0]);
2459
2459
  }
2460
2460
  };
2461
2461
  const commandEvent = `command:${this.name()}`;
@@ -3381,7 +3381,7 @@ var program = new Command();
3381
3381
  import { createServer } from "node:http";
3382
3382
  import { randomUUID as randomUUID3 } from "node:crypto";
3383
3383
  import { fileURLToPath } from "node:url";
3384
- import { readFile as readFile7 } from "node:fs/promises";
3384
+ import { readFile as readFile8 } from "node:fs/promises";
3385
3385
 
3386
3386
  // ../../packages/core/dist/domain/repository-status.js
3387
3387
  function summarizeChanges(changes2) {
@@ -3659,16 +3659,16 @@ var RepositoryReadError = class extends Error {
3659
3659
  // ../../packages/core/dist/use-cases/read-repository-status.js
3660
3660
  async function readRepositoryStatus(reader, services) {
3661
3661
  const startedAt = services.now();
3662
- const state = await reader.read();
3662
+ const state2 = await reader.read();
3663
3663
  return {
3664
- ...state,
3664
+ ...state2,
3665
3665
  observation: {
3666
3666
  id: services.newId(),
3667
3667
  startedAt,
3668
3668
  completedAt: services.now(),
3669
3669
  consistency: "best-effort"
3670
3670
  },
3671
- summary: summarizeChanges(state.changes)
3671
+ summary: summarizeChanges(state2.changes)
3672
3672
  };
3673
3673
  }
3674
3674
 
@@ -3775,9 +3775,9 @@ function extractLinks(body2) {
3775
3775
  fence = void 0;
3776
3776
  continue;
3777
3777
  }
3778
- const open5 = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
3779
- if (open5) {
3780
- fence = { char: open5[1][0], size: open5[1].length };
3778
+ const open6 = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
3779
+ if (open6) {
3780
+ fence = { char: open6[1][0], size: open6[1].length };
3781
3781
  continue;
3782
3782
  }
3783
3783
  for (const m of line2.replace(/`[^`]*`/g, "").matchAll(/!?\[[^\]]*\]\((?:<([^>]+)>|([^)\s]+))(?:\s+"[^"]*")?\)/g))
@@ -3925,9 +3925,9 @@ function parseDesign(source2, specId) {
3925
3925
  fence = void 0;
3926
3926
  return line2;
3927
3927
  }
3928
- const open5 = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
3929
- if (open5) {
3930
- fence = { char: open5[1][0], size: open5[1].length };
3928
+ const open6 = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
3929
+ if (open6) {
3930
+ fence = { char: open6[1][0], size: open6[1].length };
3931
3931
  return line2;
3932
3932
  }
3933
3933
  if (/^#\s/.test(line2))
@@ -3970,9 +3970,9 @@ function parseDocument(path3, source2) {
3970
3970
  fence = void 0;
3971
3971
  continue;
3972
3972
  }
3973
- const open5 = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
3974
- if (open5) {
3975
- fence = { char: open5[1][0], size: open5[1].length };
3973
+ const open6 = /^ {0,3}(`{3,}|~{3,})(.*)$/.exec(line2);
3974
+ if (open6) {
3975
+ fence = { char: open6[1][0], size: open6[1].length };
3976
3976
  continue;
3977
3977
  }
3978
3978
  if (/^#\s/.test(line2))
@@ -4404,14 +4404,14 @@ function sections(raw, path3) {
4404
4404
  found.at(-1)?.lines.push(line2);
4405
4405
  continue;
4406
4406
  }
4407
- const open5 = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
4408
- if (open5)
4409
- fence = { char: open5[1][0], size: open5[1].length };
4407
+ const open6 = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
4408
+ if (open6)
4409
+ fence = { char: open6[1][0], size: open6[1].length };
4410
4410
  else if (/^#\s/.test(line2))
4411
4411
  throw new DocumentError("BODY_HEADING", path3, t("doc.BODY_HEADING", { path: path3 }));
4412
4412
  else if (/<!-- gitifact-/.test(line2))
4413
4413
  throw new DocumentError("BODY_MARKER", path3, t("doc.BODY_MARKER", { path: path3 }));
4414
- const heading = !open5 && /^##\s+(.+?)\s*#*\s*$/.exec(line2);
4414
+ const heading = !open6 && /^##\s+(.+?)\s*#*\s*$/.exec(line2);
4415
4415
  if (heading) {
4416
4416
  const key = sectionKeyOf(heading[1]);
4417
4417
  if (!key || found.some((s) => s.key === key))
@@ -4595,9 +4595,9 @@ function body(raw, path3) {
4595
4595
  fence = void 0;
4596
4596
  continue;
4597
4597
  }
4598
- const open5 = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
4599
- if (open5) {
4600
- fence = { char: open5[1][0], size: open5[1].length };
4598
+ const open6 = /^ {0,3}(`{3,}|~{3,})/.exec(line2);
4599
+ if (open6) {
4600
+ fence = { char: open6[1][0], size: open6[1].length };
4601
4601
  continue;
4602
4602
  }
4603
4603
  if (/^#\s/.test(line2))
@@ -4657,6 +4657,19 @@ function parseDocumentFile(path3, source2) {
4657
4657
  }
4658
4658
  return { kind: "design", feature: where.feature, order: order(), requirements: refs?.type === "list" ? refs.items : [], sources: sources(fields2, path3), ...common };
4659
4659
  }
4660
+ var isInstructionReference = (path3) => path3.endsWith(".md");
4661
+ function parseInstructionFile(path3, source2) {
4662
+ if (source2.includes("\0") || /\r(?!\n)/.test(source2) || source2.charCodeAt(0) === 65279)
4663
+ throw new DocumentError("INVALID_CHARACTERS", path3, t("doc.INVALID_CHARACTERS", { path: path3 }));
4664
+ const { fields: fields2 } = parseFrontmatterBlock(source2.replace(/\r\n/g, "\n"), path3);
4665
+ for (const key of fields2.keys())
4666
+ if (key !== "title" && key !== "description")
4667
+ throw new DocumentError("FRONTMATTER_UNKNOWN_KEY", path3, t("doc.FRONTMATTER_UNKNOWN_KEY", { path: path3, key }));
4668
+ for (const key of ["title", "description"])
4669
+ if (!fields2.has(key))
4670
+ throw new DocumentError("FRONTMATTER_MISSING_KEY", path3, t("doc.FRONTMATTER_MISSING_KEY", { path: path3, key }));
4671
+ return { title: text(fields2, "title", path3, 200), description: text(fields2, "description", path3, 300) };
4672
+ }
4660
4673
  function renderDocumentFile(doc) {
4661
4674
  const front = renderFrontmatterBlock([
4662
4675
  ["id", doc.id],
@@ -4725,8 +4738,10 @@ function checkDocuments(files) {
4725
4738
  }
4726
4739
  if (where.type === "instruction-file") {
4727
4740
  instructionFolders.set(where.name, instructionFolders.get(where.name) === true);
4728
- if (path3.endsWith(".md"))
4741
+ if (isInstructionReference(path3)) {
4729
4742
  specLinks(path3, source2);
4743
+ parseInstructionFile(path3, source2);
4744
+ }
4730
4745
  continue;
4731
4746
  }
4732
4747
  if (where.kind === "instruction")
@@ -10387,26 +10402,26 @@ function settle(inst, answer) {
10387
10402
  function isRecursiveSchema(inst) {
10388
10403
  return isRecursive(inst, /* @__PURE__ */ new Set(), true) !== NONE;
10389
10404
  }
10390
- function bucketFor(state, inst) {
10391
- let bucket = state.buckets.get(inst);
10405
+ function bucketFor(state2, inst) {
10406
+ let bucket = state2.buckets.get(inst);
10392
10407
  if (!bucket) {
10393
10408
  bucket = /* @__PURE__ */ new WeakMap();
10394
- state.buckets.set(inst, bucket);
10409
+ state2.buckets.set(inst, bucket);
10395
10410
  }
10396
10411
  return bucket;
10397
10412
  }
10398
10413
  var handoff;
10399
10414
  var open = [];
10400
10415
  var memo = {
10401
- alloc(_inst, payload, empty) {
10416
+ alloc(_inst, payload, empty2) {
10402
10417
  const bucket = handoff;
10403
10418
  if (!bucket)
10404
- return empty;
10419
+ return empty2;
10405
10420
  handoff = void 0;
10406
- const entry2 = { value: empty, issues: null };
10421
+ const entry2 = { value: empty2, issues: null };
10407
10422
  bucket.set(payload.value, entry2);
10408
10423
  open.push(entry2);
10409
- return empty;
10424
+ return empty2;
10410
10425
  },
10411
10426
  guard(inst) {
10412
10427
  var _a3;
@@ -10449,16 +10464,16 @@ var memo = {
10449
10464
  const input2 = payload.value;
10450
10465
  if (!isRef(input2))
10451
10466
  return base(payload, ctx);
10452
- let state = ctx[STATE];
10453
- if (!state) {
10454
- state = { buckets: /* @__PURE__ */ new WeakMap(), backEdges: void 0 };
10455
- ctx[STATE] = state;
10467
+ let state2 = ctx[STATE];
10468
+ if (!state2) {
10469
+ state2 = { buckets: /* @__PURE__ */ new WeakMap(), backEdges: void 0 };
10470
+ ctx[STATE] = state2;
10456
10471
  }
10457
10472
  let bucket;
10458
10473
  if (lastCtx === ctx) {
10459
10474
  bucket = lastBucket;
10460
10475
  } else {
10461
- bucket = bucketFor(state, inst);
10476
+ bucket = bucketFor(state2, inst);
10462
10477
  lastCtx = ctx;
10463
10478
  lastBucket = bucket;
10464
10479
  }
@@ -10470,8 +10485,8 @@ var memo = {
10470
10485
  payload.issues.push(...cloneIssues(hit.issues));
10471
10486
  } else {
10472
10487
  payload.memo = true;
10473
- state.backEdges ?? (state.backEdges = /* @__PURE__ */ new WeakSet());
10474
- state.backEdges.add(hit.value);
10488
+ state2.backEdges ?? (state2.backEdges = /* @__PURE__ */ new WeakSet());
10489
+ state2.backEdges.add(hit.value);
10475
10490
  }
10476
10491
  return payload;
10477
10492
  }
@@ -13077,8 +13092,8 @@ var error20 = () => {
13077
13092
  case "invalid_union":
13078
13093
  return "\u05E7\u05DC\u05D8 \u05DC\u05D0 \u05EA\u05E7\u05D9\u05DF";
13079
13094
  case "invalid_element": {
13080
- const place2 = withDefinite(issue2.origin ?? "array");
13081
- return `\u05E2\u05E8\u05DA \u05DC\u05D0 \u05EA\u05E7\u05D9\u05DF \u05D1${place2}`;
13095
+ const place3 = withDefinite(issue2.origin ?? "array");
13096
+ return `\u05E2\u05E8\u05DA \u05DC\u05D0 \u05EA\u05E7\u05D9\u05DF \u05D1${place3}`;
13082
13097
  }
13083
13098
  default:
13084
13099
  return `\u05E7\u05DC\u05D8 \u05DC\u05D0 \u05EA\u05E7\u05D9\u05DF`;
@@ -25003,9 +25018,11 @@ var browserHttpErrorV1 = external_exports.strictObject({
25003
25018
  });
25004
25019
 
25005
25020
  // ../../packages/contracts/dist/versions/browser-specs-v6.js
25021
+ var state = external_exports.enum(["committed", "added", "modified", "deleted"]);
25022
+ var standing = { state, previousPath: external_exports.string().optional() };
25006
25023
  var contributor = external_exports.strictObject({ email: external_exports.string(), name: external_exports.string(), commits: external_exports.number().int().nonnegative(), latest: external_exports.string() });
25007
25024
  var source = external_exports.strictObject({ id: external_exports.string().optional(), title: external_exports.string().optional(), path: external_exports.string().optional(), url: external_exports.string().optional(), note: external_exports.string().optional() });
25008
- var requirement = external_exports.strictObject({ id: external_exports.string(), path: external_exports.string(), title: external_exports.string(), description: external_exports.string(), order: external_exports.number().int().nonnegative(), body: external_exports.string() });
25025
+ var requirement = external_exports.strictObject({ id: external_exports.string(), path: external_exports.string(), title: external_exports.string(), description: external_exports.string(), order: external_exports.number().int().nonnegative(), body: external_exports.string(), ...standing });
25009
25026
  var design = external_exports.strictObject({
25010
25027
  id: external_exports.string(),
25011
25028
  path: external_exports.string(),
@@ -25014,7 +25031,8 @@ var design = external_exports.strictObject({
25014
25031
  order: external_exports.number().int().nonnegative(),
25015
25032
  body: external_exports.string(),
25016
25033
  requirements: external_exports.array(external_exports.string()),
25017
- sources: external_exports.array(source)
25034
+ sources: external_exports.array(source),
25035
+ ...standing
25018
25036
  });
25019
25037
  var feature = external_exports.strictObject({
25020
25038
  id: external_exports.string(),
@@ -25022,13 +25040,14 @@ var feature = external_exports.strictObject({
25022
25040
  title: external_exports.string(),
25023
25041
  description: external_exports.string(),
25024
25042
  body: external_exports.string(),
25043
+ ...standing,
25025
25044
  requirements: external_exports.array(requirement),
25026
25045
  designs: external_exports.array(design),
25027
25046
  // Authors of commits touching the feature folder and the latest such commit; empty until first committed.
25028
25047
  contributors: external_exports.array(contributor),
25029
25048
  updatedAt: external_exports.string().nullable()
25030
25049
  });
25031
- var instructionFile = external_exports.strictObject({ path: external_exports.string(), size: external_exports.number().int().nonnegative() });
25050
+ var instructionFile = external_exports.strictObject({ path: external_exports.string(), size: external_exports.number().int().nonnegative(), title: external_exports.string().optional(), description: external_exports.string().optional() });
25032
25051
  var instruction = external_exports.strictObject({
25033
25052
  id: external_exports.string(),
25034
25053
  name: external_exports.string(),
@@ -25036,6 +25055,7 @@ var instruction = external_exports.strictObject({
25036
25055
  title: external_exports.string(),
25037
25056
  description: external_exports.string(),
25038
25057
  body: external_exports.string(),
25058
+ ...standing,
25039
25059
  // Paths relative to the instruction folder, index.md left out; `limited` when the folder holds more than the list carries.
25040
25060
  files: external_exports.array(instructionFile),
25041
25061
  filesLimited: external_exports.boolean(),
@@ -25044,19 +25064,73 @@ var instruction = external_exports.strictObject({
25044
25064
  var agents = external_exports.strictObject({ path: external_exports.string(), body: external_exports.string(), updatedAt: external_exports.string().nullable() });
25045
25065
  var problem = external_exports.strictObject({ code: external_exports.string(), path: external_exports.string(), message: external_exports.string() });
25046
25066
  var oid2 = external_exports.string().regex(/^(?:[a-f0-9]{40}|[a-f0-9]{64})$/);
25047
- var browserSpecsV6 = external_exports.strictObject({
25048
- contract: external_exports.literal("browser-specs"),
25049
- version: external_exports.literal(6),
25067
+ var indexFeature = external_exports.strictObject({
25068
+ id: external_exports.string(),
25069
+ path: external_exports.string(),
25070
+ title: external_exports.string(),
25071
+ state,
25072
+ requirements: external_exports.array(external_exports.strictObject({ id: external_exports.string(), path: external_exports.string(), title: external_exports.string(), description: external_exports.string() })),
25073
+ designs: external_exports.array(external_exports.strictObject({ id: external_exports.string(), path: external_exports.string(), title: external_exports.string() }))
25074
+ });
25075
+ var indexInstruction = external_exports.strictObject({ id: external_exports.string(), name: external_exports.string(), path: external_exports.string(), title: external_exports.string() });
25076
+ var browserCheckoutV1 = external_exports.strictObject({
25077
+ contract: external_exports.literal("browser-checkout"),
25078
+ version: external_exports.literal(1),
25050
25079
  sessionId: external_exports.string(),
25051
25080
  head: oid2.nullable(),
25052
25081
  observedAt: external_exports.string(),
25053
25082
  working: external_exports.boolean(),
25054
- features: external_exports.array(feature),
25055
- instructions: external_exports.array(instruction),
25056
- agents: agents.nullable(),
25083
+ stamp: external_exports.string(),
25057
25084
  problems: external_exports.array(problem),
25085
+ index: external_exports.strictObject({ features: external_exports.array(indexFeature), instructions: external_exports.array(indexInstruction), people: external_exports.array(external_exports.strictObject({ name: external_exports.string(), email: external_exports.string() })) })
25086
+ });
25087
+ var featureRow = external_exports.strictObject({
25088
+ id: external_exports.string(),
25089
+ path: external_exports.string(),
25090
+ title: external_exports.string(),
25091
+ description: external_exports.string(),
25092
+ state,
25093
+ designs: external_exports.number().int().nonnegative(),
25058
25094
  contributors: external_exports.array(contributor),
25059
- contributorsLimited: external_exports.boolean()
25095
+ updatedAt: external_exports.string().nullable(),
25096
+ requirementCount: external_exports.number().int().nonnegative(),
25097
+ // `acceptance`: the numbered items under the acceptance heading, null without one. `designed`: a design names it.
25098
+ requirements: external_exports.array(external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), description: external_exports.string(), state, acceptance: external_exports.number().int().positive().nullable(), designed: external_exports.boolean() })),
25099
+ hidden: external_exports.number().int().nonnegative()
25100
+ });
25101
+ var browserFeaturesV1 = external_exports.strictObject({
25102
+ contract: external_exports.literal("browser-features"),
25103
+ version: external_exports.literal(1),
25104
+ sessionId: external_exports.string(),
25105
+ total: external_exports.number().int().nonnegative(),
25106
+ all: external_exports.number().int().nonnegative(),
25107
+ requirements: external_exports.number().int().nonnegative(),
25108
+ mostRequirements: external_exports.number().int().nonnegative(),
25109
+ next: external_exports.string().nullable(),
25110
+ features: external_exports.array(featureRow)
25111
+ });
25112
+ var browserFeatureV1 = external_exports.strictObject({ contract: external_exports.literal("browser-feature"), version: external_exports.literal(1), sessionId: external_exports.string(), feature });
25113
+ var browserInstructionsV1 = external_exports.strictObject({
25114
+ contract: external_exports.literal("browser-instructions"),
25115
+ version: external_exports.literal(1),
25116
+ sessionId: external_exports.string(),
25117
+ instructions: external_exports.array(instruction),
25118
+ agents: agents.nullable()
25119
+ });
25120
+ var browserContributorsV1 = external_exports.strictObject({
25121
+ contract: external_exports.literal("browser-contributors"),
25122
+ version: external_exports.literal(1),
25123
+ sessionId: external_exports.string(),
25124
+ total: external_exports.number().int().nonnegative(),
25125
+ next: external_exports.string().nullable(),
25126
+ people: external_exports.array(external_exports.strictObject({ email: external_exports.string(), name: external_exports.string(), commits: external_exports.number().int().nonnegative(), latest: external_exports.string(), features: external_exports.number().int().nonnegative() }))
25127
+ });
25128
+ var browserContributorV1 = external_exports.strictObject({
25129
+ contract: external_exports.literal("browser-contributor"),
25130
+ version: external_exports.literal(1),
25131
+ sessionId: external_exports.string(),
25132
+ person: contributor,
25133
+ features: external_exports.array(external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), commits: external_exports.number().int().nonnegative(), requirements: external_exports.number().int().nonnegative() }))
25060
25134
  });
25061
25135
  var kind = external_exports.enum(["feature", "requirement", "design", "wiki", "instruction"]);
25062
25136
  var reference = external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), specId: external_exports.string(), path: external_exports.string() }).nullable();
@@ -25090,21 +25164,24 @@ var event = external_exports.strictObject({
25090
25164
  after: reference,
25091
25165
  records: external_exports.array(record2)
25092
25166
  });
25093
- var browserHistoryV5 = external_exports.strictObject({
25167
+ var browserHistoryV6 = external_exports.strictObject({
25094
25168
  contract: external_exports.literal("browser-history"),
25095
- version: external_exports.literal(5),
25169
+ version: external_exports.literal(6),
25096
25170
  sessionId: external_exports.string(),
25097
25171
  head: oid2,
25098
- // Matching changes in all of history, not in this page; `offset` is where this page starts among them.
25172
+ // Matching changes and the commits that hold them in all of history, not in this page.
25099
25173
  total: external_exports.number().int().nonnegative(),
25100
- offset: external_exports.number().int().nonnegative(),
25174
+ commits: external_exports.number().int().nonnegative(),
25175
+ next: oid2.nullable(),
25101
25176
  events: external_exports.array(event)
25102
25177
  });
25103
- var browserHistorySummaryV4 = external_exports.strictObject({
25178
+ var browserHistorySummaryV5 = external_exports.strictObject({
25104
25179
  contract: external_exports.literal("browser-history-summary"),
25105
- version: external_exports.literal(4),
25180
+ version: external_exports.literal(5),
25106
25181
  sessionId: external_exports.string(),
25107
25182
  head: oid2,
25183
+ // The three who committed most, and how many others there are and how many commits they made between them.
25184
+ people: external_exports.strictObject({ total: external_exports.number().int().nonnegative(), top: external_exports.array(contributor), rest: external_exports.strictObject({ count: external_exports.number().int().nonnegative(), commits: external_exports.number().int().nonnegative() }) }),
25108
25185
  total: external_exports.number().int().nonnegative(),
25109
25186
  byType: external_exports.strictObject({ created: external_exports.number().int().nonnegative(), modified: external_exports.number().int().nonnegative(), moved: external_exports.number().int().nonnegative(), deleted: external_exports.number().int().nonnegative() }),
25110
25187
  // One entry per commit within three weeks of the newest change, so the reader can count by its own calendar day.
@@ -25112,29 +25189,30 @@ var browserHistorySummaryV4 = external_exports.strictObject({
25112
25189
  // The newest commits: a few of their changes each and how many there are in all.
25113
25190
  recent: external_exports.array(external_exports.strictObject({ commit: oid2, count: external_exports.number().int().positive(), events: external_exports.array(event) }))
25114
25191
  });
25115
- var browserSearchV2 = external_exports.strictObject({
25192
+ var searchKind = external_exports.enum(["feature", "requirement", "design", "instruction", "record", "commit"]);
25193
+ var searchHit = external_exports.strictObject({
25194
+ id: external_exports.string(),
25195
+ kind: searchKind,
25196
+ title: external_exports.string(),
25197
+ where: external_exports.string(),
25198
+ line: external_exports.string(),
25199
+ // What the hit opens: a feature (with the requirement or design tab), an instruction, a record, or a commit.
25200
+ featureId: external_exports.string().optional(),
25201
+ commit: oid2.optional()
25202
+ });
25203
+ var browserSearchV3 = external_exports.strictObject({
25116
25204
  contract: external_exports.literal("browser-search"),
25117
- version: external_exports.literal(2),
25205
+ version: external_exports.literal(3),
25118
25206
  sessionId: external_exports.string(),
25119
25207
  query: external_exports.string(),
25120
- hits: external_exports.array(external_exports.strictObject({
25121
- id: external_exports.string(),
25122
- kind: external_exports.enum(["feature", "requirement", "design", "instruction", "history"]),
25123
- title: external_exports.string(),
25124
- where: external_exports.string(),
25125
- line: external_exports.string(),
25126
- // What the hit opens: a feature (with the requirement or design tab), an instruction, or one change in the activity.
25127
- featureId: external_exports.string().optional(),
25128
- documentId: external_exports.string().optional(),
25129
- key: external_exports.string().optional()
25130
- }))
25208
+ groups: external_exports.array(external_exports.strictObject({ group: external_exports.union([searchKind, external_exports.literal("recent")]), total: external_exports.number().int().nonnegative(), next: external_exports.string().nullable(), hits: external_exports.array(searchHit) }))
25131
25209
  });
25132
25210
  var headQuery = oid2;
25133
25211
  var count = (max) => external_exports.string().regex(/^(0|[1-9]\d{0,6})$/).transform(Number).pipe(external_exports.number().int().min(0).max(max));
25134
- var browserHistoryQueryV3 = external_exports.strictObject({
25212
+ var browserHistoryQueryV4 = external_exports.strictObject({
25135
25213
  head: headQuery,
25136
- offset: count(1e6).optional(),
25137
- limit: count(100).pipe(external_exports.number().min(1)).optional(),
25214
+ after: oid2.optional(),
25215
+ limit: count(50).pipe(external_exports.number().min(1)).optional(),
25138
25216
  kind: changeType.optional(),
25139
25217
  document: kind.optional(),
25140
25218
  feature: external_exports.string().regex(/^S-[a-z2-7]{10}$/).optional(),
@@ -25142,7 +25220,26 @@ var browserHistoryQueryV3 = external_exports.strictObject({
25142
25220
  q: external_exports.string().max(200).optional()
25143
25221
  });
25144
25222
  var browserHistorySummaryQueryV1 = external_exports.strictObject({ head: headQuery });
25145
- var browserSearchQueryV1 = external_exports.strictObject({ q: external_exports.string().min(1).max(200), head: headQuery.optional() });
25223
+ var featureId = external_exports.string().regex(/^S-[a-z2-7]{10}$/);
25224
+ var browserFeaturesQueryV1 = external_exports.strictObject({
25225
+ q: external_exports.string().max(200).optional(),
25226
+ design: external_exports.enum(["yes", "no"]).optional(),
25227
+ author: external_exports.string().min(1).max(320).optional(),
25228
+ sort: external_exports.enum(["title", "requirements", "updated"]).optional(),
25229
+ dir: external_exports.enum(["asc", "desc"]).optional(),
25230
+ after: featureId.optional(),
25231
+ limit: count(50).pipe(external_exports.number().min(1)).optional()
25232
+ });
25233
+ var browserFeatureQueryV1 = external_exports.strictObject({ id: featureId });
25234
+ var browserContributorsQueryV1 = external_exports.strictObject({ q: external_exports.string().max(200).optional(), after: external_exports.string().min(1).max(320).optional(), limit: count(50).pipe(external_exports.number().min(1)).optional() });
25235
+ var browserContributorQueryV1 = external_exports.strictObject({ email: external_exports.string().min(1).max(320) });
25236
+ var browserSearchQueryV2 = external_exports.strictObject({
25237
+ q: external_exports.string().max(200),
25238
+ head: headQuery.optional(),
25239
+ group: searchKind.optional(),
25240
+ after: external_exports.string().min(1).max(200).optional(),
25241
+ limit: count(50).pipe(external_exports.number().min(1)).optional()
25242
+ });
25146
25243
  var browserInstructionFileQueryV1 = external_exports.strictObject({ id: external_exports.string().regex(/^I-[a-z2-7]{10}$/), path: external_exports.string().min(1).max(1e3) });
25147
25244
  var changelogQueryV1 = external_exports.strictObject({ lang: external_exports.string().regex(/^[a-z]{2}(?:-[A-Z]{2})?$/).optional() });
25148
25245
  var fileStatus = external_exports.enum(["added", "modified", "deleted", "renamed"]);
@@ -25154,12 +25251,13 @@ var commitFile = external_exports.strictObject({
25154
25251
  additions: external_exports.number().int().nonnegative().nullable(),
25155
25252
  deletions: external_exports.number().int().nonnegative().nullable()
25156
25253
  });
25157
- var browserCommitFilesV1 = external_exports.strictObject({
25254
+ var browserCommitFilesV2 = external_exports.strictObject({
25158
25255
  contract: external_exports.literal("browser-commit-files"),
25159
- version: external_exports.literal(1),
25256
+ version: external_exports.literal(2),
25160
25257
  sessionId: external_exports.string(),
25161
25258
  commit: oid2,
25162
25259
  total: external_exports.number().int().nonnegative(),
25260
+ next: external_exports.string().nullable(),
25163
25261
  files: external_exports.array(commitFile)
25164
25262
  });
25165
25263
  var browserCommitFileV1 = external_exports.strictObject({
@@ -25174,11 +25272,11 @@ var browserCommitFileV1 = external_exports.strictObject({
25174
25272
  binary: external_exports.boolean(),
25175
25273
  tooLarge: external_exports.boolean()
25176
25274
  });
25177
- var browserCommitFilesQueryV1 = external_exports.strictObject({ commit: oid2 });
25275
+ var browserCommitFilesQueryV2 = external_exports.strictObject({ commit: oid2, after: external_exports.string().min(1).max(4096).optional(), limit: count(100).pipe(external_exports.number().min(1)).optional() });
25178
25276
  var browserCommitFileQueryV1 = external_exports.strictObject({ commit: oid2, path: external_exports.string().min(1).max(4096) });
25179
- var browserCommitV3 = external_exports.strictObject({
25277
+ var browserCommitV4 = external_exports.strictObject({
25180
25278
  contract: external_exports.literal("browser-commit"),
25181
- version: external_exports.literal(3),
25279
+ version: external_exports.literal(4),
25182
25280
  sessionId: external_exports.string(),
25183
25281
  commit: oid2,
25184
25282
  author: external_exports.string(),
@@ -25186,17 +25284,31 @@ var browserCommitV3 = external_exports.strictObject({
25186
25284
  committer: external_exports.string(),
25187
25285
  date: external_exports.string(),
25188
25286
  message: external_exports.string(),
25189
- changes: external_exports.array(external_exports.strictObject({ event, before: snapshot, after: snapshot }))
25287
+ total: external_exports.number().int().nonnegative(),
25288
+ next: external_exports.string().nullable(),
25289
+ changes: external_exports.array(event)
25190
25290
  });
25191
- var browserCommitQueryV1 = external_exports.strictObject({ commit: oid2 });
25192
25291
  var recordId = external_exports.string().regex(/^(?:DR|H)-[A-Za-z0-9_-]{1,64}$/);
25193
- var browserRecordV1 = external_exports.strictObject({
25194
- contract: external_exports.literal("browser-record"),
25292
+ var browserCommitQueryV3 = external_exports.strictObject({ commit: oid2, after: external_exports.string().min(1).max(200).optional(), limit: count(100).pipe(external_exports.number().min(1)).optional(), record: recordId.optional() });
25293
+ var browserCommitChangeV1 = external_exports.strictObject({
25294
+ contract: external_exports.literal("browser-commit-change"),
25195
25295
  version: external_exports.literal(1),
25196
25296
  sessionId: external_exports.string(),
25297
+ commit: oid2,
25298
+ event,
25299
+ before: snapshot,
25300
+ after: snapshot
25301
+ });
25302
+ var browserCommitChangeQueryV1 = external_exports.strictObject({ commit: oid2, id: external_exports.string().min(1).max(100) });
25303
+ var browserRecordV2 = external_exports.strictObject({
25304
+ contract: external_exports.literal("browser-record"),
25305
+ version: external_exports.literal(2),
25306
+ sessionId: external_exports.string(),
25197
25307
  head: oid2,
25198
25308
  id: recordId,
25199
- commit: oid2
25309
+ commit: oid2,
25310
+ record: record2,
25311
+ others: external_exports.number().int().nonnegative()
25200
25312
  });
25201
25313
  var browserRecordQueryV1 = external_exports.strictObject({ head: oid2, id: recordId });
25202
25314
  var browserInstructionFileV1 = external_exports.strictObject({
@@ -25210,6 +25322,27 @@ var browserInstructionFileV1 = external_exports.strictObject({
25210
25322
  binary: external_exports.boolean(),
25211
25323
  tooLarge: external_exports.boolean()
25212
25324
  });
25325
+ var workingChange = external_exports.strictObject({ id: external_exports.string(), kind, title: external_exports.string(), path: external_exports.string(), previousPath: external_exports.string().optional(), types: external_exports.array(changeType) });
25326
+ var pendingRecord = external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), docs: external_exports.array(external_exports.string()), sections: external_exports.array(recordSection), draft: external_exports.boolean() });
25327
+ var browserWorkingV1 = external_exports.strictObject({
25328
+ contract: external_exports.literal("browser-working"),
25329
+ version: external_exports.literal(1),
25330
+ sessionId: external_exports.string(),
25331
+ head: oid2.nullable(),
25332
+ records: external_exports.array(pendingRecord),
25333
+ changes: external_exports.array(workingChange),
25334
+ withoutRecord: external_exports.array(external_exports.string())
25335
+ });
25336
+ var browserWorkingChangeV1 = external_exports.strictObject({
25337
+ contract: external_exports.literal("browser-working-change"),
25338
+ version: external_exports.literal(1),
25339
+ sessionId: external_exports.string(),
25340
+ change: workingChange,
25341
+ before: snapshot,
25342
+ after: snapshot
25343
+ });
25344
+ var browserWorkingChangeQueryV1 = external_exports.strictObject({ id: external_exports.string().regex(/^[SRDWI]-[a-z2-7]{10}$/) });
25345
+ var browserStampV1 = external_exports.strictObject({ contract: external_exports.literal("browser-stamp"), version: external_exports.literal(1), sessionId: external_exports.string(), stamp: external_exports.string() });
25213
25346
 
25214
25347
  // ../../packages/contracts/dist/versions/changelog-v1.js
25215
25348
  var items = external_exports.array(external_exports.string().min(1));
@@ -25243,11 +25376,11 @@ var updateStateV1 = external_exports.strictObject({
25243
25376
  latestVersion: release.nullable()
25244
25377
  }).refine((value) => (value.status === "available" || value.status === "up-to-date") === (value.latestVersion !== null), "latestVersion is set exactly when the check produced a result.");
25245
25378
 
25246
- // ../../packages/contracts/dist/versions/project-init-v7.js
25247
- var projectInitV7 = external_exports.discriminatedUnion("ok", [
25379
+ // ../../packages/contracts/dist/versions/project-init-v8.js
25380
+ var projectInitV8 = external_exports.discriminatedUnion("ok", [
25248
25381
  external_exports.strictObject({
25249
25382
  contract: external_exports.literal("project-init"),
25250
- version: external_exports.literal(7),
25383
+ version: external_exports.literal(8),
25251
25384
  ok: external_exports.literal(true),
25252
25385
  outcome: external_exports.enum(["planned", "created", "already-initialized"]),
25253
25386
  rootPath: external_exports.string(),
@@ -25258,11 +25391,13 @@ var projectInitV7 = external_exports.discriminatedUnion("ok", [
25258
25391
  external_exports.strictObject({ kind: external_exports.literal("commit"), objectFormat: external_exports.enum(["sha1", "sha256"]), commit: external_exports.string() })
25259
25392
  ]),
25260
25393
  agentDocs: external_exports.strictObject({ mode: external_exports.enum(["install", "remove", "skip"]), paths: external_exports.array(external_exports.string()) }),
25394
+ // The file that keeps documents in LF on every OS. `created` is false when it was already there or the run is planned.
25395
+ lineEndings: external_exports.strictObject({ path: external_exports.literal(".gitifact/.gitattributes"), created: external_exports.boolean() }),
25261
25396
  update: updateStateV1,
25262
25397
  // Present only when a newer release exists. npx refreshes the project blocks without a global install; npmGlobal remains an alternative.
25263
25398
  install: external_exports.strictObject({ npx: external_exports.string().min(1), npmGlobal: external_exports.string().min(1) }).nullable()
25264
25399
  }),
25265
- external_exports.strictObject({ contract: external_exports.literal("project-init"), version: external_exports.literal(7), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
25400
+ external_exports.strictObject({ contract: external_exports.literal("project-init"), version: external_exports.literal(8), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
25266
25401
  ]);
25267
25402
 
25268
25403
  // ../../packages/contracts/dist/versions/update-v5.js
@@ -25448,6 +25583,10 @@ var messages_default3 = {
25448
25583
  "server.sessionChanged": "\uC11C\uBC84 \uC138\uC158\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.",
25449
25584
  "server.badRange": "\uC870\uD68C \uBC94\uC704\uB97C \uD655\uC778\uD558\uC138\uC694.",
25450
25585
  "server.commitNotFound": "\uC774 \uC800\uC7A5\uC18C\uC5D0 \uADF8 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25586
+ "server.featureNotFound": "\uCCB4\uD06C\uC544\uC6C3\uC5D0 \uADF8 \uAE30\uB2A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25587
+ "server.contributorNotFound": "HEAD\uC5D0 \uADF8 \uC774\uBA54\uC77C\uB85C \uCEE4\uBC0B\uD55C \uC0AC\uB78C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25588
+ "server.cursorNotFound": "\uC774\uC5B4 \uC77D\uC744 \uC704\uCE58\uB97C \uBAA9\uB85D\uC5D0\uC11C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uCC98\uC74C\uBD80\uD130 \uB2E4\uC2DC \uBD88\uB7EC\uC624\uC138\uC694.",
25589
+ "server.workingChangeNotFound": "\uCEE4\uBC0B \uC804 \uBCC0\uACBD\uC5D0 \uADF8 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
25451
25590
  "server.recordNotFound": "\uC774 \uC774\uB825\uC5D0 \uADF8 \uACB0\uC815\uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25452
25591
  "server.commitFileNotFound": "\uADF8 \uCEE4\uBC0B\uC774 \uBC14\uAFBC \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
25453
25592
  "server.instructionFileNotFound": "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -25491,6 +25630,7 @@ var messages_default3 = {
25491
25630
  "init.text.blockUpdated": "\uBE14\uB85D \uAC31\uC2E0",
25492
25631
  "init.text.storage": "\uC800\uC7A5 \uADDC\uC57D",
25493
25632
  "init.text.agentDocs": "\uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68",
25633
+ "init.text.lineEndings": "\uC904\uBC14\uAFC8: {path}\uB85C \uBB38\uC11C\uB97C LF\uB85C \uACE0\uC815\uD588\uC2B5\uB2C8\uB2E4. \uC774\uBBF8 \uC788\uB358 \uBB38\uC11C\uAC00 \uBCC0\uACBD\uC73C\uB85C \uBCF4\uC774\uBA74 \uC904\uBC14\uAFC8\uB9CC \uBC14\uB010 \uAC83\uC774\uB2C8 \uADF8 \uBB38\uC11C\uB4E4\uC744 \uD55C \uBC88 \uCEE4\uBC0B\uD574 \uC815\uB9AC\uD558\uC138\uC694.",
25494
25634
  "init.text.updateAvailable": "\uC0C8 \uBC84\uC804 {version}\uC774 \uC788\uC2B5\uB2C8\uB2E4. {command}\uB85C \uC0C8 \uBC84\uC804\uC744 \uC2E4\uD589\uD558\uACE0 \uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68\uC744 \uAC31\uC2E0\uD558\uC138\uC694.",
25495
25635
  "init.failed": "\uCD08\uAE30\uD654\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4. \uD30C\uC77C \uC811\uADFC \uAD8C\uD55C\uACFC \uC0DD\uC131\uB41C \uC124\uC815\xB7\uC784\uC2DC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694.",
25496
25636
  "config.storeNotDirectory": ".gitifact\uB294 \uB9C1\uD06C\uAC00 \uC544\uB2CC \uC77C\uBC18 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
@@ -25511,7 +25651,6 @@ var messages_default3 = {
25511
25651
  "agentBlock.duplicateStart": "GITIFACT \uC2DC\uC791 \uB9C8\uCEE4\uAC00 \uC5EC\uB7EC \uAC1C\uC785\uB2C8\uB2E4. \uC911\uBCF5 \uBE14\uB85D\uC744 \uC815\uB9AC\uD55C \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
25512
25652
  "specReader.headUnreadable": "HEAD\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
25513
25653
  "specReader.schemaRequired": "\uC0C8 \uC2A4\uD0A4\uB9C8 \uD504\uB85C\uC81D\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
25514
- "specReader.authorUnreadable": "Git \uC791\uC131\uC790\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
25515
25654
  "specReader.historyUnreadable": "Git \uC774\uB825 \uD615\uC2DD\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
25516
25655
  "specReader.projectChanged": "\uC870\uD68C \uC911 \uD504\uB85C\uC81D\uD2B8\uAC00 \uBC14\uB00C\uC5C8\uC2B5\uB2C8\uB2E4. \uC0C8\uB85C\uACE0\uCE68\uD558\uC138\uC694.",
25517
25656
  "browser.invalidPort": "\uD3EC\uD2B8\uB294 0\uBD80\uD130 65535 \uC0AC\uC774\uC758 \uC815\uC218\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
@@ -25534,7 +25673,7 @@ var messages_default3 = {
25534
25673
  "update.text.noBlock": "GITIFACT \uBE14\uB85D\uC774 \uC788\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. npx --yes gitifact@{version} init\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694.",
25535
25674
  "update.text.notInitialized": "\uCD08\uAE30\uD654\uB41C \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2C8\uC5B4\uC11C \uBC84\uC804\uB9CC \uD655\uC778\uD588\uC2B5\uB2C8\uB2E4.",
25536
25675
  "update.text.missing": "{paths} \uD30C\uC77C\uC774 \uC5C6\uC5B4 Claude Code\uAC00 AGENTS.md\uC758 \uC9C0\uCE68\uC744 \uC77D\uC9C0 \uBABB\uD569\uB2C8\uB2E4. npx --yes gitifact@{version} init\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 AGENTS.md\uB97C \uAC00\uC838\uC624\uB294 \uD30C\uC77C\uC744 \uB9CC\uB4ED\uB2C8\uB2E4.",
25537
- "update.text.migrationRequired": "\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uBB38\uC11C\uB294 \uC544\uC9C1 0.7 \uC800\uC7A5 \uADDC\uC57D(schemaVersion 2)\uC785\uB2C8\uB2E4. \uC9C0\uCE68 \uBE14\uB85D\uC740 \uC0C8 \uBA85\uB839\uC744 \uC548\uB0B4\uD558\uC9C0\uB9CC \uC804\uD658 \uC804\uC5D0\uB294 docs\xB7changes \uBA85\uB839\uC774 \uB3D9\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. npx --yes gitifact@{version} guide show migrate\uC758 \uC808\uCC28\uB85C \uBB38\uC11C\uB97C \uC804\uD658\uD558\uC138\uC694.",
25676
+ "update.text.migrationRequired": "\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uBB38\uC11C\uB294 \uC544\uC9C1 0.7 \uC800\uC7A5 \uADDC\uC57D(schemaVersion 2)\uC785\uB2C8\uB2E4. \uC9C0\uCE68 \uBE14\uB85D\uC740 \uC0C8 \uBA85\uB839\uC744 \uC548\uB0B4\uD558\uC9C0\uB9CC \uC804\uD658 \uC804\uC5D0\uB294 specs\xB7instructions\xB7records\xB7check\xB7changes \uBA85\uB839\uC774 \uB3D9\uC791\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. npx --yes gitifact@{version} guide show migrate\uC758 \uC808\uCC28\uB85C \uBB38\uC11C\uB97C \uC804\uD658\uD558\uC138\uC694.",
25538
25677
  "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
25539
25678
  "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25540
25679
  "update.text.commitSkipped.untracked": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. Git\uC774 \uCD94\uC801\uD558\uC9C0 \uC54A\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC785\uB2C8\uB2E4: {paths}",
@@ -25561,6 +25700,9 @@ var messages_default3 = {
25561
25700
  "input.size": "\uC785\uB825 \uD06C\uAE30\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.",
25562
25701
  "input.json": "\uC785\uB825 \uD30C\uC77C\uC740 UTF-8 JSON\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
25563
25702
  "docs.draft": "\uCD08\uC548",
25703
+ "docs.state.added": "\uCEE4\uBC0B \uC804: \uCD94\uAC00",
25704
+ "docs.state.modified": "\uCEE4\uBC0B \uC804: \uBCC0\uACBD",
25705
+ "docs.state.deleted": "\uCEE4\uBC0B \uC804: \uC0AD\uC81C \uC608\uC815",
25564
25706
  "docs.feature": "\uAE30\uB2A5",
25565
25707
  "docs.requirements": "\uC694\uAD6C\uC0AC\uD56D",
25566
25708
  "docs.designs": "\uC124\uACC4",
@@ -25644,7 +25786,7 @@ var messages_default3 = {
25644
25786
  "help.newTitle": "\uBB38\uC11C \uC81C\uBAA9",
25645
25787
  "help.newDescription": "\uD55C \uC904 \uC124\uBA85",
25646
25788
  "help.instructions": "\uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68\uC758 \uBAA9\uB85D\xB7\uC6D0\uBB38\xB7\uC0DD\uC131",
25647
- "help.instructionsList": "AGENTS.md\uC640 \uC9C0\uCE68 \uBAA9\uB85D (\uC9C0\uCE68\uB9C8\uB2E4 \uB538\uB9B0 \uD30C\uC77C \uC218)",
25789
+ "help.instructionsList": "AGENTS.md\uC640 \uC9C0\uCE68 \uBAA9\uB85D (\uC9C0\uCE68\uB9C8\uB2E4 \uB538\uB9B0 \uD30C\uC77C\uC758 \uC81C\uBAA9\uACFC \uC124\uBA85)",
25648
25790
  "help.instructionsShow": "\uC9C0\uCE68 index.md \uC6D0\uBB38, \uB530\uB974\uB294 \uC124\uACC4, \uD3F4\uB354 \uD30C\uC77C \uBAA9\uB85D",
25649
25791
  "help.instructionsShowTargets": "\uC9C0\uCE68 \uD3F4\uB354 \uC774\uB984\uC774\uB098 I- ID (\uC5EC\uB7EC \uAC1C \uAC00\uB2A5)",
25650
25792
  "help.instructionsShowFile": "\uC9C0\uCE68 \uD3F4\uB354 \uC548\uC758 \uD30C\uC77C \uD558\uB098\uB97C \uC77D\uC74C (\uC608: references/code-style.md)",
@@ -25657,18 +25799,26 @@ var messages_default3 = {
25657
25799
  "help.listQ": "\uC81C\uBAA9\xB7\uC124\uBA85\xB7\uBCF8\uBB38\uC5D0 \uC774 \uB9D0\uC774 \uC788\uB294 \uAC83\uB9CC",
25658
25800
  "help.listAuthor": "\uC774 Git \uC791\uC131\uC790(\uC774\uB984\uC774\uB098 \uC774\uBA54\uC77C)\uAC00 \uBC14\uAFBC \uAC83\uB9CC",
25659
25801
  "help.listSort": "\uC815\uB82C \uAE30\uC900",
25660
- "help.listLimit": "\uC55E\uC758 n\uAC1C\uB9CC",
25802
+ "help.listLimit": "\uD55C \uD398\uC774\uC9C0\uC5D0 n\uAC1C (\uAE30\uBCF8 20)",
25803
+ "help.listAll": "\uD398\uC774\uC9C0\uB85C \uB098\uB204\uC9C0 \uC54A\uACE0 \uC804\uBD80",
25804
+ "help.listAfter": "\uC774 \uD56D\uBAA9 \uB2E4\uC74C\uBD80\uD130 (\uC55E \uD398\uC774\uC9C0 \uB05D\uC5D0 \uB098\uC628 \uAC12)",
25661
25805
  "help.listFields": "\uC774 \uC5F4\uB9CC \uC27C\uD45C\uB85C (\uD14D\uC2A4\uD2B8\uB294 \uD0ED\uC73C\uB85C \uB098\uB208 \uD55C \uC904)",
25662
25806
  "list.limit": "1 \uC774\uC0C1\uC758 \uC815\uC218\uB97C \uC801\uC73C\uC138\uC694.",
25663
25807
  "list.unknownFields": "\uC5C6\uB294 \uC5F4\uC785\uB2C8\uB2E4: {fields}. \uC4F8 \uC218 \uC788\uB294 \uC5F4: {columns}",
25664
25808
  "list.since": "\uB0A0\uC9DC(YYYY-MM-DD)\uB098 \uCEE4\uBC0B\uC774 \uC544\uB2D9\uB2C8\uB2E4: {value}",
25809
+ "list.after": "\uBAA9\uB85D\uC5D0 \uC5C6\uB294 \uCEE4\uC11C\uC785\uB2C8\uB2E4: {cursor}. --after \uC5C6\uC774 \uCC98\uC74C\uBD80\uD130 \uB2E4\uC2DC \uC77D\uC73C\uC138\uC694.",
25810
+ "list.more": "{unit} {shown}/{total}\uAC1C. \uB2E4\uC74C \uD398\uC774\uC9C0: \uAC19\uC740 \uBA85\uB839\uC5D0 --after {cursor}",
25811
+ "list.items": "\uD56D\uBAA9",
25812
+ "list.features": "\uAE30\uB2A5",
25813
+ "list.commits": "\uCEE4\uBC0B",
25814
+ "list.records": "\uACB0\uC815\uAE30\uB85D",
25665
25815
  "docs.otherResource": "\uC774 \uBA85\uB839\uC774 \uB2E4\uB8E8\uB294 \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4: {ids}. {command}\uB85C \uC77D\uC73C\uC138\uC694.",
25666
25816
  "docs.noMatch": "\uC870\uAC74\uC5D0 \uB9DE\uB294 \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25667
25817
  "docs.requirement": "\uC694\uAD6C\uC0AC\uD56D",
25668
25818
  "docs.design": "\uC124\uACC4",
25669
25819
  "instructions.agents": "AGENTS.md \u2014 \uBE14\uB85D \uBC16 \uC0C9\uC778\uC774 \uC5B4\uB5A4 \uC791\uC5C5\uC5D0 \uC5B4\uB290 \uC9C0\uCE68\uC744 \uC77D\uC744\uC9C0 \uC54C\uB9B0\uB2E4",
25670
25820
  "instructions.noAgents": "AGENTS.md \uC5C6\uC74C \u2014 \uC9C0\uCE68\uC744 \uC5B8\uC81C \uC77D\uC744\uC9C0 \uC54C\uB9AC\uB294 \uC0C9\uC778\uC774 \uC5C6\uC2B5\uB2C8\uB2E4",
25671
- "instructions.files": "\uD30C\uC77C {count}\uAC1C",
25821
+ "instructions.filesLimited": "(\uD30C\uC77C\uC774 \uB354 \uC788\uC5B4 \uC55E {count}\uAC1C\uB9CC \uBCF4\uC785\uB2C8\uB2E4)",
25672
25822
  "instructions.empty": "\uC9C0\uCE68\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. instructions new\uB85C \uB9CC\uB4DC\uC138\uC694.",
25673
25823
  "instructions.fileWithOne": "--file\uC740 \uC9C0\uCE68 \uD558\uB098\uC640 \uD568\uAED8 \uC4F0\uBA70 --ref\uC640 \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
25674
25824
  "instructions.unknownFile": "\uC9C0\uCE68 {name} \uD3F4\uB354\uC5D0 \uADF8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
@@ -25738,6 +25888,10 @@ var messages_default4 = {
25738
25888
  "server.sessionChanged": "The server session changed. Reconnect to continue.",
25739
25889
  "server.badRange": "Check the requested range.",
25740
25890
  "server.commitNotFound": "This repository has no such commit.",
25891
+ "server.featureNotFound": "The checkout has no such feature.",
25892
+ "server.contributorNotFound": "Nobody with this email committed to HEAD.",
25893
+ "server.cursorNotFound": "The place to read on from is no longer in the list. Load it again from the start.",
25894
+ "server.workingChangeNotFound": "No uncommitted change of that document.",
25741
25895
  "server.recordNotFound": "This history has no such decision record.",
25742
25896
  "server.commitFileNotFound": "That commit did not change this file.",
25743
25897
  "server.instructionFileNotFound": "The instruction file was not found.",
@@ -25781,6 +25935,7 @@ var messages_default4 = {
25781
25935
  "init.text.blockUpdated": "Block refreshed",
25782
25936
  "init.text.storage": "Storage convention",
25783
25937
  "init.text.agentDocs": "Agent instructions",
25938
+ "init.text.lineEndings": "Line endings: {path} keeps documents in LF. Documents that now show as changed differ only in line endings; commit them once to settle it.",
25784
25939
  "init.text.updateAvailable": "Version {version} is available. Run {command} to use it and refresh the project instructions.",
25785
25940
  "init.failed": "Initialization failed. Check file permissions and the generated configuration and temporary files.",
25786
25941
  "config.storeNotDirectory": ".gitifact must be a regular directory, not a link.",
@@ -25801,7 +25956,6 @@ var messages_default4 = {
25801
25956
  "agentBlock.duplicateStart": "Multiple GITIFACT start markers found. Remove duplicate blocks before retrying.",
25802
25957
  "specReader.headUnreadable": "Could not read HEAD.",
25803
25958
  "specReader.schemaRequired": "A project using the supported schema is required.",
25804
- "specReader.authorUnreadable": "Could not read Git authors.",
25805
25959
  "specReader.historyUnreadable": "Could not read Git history.",
25806
25960
  "specReader.projectChanged": "The project changed while reading. Refresh to continue.",
25807
25961
  "browser.invalidPort": "Port must be an integer between 0 and 65535.",
@@ -25824,7 +25978,7 @@ var messages_default4 = {
25824
25978
  "update.text.noBlock": "No instruction files contain a GITIFACT block. Run npx --yes gitifact@{version} init to install one.",
25825
25979
  "update.text.notInitialized": "Only the version was checked because this project is not initialized.",
25826
25980
  "update.text.missing": "{paths} is missing, so Claude Code cannot load the AGENTS.md instructions. Rerun npx --yes gitifact@{version} init to create the import file.",
25827
- "update.text.migrationRequired": "This project's documents still use the 0.7 storage convention (schemaVersion 2). The blocks describe the new commands, but docs and changes refuse to run until the documents are moved. Follow npx --yes gitifact@{version} guide show migrate to move them.",
25981
+ "update.text.migrationRequired": "This project's documents still use the 0.7 storage convention (schemaVersion 2). The blocks describe the new commands, but the specs, instructions, records, check and changes commands refuse to run until the documents are moved. Follow npx --yes gitifact@{version} guide show migrate to move them.",
25828
25982
  "update.text.committed": "Instruction blocks committed: {commit} ({paths})",
25829
25983
  "update.text.commitNothing": "No instruction block changes to commit.",
25830
25984
  "update.text.commitSkipped.untracked": "Not committed. Instruction files are untracked: {paths}",
@@ -25851,6 +26005,9 @@ var messages_default4 = {
25851
26005
  "input.size": "The input is too large.",
25852
26006
  "input.json": "The input file must be UTF-8 JSON.",
25853
26007
  "docs.draft": "draft",
26008
+ "docs.state.added": "uncommitted: added",
26009
+ "docs.state.modified": "uncommitted: modified",
26010
+ "docs.state.deleted": "uncommitted: to be deleted",
25854
26011
  "docs.feature": "Feature",
25855
26012
  "docs.requirements": "Requirements",
25856
26013
  "docs.designs": "Design",
@@ -25934,7 +26091,7 @@ var messages_default4 = {
25934
26091
  "help.newTitle": "Document title",
25935
26092
  "help.newDescription": "One-line description",
25936
26093
  "help.instructions": "List, read and create project instructions",
25937
- "help.instructionsList": "AGENTS.md and the instructions (with how many files each folder holds)",
26094
+ "help.instructionsList": "AGENTS.md and the instructions (with the title and description of each file in their folders)",
25938
26095
  "help.instructionsShow": "An instruction's index.md as written, the designs that follow it and its folder's files",
25939
26096
  "help.instructionsShowTargets": "Instruction folder names or I- IDs (one or more)",
25940
26097
  "help.instructionsShowFile": "Read one file of the instruction folder (e.g. references/code-style.md)",
@@ -25947,18 +26104,26 @@ var messages_default4 = {
25947
26104
  "help.listQ": "Only those whose title, description or text holds these words",
25948
26105
  "help.listAuthor": "Only those this Git author (name or email) changed",
25949
26106
  "help.listSort": "Sort key",
25950
- "help.listLimit": "Only the first n",
26107
+ "help.listLimit": "n per page (20 by default)",
26108
+ "help.listAll": "Everything, not split into pages",
26109
+ "help.listAfter": "Start after this item (the value at the end of the previous page)",
25951
26110
  "help.listFields": "Only these columns, comma-separated (text prints one tab-separated line each)",
25952
26111
  "list.limit": "Give a whole number of 1 or more.",
25953
26112
  "list.unknownFields": "Unknown columns: {fields}. Available columns: {columns}",
25954
26113
  "list.since": "Not a date (YYYY-MM-DD) or a commit: {value}",
26114
+ "list.after": "No such cursor in the list: {cursor}. Read again from the start without --after.",
26115
+ "list.more": "{unit}: {shown} of {total}. Next page: the same command with --after {cursor}",
26116
+ "list.items": "Items",
26117
+ "list.features": "Features",
26118
+ "list.commits": "Commits",
26119
+ "list.records": "Records",
25955
26120
  "docs.otherResource": "Not a document this command reads: {ids}. Use {command}.",
25956
26121
  "docs.noMatch": "Nothing matches.",
25957
26122
  "docs.requirement": "requirement",
25958
26123
  "docs.design": "design",
25959
26124
  "instructions.agents": "AGENTS.md \u2014 the index outside the block says which instruction to read for which work",
25960
26125
  "instructions.noAgents": "No AGENTS.md \u2014 nothing tells when to read which instruction",
25961
- "instructions.files": "{count} files",
26126
+ "instructions.filesLimited": "(the folder holds more; only the first {count} are shown)",
25962
26127
  "instructions.empty": "No instructions yet. Create one with instructions new.",
25963
26128
  "instructions.fileWithOne": "--file takes one instruction and does not go with --ref.",
25964
26129
  "instructions.unknownFile": "The folder of instruction {name} has no such file: {path}",
@@ -26069,9 +26234,9 @@ async function dispatch(routes, sessionId, url2, path3, request, response, allow
26069
26234
  if (found.query) {
26070
26235
  const keys2 = [...url2.searchParams.keys()];
26071
26236
  if (new Set(keys2).size !== keys2.length) return fail2(response, 400, "BAD_REQUEST", t2("server.badRange"));
26072
- const parsed = found.query.safeParse(Object.fromEntries(url2.searchParams));
26073
- if (!parsed.success) return fail2(response, 400, "BAD_REQUEST", t2("server.badRange"));
26074
- query = parsed.data;
26237
+ const parsed2 = found.query.safeParse(Object.fromEntries(url2.searchParams));
26238
+ if (!parsed2.success) return fail2(response, 400, "BAD_REQUEST", t2("server.badRange"));
26239
+ query = parsed2.data;
26075
26240
  } else if (url2.search) return fail2(response, 400, "BAD_REQUEST", t2("server.noQuery"));
26076
26241
  if (found.session && request.headers["x-gitifact-session"] !== sessionId) return fail2(response, 409, "SESSION_CHANGED", t2("server.sessionChanged"));
26077
26242
  try {
@@ -26329,7 +26494,7 @@ function storeReader(cwd) {
26329
26494
  import { DatabaseSync } from "node:sqlite";
26330
26495
  import { lstat, mkdir, rm, writeFile } from "node:fs/promises";
26331
26496
  import { join as join2 } from "node:path";
26332
- var CACHE_FORMAT = 8;
26497
+ var CACHE_FORMAT = 11;
26333
26498
  var CACHE_DIR = ".gitifact/cache";
26334
26499
  var SCHEMA = `
26335
26500
  -- Working-tree files already read: the modification time and size decide whether a file is read again.
@@ -26344,18 +26509,32 @@ var SCHEMA = `
26344
26509
  CREATE INDEX doc_references_to ON doc_references (to_id);
26345
26510
  -- Commits already read, and with which parser: 'current', 'legacy' (0.7, before a migration) or 'migration' (hidden).
26346
26511
  CREATE TABLE commits (oid TEXT PRIMARY KEY, reader TEXT NOT NULL);
26347
- -- One row per change. 'row' is the list shape as JSON; 'detail' the text on both sides.
26512
+ -- One row per change. 'row' is the list shape as JSON with its records by ID; 'detail' where each side's text is read
26513
+ -- again from Git (commit and path), or the text itself for 0.7 history, which has no files to read again.
26348
26514
  CREATE TABLE changes (
26349
26515
  key TEXT PRIMARY KEY, oid TEXT NOT NULL, ord INTEGER NOT NULL,
26350
26516
  id TEXT NOT NULL, kind TEXT NOT NULL, types TEXT NOT NULL, email TEXT NOT NULL, date TEXT NOT NULL,
26351
26517
  before_spec TEXT, after_spec TEXT, needle TEXT NOT NULL, row TEXT NOT NULL, detail TEXT NOT NULL);
26352
26518
  CREATE INDEX changes_by_commit ON changes (oid, ord);
26353
26519
  CREATE INDEX changes_by_id ON changes (id);
26520
+ -- The records each commit added, once each; change rows name them by ID.
26521
+ CREATE TABLE records (oid TEXT NOT NULL, id TEXT NOT NULL, title TEXT NOT NULL, sections TEXT NOT NULL, PRIMARY KEY (oid, id));
26522
+ CREATE INDEX records_by_id ON records (id);
26354
26523
  -- All reachable record commits of one HEAD, children before parents (pos 0 is newest).
26355
26524
  CREATE TABLE lineage (head TEXT NOT NULL, pos INTEGER NOT NULL, oid TEXT NOT NULL, PRIMARY KEY (head, pos));
26356
26525
  CREATE INDEX lineage_by_commit ON lineage (head, oid);
26357
26526
  CREATE TABLE heads (head TEXT PRIMARY KEY, seen INTEGER NOT NULL);
26358
- -- Everything the search box finds: documents of the working tree (scope 'checkout') and past changes ('history').
26527
+ -- Every commit the log has met, code commits too: who made it and when, and whether it is a format migration.
26528
+ -- 'reach' is 1 for the commits of the HEAD the log follows now; contributors are counted over those.
26529
+ CREATE TABLE log (oid TEXT PRIMARY KEY, name TEXT NOT NULL, email TEXT NOT NULL, time INTEGER NOT NULL, date TEXT NOT NULL,
26530
+ subject TEXT NOT NULL, migration INTEGER NOT NULL, reach INTEGER NOT NULL);
26531
+ CREATE INDEX log_by_reach ON log (reach, email);
26532
+ -- The store folders a commit touched: a feature folder, an instruction folder, or AGENTS.md.
26533
+ CREATE TABLE touches (oid TEXT NOT NULL, folder TEXT NOT NULL, PRIMARY KEY (oid, folder));
26534
+ CREATE INDEX touches_by_folder ON touches (folder);
26535
+ -- What the log follows: the HEAD ('head') and the fingerprint of the .mailmap its names were read with ('mailmap').
26536
+ CREATE TABLE state (key TEXT PRIMARY KEY, value TEXT NOT NULL);
26537
+ -- What the search box finds among the working tree's documents (scope 'checkout'); records are searched in their table.
26359
26538
  -- The searched columns hold lower-cased text; trigram lets LIKE '%...%' use the index from three characters on.
26360
26539
  CREATE VIRTUAL TABLE search USING fts5(
26361
26540
  scope UNINDEXED, kind UNINDEXED, ref UNINDEXED, oid UNINDEXED, payload UNINDEXED,
@@ -26377,6 +26556,10 @@ function prepare(db) {
26377
26556
  db.exec("ROLLBACK");
26378
26557
  throw error62;
26379
26558
  }
26559
+ try {
26560
+ db.exec("VACUUM");
26561
+ } catch {
26562
+ }
26380
26563
  }
26381
26564
  function createCacheDatabase(root) {
26382
26565
  let path3;
@@ -26472,24 +26655,23 @@ var place = (doc) => doc.path.replace(/^\.gitifact\//, "");
26472
26655
  function createDocumentCache(root, database) {
26473
26656
  let syncing;
26474
26657
  async function walk() {
26475
- const found = /* @__PURE__ */ new Map();
26658
+ const found = [];
26476
26659
  async function visit2(relative, depth) {
26477
26660
  const info5 = await lstat2(join3(root, ...relative.split("/"))).catch((error62) => {
26478
26661
  if (error62.code === "ENOENT") return void 0;
26479
26662
  throw error62;
26480
26663
  });
26481
- if (!info5 || found.size >= COUNT_LIMIT) return;
26664
+ if (!info5 || found.length >= 2 * COUNT_LIMIT) return;
26482
26665
  if (info5.isDirectory() && !info5.isSymbolicLink()) {
26483
26666
  if (depth > 10) return;
26484
- for (const name2 of (await readdir2(join3(root, ...relative.split("/")))).sort()) await visit2(relative + "/" + name2, depth + 1);
26667
+ await Promise.all((await readdir2(join3(root, ...relative.split("/")))).map((name2) => visit2(relative + "/" + name2, depth + 1)));
26485
26668
  return;
26486
26669
  }
26487
- found.set(relative, { mtime: info5.mtimeMs, size: info5.size, link: info5.isSymbolicLink() || !info5.isFile() });
26670
+ found.push([relative, { mtime: info5.mtimeMs, size: info5.size, link: info5.isSymbolicLink() || !info5.isFile() }]);
26488
26671
  }
26489
- await visit2(SPEC_ROOT, 0);
26490
- await visit2(WIKI_ROOT, 0);
26491
- await visit2(INSTRUCTIONS_ROOT, 0);
26492
- return found;
26672
+ await Promise.all([visit2(SPEC_ROOT, 0), visit2(WIKI_ROOT, 0), visit2(INSTRUCTIONS_ROOT, 0)]);
26673
+ found.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
26674
+ return new Map(found.slice(0, COUNT_LIMIT));
26493
26675
  }
26494
26676
  async function read(path3, seen) {
26495
26677
  try {
@@ -26513,7 +26695,7 @@ function createDocumentCache(root, database) {
26513
26695
  });
26514
26696
  const gone = [...known.keys()].filter((path3) => !seen.has(path3));
26515
26697
  if (!changed.length && !gone.length) return;
26516
- const parsed = await Promise.all(changed.map(async ([path3, s]) => ({ path: path3, seen: s, ...await read(path3, s) })));
26698
+ const parsed2 = await Promise.all(changed.map(async ([path3, s]) => ({ path: path3, seen: s, ...await read(path3, s) })));
26517
26699
  await database.with((db) => transaction(db, () => {
26518
26700
  const forget = (path3) => {
26519
26701
  const row = db.prepare("SELECT id FROM documents WHERE path = ?").get(path3);
@@ -26530,7 +26712,7 @@ function createDocumentCache(root, database) {
26530
26712
  const document = db.prepare("INSERT INTO documents (path, id, kind, feature, title, description, ord, doc) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
26531
26713
  const reference2 = db.prepare("INSERT INTO doc_references (from_id, to_id, type) VALUES (?, ?, ?)");
26532
26714
  const search = db.prepare("INSERT INTO search (scope, kind, ref, oid, payload, title, place, body) VALUES (?,?,?,?,?,?,?,?)");
26533
- for (const p of parsed) {
26715
+ for (const p of parsed2) {
26534
26716
  forget(p.path);
26535
26717
  file2.run(p.path, p.seen.mtime, p.seen.size, p.problem ? JSON.stringify(p.problem) : null);
26536
26718
  const doc = p.doc;
@@ -26589,28 +26771,23 @@ function createDocumentCache(root, database) {
26589
26771
  throw error62;
26590
26772
  });
26591
26773
  if (history) seen.set(HISTORY_PATH, { mtime: history.mtimeMs, size: history.size, link: history.isSymbolicLink() || !history.isFile() });
26592
- const files = /* @__PURE__ */ new Map();
26593
- const problems = [];
26594
- for (const [path3, s] of seen) {
26595
- if (!readable(path3)) continue;
26596
- if (instructionFile3(path3) && !path3.endsWith(".md")) {
26597
- files.set(path3, "");
26598
- continue;
26599
- }
26600
- if (s.link) {
26601
- problems.push(docProblem("PATH_UNSUPPORTED", path3));
26602
- continue;
26603
- }
26604
- if (s.size > (path3 === HISTORY_PATH ? 64 * FILE_LIMIT : FILE_LIMIT)) {
26605
- problems.push(docProblem("FILE_TOO_LARGE", path3));
26606
- continue;
26607
- }
26774
+ const read2 = await Promise.all([...seen].map(async ([path3, s]) => {
26775
+ if (!readable(path3)) return { path: path3 };
26776
+ if (instructionFile3(path3) && !path3.endsWith(".md")) return { path: path3, text: "" };
26777
+ if (s.link) return { path: path3, problem: docProblem("PATH_UNSUPPORTED", path3) };
26778
+ if (s.size > (path3 === HISTORY_PATH ? 64 * FILE_LIMIT : FILE_LIMIT)) return { path: path3, problem: docProblem("FILE_TOO_LARGE", path3) };
26608
26779
  try {
26609
- files.set(path3, decode3(await readFile2(join3(root, ...path3.split("/")))));
26780
+ return { path: path3, text: decode3(await readFile2(join3(root, ...path3.split("/")))) };
26610
26781
  } catch (error62) {
26611
- if (error62 instanceof TypeError) problems.push(docProblem("INVALID_CHARACTERS", path3));
26612
- else throw error62;
26782
+ if (error62 instanceof TypeError) return { path: path3, problem: docProblem("INVALID_CHARACTERS", path3) };
26783
+ throw error62;
26613
26784
  }
26785
+ }));
26786
+ const files = /* @__PURE__ */ new Map();
26787
+ const problems = [];
26788
+ for (const r of read2) {
26789
+ if (r.text !== void 0) files.set(r.path, r.text);
26790
+ if (r.problem) problems.push(r.problem);
26614
26791
  }
26615
26792
  return { files, problems };
26616
26793
  },
@@ -26641,6 +26818,40 @@ function createDocumentCache(root, database) {
26641
26818
  }));
26642
26819
  });
26643
26820
  },
26821
+ /**
26822
+ * The working documents of one kind whose title, place or text holds the query, a page at a time: a title that
26823
+ * starts with it first, then a title, a place, and the text; by title within each. `after` is the ID of the last
26824
+ * hit of the page before; one that no longer matches answers undefined. Each hit names its feature's ID.
26825
+ */
26826
+ async searchKind(kind2, raw, after, limit) {
26827
+ const query = raw.trim().toLowerCase();
26828
+ await sync();
26829
+ return database.with((db) => {
26830
+ const literal2 = /[\\%_]/.test(query);
26831
+ const match = (column) => literal2 ? `instr(${column}, :q) > 0` : `${column} LIKE :like`;
26832
+ const ordered = `WITH hits AS (
26833
+ SELECT json_extract(payload, '$.id') AS id, payload, title, ref,
26834
+ CASE WHEN substr(title, 1, length(:q)) = :q THEN 0 WHEN instr(title, :q) > 0 THEN 1 WHEN instr(place, :q) > 0 THEN 2 ELSE 3 END AS rank
26835
+ FROM search WHERE scope = 'checkout' AND kind = :kind AND (${match("title")} OR ${match("place")} OR ${match("body")})),
26836
+ ordered AS (SELECT *, row_number() OVER (ORDER BY rank, title, ref) AS n FROM hits)`;
26837
+ const params = { q: query, kind: kind2, ...literal2 ? {} : { like: containing(query) } };
26838
+ const total = Number(db.prepare(`${ordered} SELECT count(*) AS n FROM ordered`).get(params).n);
26839
+ let start = 0;
26840
+ if (after !== void 0) {
26841
+ const at = db.prepare(`${ordered} SELECT n FROM ordered WHERE id = :after`).get({ ...params, after });
26842
+ if (!at) return void 0;
26843
+ start = Number(at.n);
26844
+ }
26845
+ const rows = db.prepare(`${ordered} SELECT payload FROM ordered WHERE n > :start ORDER BY n LIMIT :limit`).all({ ...params, start, limit: limit + 1 });
26846
+ const features = new Map(db.prepare("SELECT feature, id FROM documents WHERE kind = 'feature'").all().map((r) => [r.feature, r.id]));
26847
+ const hits = rows.slice(0, limit).map((r) => {
26848
+ const p = JSON.parse(r.payload);
26849
+ const featureId2 = p.feature ? features.get(p.feature) : void 0;
26850
+ return { id: p.id, kind: kind2, title: p.title, where: p.where, line: snippet(p.body, query), ...featureId2 ? { featureId: featureId2 } : {} };
26851
+ });
26852
+ return { total, next: rows.length > limit ? hits[hits.length - 1].id : null, hits };
26853
+ });
26854
+ },
26644
26855
  /** Documents that refer to `id` from their frontmatter: designs naming a requirement or a source. */
26645
26856
  async referencing(id) {
26646
26857
  await sync();
@@ -26866,7 +27077,8 @@ function createCommitChanges(git) {
26866
27077
  types: change2.types,
26867
27078
  before: change2.before,
26868
27079
  after: change2.after,
26869
- records: recordsFor(records2, change2.id)
27080
+ records: recordsFor(records2, change2.id),
27081
+ source: { before: c.parent ?? null, after: c.commit }
26870
27082
  }));
26871
27083
  const newReasons = (before, after) => [...after.reasons.values()].filter((r) => before.every((b) => !b.reasons.has(r.id))).map((r) => reasonRecord(r.id, r.docs, r.reason));
26872
27084
  const recordsAt = (blobs, rev, paths2) => paths2.flatMap((path3) => {
@@ -26951,6 +27163,21 @@ function createCommitChanges(git) {
26951
27163
  return {
26952
27164
  /** Every document and reason file of a commit (path → text): what `specs show --ref` and `changes` compare against. */
26953
27165
  tree,
27166
+ /**
27167
+ * Documents read again at the commits and paths a change was read at, by one `cat-file --batch`: the text a
27168
+ * change's detail shows, which the cache does not keep. A place that no longer reads is null.
27169
+ */
27170
+ async sides(places) {
27171
+ const blobs = await readBlobs([...new Set(places.map((p) => p.rev + ":" + p.path))]);
27172
+ return places.map((p) => {
27173
+ const text4 = blobs.get(p.rev + ":" + p.path);
27174
+ try {
27175
+ return text4 === void 0 ? null : snapshot3(parseDocumentFile(p.path, text4), p.specId);
27176
+ } catch {
27177
+ return null;
27178
+ }
27179
+ });
27180
+ },
26954
27181
  /** All reachable record commits, children before parents even when author clocks differ. */
26955
27182
  async lineage(head) {
26956
27183
  return git.decode(await git.run(["rev-list", "--full-history", "--date-order", head, "--", ...HISTORY_PATHSPECS])).split("\n").filter(Boolean);
@@ -26989,25 +27216,30 @@ function createCommitChanges(git) {
26989
27216
  // src/adapters/cache/history.ts
26990
27217
  var listed = (e) => {
26991
27218
  const reference2 = (s) => s ? { id: s.id, title: s.title, specId: s.specId, path: s.path } : null;
26992
- return { ...e, before: reference2(e.before), after: reference2(e.after) };
27219
+ const { source: _source, ...event2 } = e;
27220
+ return { ...event2, before: reference2(e.before), after: reference2(e.after) };
26993
27221
  };
26994
- var titleOf = (e) => (e.after ?? e.before)?.title ?? e.id;
27222
+ var sideOf3 = (snapshot5, rev) => !snapshot5 ? null : rev ? { rev, path: snapshot5.path } : snapshot5;
26995
27223
  var DAY = 864e5;
26996
27224
  var KEPT_HEADS = 4;
26997
27225
  var RECENT_COMMITS = 3;
26998
27226
  var RECENT_CHANGES = 12;
26999
- function createHistory(database, git) {
27227
+ function createHistory(database, git, originals) {
27000
27228
  const changes2 = createCommitChanges(git);
27229
+ const texts = originals ?? changes2;
27001
27230
  const building = /* @__PURE__ */ new Map();
27002
27231
  const built = /* @__PURE__ */ new Set();
27003
27232
  function insert(db, commits, readers) {
27004
27233
  const commit = db.prepare("INSERT OR IGNORE INTO commits (oid, reader) VALUES (?, ?)");
27005
27234
  const change2 = db.prepare("INSERT OR IGNORE INTO changes (key, oid, ord, id, kind, types, email, date, before_spec, after_spec, needle, row, detail) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?)");
27006
- const search = db.prepare("INSERT INTO search (scope, kind, ref, oid, payload, title, place, body) VALUES (?,?,?,?,?,?,?,?)");
27235
+ const record4 = db.prepare("INSERT OR IGNORE INTO records (oid, id, title, sections) VALUES (?, ?, ?, ?)");
27007
27236
  transaction(db, () => {
27008
27237
  for (const c of commits) {
27009
27238
  if (!Number(commit.run(c.commit, readers.get(c.commit) ?? "current").changes)) continue;
27010
27239
  c.events.forEach((e, ord) => {
27240
+ for (const r of e.records) record4.run(e.commit, r.id, r.title, JSON.stringify(r.sections));
27241
+ const row = { ...listed(e), records: e.records.map((r) => r.id) };
27242
+ const detail = { before: sideOf3(e.before, e.source?.before), after: sideOf3(e.after, e.source?.after) };
27011
27243
  change2.run(
27012
27244
  e.key,
27013
27245
  e.commit,
@@ -27020,26 +27252,22 @@ function createHistory(database, git) {
27020
27252
  e.before?.specId ?? null,
27021
27253
  e.after?.specId ?? null,
27022
27254
  [e.id, e.before?.title ?? "", e.after?.title ?? ""].join(" ").toLowerCase(),
27023
- JSON.stringify(listed(e)),
27024
- JSON.stringify({ before: e.before, after: e.after })
27025
- );
27026
- const line2 = e.records.length ? e.records.map((r) => [r.title, ...r.sections.map((s) => s.body)].join(" ")).join(" \xB7 ") : e.message;
27027
- const where2 = e.commit.slice(0, 7) + " \xB7 " + e.author;
27028
- search.run(
27029
- "history",
27030
- "history",
27031
- e.key,
27032
- e.commit,
27033
- JSON.stringify({ title: titleOf(e), where: where2, line: line2 }),
27034
- titleOf(e).toLowerCase(),
27035
- where2.toLowerCase(),
27036
- [line2, e.message, e.id].join(" ").toLowerCase()
27255
+ JSON.stringify(row),
27256
+ JSON.stringify(detail)
27037
27257
  );
27038
27258
  });
27039
27259
  }
27040
27260
  });
27041
27261
  }
27042
27262
  async function build(head) {
27263
+ const known = await database.with((db) => {
27264
+ const seen = !!db.prepare("SELECT 1 FROM heads WHERE head = ?").get(head);
27265
+ if (seen) db.prepare("UPDATE heads SET seen = ? WHERE head = ?").run(Date.now(), head);
27266
+ return seen;
27267
+ });
27268
+ if (!known) await read(head);
27269
+ }
27270
+ async function read(head) {
27043
27271
  const lineage = await changes2.lineage(head);
27044
27272
  const readers = await changes2.readers(head, lineage);
27045
27273
  const known = await database.with((db) => new Map(db.prepare("SELECT oid, reader FROM commits").all().map((r) => [r.oid, r.reader])));
@@ -27048,13 +27276,15 @@ function createHistory(database, git) {
27048
27276
  for (const oid3 of stale) {
27049
27277
  db.prepare("DELETE FROM commits WHERE oid = ?").run(oid3);
27050
27278
  db.prepare("DELETE FROM changes WHERE oid = ?").run(oid3);
27051
- db.prepare("DELETE FROM search WHERE scope = 'history' AND oid = ?").run(oid3);
27279
+ db.prepare("DELETE FROM records WHERE oid = ?").run(oid3);
27052
27280
  }
27281
+ db.prepare("DELETE FROM lineage WHERE head != ?").run(head);
27282
+ db.prepare("DELETE FROM heads WHERE head != ?").run(head);
27053
27283
  }));
27054
27284
  const missing2 = lineage.filter((oid3) => !known.has(oid3) || stale.includes(oid3));
27055
27285
  for (let i = 0; i < missing2.length; i += 100) {
27056
- const read = await changes2.of(missing2.slice(i, i + 100), readers);
27057
- await database.with((db) => insert(db, read, readers));
27286
+ const read2 = await changes2.of(missing2.slice(i, i + 100), readers);
27287
+ await database.with((db) => insert(db, read2, readers));
27058
27288
  }
27059
27289
  await database.with((db) => transaction(db, () => {
27060
27290
  db.prepare("DELETE FROM lineage WHERE head = ?").run(head);
@@ -27078,6 +27308,45 @@ function createHistory(database, git) {
27078
27308
  }
27079
27309
  return pending;
27080
27310
  }
27311
+ async function readCommit(commit) {
27312
+ const known = await database.with((db) => !!db.prepare("SELECT 1 FROM commits WHERE oid = ?").get(commit));
27313
+ if (known) return;
27314
+ const read2 = await changes2.of([commit]).catch(() => void 0);
27315
+ if (read2) await database.with((db) => insert(db, read2, /* @__PURE__ */ new Map()));
27316
+ }
27317
+ function sameRecord(db, commit, id) {
27318
+ if (!id.startsWith("H-")) return [id];
27319
+ const own2 = db.prepare("SELECT title, sections FROM records WHERE oid = ? AND id = ?").get(commit, id);
27320
+ if (!own2) return [id];
27321
+ return db.prepare("SELECT id FROM records WHERE oid = ? AND id LIKE 'H-%' AND title = ? AND sections = ?").all(commit, own2.title, own2.sections).map((r) => r.id);
27322
+ }
27323
+ function hydrate(db, rows) {
27324
+ const events = rows.map((row) => JSON.parse(row));
27325
+ const read2 = db.prepare("SELECT id, title, sections FROM records WHERE oid = ?");
27326
+ const byCommit = /* @__PURE__ */ new Map();
27327
+ for (const e of events) if (e.records.length && !byCommit.has(e.commit)) {
27328
+ byCommit.set(e.commit, new Map(read2.all(e.commit).map((r) => [r.id, { id: r.id, title: r.title, sections: JSON.parse(r.sections) }])));
27329
+ }
27330
+ return events.map((e) => ({ ...e, records: e.records.flatMap((id) => {
27331
+ const r = byCommit.get(e.commit)?.get(id);
27332
+ return r ? [r] : [];
27333
+ }) }));
27334
+ }
27335
+ async function detailOf(rows) {
27336
+ const events = await database.with((db) => hydrate(db, rows.map((r) => r.row)));
27337
+ const sides = rows.map((r) => JSON.parse(r.detail));
27338
+ const places = [];
27339
+ sides.forEach((side, i) => {
27340
+ for (const key of ["before", "after"]) {
27341
+ const value = side[key];
27342
+ if (value && "rev" in value) places.push({ rev: value.rev, path: value.path, specId: events[i][key]?.specId ?? "" });
27343
+ }
27344
+ });
27345
+ const read2 = places.length ? await texts.sides(places) : [];
27346
+ const text4 = new Map(places.map((p, i) => [p.rev + ":" + p.path, read2[i] ?? null]));
27347
+ const resolve = (value) => !value ? null : "rev" in value ? text4.get(value.rev + ":" + value.path) ?? null : value;
27348
+ return events.map((event2, i) => ({ event: event2, before: resolve(sides[i].before), after: resolve(sides[i].after) }));
27349
+ }
27081
27350
  function where(filter) {
27082
27351
  const clauses = [];
27083
27352
  const params = [];
@@ -27106,22 +27375,60 @@ function createHistory(database, git) {
27106
27375
  return {
27107
27376
  ensure,
27108
27377
  /** The document and reason files of one commit, read from Git and not kept. */
27109
- filesAt: (rev) => changes2.tree(rev),
27110
- /** One page of the changes that match, newest first, with how many match in all. */
27111
- async page(head, filter, offset, limit) {
27378
+ filesAt: (rev) => texts.tree(rev),
27379
+ /**
27380
+ * The changes that match, newest first, a page of whole commits: `limit` commits after the commit `after`, each
27381
+ * with all of its matching changes, and how many changes and commits match in all. `next` is the last commit of the
27382
+ * page when more follow. A commit `after` that is not in `head`'s matching history answers undefined.
27383
+ */
27384
+ async commits(head, filter, after, limit) {
27112
27385
  await ensure(head);
27113
27386
  return database.with((db) => {
27114
27387
  const { sql, params } = where(filter);
27115
27388
  const from = "FROM lineage l JOIN changes c ON c.oid = l.oid WHERE l.head = ?" + sql;
27116
- const total = Number(db.prepare("SELECT count(*) AS n " + from).get(head, ...params).n);
27117
- const rows = db.prepare(`SELECT c.row ${from} ORDER BY l.pos, c.ord LIMIT ? OFFSET ?`).all(head, ...params, limit, offset);
27118
- return { total, events: rows.map((r) => JSON.parse(r.row)) };
27389
+ const counted = db.prepare("SELECT count(*) AS n, count(DISTINCT c.oid) AS commits " + from).get(head, ...params);
27390
+ let start = -1;
27391
+ if (after !== void 0) {
27392
+ const found = db.prepare("SELECT l.pos AS pos " + from + " AND l.oid = ? LIMIT 1").get(head, ...params, after);
27393
+ if (!found) return void 0;
27394
+ start = found.pos;
27395
+ }
27396
+ const commits = db.prepare(`SELECT l.pos AS pos, l.oid AS oid ${from} AND l.pos > ? GROUP BY l.pos, l.oid ORDER BY l.pos LIMIT ?`).all(head, ...params, start, limit + 1);
27397
+ const shown = commits.slice(0, limit);
27398
+ const rows = !shown.length ? [] : db.prepare(`SELECT c.row ${from} AND l.pos >= ? AND l.pos <= ? ORDER BY l.pos, c.ord`).all(head, ...params, shown[0].pos, shown[shown.length - 1].pos);
27399
+ return {
27400
+ total: Number(counted.n),
27401
+ commits: Number(counted.commits),
27402
+ next: commits.length > limit ? shown[shown.length - 1].oid : null,
27403
+ events: hydrate(db, rows.map((r) => r.row))
27404
+ };
27405
+ });
27406
+ },
27407
+ /**
27408
+ * The changes of one commit without their text, a page at a time after the change `after` (a key): the commit page's
27409
+ * list. With `record`, only the changes that record explains: the record page's documents.
27410
+ */
27411
+ async commitChanges(commit, after, limit, record4) {
27412
+ await readCommit(commit);
27413
+ return database.with((db) => {
27414
+ const ids = record4 === void 0 ? void 0 : JSON.stringify(sameRecord(db, commit, record4));
27415
+ const all = ids === void 0 ? db.prepare("SELECT key, row FROM changes WHERE oid = ? ORDER BY ord").all(commit) : db.prepare("SELECT key, row FROM changes c WHERE oid = ? AND EXISTS (SELECT 1 FROM json_each(c.row, '$.records') j WHERE j.value IN (SELECT value FROM json_each(?))) ORDER BY ord").all(commit, ids);
27416
+ const start = after === void 0 ? 0 : all.findIndex((r) => r.key === after) + 1;
27417
+ if (after !== void 0 && !start) return void 0;
27418
+ const shown = all.slice(start, start + limit);
27419
+ return { total: all.length, next: start + shown.length < all.length ? shown[shown.length - 1].key : null, events: hydrate(db, shown.map((r) => r.row)) };
27119
27420
  });
27120
27421
  },
27422
+ /** One change of a commit with the text on both sides, read again from Git: the change the reader opened. */
27423
+ async commitChange(commit, id) {
27424
+ await readCommit(commit);
27425
+ const rows = await database.with((db) => db.prepare("SELECT row, detail FROM changes WHERE oid = ? AND id = ? ORDER BY ord LIMIT 1").all(commit, id));
27426
+ return rows.length ? (await detailOf(rows))[0] : void 0;
27427
+ },
27121
27428
  /** Every change of one document, newest first: `records list --doc`. */
27122
27429
  async ofDocument(head, id) {
27123
27430
  await ensure(head);
27124
- return database.with((db) => db.prepare("SELECT c.row FROM lineage l JOIN changes c ON c.oid = l.oid WHERE l.head = ? AND c.id = ? ORDER BY l.pos, c.ord").all(head, id).map((r) => JSON.parse(r.row)));
27431
+ return database.with((db) => hydrate(db, db.prepare("SELECT c.row FROM lineage l JOIN changes c ON c.oid = l.oid WHERE l.head = ? AND c.id = ? ORDER BY l.pos, c.ord").all(head, id).map((r) => r.row)));
27125
27432
  },
27126
27433
  /** Who changed which document in which commit of `head`'s history, newest first: what the lists filter and sort by. */
27127
27434
  async changesOf(head) {
@@ -27130,25 +27437,60 @@ function createHistory(database, git) {
27130
27437
  FROM lineage l JOIN changes c ON c.oid = l.oid WHERE l.head = ? ORDER BY l.pos, c.ord`).all(head));
27131
27438
  },
27132
27439
  /**
27133
- * Every record of `head`'s history, newest first, once each: the commit that carries it and the documents it
27134
- * explains there. A record reaches the cache through the changes it explains, so one row per change is folded.
27440
+ * The records of `head`'s history that match, newest first, a page at a time: what `records list` shows after the
27441
+ * records not committed yet. The filters and the page are SQL over the records table, so a page reads only its
27442
+ * rows. `after` is the ID of the last record of the previous page; `limit` undefined is every match.
27135
27443
  */
27136
- async recordsOf(head) {
27444
+ async records(head, filter, after, limit) {
27137
27445
  await ensure(head);
27138
- const rows = await database.with((db) => db.prepare(`SELECT c.oid AS "commit", c.id AS doc, c.date AS date, json_extract(c.row, '$.author') AS author, c.email AS email,
27139
- json_extract(c.row, '$.message') AS message, r.value AS record
27140
- FROM lineage l JOIN changes c ON c.oid = l.oid, json_each(c.row, '$.records') r WHERE l.head = ? ORDER BY l.pos, c.ord`).all(head));
27141
- const records2 = /* @__PURE__ */ new Map();
27142
- for (const row of rows) {
27143
- const record4 = JSON.parse(row.record);
27144
- const known = records2.get(record4.id);
27145
- if (known) {
27146
- if (known.commit === row.commit && !known.docs.includes(row.doc)) known.docs.push(row.doc);
27147
- continue;
27446
+ return database.with((db) => {
27447
+ const clauses = [];
27448
+ const params = [];
27449
+ if (filter.author) {
27450
+ clauses.push("(lower(json_extract(row, '$.author')) = ? OR lower(json_extract(row, '$.email')) = ?)");
27451
+ params.push(filter.author.toLowerCase(), filter.author.toLowerCase());
27148
27452
  }
27149
- records2.set(record4.id, { id: record4.id, title: record4.title, sections: record4.sections, commit: row.commit, date: row.date, author: row.author, email: row.email, message: row.message, docs: [row.doc] });
27150
- }
27151
- return [...records2.values()];
27453
+ if (filter.q?.trim()) {
27454
+ clauses.push("instr(lower(title || ' ' || body || ' ' || json_extract(row, '$.message')), ?) > 0");
27455
+ params.push(filter.q.trim().toLowerCase());
27456
+ }
27457
+ if (filter.from !== void 0) {
27458
+ clauses.push("unixepoch(json_extract(row, '$.date')) * 1000 >= ?");
27459
+ params.push(filter.from);
27460
+ }
27461
+ if (filter.commits) {
27462
+ clauses.push("oid IN (SELECT value FROM json_each(?))");
27463
+ params.push(JSON.stringify(filter.commits));
27464
+ }
27465
+ const matching = `WITH meta AS (SELECT l.pos AS pos, r.oid AS oid, r.id AS id, r.title AS title, r.sections AS sections,
27466
+ (SELECT group_concat(json_extract(s.value, '$.body'), ' ') FROM json_each(r.sections) s) AS body,
27467
+ (SELECT c.row FROM changes c WHERE c.oid = r.oid ORDER BY c.ord LIMIT 1) AS row
27468
+ FROM lineage l JOIN records r ON r.oid = l.oid WHERE l.head = ?)
27469
+ SELECT * FROM meta WHERE 1 = 1${clauses.map((c) => " AND " + c).join("")}`;
27470
+ const total = Number(db.prepare(`SELECT count(*) AS n FROM (${matching})`).get(head, ...params).n);
27471
+ let from;
27472
+ if (after !== void 0) {
27473
+ from = db.prepare(`SELECT pos, id FROM (${matching}) WHERE id = ?`).get(head, ...params, after);
27474
+ if (!from) return void 0;
27475
+ }
27476
+ const rows = db.prepare(`SELECT oid, id, title, sections, row FROM (${matching})${from ? " WHERE (pos, id) > (?, ?)" : ""} ORDER BY pos, id LIMIT ?`).all(head, ...params, ...from ? [from.pos, from.id] : [], limit ?? -1);
27477
+ const docsOf = db.prepare("SELECT c.id AS id FROM changes c, json_each(c.row, '$.records') j WHERE c.oid = ? AND j.value = ? ORDER BY c.ord");
27478
+ const records2 = rows.map((r) => {
27479
+ const commit = JSON.parse(r.row);
27480
+ return {
27481
+ id: r.id,
27482
+ title: r.title,
27483
+ sections: JSON.parse(r.sections),
27484
+ commit: r.oid,
27485
+ date: commit.date,
27486
+ author: commit.author,
27487
+ email: commit.email,
27488
+ message: commit.message,
27489
+ docs: docsOf.all(r.oid, r.id).map((d) => d.id)
27490
+ };
27491
+ });
27492
+ return { records: records2, total };
27493
+ });
27152
27494
  },
27153
27495
  /** Counts over all of history and its newest commits, for the overview. */
27154
27496
  async summary(head) {
@@ -27160,110 +27502,299 @@ function createHistory(database, git) {
27160
27502
  const commits = db.prepare(`SELECT l.oid AS oid, max(c.date) AS date, count(*) AS n ${from} GROUP BY l.pos, l.oid ORDER BY l.pos`).all(head);
27161
27503
  const newest = Math.max(0, ...commits.map((c) => Date.parse(c.date)).filter(Number.isFinite));
27162
27504
  const pulse = commits.filter((c) => Date.parse(c.date) >= newest - 22 * DAY).map((c) => ({ date: c.date, count: Number(c.n) }));
27163
- const recent = commits.slice(0, RECENT_COMMITS).map((c) => ({
27505
+ const recent2 = commits.slice(0, RECENT_COMMITS).map((c) => ({
27164
27506
  commit: c.oid,
27165
27507
  count: Number(c.n),
27166
- events: db.prepare("SELECT row FROM changes WHERE oid = ? ORDER BY ord LIMIT ?").all(c.oid, RECENT_CHANGES).map((r) => JSON.parse(r.row))
27508
+ events: hydrate(db, db.prepare("SELECT row FROM changes WHERE oid = ? ORDER BY ord LIMIT ?").all(c.oid, RECENT_CHANGES).map((r) => r.row))
27167
27509
  }));
27168
- return { total, byType: { created: count2("created"), modified: count2("modified"), moved: count2("moved"), deleted: count2("deleted") }, pulse, recent };
27510
+ return { total, byType: { created: count2("created"), modified: count2("modified"), moved: count2("moved"), deleted: count2("deleted") }, pulse, recent: recent2 };
27169
27511
  });
27170
27512
  },
27171
- /** The commit of `head`'s history that added a record, found through the records its changes carry. */
27172
- async commitOfRecord(head, id) {
27513
+ /**
27514
+ * The record of `head`'s history with the commit that added it, and how many other records that commit holds (a
27515
+ * 0.7 reason written once over several documents counts once). Undefined when the history has no such record.
27516
+ */
27517
+ async recordOf(head, id) {
27173
27518
  await ensure(head);
27174
- return database.with((db) => db.prepare(`SELECT l.oid AS oid FROM lineage l JOIN changes c ON c.oid = l.oid, json_each(c.row, '$.records') r
27175
- WHERE l.head = ? AND json_extract(r.value, '$.id') = ? ORDER BY l.pos LIMIT 1`).get(head, id)?.oid);
27176
- },
27177
- /** Every change of one commit with the text on both sides: what the commit page reads. */
27178
- async ofCommit(commit) {
27179
- const rows = await database.with((db) => db.prepare("SELECT row, detail FROM changes WHERE oid = ? ORDER BY ord").all(commit));
27180
- if (rows.length) return rows.map((r) => ({ event: JSON.parse(r.row), ...JSON.parse(r.detail) }));
27181
- const known = await database.with((db) => !!db.prepare("SELECT 1 FROM commits WHERE oid = ?").get(commit));
27182
- if (known) return [];
27183
- const read = await changes2.of([commit]).catch(() => void 0);
27184
- if (!read) return [];
27185
- await database.with((db) => insert(db, read, /* @__PURE__ */ new Map()));
27186
- return database.with((db) => db.prepare("SELECT row, detail FROM changes WHERE oid = ? ORDER BY ord").all(commit).map((r) => ({ event: JSON.parse(r.row), ...JSON.parse(r.detail) })));
27519
+ return database.with((db) => {
27520
+ const found = db.prepare("SELECT r.oid AS oid, r.title AS title, r.sections AS sections FROM lineage l JOIN records r ON r.oid = l.oid WHERE l.head = ? AND r.id = ? ORDER BY l.pos LIMIT 1").get(head, id);
27521
+ if (!found) return void 0;
27522
+ const all = db.prepare("SELECT id, title, sections FROM records WHERE oid = ?").all(found.oid);
27523
+ const groups = new Set(all.map((r) => r.id.startsWith("H-") ? "H\0" + r.title + "\0" + r.sections : r.id));
27524
+ return { commit: found.oid, record: { id, title: found.title, sections: JSON.parse(found.sections) }, others: groups.size - 1 };
27525
+ });
27187
27526
  },
27188
27527
  /**
27189
- * Documents whose title, place or text holds the query — a title match before a place match before a text match —
27190
- * then past changes of `head`'s history, newest first. The caller syncs the working documents first.
27528
+ * The records of `head`'s history whose title, ID or sections hold the query, one hit per record file, a page at a
27529
+ * time: a title that starts with it first, then a title, the ID, and the sections; newest first within each. `after`
27530
+ * is the ID of the last hit of the page before; one that no longer matches answers undefined.
27191
27531
  */
27192
- async search(head, raw) {
27532
+ async searchRecords(head, raw, after, limit) {
27193
27533
  const query = raw.trim().toLowerCase();
27194
- if (!query) return [];
27195
- if (head) await ensure(head);
27534
+ await ensure(head);
27196
27535
  return database.with((db) => {
27197
- const literal2 = /[\\%_]/.test(query);
27198
- const match = (column) => literal2 ? `instr(${column}, ?) > 0` : `${column} LIKE ?`;
27199
- const needle = literal2 ? query : containing(query);
27200
- const features = new Map(db.prepare("SELECT feature, id FROM documents WHERE kind = 'feature'").all().map((r) => [r.feature, r.id]));
27201
- const found = db.prepare(`SELECT kind, payload, title, place FROM search WHERE scope = 'checkout' AND (${match("title")} OR ${match("place")} OR ${match("body")}) LIMIT 400`).all(needle, needle, needle);
27202
- const documents = found.map((r) => {
27203
- const at = r.title.indexOf(query);
27204
- const rank = at === 0 ? 0 : at > 0 ? 1 : r.place.includes(query) ? 2 : 3;
27205
- const p = JSON.parse(r.payload);
27206
- const featureId = p.feature ? features.get(p.feature) : void 0;
27207
- const hit = {
27208
- id: p.id,
27209
- kind: r.kind,
27210
- title: p.title,
27211
- where: p.where,
27212
- line: snippet(p.body, query),
27213
- ...featureId ? { featureId } : {}
27214
- };
27215
- return { rank, hit };
27216
- }).sort((a, b) => a.rank - b.rank || a.hit.title.localeCompare(b.hit.title)).slice(0, 24).map((r) => r.hit);
27217
- const past = !head ? [] : db.prepare(`SELECT s.ref AS ref, s.payload AS payload FROM search s JOIN lineage l ON l.head = ? AND l.oid = s.oid
27218
- WHERE s.scope = 'history' AND (${match("s.title")} OR ${match("s.body")}) ORDER BY l.pos LIMIT 8`).all(head, needle, needle).map((r) => {
27219
- const p = JSON.parse(r.payload);
27220
- return { id: r.ref, kind: "history", title: p.title, where: p.where, line: snippet(p.line, query), key: r.ref };
27536
+ const ordered = `WITH own AS (
27537
+ SELECT r.id AS id, r.title AS title, r.oid AS oid, min(l.pos) AS pos, r.sections AS sections,
27538
+ lower((SELECT group_concat(json_extract(s.value, '$.body'), ' ') FROM json_each(r.sections) s)) AS body
27539
+ FROM lineage l JOIN records r ON r.oid = l.oid WHERE l.head = :head GROUP BY r.id),
27540
+ hits AS (SELECT *, CASE WHEN substr(lower(title), 1, length(:q)) = :q THEN 0 WHEN instr(lower(title), :q) > 0 THEN 1
27541
+ WHEN instr(lower(id), :q) > 0 THEN 2 ELSE 3 END AS rank
27542
+ FROM own WHERE instr(lower(title), :q) > 0 OR instr(lower(id), :q) > 0 OR instr(body, :q) > 0),
27543
+ ordered AS (SELECT *, row_number() OVER (ORDER BY rank, pos, id) AS n FROM hits)`;
27544
+ const params = { head, q: query };
27545
+ const total = Number(db.prepare(`${ordered} SELECT count(*) AS n FROM ordered`).get(params).n);
27546
+ let start = 0;
27547
+ if (after !== void 0) {
27548
+ const at = db.prepare(`${ordered} SELECT n FROM ordered WHERE id = :after`).get({ ...params, after });
27549
+ if (!at) return void 0;
27550
+ start = Number(at.n);
27551
+ }
27552
+ const rows = db.prepare(`${ordered} SELECT id, title, oid, sections FROM ordered WHERE n > :start ORDER BY n LIMIT :limit`).all({ ...params, start, limit: limit + 1 });
27553
+ const hits = rows.slice(0, limit).map((r) => {
27554
+ const text4 = JSON.parse(r.sections).map((s) => s.body).join(" ");
27555
+ return { id: r.id, kind: "record", title: r.title, where: r.id + " \xB7 " + r.oid.slice(0, 7), line: snippet(text4, query), commit: r.oid };
27221
27556
  });
27222
- return [...documents, ...past];
27557
+ return { total, next: rows.length > limit ? hits[hits.length - 1].id : null, hits };
27558
+ });
27559
+ }
27560
+ };
27561
+ }
27562
+
27563
+ // src/adapters/cache/commit-log.ts
27564
+ import { lstat as lstat3 } from "node:fs/promises";
27565
+ import { join as join4 } from "node:path";
27566
+ var FORMAT2 = `%x1e%H%x00%aN%x00%aE%x00%at%x00%aI%x00%s%x00%(trailers:key=${MIGRATION_TRAILER},valueonly,separator=%x2C)`;
27567
+ var CHUNK = 1e5;
27568
+ var folderOf2 = (path3) => path3 === "AGENTS.md" ? path3 : new RegExp(`^(${SPEC_ROOT.replace(/\./g, "\\.")}/[^/]+|${INSTRUCTIONS_ROOT.replace(/\./g, "\\.")}/[^/]+)/`).exec(path3)?.[1];
27569
+ function createCommitLog(database, git, root) {
27570
+ let following;
27571
+ let followingHead;
27572
+ const read = async (args) => git.decode(await git.run(args));
27573
+ async function commits(range) {
27574
+ const out = [];
27575
+ for (let skip = 0; ; skip += CHUNK) {
27576
+ const text4 = await read(["log", `--format=${FORMAT2}`, `--max-count=${CHUNK}`, `--skip=${skip}`, ...range, "--"]);
27577
+ const chunk = text4.split("").filter(Boolean).map((entry2) => {
27578
+ const [oid3, name2, email3, time3, date5, subject, migration] = entry2.split("\0");
27579
+ return { oid: oid3, name: name2 ?? "", email: email3 ?? "", time: Number(time3), date: date5 ?? "", subject: subject ?? "", migration: !!migration?.trim() };
27580
+ });
27581
+ out.push(...chunk);
27582
+ if (chunk.length < CHUNK) return out;
27583
+ }
27584
+ }
27585
+ async function touches(range) {
27586
+ const text4 = await read(["log", "--format=%x1e%H", "-z", "--name-only", "--no-renames", ...range, "--", SPEC_ROOT, INSTRUCTIONS_ROOT, "AGENTS.md"]);
27587
+ const out = [];
27588
+ for (const entry2 of text4.split("").filter(Boolean)) {
27589
+ const [oid3, ...paths2] = entry2.split("\0");
27590
+ const folders = new Set(paths2.map((p) => folderOf2(p.replace(/^\n/, ""))).filter((f) => !!f));
27591
+ for (const folder of folders) out.push([oid3.trim(), folder]);
27592
+ }
27593
+ return out;
27594
+ }
27595
+ async function mailmap() {
27596
+ const info5 = await lstat3(join4(root, ".mailmap")).catch(() => void 0);
27597
+ return info5 ? info5.mtimeMs + ":" + info5.size : "-";
27598
+ }
27599
+ const stateOf = (db, key) => db.prepare("SELECT value FROM state WHERE key = ?").get(key)?.value;
27600
+ async function follow(head, attempts = 3) {
27601
+ const names = await mailmap();
27602
+ const [before, named] = await database.with((db) => [stateOf(db, "head"), stateOf(db, "mailmap")]);
27603
+ if (before === head && named === names) return;
27604
+ let change2;
27605
+ if (before && named === names) {
27606
+ try {
27607
+ const range = [head, "^" + before];
27608
+ const [added2, removed2, touched2] = await Promise.all([commits(range), read(["rev-list", before, "^" + head, "--"]), touches(range)]);
27609
+ change2 = { added: added2, removed: removed2.split("\n").filter(Boolean), touched: touched2, whole: false };
27610
+ } catch {
27611
+ change2 = void 0;
27612
+ }
27613
+ }
27614
+ if (!change2) {
27615
+ const [added2, touched2] = await Promise.all([commits([head]), touches([head])]);
27616
+ change2 = { added: added2, removed: [], touched: touched2, whole: true };
27617
+ }
27618
+ const { added, removed, touched, whole } = change2;
27619
+ const written = await database.with((db) => transaction(db, () => {
27620
+ if (stateOf(db, "head") !== before || stateOf(db, "mailmap") !== named) return false;
27621
+ if (whole) {
27622
+ db.exec("DELETE FROM log");
27623
+ db.exec("DELETE FROM touches");
27624
+ }
27625
+ const put = db.prepare(`INSERT INTO log (oid, name, email, time, date, subject, migration, reach) VALUES (?, ?, ?, ?, ?, ?, ?, 1)
27626
+ ON CONFLICT (oid) DO UPDATE SET name = excluded.name, email = excluded.email, reach = 1`);
27627
+ for (const c of added) put.run(c.oid, c.name, c.email, c.time, c.date, c.subject, c.migration ? 1 : 0);
27628
+ const leave = db.prepare("UPDATE log SET reach = 0 WHERE oid = ?");
27629
+ for (const oid3 of removed) leave.run(oid3);
27630
+ const touch = db.prepare("INSERT OR IGNORE INTO touches (oid, folder) VALUES (?, ?)");
27631
+ for (const [oid3, folder] of touched) touch.run(oid3, folder);
27632
+ const set2 = db.prepare("INSERT INTO state (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value");
27633
+ set2.run("head", head);
27634
+ set2.run("mailmap", names);
27635
+ return true;
27636
+ }));
27637
+ if (!written && attempts > 1) return follow(head, attempts - 1);
27638
+ }
27639
+ async function ensure(head) {
27640
+ while (following && followingHead !== head) await following.catch(() => void 0);
27641
+ if (!following) {
27642
+ followingHead = head;
27643
+ following = follow(head).finally(() => {
27644
+ following = void 0;
27645
+ followingHead = void 0;
27646
+ });
27647
+ }
27648
+ return following;
27649
+ }
27650
+ return {
27651
+ ensure,
27652
+ /** Everyone who committed to `head`, most commits first, format migrations left out. `latest` is their newest commit. */
27653
+ async contributors(head) {
27654
+ await ensure(head);
27655
+ return database.with((db) => db.prepare(`SELECT email, name, count(*) AS commits, max(time) AS time, date AS latest FROM log
27656
+ WHERE reach = 1 AND migration = 0 GROUP BY email ORDER BY commits DESC, name, email`).all().map(({ name: name2, email: email3, commits: commits2, latest }) => ({ name: name2, email: email3, commits: Number(commits2), latest })));
27657
+ },
27658
+ /**
27659
+ * The commits of `head` a hash names: the whole hash, or its start from seven characters on, as `git log --oneline`
27660
+ * prints it. Anything shorter or not a hash names none. Newest first, a page after the commit `after`.
27661
+ */
27662
+ async commitsNamed(head, raw, after, limit) {
27663
+ const prefix = raw.trim().toLowerCase();
27664
+ if (!/^[0-9a-f]{7,64}$/.test(prefix)) return { total: 0, next: null, hits: [] };
27665
+ await ensure(head);
27666
+ return database.with((db) => {
27667
+ const rows = db.prepare("SELECT oid, name, date, subject FROM log WHERE reach = 1 AND substr(oid, 1, length(?)) = ? ORDER BY time DESC, oid").all(prefix, prefix);
27668
+ const start = after === void 0 ? 0 : rows.findIndex((r) => r.oid === after) + 1;
27669
+ if (after !== void 0 && !start) return void 0;
27670
+ const shown = rows.slice(start, start + limit);
27671
+ return {
27672
+ total: rows.length,
27673
+ next: start + shown.length < rows.length ? shown[shown.length - 1].oid : null,
27674
+ hits: shown.map((r) => ({ id: r.oid, kind: "commit", title: r.subject, where: r.oid.slice(0, 7) + " \xB7 " + r.name, line: r.date.slice(0, 10), commit: r.oid }))
27675
+ };
27676
+ });
27677
+ },
27678
+ /** Each store folder's authors, most commits first, and when it was last touched, format migrations left out. */
27679
+ async folders(head) {
27680
+ await ensure(head);
27681
+ return database.with((db) => {
27682
+ const rows = db.prepare(`SELECT t.folder AS folder, l.email AS email, l.name AS name, count(*) AS commits, max(l.time) AS time, l.date AS latest
27683
+ FROM touches t JOIN log l ON l.oid = t.oid WHERE l.reach = 1 AND l.migration = 0
27684
+ GROUP BY t.folder, l.email ORDER BY t.folder, commits DESC, name, email`).all();
27685
+ const out = /* @__PURE__ */ new Map();
27686
+ for (const r of rows) {
27687
+ let entry2 = out.get(r.folder);
27688
+ if (!entry2) out.set(r.folder, entry2 = { people: [], latest: r.latest, time: r.time });
27689
+ entry2.people.push({ name: r.name, email: r.email, commits: Number(r.commits), latest: r.latest });
27690
+ if (r.time > entry2.time) {
27691
+ entry2.time = r.time;
27692
+ entry2.latest = r.latest;
27693
+ }
27694
+ }
27695
+ return new Map([...out].map(([folder, { people, latest }]) => [folder, { people, latest }]));
27223
27696
  });
27224
27697
  }
27225
27698
  };
27226
27699
  }
27227
27700
 
27228
27701
  // src/adapters/cache/index.ts
27229
- function openCache(root, git) {
27702
+ function openCache(root, git, originals) {
27230
27703
  const database = createCacheDatabase(root);
27231
27704
  const documents = createDocumentCache(root, database);
27232
- const history = createHistory(database, git);
27705
+ const history = createHistory(database, git, originals);
27706
+ const log = createCommitLog(database, git, root);
27233
27707
  return {
27234
27708
  documents,
27235
27709
  history,
27236
- location: database.location,
27237
- /** Documents first, so a search right after an edit finds the edited text. */
27238
- async search(head, query) {
27239
- await documents.sync();
27240
- return history.search(head, query);
27241
- }
27710
+ log,
27711
+ location: database.location
27242
27712
  };
27243
27713
  }
27244
27714
 
27715
+ // src/adapters/git/pending-records.ts
27716
+ import { lstat as lstat4, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
27717
+ import { join as join5 } from "node:path";
27718
+ var decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
27719
+ var info = (path3) => lstat4(path3).catch((error62) => {
27720
+ if (error62.code === "ENOENT") return void 0;
27721
+ throw error62;
27722
+ });
27723
+ async function recordFolders(root) {
27724
+ return (await readdir3(join5(root, ...RECORDS_ROOT.split("/"))).catch((error62) => {
27725
+ if (error62.code === "ENOENT") return [];
27726
+ throw error62;
27727
+ })).filter(isRecordFolder);
27728
+ }
27729
+ async function findRecord(root, id) {
27730
+ for (const folder of await recordFolders(root)) {
27731
+ const path3 = `${RECORDS_ROOT}/${folder}/${id}.md`;
27732
+ if ((await info(join5(root, ...path3.split("/"))))?.isFile()) return path3;
27733
+ }
27734
+ return void 0;
27735
+ }
27736
+ async function readPendingRecords(root, run) {
27737
+ const status = (await run(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", RECORDS_ROOT])).toString("utf8").split("\0").filter(Boolean);
27738
+ const files = /* @__PURE__ */ new Map();
27739
+ const altered = [];
27740
+ const problems = [];
27741
+ for (const entry2 of status) {
27742
+ const state2 = entry2.slice(0, 2);
27743
+ const path3 = entry2.slice(3);
27744
+ if (state2 !== "??" && !state2.includes("A")) {
27745
+ altered.push(path3);
27746
+ continue;
27747
+ }
27748
+ const file2 = join5(root, ...path3.split("/"));
27749
+ const stat3 = await info(file2);
27750
+ if (!stat3?.isFile() || stat3.isSymbolicLink()) {
27751
+ problems.push(docProblem("PATH_UNSUPPORTED", path3));
27752
+ continue;
27753
+ }
27754
+ if (stat3.size > 1024 * 1024) {
27755
+ problems.push(docProblem("FILE_TOO_LARGE", path3));
27756
+ continue;
27757
+ }
27758
+ try {
27759
+ files.set(path3, decoder.decode(await readFile3(file2)));
27760
+ } catch {
27761
+ problems.push(docProblem("INVALID_CHARACTERS", path3));
27762
+ }
27763
+ }
27764
+ const folders = await recordFolders(root);
27765
+ for (const path3 of files.keys()) {
27766
+ const id = recordIdOf(path3);
27767
+ if (!id) continue;
27768
+ for (const folder of folders) {
27769
+ const other = `${RECORDS_ROOT}/${folder}/${id}.md`;
27770
+ if (other !== path3 && !files.has(other) && (await info(join5(root, ...other.split("/"))))?.isFile()) problems.push(docProblem("DUPLICATE_RECORD_ID", path3, { id, other }));
27771
+ }
27772
+ }
27773
+ return { files, altered: altered.sort(), problems };
27774
+ }
27775
+
27245
27776
  // src/adapters/filesystem/config-file.ts
27246
- import { lstat as lstat3, readFile as readFile3, realpath, mkdir as mkdir2, open as open2, link, rename, unlink } from "node:fs/promises";
27247
- import { join as join4 } from "node:path";
27777
+ import { lstat as lstat5, readFile as readFile4, realpath, mkdir as mkdir2, open as open2, link, rename, unlink } from "node:fs/promises";
27778
+ import { join as join6 } from "node:path";
27248
27779
  import { randomUUID as randomUUID2 } from "node:crypto";
27249
27780
  var missing = (error62) => error62.code === "ENOENT";
27250
- var fileInfo = (path3) => lstat3(path3).catch((error62) => {
27781
+ var fileInfo = (path3) => lstat5(path3).catch((error62) => {
27251
27782
  if (missing(error62)) return void 0;
27252
27783
  throw error62;
27253
27784
  });
27254
27785
  async function configDirectory(root) {
27255
- const path3 = join4(root, ".gitifact");
27786
+ const path3 = join6(root, ".gitifact");
27256
27787
  const info5 = await fileInfo(path3);
27257
27788
  if (info5 && (!info5.isDirectory() || info5.isSymbolicLink())) throw new InitError("PATH_CONFLICT", t2("config.storeNotDirectory"));
27258
27789
  return info5;
27259
27790
  }
27260
27791
  async function readConfigFile(root) {
27261
27792
  await configDirectory(root);
27262
- const path3 = join4(root, ".gitifact", "config.json");
27793
+ const path3 = join6(root, ".gitifact", "config.json");
27263
27794
  const info5 = await fileInfo(path3);
27264
27795
  if (!info5) return void 0;
27265
27796
  if (!info5.isFile() || info5.isSymbolicLink() || info5.size > 65536) throw new InitError("PATH_CONFLICT", t2("config.fileShape"));
27266
- const bytes = await readFile3(path3);
27797
+ const bytes = await readFile4(path3);
27267
27798
  const after = await fileInfo(path3);
27268
27799
  await configDirectory(root);
27269
27800
  if (!after || after.ino !== info5.ino || after.dev !== info5.dev || after.mtimeMs !== info5.mtimeMs || bytes.length > 65536) {
@@ -27276,7 +27807,7 @@ async function readConfigFile(root) {
27276
27807
  }
27277
27808
  }
27278
27809
  async function publishConfig(root, text4, recheck, beforePublish, replace = false) {
27279
- const directory = join4(root, ".gitifact");
27810
+ const directory = join6(root, ".gitifact");
27280
27811
  await configDirectory(root);
27281
27812
  await mkdir2(directory).catch((error62) => {
27282
27813
  if (error62.code !== "EEXIST") throw error62;
@@ -27289,7 +27820,7 @@ async function publishConfig(root, text4, recheck, beforePublish, replace = fals
27289
27820
  throw new InitError("INPUT_CHANGED", t2("config.directoryChanged"));
27290
27821
  }
27291
27822
  };
27292
- const temporary = join4(directory, ".init-" + randomUUID2() + ".tmp");
27823
+ const temporary = join6(directory, ".init-" + randomUUID2() + ".tmp");
27293
27824
  const handle = await open2(temporary, "wx", 384);
27294
27825
  const tempOwner = await handle.stat();
27295
27826
  try {
@@ -27303,11 +27834,11 @@ async function publishConfig(root, text4, recheck, beforePublish, replace = fals
27303
27834
  await unchanged();
27304
27835
  await recheck();
27305
27836
  await unchanged();
27306
- const bytes = await readFile3(temporary, "utf8");
27837
+ const bytes = await readFile4(temporary, "utf8");
27307
27838
  if (bytes !== text4) throw new InitError("INPUT_CHANGED", t2("config.temporaryChanged"));
27308
27839
  parseManagedConfig(bytes);
27309
- if (replace) await rename(temporary, join4(directory, "config.json"));
27310
- else await link(temporary, join4(directory, "config.json"));
27840
+ if (replace) await rename(temporary, join6(directory, "config.json"));
27841
+ else await link(temporary, join6(directory, "config.json"));
27311
27842
  } finally {
27312
27843
  await unchanged();
27313
27844
  const current2 = await fileInfo(temporary);
@@ -27316,42 +27847,64 @@ async function publishConfig(root, text4, recheck, beforePublish, replace = fals
27316
27847
  }
27317
27848
 
27318
27849
  // src/adapters/filesystem/instruction-folder.ts
27319
- import { lstat as lstat4, readdir as readdir3, readFile as readFile4 } from "node:fs/promises";
27320
- import { join as join5 } from "node:path";
27850
+ import { lstat as lstat6, open as open3, readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
27851
+ import { join as join7 } from "node:path";
27321
27852
  var INSTRUCTION_FILE_LIMIT = 500;
27322
27853
  var INSTRUCTION_TEXT_LIMIT = 512 * 1024;
27323
- var folderOf2 = (root, instructionPath) => join5(root, ...instructionPath.split("/").slice(0, -1));
27854
+ var folderOf3 = (root, instructionPath) => join7(root, ...instructionPath.split("/").slice(0, -1));
27324
27855
  async function listInstructionFiles(root, instructionPath) {
27325
27856
  const files = [];
27326
27857
  let limited = false;
27327
27858
  async function visit2(folder, prefix, depth) {
27328
27859
  if (depth > 10) return;
27329
- const entries = await readdir3(folder, { withFileTypes: true }).catch(() => []);
27860
+ const entries = await readdir4(folder, { withFileTypes: true }).catch(() => []);
27330
27861
  for (const entry2 of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
27331
27862
  if (files.length >= INSTRUCTION_FILE_LIMIT) {
27332
27863
  limited = true;
27333
27864
  return;
27334
27865
  }
27335
27866
  const path3 = prefix + entry2.name;
27336
- if (entry2.isDirectory()) await visit2(join5(folder, entry2.name), path3 + "/", depth + 1);
27337
- else if (entry2.isFile() && path3 !== INSTRUCTION_FILE) files.push({ path: path3, size: (await lstat4(join5(folder, entry2.name))).size });
27867
+ if (entry2.isDirectory()) await visit2(join7(folder, entry2.name), path3 + "/", depth + 1);
27868
+ else if (entry2.isFile() && path3 !== INSTRUCTION_FILE) files.push({ path: path3, size: (await lstat6(join7(folder, entry2.name))).size });
27338
27869
  }
27339
27870
  }
27340
- await visit2(folderOf2(root, instructionPath), "", 0);
27871
+ await visit2(folderOf3(root, instructionPath), "", 0);
27341
27872
  return { files, limited };
27342
27873
  }
27874
+ var FRONTMATTER_WINDOW = 8 * 1024;
27875
+ async function describeFiles(root, instructionPath, files) {
27876
+ const folder = folderOf3(root, instructionPath);
27877
+ return Promise.all(files.map(async (file2) => {
27878
+ if (!isInstructionReference(file2.path)) return file2;
27879
+ let handle;
27880
+ try {
27881
+ handle = await open3(join7(folder, ...file2.path.split("/")), "r");
27882
+ const buffer = Buffer.alloc(Math.min(FRONTMATTER_WINDOW, file2.size));
27883
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
27884
+ return { ...file2, ...parseInstructionFile(file2.path, buffer.subarray(0, bytesRead).toString("utf8")) };
27885
+ } catch {
27886
+ return file2;
27887
+ } finally {
27888
+ await handle?.close();
27889
+ }
27890
+ }));
27891
+ }
27892
+ async function readInstructionFiles(root, instructionPath) {
27893
+ const { files, limited } = await listInstructionFiles(root, instructionPath);
27894
+ return { files: await describeFiles(root, instructionPath, files), limited };
27895
+ }
27343
27896
  async function readInstructionFile(root, instructionPath, path3) {
27344
27897
  const parts = path3.split("/");
27345
27898
  if (parts.some((p) => !p || p === "." || p === ".." || /[\\:\0]/.test(p))) return void 0;
27346
- let full = folderOf2(root, instructionPath);
27899
+ let full = folderOf3(root, instructionPath);
27347
27900
  for (const [index, part] of parts.entries()) {
27348
- full = join5(full, part);
27349
- const info5 = await lstat4(full).catch(() => void 0);
27901
+ full = join7(full, part);
27902
+ const info5 = await lstat6(full).catch(() => void 0);
27350
27903
  if (!info5 || info5.isSymbolicLink() || (index < parts.length - 1 ? !info5.isDirectory() : !info5.isFile())) return void 0;
27351
27904
  }
27352
- const size = (await lstat4(full)).size;
27905
+ const size = (await lstat6(full)).size;
27353
27906
  if (size > INSTRUCTION_TEXT_LIMIT) return { text: null, size, binary: false, tooLarge: true };
27354
- const bytes = await readFile4(full);
27907
+ const bytes = await readFile5(full);
27355
27908
  try {
27356
27909
  if (bytes.includes(0)) throw new TypeError("binary");
27357
27910
  return { text: new TextDecoder("utf-8", { fatal: true }).decode(bytes), size, binary: false, tooLarge: false };
@@ -27361,34 +27914,122 @@ async function readInstructionFile(root, instructionPath, path3) {
27361
27914
  }
27362
27915
 
27363
27916
  // src/adapters/filesystem/agents-file.ts
27364
- import { lstat as lstat5, readFile as readFile5 } from "node:fs/promises";
27365
- import { join as join6 } from "node:path";
27917
+ import { lstat as lstat7, readFile as readFile6 } from "node:fs/promises";
27918
+ import { join as join8 } from "node:path";
27366
27919
  var LIMIT = 1024 * 1024;
27367
27920
  async function readAgentsFile(root) {
27368
- const file2 = join6(root, "AGENTS.md");
27369
- const info5 = await lstat5(file2).catch(() => void 0);
27921
+ const file2 = join8(root, "AGENTS.md");
27922
+ const info5 = await lstat7(file2).catch(() => void 0);
27370
27923
  if (!info5?.isFile() || info5.size > LIMIT) return null;
27371
27924
  try {
27372
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(await readFile5(file2));
27925
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(await readFile6(file2));
27373
27926
  } catch {
27374
27927
  return null;
27375
27928
  }
27376
27929
  }
27377
27930
 
27378
- // src/server/checkout/checkout-reader.ts
27379
- var tally = (people, name2, email3, latest) => {
27380
- const person = people.get(email3);
27381
- if (person) person.commits++;
27382
- else people.set(email3, { name: name2, email: email3, latest, commits: 1 });
27931
+ // src/queries/document-states.ts
27932
+ var isDocument = (path3) => {
27933
+ try {
27934
+ return classifyDocPath(path3).type === "doc";
27935
+ } catch {
27936
+ return false;
27937
+ }
27383
27938
  };
27384
- async function settled(reads) {
27385
- const results = await Promise.allSettled(reads);
27386
- const failure2 = results.find((r) => r.status === "rejected");
27939
+ async function documentStates(project, working2) {
27940
+ const head = await project.head();
27941
+ const states = /* @__PURE__ */ new Map();
27942
+ const previous = /* @__PURE__ */ new Map();
27943
+ const result = (documents) => ({ documents, state: (id) => states.get(id) ?? "committed", previousPath: (id) => previous.get(id) });
27944
+ if (!head) {
27945
+ for (const doc of working2) states.set(doc.id, "added");
27946
+ return result(working2);
27947
+ }
27948
+ const status = project.reader.decode(await project.reader.run(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", ...[SPEC_ROOT, INSTRUCTIONS_ROOT, WIKI_ROOT].map((root) => ":(top)" + root)])).split("\0").filter(Boolean);
27949
+ const byPath2 = new Map(working2.map((d) => [d.path, d]));
27950
+ const added = [];
27951
+ const deletedPaths = [];
27952
+ for (const entry2 of status) {
27953
+ const [x, y] = [entry2[0], entry2[1]];
27954
+ const path3 = entry2.slice(3);
27955
+ if (!isDocument(path3)) continue;
27956
+ const doc = byPath2.get(path3);
27957
+ const atHead = x !== "?" && x !== "A";
27958
+ if (!doc) {
27959
+ if (atHead && (x === "D" || y === "D")) deletedPaths.push(path3);
27960
+ continue;
27961
+ }
27962
+ if (!atHead) {
27963
+ added.push(doc);
27964
+ continue;
27965
+ }
27966
+ if (x !== " " || y !== " ") states.set(doc.id, "modified");
27967
+ }
27968
+ const gone = await documentsAtHead(project, head, deletedPaths);
27969
+ const goneById = new Map(gone.map((d) => [d.id, d]));
27970
+ for (const doc of added) {
27971
+ const before = goneById.get(doc.id);
27972
+ if (before) {
27973
+ states.set(doc.id, "modified");
27974
+ previous.set(doc.id, before.path);
27975
+ goneById.delete(doc.id);
27976
+ } else states.set(doc.id, "added");
27977
+ }
27978
+ const ids = new Set(working2.map((d) => d.id));
27979
+ const deleted = [...goneById.values()].filter((d) => !ids.has(d.id));
27980
+ for (const doc of deleted) states.set(doc.id, "deleted");
27981
+ return result([...working2, ...deleted]);
27982
+ }
27983
+ async function documentsAtHead(project, head, paths2) {
27984
+ if (!paths2.length) return [];
27985
+ const listing = project.reader.decode(await project.reader.run(["ls-tree", "--full-tree", "-z", head, "--", ...paths2])).split("\0").filter(Boolean).map((row) => /^\d+ blob ([a-f0-9]+)\t([\s\S]+)$/.exec(row)).filter((m) => !!m).map((m) => ({ blob: m[1], path: m[2] }));
27986
+ const blobs = await project.reader.blobs([...new Set(listing.map((l) => l.blob))]).catch(() => /* @__PURE__ */ new Map());
27987
+ return listing.flatMap(({ blob, path: path3 }) => {
27988
+ const text4 = blobs.get(blob);
27989
+ try {
27990
+ return text4 === void 0 ? [] : [parseDocumentFile(path3, text4)];
27991
+ } catch {
27992
+ return [];
27993
+ }
27994
+ });
27995
+ }
27996
+
27997
+ // src/server/checkout/stamp.ts
27998
+ import { createHash as createHash2 } from "node:crypto";
27999
+ import { lstat as lstat8 } from "node:fs/promises";
28000
+ import { join as join9 } from "node:path";
28001
+ function createStampReader(root, inherited = process.env) {
28002
+ const runner = createGitRunner();
28003
+ const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_REPLACE_OBJECTS: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "0", LC_ALL: "C" };
28004
+ const git = async (args, acceptedExitCodes = [0]) => (await runner(["--no-optional-locks", "-c", "core.fsmonitor=false", ...args], { cwd: root, env, timeoutMs: 15e3, maxBytes: 16 * 1024 * 1024, acceptedExitCodes })).toString("utf8");
28005
+ return async () => {
28006
+ const [head, status] = await Promise.all([
28007
+ git(["rev-parse", "--verify", "--quiet", "HEAD"], [0, 1]),
28008
+ git(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", ":(top).gitifact", ":(top)AGENTS.md"])
28009
+ ]);
28010
+ const paths2 = status.split("\0").filter(Boolean).map((entry2) => entry2.slice(3)).filter((path3) => !path3.startsWith(".gitifact/cache/"));
28011
+ const seen = await Promise.all(paths2.map(async (path3) => {
28012
+ const info5 = await lstat8(join9(root, ...path3.split("/"))).catch(() => void 0);
28013
+ return path3 + " " + (info5 ? info5.mtimeMs + ":" + info5.size : "-");
28014
+ }));
28015
+ return createHash2("sha256").update(head.trim() + "\n" + seen.sort().join("\n")).digest("hex").slice(0, 20);
28016
+ };
28017
+ }
28018
+
28019
+ // src/server/checkout/checkout-reader.ts
28020
+ async function settled(reads) {
28021
+ const results = await Promise.allSettled(reads);
28022
+ const failure2 = results.find((r) => r.status === "rejected");
27387
28023
  if (failure2) throw failure2.reason;
27388
28024
  return results.map((r) => r.value);
27389
28025
  }
27390
- var folderOf3 = (path3) => path3.split("/").slice(0, 3).join("/");
27391
- function checkoutDocuments(documents) {
28026
+ var folderOf4 = (path3) => path3.split("/").slice(0, 3).join("/");
28027
+ function checkoutDocuments(states, authors) {
28028
+ const documents = states.documents;
28029
+ const standing2 = (id) => {
28030
+ const previousPath = states.previousPath(id);
28031
+ return { state: states.state(id), ...previousPath ? { previousPath } : {} };
28032
+ };
27392
28033
  const byId = new Map(documents.map((d) => [d.id, d]));
27393
28034
  const source2 = (s) => {
27394
28035
  if (!("id" in s)) return { title: s.title, url: s.url, ...s.note ? { note: s.note } : {} };
@@ -27396,28 +28037,45 @@ function checkoutDocuments(documents) {
27396
28037
  return { id: s.id, ...target ? { title: target.title, path: target.path } : {}, ...s.note ? { note: s.note } : {} };
27397
28038
  };
27398
28039
  const arranged = arrangeDocuments(documents);
27399
- const features = arranged.features.map(({ index, requirements, designs }) => ({
27400
- id: index.id,
27401
- path: index.path,
27402
- title: index.title,
27403
- description: index.description,
27404
- body: index.body,
27405
- requirements: requirements.map((r) => ({ id: r.id, path: r.path, title: r.title, description: r.description, order: r.order, body: r.body })),
27406
- designs: designs.map((d) => ({
27407
- id: d.id,
27408
- path: d.path,
27409
- title: d.title,
27410
- description: d.description,
27411
- order: d.order,
27412
- body: d.body,
27413
- requirements: d.requirements,
27414
- sources: d.sources.map(source2)
27415
- }))
28040
+ const features = arranged.features.map(({ index, requirements, designs }) => {
28041
+ const entry2 = authors.get(folderOf4(index.path));
28042
+ return {
28043
+ id: index.id,
28044
+ path: index.path,
28045
+ title: index.title,
28046
+ description: index.description,
28047
+ body: index.body,
28048
+ ...standing2(index.id),
28049
+ requirements: requirements.map((r) => ({ id: r.id, path: r.path, title: r.title, description: r.description, order: r.order, body: r.body, ...standing2(r.id) })),
28050
+ designs: designs.map((d) => ({
28051
+ id: d.id,
28052
+ path: d.path,
28053
+ title: d.title,
28054
+ description: d.description,
28055
+ order: d.order,
28056
+ body: d.body,
28057
+ requirements: d.requirements,
28058
+ sources: d.sources.map(source2),
28059
+ ...standing2(d.id)
28060
+ })),
28061
+ // Who committed to the feature folder, most first, and when it was last touched; nobody until first committed.
28062
+ contributors: entry2?.people ?? [],
28063
+ updatedAt: entry2?.latest ?? null
28064
+ };
28065
+ });
28066
+ const instructions2 = arranged.instructions.map((k) => ({
28067
+ id: k.id,
28068
+ name: k.name,
28069
+ path: k.path,
28070
+ title: k.title,
28071
+ description: k.description,
28072
+ body: k.body,
28073
+ ...standing2(k.id),
28074
+ updatedAt: authors.get(folderOf4(k.path))?.latest ?? null
27416
28075
  }));
27417
- const instructions2 = arranged.instructions.map((k) => ({ id: k.id, name: k.name, path: k.path, title: k.title, description: k.description, body: k.body }));
27418
28076
  return { features, instructions: instructions2, orphans: arranged.orphans };
27419
28077
  }
27420
- function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
28078
+ function createCheckoutReader(root, cache, inherited = process.env) {
27421
28079
  const reader = storeReader(root);
27422
28080
  const runner = createGitRunner();
27423
28081
  const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_REPLACE_OBJECTS: "1", GIT_TERMINAL_PROMPT: "0", LC_ALL: "C" };
@@ -27429,84 +28087,37 @@ function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
27429
28087
  return head || null;
27430
28088
  };
27431
28089
  const pending = /* @__PURE__ */ new Map();
27432
- const notMigration = ["-E", "--invert-grep", `--grep=^${MIGRATION_TRAILER}: `];
27433
- async function storeAuthors(head) {
27434
- const text4 = await git(["log", ...notMigration, "--format=%x1e%aN%x00%aE%x00%aI", "-z", "--name-only", "--no-renames", "--max-count=20000", head, "--", ".gitifact/spec", INSTRUCTIONS_ROOT, "AGENTS.md"]);
27435
- const folders = /* @__PURE__ */ new Map();
27436
- const instructions2 = /* @__PURE__ */ new Map();
27437
- for (const chunk of text4.split("")) {
27438
- if (!chunk) continue;
27439
- const [name2, email3, date5, ...paths2] = chunk.split("\0");
27440
- if (!name2 || !email3 || !date5) throw new StoreError(t2("specReader.authorUnreadable"));
27441
- const seen = /* @__PURE__ */ new Set();
27442
- for (const raw of paths2) {
27443
- const path3 = raw.replace(/^\n/, "");
27444
- if (!path3) continue;
27445
- const folder = /^(\.gitifact\/spec\/[^/]+)\//.exec(path3)?.[1];
27446
- if (folder && !seen.has(folder)) {
27447
- seen.add(folder);
27448
- let entry2 = folders.get(folder);
27449
- if (!entry2) folders.set(folder, entry2 = { people: /* @__PURE__ */ new Map(), count: 0, latest: date5 });
27450
- if (entry2.count < 2e3) {
27451
- entry2.count++;
27452
- tally(entry2.people, name2, email3, date5);
27453
- }
27454
- }
27455
- const instruction2 = path3 === "AGENTS.md" ? path3 : /^(\.gitifact\/instructions\/[^/]+)\//.exec(path3)?.[1];
27456
- if (instruction2 && !instructions2.has(instruction2)) instructions2.set(instruction2, date5);
27457
- }
27458
- }
27459
- return { folders, instructions: instructions2 };
27460
- }
28090
+ const readStamp = createStampReader(root, inherited);
27461
28091
  async function read() {
27462
28092
  const raw = await readConfigFile(root);
27463
28093
  if (!raw) throw new StoreError(t2("specReader.schemaRequired"));
27464
28094
  parseManagedConfig(raw);
27465
28095
  const head = await readHead();
27466
- const [current2, dirty, authors, everyone] = await settled([
28096
+ const stamp = await readStamp();
28097
+ const [current2, dirty, authors, people] = await settled([
27467
28098
  cache.documents.list(),
27468
28099
  head ? git(["status", "--porcelain=v1", "--", ".gitifact/spec", INSTRUCTIONS_ROOT]) : Promise.resolve(""),
27469
- head ? storeAuthors(head) : Promise.resolve(void 0),
27470
- // Git mailmap may change without a new HEAD; refresh names with every observation that carries them.
27471
- head ? git(["log", ...notMigration, "--format=%aN%x00%aE%x00%aI", "--max-count=10001", head]) : Promise.resolve("")
28100
+ // Who touched each folder and who committed at all, counted over the commit log in the cache.
28101
+ head ? cache.log.folders(head) : Promise.resolve(/* @__PURE__ */ new Map()),
28102
+ head ? cache.log.contributors(head) : Promise.resolve([])
27472
28103
  ]);
27473
- const people = /* @__PURE__ */ new Map();
27474
- const lines = everyone.trim().split("\n").filter(Boolean);
27475
- for (const line2 of lines.slice(0, 1e4)) {
27476
- const [name2, email3, latest] = line2.split("\0");
27477
- if (!name2 || !email3 || !latest) throw new StoreError(t2("specReader.authorUnreadable"));
27478
- tally(people, name2, email3, latest);
27479
- }
27480
- const arranged = checkoutDocuments(current2.documents);
27481
- const features = arranged.features.map((feature2) => {
27482
- const entry2 = authors?.folders.get(folderOf3(feature2.path));
27483
- return { ...feature2, contributors: entry2 ? [...entry2.people.values()].sort((a, b) => b.commits - a.commits) : [], updatedAt: entry2?.latest ?? null };
27484
- });
27485
- const instructions2 = await Promise.all(arranged.instructions.map(async (instruction2) => {
27486
- const { files, limited } = await listInstructionFiles(root, instruction2.path);
27487
- return { ...instruction2, files, filesLimited: limited, updatedAt: authors?.instructions.get(instruction2.path.split("/").slice(0, 3).join("/")) ?? null };
27488
- }));
27489
- const agentsText = await readAgentsFile(root);
27490
- const agents2 = agentsText === null ? null : { path: "AGENTS.md", body: agentsText, updatedAt: authors?.instructions.get("AGENTS.md") ?? null };
27491
- const problems = [...current2.problems, ...arranged.orphans.map((feature2) => docProblem("FEATURE_INDEX_REQUIRED", `.gitifact/spec/${feature2}/index.md`, { feature: feature2 }))];
28104
+ const states = await documentStates({ head: async () => head, reader }, current2.documents);
28105
+ const { features, instructions: instructions2, orphans } = checkoutDocuments(states, authors);
28106
+ const problems = [...current2.problems, ...orphans.map((feature2) => docProblem("FEATURE_INDEX_REQUIRED", `.gitifact/spec/${feature2}/index.md`, { feature: feature2 }))];
27492
28107
  if (await readHead() !== head) throw new StoreError(t2("specReader.projectChanged"));
27493
- const checkout = browserSpecsV6.parse({
27494
- contract: "browser-specs",
27495
- version: 6,
27496
- sessionId,
28108
+ return {
27497
28109
  head,
28110
+ stamp,
27498
28111
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
27499
28112
  working: head ? !!dirty.trim() : features.length > 0 || instructions2.length > 0,
27500
28113
  features,
27501
28114
  instructions: instructions2,
27502
- agents: agents2,
27503
28115
  problems,
27504
- contributors: [...people.values()].sort((a, b) => b.commits - a.commits),
27505
- contributorsLimited: lines.length > 1e4
27506
- });
27507
- return { checkout, head };
28116
+ people,
28117
+ folders: authors
28118
+ };
27508
28119
  }
27509
- return () => {
28120
+ const base = () => {
27510
28121
  const language = getLanguage();
27511
28122
  let value = pending.get(language);
27512
28123
  if (!value) {
@@ -27515,10 +28126,37 @@ function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
27515
28126
  }
27516
28127
  return value;
27517
28128
  };
28129
+ return {
28130
+ base,
28131
+ /** Every instruction by name with the other files of its folder, and AGENTS.md: few enough to read whole. */
28132
+ async instructions() {
28133
+ const checkout = await base();
28134
+ const instructions2 = await Promise.all([...checkout.instructions].sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0).map(async (instruction2) => {
28135
+ const { files, limited } = instruction2.state === "deleted" ? { files: [], limited: false } : await readInstructionFiles(root, instruction2.path);
28136
+ return { ...instruction2, files, filesLimited: limited };
28137
+ }));
28138
+ const agentsText = await readAgentsFile(root);
28139
+ return { instructions: instructions2, agents: agentsText === null ? null : { path: "AGENTS.md", body: agentsText, updatedAt: checkout.folders.get("AGENTS.md")?.latest ?? null } };
28140
+ }
28141
+ };
28142
+ }
28143
+
28144
+ // src/queries/paging.ts
28145
+ var PAGE_SIZE = 20;
28146
+ function pageOf(rows, keyOf, request) {
28147
+ let start = 0;
28148
+ if (request.after !== void 0) {
28149
+ const at = rows.findIndex((row) => keyOf(row) === request.after);
28150
+ if (at < 0) return void 0;
28151
+ start = at + 1;
28152
+ }
28153
+ const size = request.all ? rows.length : request.limit ?? PAGE_SIZE;
28154
+ const shown = rows.slice(start, start + size);
28155
+ const more = start + shown.length < rows.length && shown.length > 0;
28156
+ return { rows: shown, total: rows.length, next: more ? keyOf(shown[shown.length - 1]) : null };
27518
28157
  }
27519
28158
 
27520
28159
  // src/server/commit/commit-files.ts
27521
- var LIST_LIMIT = 500;
27522
28160
  var TEXT_LIMIT = 512 * 1024;
27523
28161
  var KEPT = 200;
27524
28162
  var statusOf = (code) => code.startsWith("R") ? "renamed" : code === "A" ? "added" : code === "D" ? "deleted" : "modified";
@@ -27574,10 +28212,15 @@ function createCommitFiles(git) {
27574
28212
  return git.run(["cat-file", "blob", `${rev}:${path3}`]);
27575
28213
  }
27576
28214
  return {
27577
- /** The first `LIST_LIMIT` source files and how many there are; undefined for a commit the repository lacks. */
27578
- async files(commit) {
28215
+ /**
28216
+ * A page of the source files after the file `after` (a path) and how many there are in all: undefined for a commit
28217
+ * the repository lacks, null for a cursor the commit does not have.
28218
+ */
28219
+ async files(commit, request) {
27579
28220
  const all = await list(commit);
27580
- return all && { ...all, files: all.files.slice(0, LIST_LIMIT) };
28221
+ if (!all) return void 0;
28222
+ const page = pageOf(all.files, (f) => f.path, request);
28223
+ return page ? { ...all, files: page.rows, next: page.next } : null;
27581
28224
  },
27582
28225
  /** One listed file on both sides; undefined when the commit did not change that path. */
27583
28226
  async file(commit, path3) {
@@ -27606,42 +28249,297 @@ function isUtf8(bytes) {
27606
28249
  }
27607
28250
  }
27608
28251
 
28252
+ // src/queries/working-changes.ts
28253
+ async function workingChanges(source2, known) {
28254
+ const head = known !== void 0 ? known : await source2.head();
28255
+ const headFiles = head ? await source2.cache.history.filesAt(head) : /* @__PURE__ */ new Map();
28256
+ const [working2, records2] = await Promise.all([source2.cache.documents.files(), source2.pendingRecords()]);
28257
+ return { head, headFiles, working: working2, records: records2, ...compareDocumentSets(headFiles, working2.files) };
28258
+ }
28259
+ var uncovered = (changes2, records2) => {
28260
+ const covered = new Set(records2.flatMap((r) => r.docs));
28261
+ return changes2.filter((c) => c.types.some((type) => type !== "created") && !covered.has(c.id)).map((c) => c.id);
28262
+ };
28263
+ async function workingOverview(source2) {
28264
+ const found = await workingChanges(source2);
28265
+ const records2 = checkDocuments(new Map([...found.working.files, ...found.records.files])).records;
28266
+ return {
28267
+ head: found.head,
28268
+ records: records2.map((r) => ({ id: r.id, title: r.title, docs: r.docs, sections: r.sections.map((s) => ({ key: s.key, body: s.body })), draft: !!r.draft })),
28269
+ changes: found.changes.map((c) => ({ id: c.id, kind: c.kind, title: c.title, path: c.path, ...c.previousPath ? { previousPath: c.previousPath } : {}, types: c.types })),
28270
+ withoutRecord: uncovered(found.changes, records2)
28271
+ };
28272
+ }
28273
+ function snapshot4(doc, docs) {
28274
+ const folder = doc.path.split("/").slice(0, 3).join("/");
28275
+ const specId = doc.kind === "wiki" || doc.kind === "instruction" ? doc.kind : docs.find((d) => d.kind === "feature" && d.path === folder + "/index.md")?.id ?? "";
28276
+ const base = { id: doc.id, kind: doc.kind, title: doc.title, description: doc.description, body: doc.body, specId, path: doc.path };
28277
+ if (doc.kind === "requirement") return { ...base, order: doc.order };
28278
+ if (doc.kind === "design") return { ...base, order: doc.order, requirements: doc.requirements, sources: doc.sources };
28279
+ return base;
28280
+ }
28281
+ var parsed = (files) => [...files].flatMap(([path3, text4]) => {
28282
+ try {
28283
+ return classifyDocPath(path3).type === "doc" ? [parseDocumentFile(path3, text4)] : [];
28284
+ } catch {
28285
+ return [];
28286
+ }
28287
+ });
28288
+ async function workingChange2(source2, id) {
28289
+ const found = await workingChanges(source2);
28290
+ const change2 = found.changes.find((c) => c.id === id);
28291
+ if (!change2) return void 0;
28292
+ const before = parsed(found.headFiles);
28293
+ const after = parsed(found.working.files);
28294
+ const side = (docs) => {
28295
+ const doc = docs.find((d) => d.id === id);
28296
+ return doc ? snapshot4(doc, docs) : null;
28297
+ };
28298
+ return {
28299
+ change: { id: change2.id, kind: change2.kind, title: change2.title, path: change2.path, ...change2.previousPath ? { previousPath: change2.previousPath } : {}, types: change2.types },
28300
+ before: side(before),
28301
+ after: side(after)
28302
+ };
28303
+ }
28304
+
28305
+ // src/queries/search.ts
28306
+ var SEARCH_GROUPS = ["feature", "requirement", "design", "instruction", "record", "commit"];
28307
+ var FIRST = 5;
28308
+ var RECENT = 6;
28309
+ var place2 = (path3) => path3.replace(/^\.gitifact\//, "");
28310
+ var folderOf5 = (path3) => path3.split("/").slice(0, 3).join("/");
28311
+ async function searchRecords(source2, request) {
28312
+ const head = request.head ?? await source2.head();
28313
+ const query = request.q.trim().toLowerCase();
28314
+ if (!query) return [await recent(source2, head)];
28315
+ const size = request.group ? request.limit ?? 20 : FIRST;
28316
+ const one = async (group) => {
28317
+ const found = group === "record" ? head ? await source2.cache.history.searchRecords(head, query, request.after, size) : empty() : group === "commit" ? head ? await source2.cache.log.commitsNamed(head, query, request.after, size) : empty() : await source2.cache.documents.searchKind(group, query, request.after, size);
28318
+ return found && { group, ...found };
28319
+ };
28320
+ const groups = await Promise.all((request.group ? [request.group] : SEARCH_GROUPS).map(one));
28321
+ if (groups.some((g) => !g)) return void 0;
28322
+ return groups.filter((g) => request.group || g.total > 0);
28323
+ }
28324
+ var empty = () => ({ total: 0, next: null, hits: [] });
28325
+ async function recent(source2, head) {
28326
+ const [{ documents }, folders] = await Promise.all([source2.cache.documents.list(), head ? source2.cache.log.folders(head) : /* @__PURE__ */ new Map()]);
28327
+ const hits = documents.filter((d) => d.kind === "feature" || d.kind === "instruction").map((d) => ({ doc: d, latest: folders.get(folderOf5(d.path))?.latest })).filter((d) => !!d.latest).sort((a, b) => Date.parse(b.latest) - Date.parse(a.latest) || a.doc.id.localeCompare(b.doc.id)).slice(0, RECENT).map(({ doc }) => ({
28328
+ id: doc.id,
28329
+ kind: doc.kind,
28330
+ title: doc.title,
28331
+ where: place2(doc.path),
28332
+ line: doc.description,
28333
+ ...doc.kind === "feature" ? { featureId: doc.id } : {}
28334
+ }));
28335
+ return { group: "recent", total: hits.length, next: null, hits };
28336
+ }
28337
+
28338
+ // src/queries/checkout.ts
28339
+ var SHOWN_REQUIREMENTS = 12;
28340
+ function checkoutIndex(base) {
28341
+ return {
28342
+ features: base.features.map((f) => ({
28343
+ id: f.id,
28344
+ path: f.path,
28345
+ title: f.title,
28346
+ state: f.state,
28347
+ requirements: f.requirements.map((r) => ({ id: r.id, path: r.path, title: r.title, description: r.description })),
28348
+ designs: f.designs.map((d) => ({ id: d.id, path: d.path, title: d.title }))
28349
+ })),
28350
+ instructions: base.instructions.map((k) => ({ id: k.id, name: k.name, path: k.path, title: k.title })),
28351
+ people: base.people.map((p) => ({ name: p.name, email: p.email }))
28352
+ };
28353
+ }
28354
+ var acceptanceHeading = /^###\s+(?:수용 조건|acceptance criteria)\s*$/i;
28355
+ function acceptanceCount(body2) {
28356
+ const lines = body2.replace(/\r\n/g, "\n").split("\n");
28357
+ const start = lines.findIndex((line2) => acceptanceHeading.test(line2.trim()));
28358
+ if (start < 0) return null;
28359
+ let count2 = 0;
28360
+ for (const line2 of lines.slice(start + 1)) {
28361
+ if (/^#{1,6}\s/.test(line2)) break;
28362
+ if (/^\d+\.\s/.test(line2)) count2++;
28363
+ }
28364
+ return count2 || null;
28365
+ }
28366
+ function listFeatures(base, filter, request) {
28367
+ const query = filter.q?.trim().toLowerCase();
28368
+ const named = (f) => !query || (f.title + " " + f.id).toLowerCase().includes(query);
28369
+ const key = filter.sort ?? "updated";
28370
+ const opens = { title: "asc", requirements: "desc", updated: "desc" };
28371
+ const way = (filter.dir ?? opens[key]) === "asc" ? 1 : -1;
28372
+ const groups = base.features.filter((f) => !filter.design || filter.design === "yes" === f.designs.length > 0).filter((f) => !filter.author || f.contributors.some((p) => p.email === filter.author)).map((f) => ({ feature: f, requirements: named(f) ? f.requirements : f.requirements.filter((r) => (r.title + " " + r.id).toLowerCase().includes(query)) })).filter((g) => named(g.feature) || g.requirements.length).sort((a, b) => way * (key === "requirements" ? a.feature.requirements.length - b.feature.requirements.length : key === "title" ? a.feature.title.localeCompare(b.feature.title) : (a.feature.updatedAt ?? "").localeCompare(b.feature.updatedAt ?? "")) || a.feature.title.localeCompare(b.feature.title));
28373
+ const page = pageOf(groups, (g) => g.feature.id, request);
28374
+ if (!page) return void 0;
28375
+ const features = page.rows.map(({ feature: f, requirements }) => {
28376
+ const designed = new Set(f.designs.flatMap((d) => d.requirements));
28377
+ return {
28378
+ id: f.id,
28379
+ path: f.path,
28380
+ title: f.title,
28381
+ description: f.description,
28382
+ state: f.state,
28383
+ designs: f.designs.length,
28384
+ contributors: f.contributors,
28385
+ updatedAt: f.updatedAt,
28386
+ requirementCount: f.requirements.length,
28387
+ requirements: requirements.slice(0, SHOWN_REQUIREMENTS).map((r) => ({
28388
+ id: r.id,
28389
+ title: r.title,
28390
+ description: r.description,
28391
+ state: r.state,
28392
+ acceptance: acceptanceCount(r.body),
28393
+ designed: designed.has(r.id)
28394
+ })),
28395
+ hidden: Math.max(0, requirements.length - SHOWN_REQUIREMENTS)
28396
+ };
28397
+ });
28398
+ return {
28399
+ total: page.total,
28400
+ all: base.features.length,
28401
+ requirements: groups.reduce((sum, g) => sum + g.requirements.length, 0),
28402
+ mostRequirements: Math.max(0, ...base.features.map((f) => f.requirements.length)),
28403
+ next: page.next,
28404
+ features
28405
+ };
28406
+ }
28407
+ var featureOf = (base, id) => base.features.find((f) => f.id === id);
28408
+ function listContributors(base, q, request) {
28409
+ const query = q?.trim().toLowerCase();
28410
+ const people = base.people.filter((p) => !query || (p.name + " " + p.email).toLowerCase().includes(query));
28411
+ const page = pageOf(people, (p) => p.email, request);
28412
+ if (!page) return void 0;
28413
+ const touched = (email3) => base.features.filter((f) => f.contributors.some((c) => c.email === email3)).length;
28414
+ return { total: page.total, next: page.next, people: page.rows.map((p) => ({ ...p, features: touched(p.email) })) };
28415
+ }
28416
+ function contributorOf(base, email3) {
28417
+ const person = base.people.find((p) => p.email === email3);
28418
+ if (!person) return void 0;
28419
+ const features = base.features.flatMap((f) => {
28420
+ const share = f.contributors.find((c) => c.email === email3);
28421
+ return share ? [{ id: f.id, title: f.title, commits: share.commits, requirements: f.requirements.length }] : [];
28422
+ });
28423
+ return { person, features };
28424
+ }
28425
+ function topContributors(people) {
28426
+ const rest = people.slice(3);
28427
+ return { total: people.length, top: people.slice(0, 3), rest: { count: rest.length, commits: rest.reduce((sum, p) => sum + p.commits, 0) } };
28428
+ }
28429
+
27609
28430
  // src/server/routes/record-routes.ts
27610
- var PAGE = 50;
28431
+ var PAGE = 20;
27611
28432
  function recordRoutes(root, sessionId, env) {
27612
28433
  const git = storeReader(root);
27613
28434
  const cache = openCache(root, { run: (args, input2) => git.run(args, input2), decode: git.decode, legacyBundles: (oids) => git.readBundles(oids) });
27614
- const readCheckout = createCheckoutReader(root, sessionId, cache, env);
28435
+ const checkout = createCheckoutReader(root, cache, env);
28436
+ const readStamp = createStampReader(root, env);
27615
28437
  const commitFiles = createCommitFiles(git);
28438
+ const source2 = { head: async () => (await git.baseline()).head, reader: git, cache };
28439
+ const working2 = { head: async () => (await git.baseline()).head, cache, pendingRecords: () => readPendingRecords(root, (args) => git.run(args)) };
27616
28440
  const unreadable2 = () => t2("server.specsUnreadable");
28441
+ const cursorGone2 = () => new HttpError(404, "NOT_FOUND", t2("server.cursorNotFound"));
27617
28442
  return [
27618
- route({ method: "GET", path: "/api/v1/specs", session: true, unreadable: unreadable2, handle: async () => ok((await readCheckout()).checkout) }),
27619
- route({ method: "GET", path: "/api/v1/history", session: true, query: browserHistoryQueryV3, unreadable: unreadable2, handle: async ({ query }) => {
27620
- const offset = query.offset ?? 0;
27621
- const page = await cache.history.page(query.head, { kind: query.kind, document: query.document, feature: query.feature, author: query.author, q: query.q }, offset, query.limit ?? PAGE);
27622
- return ok(browserHistoryV5.parse({ contract: "browser-history", version: 5, sessionId, head: query.head, offset, ...page }));
28443
+ // The checkout a part at a time, each shaped by the query layer (`queries/checkout.ts`): the frame every screen reads
28444
+ // first, the feature list and one feature, the instructions, and the contributors.
28445
+ route({ method: "GET", path: "/api/v1/checkout", session: true, unreadable: unreadable2, handle: async () => {
28446
+ const base = await checkout.base();
28447
+ return ok(browserCheckoutV1.parse({
28448
+ contract: "browser-checkout",
28449
+ version: 1,
28450
+ sessionId,
28451
+ head: base.head,
28452
+ observedAt: base.observedAt,
28453
+ stamp: base.stamp,
28454
+ working: base.working,
28455
+ problems: base.problems,
28456
+ index: checkoutIndex(base)
28457
+ }));
27623
28458
  } }),
27624
- route({ method: "GET", path: "/api/v1/history/summary", session: true, query: browserHistorySummaryQueryV1, unreadable: unreadable2, handle: async ({ query }) => ok(browserHistorySummaryV4.parse({ contract: "browser-history-summary", version: 4, sessionId, head: query.head, ...await cache.history.summary(query.head) })) }),
27625
- // One commit as its page reads it: its documents with both sides, and who made it even when it changed none.
27626
- route({ method: "GET", path: "/api/v1/commit", session: true, query: browserCommitQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
27627
- const changes2 = await cache.history.ofCommit(query.commit);
27628
- const first = changes2[0]?.event;
28459
+ route({ method: "GET", path: "/api/v1/features", session: true, query: browserFeaturesQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28460
+ const page = listFeatures(await checkout.base(), query, { after: query.after, limit: query.limit ?? PAGE });
28461
+ if (!page) throw cursorGone2();
28462
+ return ok(browserFeaturesV1.parse({ contract: "browser-features", version: 1, sessionId, ...page }));
28463
+ } }),
28464
+ route({ method: "GET", path: "/api/v1/feature", session: true, query: browserFeatureQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28465
+ const feature2 = featureOf(await checkout.base(), query.id);
28466
+ if (!feature2) throw new HttpError(404, "NOT_FOUND", t2("server.featureNotFound"));
28467
+ return ok(browserFeatureV1.parse({ contract: "browser-feature", version: 1, sessionId, feature: feature2 }));
28468
+ } }),
28469
+ route({ method: "GET", path: "/api/v1/instructions", session: true, unreadable: unreadable2, handle: async () => ok(browserInstructionsV1.parse({ contract: "browser-instructions", version: 1, sessionId, ...await checkout.instructions() })) }),
28470
+ route({ method: "GET", path: "/api/v1/contributors", session: true, query: browserContributorsQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28471
+ const page = listContributors(await checkout.base(), query.q, { after: query.after, limit: query.limit ?? PAGE });
28472
+ if (!page) throw cursorGone2();
28473
+ return ok(browserContributorsV1.parse({ contract: "browser-contributors", version: 1, sessionId, ...page }));
28474
+ } }),
28475
+ route({ method: "GET", path: "/api/v1/contributor", session: true, query: browserContributorQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28476
+ const found = contributorOf(await checkout.base(), query.email);
28477
+ if (!found) throw new HttpError(404, "NOT_FOUND", t2("server.contributorNotFound"));
28478
+ return ok(browserContributorV1.parse({ contract: "browser-contributor", version: 1, sessionId, ...found }));
28479
+ } }),
28480
+ // Whether the screen is behind, asked when the reader comes back to the tab; cheap enough to ask every time.
28481
+ route({ method: "GET", path: "/api/v1/stamp", session: true, unreadable: unreadable2, handle: async () => ok(browserStampV1.parse({ contract: "browser-stamp", version: 1, sessionId, stamp: await readStamp() })) }),
28482
+ route({ method: "GET", path: "/api/v1/history", session: true, query: browserHistoryQueryV4, unreadable: unreadable2, handle: async ({ query }) => {
28483
+ const page = await cache.history.commits(query.head, { kind: query.kind, document: query.document, feature: query.feature, author: query.author, q: query.q }, query.after, query.limit ?? PAGE);
28484
+ if (!page) throw cursorGone2();
28485
+ return ok(browserHistoryV6.parse({ contract: "browser-history", version: 6, sessionId, head: query.head, ...page }));
28486
+ } }),
28487
+ route({ method: "GET", path: "/api/v1/history/summary", session: true, query: browserHistorySummaryQueryV1, unreadable: unreadable2, handle: async ({ query }) => ok(browserHistorySummaryV5.parse({
28488
+ contract: "browser-history-summary",
28489
+ version: 5,
28490
+ sessionId,
28491
+ head: query.head,
28492
+ ...await cache.history.summary(query.head),
28493
+ people: topContributors(await cache.log.contributors(query.head))
28494
+ })) }),
28495
+ // One commit as its page reads it: who made it and a page of the documents it changed, even when it changed none.
28496
+ route({ method: "GET", path: "/api/v1/commit", session: true, query: browserCommitQueryV3, unreadable: unreadable2, handle: async ({ query }) => {
28497
+ const page = await cache.history.commitChanges(query.commit, query.after, query.limit ?? PAGE, query.record);
28498
+ if (!page) throw cursorGone2();
28499
+ const first = page.events[0];
27629
28500
  const line2 = first ? [] : git.decode(await git.run(["log", "-1", "--format=%aN%x00%aE%x00%cN%x00%aI%x00%s", query.commit, "--"]).catch(() => Buffer.from(""))).split("\0");
27630
28501
  if (!first && line2.length < 5) throw new HttpError(404, "NOT_FOUND", t2("server.commitNotFound"));
27631
28502
  const [author, email3, committer, date5, message] = first ? [first.author, first.email, first.committer, first.date, first.message] : [line2[0] ?? "", line2[1] ?? "", line2[2] ?? "", line2[3] ?? "", (line2[4] ?? "").replace(/\n$/, "")];
27632
- return ok(browserCommitV3.parse({ contract: "browser-commit", version: 3, sessionId, commit: query.commit, author, email: email3, committer, date: date5, message, changes: changes2 }));
28503
+ return ok(browserCommitV4.parse({
28504
+ contract: "browser-commit",
28505
+ version: 4,
28506
+ sessionId,
28507
+ commit: query.commit,
28508
+ author,
28509
+ email: email3,
28510
+ committer,
28511
+ date: date5,
28512
+ message,
28513
+ total: page.total,
28514
+ next: page.next,
28515
+ changes: page.events
28516
+ }));
28517
+ } }),
28518
+ // The text on both sides of one change, read from Git when the reader opens that document.
28519
+ route({ method: "GET", path: "/api/v1/commit/change", session: true, query: browserCommitChangeQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28520
+ const change2 = await cache.history.commitChange(query.commit, query.id);
28521
+ if (!change2) throw new HttpError(404, "NOT_FOUND", t2("server.commitNotFound"));
28522
+ return ok(browserCommitChangeV1.parse({ contract: "browser-commit-change", version: 1, sessionId, commit: query.commit, event: change2.event, before: change2.before, after: change2.after }));
27633
28523
  } }),
27634
28524
  // A record's page names the record; which commit added it is read from the history of the HEAD the reader is on.
27635
28525
  route({ method: "GET", path: "/api/v1/record", session: true, query: browserRecordQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
27636
- const commit = await cache.history.commitOfRecord(query.head, query.id);
27637
- if (!commit) throw new HttpError(404, "NOT_FOUND", t2("server.recordNotFound"));
27638
- return ok(browserRecordV1.parse({ contract: "browser-record", version: 1, sessionId, head: query.head, id: query.id, commit }));
28526
+ const found = await cache.history.recordOf(query.head, query.id);
28527
+ if (!found) throw new HttpError(404, "NOT_FOUND", t2("server.recordNotFound"));
28528
+ return ok(browserRecordV2.parse({ contract: "browser-record", version: 2, sessionId, head: query.head, id: query.id, ...found }));
28529
+ } }),
28530
+ // What is not committed yet, worked out again on every request: the uncommitted entry of the records list.
28531
+ route({ method: "GET", path: "/api/v1/working", session: true, unreadable: unreadable2, handle: async () => ok(browserWorkingV1.parse({ contract: "browser-working", version: 1, sessionId, ...await workingOverview(working2) })) }),
28532
+ route({ method: "GET", path: "/api/v1/working/change", session: true, query: browserWorkingChangeQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28533
+ const change2 = await workingChange2(working2, query.id);
28534
+ if (!change2) throw new HttpError(404, "NOT_FOUND", t2("server.workingChangeNotFound"));
28535
+ return ok(browserWorkingChangeV1.parse({ contract: "browser-working-change", version: 1, sessionId, ...change2 }));
27639
28536
  } }),
27640
28537
  // The source a commit changed beside its documents, read from Git when the commit or record page asks for it.
27641
- route({ method: "GET", path: "/api/v1/commit/files", session: true, query: browserCommitFilesQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
27642
- const found = await commitFiles.files(query.commit);
27643
- if (!found) throw new HttpError(404, "NOT_FOUND", t2("server.commitNotFound"));
27644
- return ok(browserCommitFilesV1.parse({ contract: "browser-commit-files", version: 1, sessionId, commit: found.commit, total: found.total, files: found.files }));
28538
+ route({ method: "GET", path: "/api/v1/commit/files", session: true, query: browserCommitFilesQueryV2, unreadable: unreadable2, handle: async ({ query }) => {
28539
+ const found = await commitFiles.files(query.commit, { after: query.after, limit: query.limit ?? PAGE });
28540
+ if (found === void 0) throw new HttpError(404, "NOT_FOUND", t2("server.commitNotFound"));
28541
+ if (found === null) throw cursorGone2();
28542
+ return ok(browserCommitFilesV2.parse({ contract: "browser-commit-files", version: 2, sessionId, commit: found.commit, total: found.total, next: found.next, files: found.files }));
27645
28543
  } }),
27646
28544
  route({ method: "GET", path: "/api/v1/commit/file", session: true, query: browserCommitFileQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
27647
28545
  const found = await commitFiles.file(query.commit, query.path);
@@ -27655,23 +28553,28 @@ function recordRoutes(root, sessionId, env) {
27655
28553
  if (!found) throw new HttpError(404, "NOT_FOUND", t2("server.instructionFileNotFound"));
27656
28554
  return ok(browserInstructionFileV1.parse({ contract: "browser-instruction-file", version: 1, sessionId, id: query.id, path: query.path, ...found }));
27657
28555
  } }),
27658
- route({ method: "GET", path: "/api/v1/search", session: true, query: browserSearchQueryV1, unreadable: unreadable2, handle: async ({ query }) => ok(browserSearchV2.parse({ contract: "browser-search", version: 2, sessionId, query: query.q, hits: await cache.search(query.head ?? null, query.q) })) })
28556
+ // The search box's groups as the query layer works them out (`queries/search.ts`); the browser only draws them.
28557
+ route({ method: "GET", path: "/api/v1/search", session: true, query: browserSearchQueryV2, unreadable: unreadable2, handle: async ({ query }) => {
28558
+ const groups = await searchRecords(source2, query);
28559
+ if (!groups) throw cursorGone2();
28560
+ return ok(browserSearchV3.parse({ contract: "browser-search", version: 3, sessionId, query: query.q, groups }));
28561
+ } })
27659
28562
  ];
27660
28563
  }
27661
28564
 
27662
28565
  // src/server/routes/asset-routes.ts
27663
- import { lstat as lstat6, readFile as readFile6 } from "node:fs/promises";
27664
- import { join as join7 } from "node:path";
28566
+ import { lstat as lstat9, readFile as readFile7 } from "node:fs/promises";
28567
+ import { join as join10 } from "node:path";
27665
28568
  function assetRoutes(root) {
27666
28569
  return [
27667
28570
  route({ method: "GET", path: "/api/v1/assets/*", session: false, handle: async ({ path: path3, response }) => {
27668
28571
  const relative = path3.slice("/api/v1/assets/".length);
27669
28572
  const assetPath = ASSETS_DIR + "/" + relative;
27670
28573
  if (!isAssetPath(assetPath)) throw new HttpError(404, "NOT_FOUND", t2("server.assetNotFound"));
27671
- const file2 = join7(root, ...assetPath.split("/"));
27672
- const stat3 = await lstat6(file2).catch(() => void 0);
28574
+ const file2 = join10(root, ...assetPath.split("/"));
28575
+ const stat3 = await lstat9(file2).catch(() => void 0);
27673
28576
  if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > 20 * 1024 * 1024) throw new HttpError(404, "NOT_FOUND", t2("server.assetNotFound"));
27674
- const bytes = await readFile6(file2);
28577
+ const bytes = await readFile7(file2);
27675
28578
  const extension = assetExtension(relative);
27676
28579
  const inline = ["png", "jpg", "jpeg", "gif", "webp", "svg"].includes(extension);
27677
28580
  response.writeHead(200, {
@@ -27688,7 +28591,7 @@ function assetRoutes(root) {
27688
28591
  }
27689
28592
 
27690
28593
  // src/server/browser-server.ts
27691
- var readBundledChangelog = (language) => readFile7(new URL("./i18n/" + language + "/changelog.md", import.meta.url), "utf8").catch((error62) => {
28594
+ var readBundledChangelog = (language) => readFile8(new URL("./i18n/" + language + "/changelog.md", import.meta.url), "utf8").catch((error62) => {
27692
28595
  if (error62.code === "ENOENT") return null;
27693
28596
  throw error62;
27694
28597
  });
@@ -27818,14 +28721,14 @@ async function runBrowser(options, version2) {
27818
28721
  }
27819
28722
 
27820
28723
  // src/commands/spec-init.ts
27821
- import { readdir as readdir4 } from "node:fs/promises";
27822
- import { join as join10 } from "node:path";
28724
+ import { readdir as readdir5 } from "node:fs/promises";
28725
+ import { join as join14 } from "node:path";
27823
28726
 
27824
28727
  // src/adapters/git/init-repository.ts
27825
- import { createHash as createHash2 } from "node:crypto";
27826
- import { lstat as lstat7, readFile as readFile8 } from "node:fs/promises";
27827
- import { join as join8 } from "node:path";
27828
- var markerPresent = (path3) => lstat7(path3).then(() => true, (error62) => {
28728
+ import { createHash as createHash3 } from "node:crypto";
28729
+ import { lstat as lstat10, readFile as readFile9 } from "node:fs/promises";
28730
+ import { join as join11 } from "node:path";
28731
+ var markerPresent = (path3) => lstat10(path3).then(() => true, (error62) => {
27829
28732
  if (error62.code === "ENOENT") return false;
27830
28733
  throw error62;
27831
28734
  });
@@ -27842,27 +28745,27 @@ function initRepository(cwd, inherited = process.env) {
27842
28745
  });
27843
28746
  return {
27844
28747
  async inspect(observedState, includeTrackedConfig = true) {
27845
- const state = observedState ?? await createRepositoryReader(cwd, { env }).read();
27846
- const root = state.repository.rootPath;
28748
+ const state2 = observedState ?? await createRepositoryReader(cwd, { env }).read();
28749
+ const root = state2.repository.rootPath;
27847
28750
  const locations = decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--absolute-git-dir", "--git-path", "index"], root)).split("\n");
27848
28751
  const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
27849
28752
  const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
27850
28753
  for (const marker2 of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"]) {
27851
- if (await markerPresent(join8(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
28754
+ if (await markerPresent(join11(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
27852
28755
  }
27853
- if (state.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
27854
- const index = await readFile8(indexPath).catch((error62) => {
28756
+ if (state2.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
28757
+ const index = await readFile9(indexPath).catch((error62) => {
27855
28758
  if (error62.code === "ENOENT") return Buffer.alloc(0);
27856
28759
  throw error62;
27857
28760
  });
27858
28761
  const [staged, committed2] = includeTrackedConfig ? await Promise.all([
27859
28762
  git(["ls-files", "--stage", "-z", "--", ".gitifact/config.json"], root),
27860
- state.head.commit ? git(["ls-tree", "-z", state.head.commit, "--", ".gitifact/config.json"], root) : Buffer.alloc(0)
28763
+ state2.head.commit ? git(["ls-tree", "-z", state2.head.commit, "--", ".gitifact/config.json"], root) : Buffer.alloc(0)
27861
28764
  ]) : [Buffer.alloc(0), Buffer.alloc(0)];
27862
28765
  return {
27863
- state,
28766
+ state: state2,
27864
28767
  trackedConfig: staged.length > 0 || committed2.length > 0,
27865
- stamp: JSON.stringify({ repository: state.repository, head: state.head, index: createHash2("sha256").update(index).digest("hex") })
28768
+ stamp: JSON.stringify({ repository: state2.repository, head: state2.head, index: createHash3("sha256").update(index).digest("hex") })
27866
28769
  };
27867
28770
  },
27868
28771
  async checkIgnore(root, path3 = ".gitifact/config.json") {
@@ -27884,19 +28787,52 @@ function initRepository(cwd, inherited = process.env) {
27884
28787
  };
27885
28788
  }
27886
28789
 
28790
+ // src/adapters/filesystem/document-file.ts
28791
+ import { randomBytes } from "node:crypto";
28792
+ import { lstat as lstat11, mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
28793
+ import { join as join12 } from "node:path";
28794
+ var generateId = (prefix) => prefix + "-" + [...randomBytes(10)].map((n) => "abcdefghijklmnopqrstuvwxyz234567"[n & 31]).join("");
28795
+ async function createDocumentFile(root, path3, text4) {
28796
+ const parts = path3.split("/");
28797
+ for (let i = 1; i < parts.length; i++) {
28798
+ const folder = join12(root, ...parts.slice(0, i));
28799
+ const info5 = await lstat11(folder).catch((error62) => {
28800
+ if (error62.code === "ENOENT") return void 0;
28801
+ throw error62;
28802
+ });
28803
+ if (!info5) {
28804
+ await mkdir3(folder);
28805
+ continue;
28806
+ }
28807
+ if (!info5.isDirectory() || info5.isSymbolicLink()) throw Object.assign(new Error(folder), { code: "ENOTDIR" });
28808
+ }
28809
+ await writeFile2(join12(root, ...parts), text4, { flag: "wx" });
28810
+ }
28811
+ var LINE_ENDINGS_PATH = ".gitifact/.gitattributes";
28812
+ var LINE_ENDINGS = "# Written by gitifact init: documents keep LF on every OS, so what Git holds and the files read the same.\n* text=auto eol=lf\n";
28813
+ async function writeLineEndings(root) {
28814
+ try {
28815
+ await createDocumentFile(root, LINE_ENDINGS_PATH, LINE_ENDINGS);
28816
+ return true;
28817
+ } catch (error62) {
28818
+ if (error62.code === "EEXIST") return false;
28819
+ throw error62;
28820
+ }
28821
+ }
28822
+
27887
28823
  // src/adapters/filesystem/managed-file.ts
27888
- import { open as open3, readFile as readFile9, mkdir as mkdir3, realpath as realpath2, rename as rename2, link as link2, unlink as unlink2 } from "node:fs/promises";
27889
- import { join as join9, dirname } from "node:path";
28824
+ import { open as open4, readFile as readFile10, mkdir as mkdir4, realpath as realpath2, rename as rename2, link as link2, unlink as unlink2 } from "node:fs/promises";
28825
+ import { join as join13, dirname } from "node:path";
27890
28826
  import { randomUUID as randomUUID4 } from "node:crypto";
27891
28827
  var DEFAULT_LIMIT = 1024 * 1024;
27892
28828
  async function managedPath(root, path3, create = false) {
27893
28829
  let full = root;
27894
28830
  const parts = path3.split("/");
27895
28831
  for (let i = 0; i < parts.length; i++) {
27896
- full = join9(full, parts[i]);
28832
+ full = join13(full, parts[i]);
27897
28833
  let info5 = await fileInfo(full);
27898
28834
  if (!info5 && create && i < parts.length - 1) {
27899
- await mkdir3(full).catch((error62) => {
28835
+ await mkdir4(full).catch((error62) => {
27900
28836
  if (error62.code !== "EEXIST") throw error62;
27901
28837
  });
27902
28838
  info5 = await fileInfo(full);
@@ -27912,7 +28848,7 @@ async function managedRead(root, path3, limit = DEFAULT_LIMIT) {
27912
28848
  const before = await fileInfo(full);
27913
28849
  if (!before) return null;
27914
28850
  if (before.size > limit) throw new InitError("FILE_TOO_LARGE", t2("managedFile.tooLarge", { kib: limit / 1024, path: path3 }));
27915
- const bytes = await readFile9(full);
28851
+ const bytes = await readFile10(full);
27916
28852
  await managedPath(root, path3);
27917
28853
  const after = await fileInfo(full);
27918
28854
  if (!after || before.ino !== after.ino || before.dev !== after.dev || before.mtimeMs !== after.mtimeMs || bytes.length > limit) {
@@ -27941,8 +28877,8 @@ async function managedWrite(root, path3, previous, next, recheck) {
27941
28877
  if (previous !== null) await unlink2(full);
27942
28878
  return;
27943
28879
  }
27944
- const temp = join9(directory, ".gitifact-managed-" + randomUUID4() + ".tmp");
27945
- const handle = await open3(temp, "wx", 384);
28880
+ const temp = join13(directory, ".gitifact-managed-" + randomUUID4() + ".tmp");
28881
+ const handle = await open4(temp, "wx", 384);
27946
28882
  const tempOwner = await handle.stat();
27947
28883
  try {
27948
28884
  try {
@@ -27953,7 +28889,7 @@ async function managedWrite(root, path3, previous, next, recheck) {
27953
28889
  }
27954
28890
  await recheck();
27955
28891
  await unchanged();
27956
- if (await managedRead(root, path3) !== previous || await readFile9(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", t2("managedFile.publishTargetChanged", { path: path3 }));
28892
+ if (await managedRead(root, path3) !== previous || await readFile10(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", t2("managedFile.publishTargetChanged", { path: path3 }));
27957
28893
  if (previous === null) await link2(temp, full);
27958
28894
  else await rename2(temp, full);
27959
28895
  } finally {
@@ -27964,10 +28900,10 @@ async function managedWrite(root, path3, previous, next, recheck) {
27964
28900
  }
27965
28901
 
27966
28902
  // src/commands/agent-block.ts
27967
- import { readFile as readFile11 } from "node:fs/promises";
28903
+ import { readFile as readFile12 } from "node:fs/promises";
27968
28904
 
27969
28905
  // src/commands/guide.ts
27970
- import { readFile as readFile10 } from "node:fs/promises";
28906
+ import { readFile as readFile11 } from "node:fs/promises";
27971
28907
 
27972
28908
  // src/commands/output.ts
27973
28909
  var formats = ["text", "json"];
@@ -28004,7 +28940,7 @@ var text2 = (lines) => lines.join("\n") + "\n";
28004
28940
  // src/commands/guide.ts
28005
28941
  var guideTopics = ["workflow", "spec", "design", "instructions", "records", "writing", "commit", "migrate"];
28006
28942
  var isGuideTopic = (value) => guideTopics.includes(value);
28007
- var readBundledGuide = (name2, lang) => readFile10(new URL("./i18n/" + lang + "/docs/" + name2 + ".md", import.meta.url), "utf8");
28943
+ var readBundledGuide = (name2, lang) => readFile11(new URL("./i18n/" + lang + "/docs/" + name2 + ".md", import.meta.url), "utf8");
28008
28944
  function guideSummary(source2) {
28009
28945
  const { fields: fields2 } = parseFrontmatterBlock(source2.replace(/\r\n/g, "\n"), "guide");
28010
28946
  const value = (key) => {
@@ -28045,7 +28981,7 @@ function boilerplateFor(path3) {
28045
28981
  const name2 = path3 === "AGENTS.md" ? path3 : path3.split("/").pop().replace(/^\./, "").replace(/\.md$/, "");
28046
28982
  return "# " + name2 + "\n\nProject-specific guidance for AI coding agents.\n";
28047
28983
  }
28048
- var readBundledBlock = (lang) => readFile11(new URL("./i18n/" + lang + "/block.md", import.meta.url), "utf8");
28984
+ var readBundledBlock = (lang) => readFile12(new URL("./i18n/" + lang + "/block.md", import.meta.url), "utf8");
28049
28985
  async function renderAgentBlock(version2, controls = {}, lang = getLanguage()) {
28050
28986
  const body2 = await (controls.readBlock ?? readBundledBlock)(lang);
28051
28987
  const filled = body2.trimEnd().replace(/\{(version|language|topics)\}/g, (_whole, name2) => name2 === "version" ? version2 : name2 === "language" ? lang : guideTopics.join(", "));
@@ -28226,11 +29162,11 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28226
29162
  const first = await repo.inspect();
28227
29163
  const root = first.state.repository.rootPath;
28228
29164
  const docsPlan = agentDocs ? await planAgentDocs(root, agentDocs) : skippedAgentDocs;
28229
- const result = async (config3, outcome) => {
29165
+ const result = async (config3, outcome, lineEndings = false) => {
28230
29166
  const checked = await update;
28231
- return projectInitV7.parse({
29167
+ return projectInitV8.parse({
28232
29168
  contract: "project-init",
28233
- version: 7,
29169
+ version: 8,
28234
29170
  ok: true,
28235
29171
  outcome,
28236
29172
  rootPath: root,
@@ -28238,6 +29174,7 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28238
29174
  schemaVersion: config3.schemaVersion,
28239
29175
  baseline: config3.baseline,
28240
29176
  agentDocs: { mode: docsPlan.mode, paths: docsPlan.paths },
29177
+ lineEndings: { path: LINE_ENDINGS_PATH, created: lineEndings },
28241
29178
  update: checked,
28242
29179
  install: checked.status === "available" ? { npx: npxUpdate(checked.latestVersion), npmGlobal: npmGlobalInstall(checked.latestVersion) } : null
28243
29180
  });
@@ -28249,8 +29186,9 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28249
29186
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text5) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
28250
29187
  };
28251
29188
  await unchanged();
28252
- if (!dryRun) await applyAgentDocs(root, docsPlan, unchanged);
28253
- return result(config3, "already-initialized");
29189
+ if (dryRun) return result(config3, "already-initialized");
29190
+ await applyAgentDocs(root, docsPlan, unchanged);
29191
+ return result(config3, "already-initialized", await writeLineEndings(root));
28254
29192
  };
28255
29193
  const config2 = { schemaVersion: SCHEMA_VERSION, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
28256
29194
  const text4 = JSON.stringify(config2, null, 2) + "\n";
@@ -28260,13 +29198,13 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28260
29198
  };
28261
29199
  await published();
28262
29200
  await applyAgentDocs(root, docsPlan, published);
28263
- return result(config2, outcome);
29201
+ return result(config2, outcome, await writeLineEndings(root));
28264
29202
  };
28265
29203
  const old = await readConfigFile(root);
28266
29204
  if (old !== void 0) return existing(old);
28267
29205
  if (first.trackedConfig) throw new InitError("CONFIG_DELETED", t2("init.configDeleted"));
28268
29206
  const checkRecords = async () => {
28269
- const entries = await readdir4(join10(root, ".gitifact")).catch((e) => {
29207
+ const entries = await readdir5(join14(root, ".gitifact")).catch((e) => {
28270
29208
  if (e.code === "ENOENT") return [];
28271
29209
  throw e;
28272
29210
  });
@@ -28318,117 +29256,33 @@ async function runInit(options, version2) {
28318
29256
  );
28319
29257
  if (!dto.ok) throw new Error("unreachable");
28320
29258
  const docs = dto.agentDocs.mode === "skip" ? t2("init.text.skipped") : dto.agentDocs.paths.join(", ") + (dto.agentDocs.mode === "remove" ? " (" + t2("init.text.blockRemoved") + ")" : dto.outcome === "planned" ? " (" + t2("init.text.blockPlanned") + ")" : " (" + t2("init.text.blockUpdated") + ")");
29259
+ const lineEndings = dto.lineEndings.created ? t2("init.text.lineEndings", { path: dto.lineEndings.path }) + "\n" : "";
28321
29260
  const newer = dto.install ? t2("init.text.updateAvailable", { version: dto.update.latestVersion, command: dto.install.npx }) + "\n" : "";
28322
29261
  process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
28323
29262
  ${t2("init.text.storage")}: schemaVersion ${dto.schemaVersion}
28324
29263
  ${t2("init.text.agentDocs")}: ${docs}
28325
- ${newer}` : JSON.stringify(dto) + "\n");
29264
+ ${lineEndings}${newer}` : JSON.stringify(dto) + "\n");
28326
29265
  } catch (error62) {
28327
29266
  cancel.abort();
28328
29267
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
28329
29268
  const failure2 = { code: known ? error62.code : "INIT_FAILED", message: known ? error62.message : t2("init.failed") };
28330
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(projectInitV7.parse({ contract: "project-init", version: 7, ok: false, error: failure2 })) + "\n");
29269
+ process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(projectInitV8.parse({ contract: "project-init", version: 8, ok: false, error: failure2 })) + "\n");
28331
29270
  process.exitCode = 1;
28332
29271
  }
28333
29272
  }
28334
29273
 
28335
29274
  // src/commands/documents.ts
28336
29275
  import { readFile as readFile13 } from "node:fs/promises";
28337
- import { join as join14 } from "node:path";
28338
-
28339
- // src/adapters/filesystem/document-file.ts
28340
- import { randomBytes } from "node:crypto";
28341
- import { lstat as lstat8, mkdir as mkdir4, writeFile as writeFile2 } from "node:fs/promises";
28342
- import { join as join11 } from "node:path";
28343
- var generateId = (prefix) => prefix + "-" + [...randomBytes(10)].map((n) => "abcdefghijklmnopqrstuvwxyz234567"[n & 31]).join("");
28344
- async function createDocumentFile(root, path3, text4) {
28345
- const parts = path3.split("/");
28346
- for (let i = 1; i < parts.length; i++) {
28347
- const folder = join11(root, ...parts.slice(0, i));
28348
- const info5 = await lstat8(folder).catch((error62) => {
28349
- if (error62.code === "ENOENT") return void 0;
28350
- throw error62;
28351
- });
28352
- if (!info5) {
28353
- await mkdir4(folder);
28354
- continue;
28355
- }
28356
- if (!info5.isDirectory() || info5.isSymbolicLink()) throw Object.assign(new Error(folder), { code: "ENOTDIR" });
28357
- }
28358
- await writeFile2(join11(root, ...parts), text4, { flag: "wx" });
28359
- }
28360
-
28361
- // src/commands/project.ts
28362
- import { join as join13 } from "node:path";
28363
-
28364
- // src/adapters/git/pending-records.ts
28365
- import { lstat as lstat9, readdir as readdir5, readFile as readFile12 } from "node:fs/promises";
28366
- import { join as join12 } from "node:path";
28367
- var decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
28368
- var info = (path3) => lstat9(path3).catch((error62) => {
28369
- if (error62.code === "ENOENT") return void 0;
28370
- throw error62;
28371
- });
28372
- async function recordFolders(root) {
28373
- return (await readdir5(join12(root, ...RECORDS_ROOT.split("/"))).catch((error62) => {
28374
- if (error62.code === "ENOENT") return [];
28375
- throw error62;
28376
- })).filter(isRecordFolder);
28377
- }
28378
- async function findRecord(root, id) {
28379
- for (const folder of await recordFolders(root)) {
28380
- const path3 = `${RECORDS_ROOT}/${folder}/${id}.md`;
28381
- if ((await info(join12(root, ...path3.split("/"))))?.isFile()) return path3;
28382
- }
28383
- return void 0;
28384
- }
28385
- async function readPendingRecords(root, run) {
28386
- const status = (await run(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", RECORDS_ROOT])).toString("utf8").split("\0").filter(Boolean);
28387
- const files = /* @__PURE__ */ new Map();
28388
- const altered = [];
28389
- const problems = [];
28390
- for (const entry2 of status) {
28391
- const state = entry2.slice(0, 2);
28392
- const path3 = entry2.slice(3);
28393
- if (state !== "??" && !state.includes("A")) {
28394
- altered.push(path3);
28395
- continue;
28396
- }
28397
- const file2 = join12(root, ...path3.split("/"));
28398
- const stat3 = await info(file2);
28399
- if (!stat3?.isFile() || stat3.isSymbolicLink()) {
28400
- problems.push(docProblem("PATH_UNSUPPORTED", path3));
28401
- continue;
28402
- }
28403
- if (stat3.size > 1024 * 1024) {
28404
- problems.push(docProblem("FILE_TOO_LARGE", path3));
28405
- continue;
28406
- }
28407
- try {
28408
- files.set(path3, decoder.decode(await readFile12(file2)));
28409
- } catch {
28410
- problems.push(docProblem("INVALID_CHARACTERS", path3));
28411
- }
28412
- }
28413
- const folders = await recordFolders(root);
28414
- for (const path3 of files.keys()) {
28415
- const id = recordIdOf(path3);
28416
- if (!id) continue;
28417
- for (const folder of folders) {
28418
- const other = `${RECORDS_ROOT}/${folder}/${id}.md`;
28419
- if (other !== path3 && !files.has(other) && (await info(join12(root, ...other.split("/"))))?.isFile()) problems.push(docProblem("DUPLICATE_RECORD_ID", path3, { id, other }));
28420
- }
28421
- }
28422
- return { files, altered: altered.sort(), problems };
28423
- }
29276
+ import { join as join16 } from "node:path";
28424
29277
 
28425
29278
  // src/commands/project.ts
29279
+ import { join as join15 } from "node:path";
28426
29280
  var operations = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
28427
29281
  async function openProject(cwd, options = {}) {
28428
29282
  const reader = storeReader(cwd);
28429
29283
  const location = await reader.location();
28430
29284
  if (options.writing) {
28431
- for (const marker2 of operations) if (await fileInfo(join13(location.gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
29285
+ for (const marker2 of operations) if (await fileInfo(join15(location.gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
28432
29286
  if (await reader.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
28433
29287
  }
28434
29288
  const raw = await readConfigFile(location.root);
@@ -28459,15 +29313,140 @@ function documentsOf(files) {
28459
29313
  return docs;
28460
29314
  }
28461
29315
 
29316
+ // src/queries/filters.ts
29317
+ function byAuthor(author) {
29318
+ if (author === void 0) return () => true;
29319
+ const wanted = author.trim().toLowerCase();
29320
+ return (change2) => change2.author.toLowerCase() === wanted || change2.email.toLowerCase() === wanted;
29321
+ }
29322
+ async function since(project, value, head) {
29323
+ if (value === void 0) return () => true;
29324
+ if (/^\d{4}-\d\d-\d\d/.test(value)) {
29325
+ const from = Date.parse(value);
29326
+ if (!Number.isFinite(from)) throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
29327
+ return (change2) => Date.parse(change2.date) >= from;
29328
+ }
29329
+ let commit;
29330
+ try {
29331
+ commit = await project.reader.resolve(value);
29332
+ } catch {
29333
+ throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
29334
+ }
29335
+ if (!head) return () => false;
29336
+ const after = new Set(project.reader.decode(await project.reader.run(["rev-list", `${commit}..${head}`, "--"])).split("\n").filter(Boolean));
29337
+ return (change2) => after.has(change2.commit);
29338
+ }
29339
+ async function sinceRange(project, value, head) {
29340
+ if (value === void 0) return {};
29341
+ if (/^\d{4}-\d\d-\d\d/.test(value)) {
29342
+ const from = Date.parse(value);
29343
+ if (!Number.isFinite(from)) throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
29344
+ return { from };
29345
+ }
29346
+ let commit;
29347
+ try {
29348
+ commit = await project.reader.resolve(value);
29349
+ } catch {
29350
+ throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
29351
+ }
29352
+ if (!head) return { commits: [] };
29353
+ return { commits: project.reader.decode(await project.reader.run(["rev-list", `${commit}..${head}`, "--"])).split("\n").filter(Boolean) };
29354
+ }
29355
+ var contains = (query) => {
29356
+ const wanted = query?.trim().toLowerCase();
29357
+ return (...texts) => !wanted || texts.some((value) => value?.toLowerCase().includes(wanted));
29358
+ };
29359
+
29360
+ // src/queries/specs.ts
29361
+ var titledSources = (d, byId) => d.sources.map((s) => "id" in s ? { id: s.id, title: byId.get(s.id)?.title ?? null, ...s.note ? { note: s.note } : {} } : s);
29362
+ async function listSpecs(source2, filter, request) {
29363
+ const listed3 = await source2.cache.documents.list();
29364
+ const problems = listed3.problems;
29365
+ const states = await documentStates(source2, listed3.documents);
29366
+ const documents = states.documents;
29367
+ const byId = new Map(documents.map((d) => [d.id, d]));
29368
+ const arranged = arrangeDocuments(documents);
29369
+ let features = arranged.features;
29370
+ if (filter.feature !== void 0) {
29371
+ features = features.filter((f) => f.index.feature === filter.feature);
29372
+ if (!features.length) throw new CommandError("UNKNOWN_FEATURE", t2("docs.unknownFeature", { feature: filter.feature }));
29373
+ }
29374
+ const titled = (id) => ({ id, title: byId.get(id)?.title ?? null });
29375
+ const designsOf = /* @__PURE__ */ new Map();
29376
+ for (const f of arranged.features) for (const d of f.designs) for (const r of d.requirements) designsOf.set(r, [...designsOf.get(r) ?? [], titled(d.id)]);
29377
+ const historyNeeded = filter.sort === "updated" || filter.changedSince !== void 0 || filter.author !== void 0 || !!filter.withUpdated;
29378
+ const latest = /* @__PURE__ */ new Map();
29379
+ const newest = /* @__PURE__ */ new Map();
29380
+ let touched;
29381
+ if (historyNeeded) {
29382
+ const head = await source2.head();
29383
+ const changes2 = head ? await source2.cache.history.changesOf(head) : [];
29384
+ changes2.forEach((c, rank) => {
29385
+ if (!latest.has(c.id)) {
29386
+ latest.set(c.id, { commit: c.commit, date: c.date, author: c.author, email: c.email });
29387
+ newest.set(c.id, rank);
29388
+ }
29389
+ });
29390
+ if (filter.changedSince !== void 0 || filter.author !== void 0) {
29391
+ const after = await since(source2, filter.changedSince, head);
29392
+ const by = byAuthor(filter.author);
29393
+ touched = new Set(changes2.filter((c) => after(c) && by(c)).map((c) => c.id));
29394
+ }
29395
+ }
29396
+ const matched = filter.q !== void 0 ? await source2.cache.documents.matching(filter.q) : void 0;
29397
+ const rows = features.flatMap((f) => [f.index, ...f.requirements, ...f.designs].filter(
29398
+ (doc) => (filter.type === void 0 || doc.kind === filter.type) && (!filter.withoutDesign || !f.designs.length) && (!filter.uncovered || doc.kind === "requirement" && !designsOf.has(doc.id)) && (!filter.draft || doc.draft) && (!touched || touched.has(doc.id)) && (!matched || matched.has(doc.id))
29399
+ ).map((doc) => ({
29400
+ id: doc.id,
29401
+ kind: doc.kind,
29402
+ feature: doc.feature,
29403
+ path: doc.path,
29404
+ title: doc.title,
29405
+ description: doc.description,
29406
+ ...doc.kind === "requirement" || doc.kind === "design" ? { order: doc.order } : {},
29407
+ ...doc.draft ? { draft: true } : {},
29408
+ state: states.state(doc.id),
29409
+ ...states.previousPath(doc.id) ? { previousPath: states.previousPath(doc.id) } : {},
29410
+ ...doc.kind === "design" ? { requirements: doc.requirements.map(titled), sources: titledSources(doc, byId) } : {},
29411
+ ...doc.kind === "requirement" ? { designs: designsOf.get(doc.id) ?? [] } : {},
29412
+ ...historyNeeded ? { updated: latest.get(doc.id) ?? null } : {},
29413
+ ...matched ? { line: matched.get(doc.id) } : {}
29414
+ })));
29415
+ if (filter.sort === "title") rows.sort((a, b) => a.title.localeCompare(b.title));
29416
+ if (filter.sort === "updated") rows.sort((a, b) => (newest.get(a.id) ?? Infinity) - (newest.get(b.id) ?? Infinity));
29417
+ const grouped = filter.sort === "order";
29418
+ const groups = features.map((f) => ({ id: f.index.id, rows: rows.filter((r) => r.feature === f.index.feature) })).filter((g) => g.rows.length);
29419
+ const page = grouped ? pageOf(groups, (g) => g.id, request) : pageOf(rows, (r) => r.id, request);
29420
+ if (!page) return void 0;
29421
+ const shown = grouped ? page.rows.flatMap((g) => g.rows) : page.rows;
29422
+ return {
29423
+ rows: shown,
29424
+ features,
29425
+ byId,
29426
+ states,
29427
+ grouped,
29428
+ problems,
29429
+ page: { rows: page.rows, total: page.total, next: page.next },
29430
+ unit: grouped ? "feature" : "document",
29431
+ orphans: filter.feature === void 0 ? arranged.orphans : []
29432
+ };
29433
+ }
29434
+
28462
29435
  // src/commands/documents.ts
28463
29436
  var draftMark = (doc) => doc.draft ? " (" + t2("docs.draft") + ")" : "";
29437
+ var stateLabel = {
29438
+ added: () => t2("docs.state.added"),
29439
+ modified: () => t2("docs.state.modified"),
29440
+ deleted: () => t2("docs.state.deleted")
29441
+ };
29442
+ var stateMark = (state2) => state2 === "committed" ? "" : " (" + stateLabel[state2]() + ")";
28464
29443
  var line = (doc) => `${doc.id} ${doc.title}${draftMark(doc)} \u2014 ${doc.description}`;
28465
- var titledSources = (d, byId) => d.sources.map((s) => "id" in s ? { id: s.id, title: byId.get(s.id)?.title ?? null, ...s.note ? { note: s.note } : {} } : s);
29444
+ var fileLine = (f) => f.title ? `${f.path} ${f.title} \u2014 ${f.description}` : f.path;
28466
29445
  async function working(project) {
28467
29446
  const { documents } = await project.cache.documents.list();
28468
29447
  return {
28469
29448
  byId: new Map(documents.map((d) => [d.id, d])),
28470
- read: (path3) => readFile13(join14(project.root, ...path3.split("/")), "utf8"),
29449
+ read: (path3) => readFile13(join16(project.root, ...path3.split("/")), "utf8"),
28471
29450
  referencing: async (id) => [...new Set((await project.cache.documents.referencing(id)).map((r) => r.from))]
28472
29451
  };
28473
29452
  }
@@ -28493,7 +29472,7 @@ async function showDocuments(project, ids, at, kinds, otherResource) {
28493
29472
  ...doc.sources.map((s) => "id" in s ? { id: s.id, title: view.byId.get(s.id)?.title ?? null } : { title: s.title, url: s.url })
28494
29473
  ] : [];
28495
29474
  const referencedBy = (await view.referencing(id)).map((from) => ({ id: from, title: view.byId.get(from)?.title ?? null }));
28496
- const files = doc.kind === "instruction" && at === void 0 ? { files: (await listInstructionFiles(project.root, doc.path)).files.map((f) => f.path) } : {};
29475
+ const files = doc.kind === "instruction" && at === void 0 ? { files: (await readInstructionFiles(project.root, doc.path)).files } : {};
28497
29476
  return { id, kind: doc.kind, path: doc.path, title: doc.title, text: await view.read(doc.path), references, referencedBy, ...files };
28498
29477
  }));
28499
29478
  const named = (r) => r.id ? r.id + " " + (r.title ?? "(" + t2("docs.missing") + ")") : r.title + " <" + r.url + ">";
@@ -28502,7 +29481,7 @@ async function showDocuments(project, ids, at, kinds, otherResource) {
28502
29481
  d.text.replace(/\n$/, ""),
28503
29482
  ...section("-- " + t2("docs.references"), d.references.map(named)),
28504
29483
  ...section("-- " + t2("docs.referencedBy"), d.referencedBy.map(named)),
28505
- ...section("-- " + t2("docs.files"), d.files ?? [])
29484
+ ...section("-- " + t2("docs.files"), (d.files ?? []).map(fileLine))
28506
29485
  ].join("\n"));
28507
29486
  return { json: { ...at ? { commit: at } : {}, documents: shown }, text: blocks.join("\n\n") + "\n" };
28508
29487
  }
@@ -28576,32 +29555,6 @@ function checkFields(fields2, columns3) {
28576
29555
  if (unknown2.length || !fields2.length) throw new CommandError("INVALID_VALUE", t2("list.unknownFields", { fields: unknown2.join(", "), columns: columns3.join(", ") }));
28577
29556
  return [...new Set(fields2)];
28578
29557
  }
28579
- function byAuthor(author) {
28580
- if (author === void 0) return () => true;
28581
- const wanted = author.trim().toLowerCase();
28582
- return (change2) => change2.author.toLowerCase() === wanted || change2.email.toLowerCase() === wanted;
28583
- }
28584
- async function since(project, value, head) {
28585
- if (value === void 0) return () => true;
28586
- if (/^\d{4}-\d\d-\d\d/.test(value)) {
28587
- const from = Date.parse(value);
28588
- if (!Number.isFinite(from)) throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
28589
- return (change2) => Date.parse(change2.date) >= from;
28590
- }
28591
- let commit;
28592
- try {
28593
- commit = await project.reader.resolve(value);
28594
- } catch {
28595
- throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
28596
- }
28597
- if (!head) return () => false;
28598
- const after = new Set(project.reader.decode(await project.reader.run(["rev-list", `${commit}..${head}`, "--"])).split("\n").filter(Boolean));
28599
- return (change2) => after.has(change2.commit);
28600
- }
28601
- var contains = (query) => {
28602
- const wanted = query?.trim().toLowerCase();
28603
- return (...texts) => !wanted || texts.some((value) => value?.toLowerCase().includes(wanted));
28604
- };
28605
29558
  function cell(value) {
28606
29559
  if (value === void 0 || value === null || value === false) return "";
28607
29560
  if (Array.isArray(value)) return value.map((v) => typeof v === "object" && v !== null ? "id" in v ? v.id : "title" in v ? v.title : JSON.stringify(v) : String(v)).join(",");
@@ -28620,69 +29573,36 @@ function selected(rows, fields2) {
28620
29573
  text: rows.length ? text2(rows.map((row) => fields2.map((f) => cell(row[f])).join(" "))) : ""
28621
29574
  };
28622
29575
  }
29576
+ function paginate(rows, keyOf, options) {
29577
+ return pageOr(pageOf(rows, keyOf, options), options);
29578
+ }
29579
+ function pageOr(page, options) {
29580
+ return page ?? cursorGone(options);
29581
+ }
29582
+ function cursorGone(options) {
29583
+ throw new CommandError("INVALID_VALUE", t2("list.after", { cursor: options.after ?? "" }));
29584
+ }
29585
+ var pageLine = (page, before = 0, unit = t2("list.items")) => page.next === null ? [] : [t2("list.more", { shown: before + page.rows.length, total: page.total, unit, cursor: page.next })];
29586
+ function aside(format2, lines) {
29587
+ if (format2 === "text" && lines.length) process.stderr.write(lines.join("\n") + "\n");
29588
+ return "";
29589
+ }
28623
29590
 
28624
29591
  // src/commands/specs.ts
28625
29592
  var specKinds = ["feature", "requirement", "design"];
28626
29593
  var specSorts = ["order", "title", "updated"];
28627
- var columns = ["id", "kind", "feature", "path", "title", "description", "order", "draft", "requirements", "sources", "designs", "updated", "line"];
29594
+ var columns = ["id", "kind", "feature", "path", "title", "description", "order", "draft", "state", "previousPath", "requirements", "sources", "designs", "updated", "line"];
28628
29595
  var kindLabel = { feature: () => t2("docs.feature"), requirement: () => t2("docs.requirement"), design: () => t2("docs.design") };
28629
29596
  var runSpecsList = (options) => runCommand("specs", options.format, async () => {
28630
29597
  const fields2 = checkFields(options.fields, columns);
28631
29598
  const project = await openProject(process.cwd());
28632
- const { documents, problems } = await project.cache.documents.list();
28633
- const byId = new Map(documents.map((d) => [d.id, d]));
28634
- const arranged = arrangeDocuments(documents);
28635
- let features = arranged.features;
28636
- if (options.feature !== void 0) {
28637
- features = features.filter((f) => f.index.feature === options.feature);
28638
- if (!features.length) throw new CommandError("UNKNOWN_FEATURE", t2("docs.unknownFeature", { feature: options.feature }));
28639
- }
28640
- const titled = (id) => ({ id, title: byId.get(id)?.title ?? null });
28641
- const designsOf = /* @__PURE__ */ new Map();
28642
- for (const f of arranged.features) for (const d of f.designs) for (const r of d.requirements) designsOf.set(r, [...designsOf.get(r) ?? [], titled(d.id)]);
28643
- const historyNeeded = options.sort === "updated" || options.changedSince !== void 0 || options.author !== void 0 || !!fields2?.includes("updated");
28644
- const latest = /* @__PURE__ */ new Map();
28645
- const newest = /* @__PURE__ */ new Map();
28646
- let touched;
28647
- if (historyNeeded) {
28648
- const head = await project.head();
28649
- const changes2 = head ? await project.cache.history.changesOf(head) : [];
28650
- changes2.forEach((c, rank) => {
28651
- if (!latest.has(c.id)) {
28652
- latest.set(c.id, { commit: c.commit, date: c.date, author: c.author, email: c.email });
28653
- newest.set(c.id, rank);
28654
- }
28655
- });
28656
- if (options.changedSince !== void 0 || options.author !== void 0) {
28657
- const after = await since(project, options.changedSince, head);
28658
- const by = byAuthor(options.author);
28659
- touched = new Set(changes2.filter((c) => after(c) && by(c)).map((c) => c.id));
28660
- }
28661
- }
28662
- const matched = options.q !== void 0 ? await project.cache.documents.matching(options.q) : void 0;
28663
- const rows = features.flatMap((f) => [f.index, ...f.requirements, ...f.designs].filter(
28664
- (doc) => (options.type === void 0 || doc.kind === options.type) && (!options.withoutDesign || !f.designs.length) && (!options.uncovered || doc.kind === "requirement" && !designsOf.has(doc.id)) && (!options.draft || doc.draft) && (!touched || touched.has(doc.id)) && (!matched || matched.has(doc.id))
28665
- ).map((doc) => ({
28666
- id: doc.id,
28667
- kind: doc.kind,
28668
- feature: doc.feature,
28669
- path: doc.path,
28670
- title: doc.title,
28671
- description: doc.description,
28672
- ...doc.kind === "requirement" || doc.kind === "design" ? { order: doc.order } : {},
28673
- ...doc.draft ? { draft: true } : {},
28674
- ...doc.kind === "design" ? { requirements: doc.requirements.map(titled), sources: titledSources(doc, byId) } : {},
28675
- ...doc.kind === "requirement" ? { designs: designsOf.get(doc.id) ?? [] } : {},
28676
- ...historyNeeded ? { updated: latest.get(doc.id) ?? null } : {},
28677
- ...matched ? { line: matched.get(doc.id) } : {}
28678
- })));
28679
- if (options.sort === "title") rows.sort((a, b) => a.title.localeCompare(b.title));
28680
- if (options.sort === "updated") rows.sort((a, b) => (newest.get(a.id) ?? Infinity) - (newest.get(b.id) ?? Infinity));
28681
- const shown = options.limit === void 0 ? rows : rows.slice(0, options.limit);
28682
- const orphans = options.feature === void 0 ? arranged.orphans : [];
29599
+ const listed3 = await listSpecs(project, { ...options, withUpdated: !!fields2?.includes("updated") }, options) ?? cursorGone(options);
29600
+ const { rows: shown, features, byId, states, grouped, problems, orphans, page } = listed3;
29601
+ const more = pageLine(page, 0, grouped ? t2("list.features") : t2("list.items"));
29602
+ const paging = { total: page.total, next: page.next, unit: listed3.unit };
28683
29603
  if (fields2) {
28684
29604
  const picked = selected(shown, fields2);
28685
- return { json: { documents: picked.json, orphans, problems }, text: picked.text };
29605
+ return { json: { documents: picked.json, orphans, problems, page: paging }, text: picked.text + aside(options.format, more) };
28686
29606
  }
28687
29607
  const refName = (r) => r.id + " " + (r.title ?? "(" + t2("docs.missing") + ")");
28688
29608
  const excerpt = (row) => row.line ? [" " + row.line] : [];
@@ -28691,29 +29611,30 @@ var runSpecsList = (options) => runCommand("specs", options.format, async () =>
28691
29611
  for (const f of features) {
28692
29612
  const own2 = shown.filter((r) => r.feature === f.index.feature);
28693
29613
  if (!own2.length) continue;
28694
- out.push(`[${t2("docs.feature")}] ${f.index.id} ${f.index.title}${draftMark(f.index)} (${f.index.feature}) \u2014 ${f.index.description}`);
29614
+ out.push(`[${t2("docs.feature")}] ${f.index.id} ${f.index.title}${draftMark(f.index)}${stateMark(states.state(f.index.id))} (${f.index.feature}) \u2014 ${f.index.description}`);
28695
29615
  const index = own2.find((r) => r.kind === "feature");
28696
29616
  if (index) out.push(...excerpt(index));
28697
- const requirement2 = (r) => [`${r.order} ${line(byId.get(r.id))}`, ...excerpt(r)];
29617
+ const requirement2 = (r) => [`${r.order} ${line(byId.get(r.id))}${stateMark(r.state)}`, ...excerpt(r)];
28698
29618
  out.push(...section(" " + t2("docs.requirements"), own2.filter((r) => r.kind === "requirement").flatMap(requirement2), " "));
28699
29619
  const designs = own2.filter((r) => r.kind === "design").flatMap((r) => {
28700
29620
  const refs = r.requirements.length ? " [" + r.requirements.map(refName).join(", ") + "]" : "";
28701
29621
  const notes = r.sources.map((s) => "id" in s ? refName(s) : s.title);
28702
- return [`${r.order} ${line(byId.get(r.id))}${refs}`, ...notes.length ? [" ".repeat(String(r.order).length + 1) + t2("docs.sources") + ": " + notes.join(", ")] : [], ...excerpt(r)];
29622
+ return [`${r.order} ${line(byId.get(r.id))}${stateMark(r.state)}${refs}`, ...notes.length ? [" ".repeat(String(r.order).length + 1) + t2("docs.sources") + ": " + notes.join(", ")] : [], ...excerpt(r)];
28703
29623
  });
28704
29624
  out.push(...section(" " + t2("docs.designs"), designs, " "));
28705
29625
  }
28706
29626
  } else {
28707
29627
  for (const r of shown) {
28708
29628
  const when = r.updated ? ` \xB7 ${r.updated.date.slice(0, 10)} ${r.updated.author}` : "";
28709
- out.push(`${r.id} ${r.title}${draftMark(r)} (${kindLabel[r.kind]()} \xB7 ${r.feature}) \u2014 ${r.description}${when}`, ...excerpt(r));
29629
+ out.push(`${r.id} ${r.title}${draftMark(r)}${stateMark(r.state)} (${kindLabel[r.kind]()} \xB7 ${r.feature}) \u2014 ${r.description}${when}`, ...excerpt(r));
28710
29630
  }
28711
29631
  }
28712
29632
  const filtered = options.type !== void 0 || options.feature !== void 0 || options.withoutDesign || options.uncovered || options.draft || options.changedSince !== void 0 || options.author !== void 0 || options.q !== void 0;
28713
29633
  if (!out.length) out.push(filtered ? t2("docs.noMatch") : t2("docs.empty"));
29634
+ out.push(...more);
28714
29635
  if (orphans.length) out.push(t2("docs.orphans", { folders: orphans.join(", ") }));
28715
29636
  if (problems.length) out.push(t2("docs.unreadable", { count: problems.length }));
28716
- return { json: { documents: shown, orphans, problems }, text: text2(out) };
29637
+ return { json: { documents: shown, orphans, problems, page: paging }, text: text2(out) };
28717
29638
  });
28718
29639
  var runSpecsShow = (ids, options) => runCommand("specs", options.format, async () => {
28719
29640
  const project = await openProject(process.cwd());
@@ -28722,24 +29643,23 @@ var runSpecsShow = (ids, options) => runCommand("specs", options.format, async (
28722
29643
  });
28723
29644
  var runSpecsNew = (kind2, path3, options) => runCommand("specs", options.format, async () => createDocument(await openProject(process.cwd()), kind2, path3, options));
28724
29645
 
28725
- // src/commands/instructions.ts
28726
- import { lstat as lstat10 } from "node:fs/promises";
28727
- import { join as join15 } from "node:path";
28728
- var instructionSorts = ["name", "updated"];
28729
- var columns2 = ["id", "name", "path", "title", "description", "draft", "files", "updated", "line"];
29646
+ // src/queries/instructions.ts
29647
+ import { lstat as lstat12 } from "node:fs/promises";
29648
+ import { join as join17 } from "node:path";
28730
29649
  var AGENTS = "AGENTS.md";
28731
- var runInstructionsList = (options) => runCommand("instructions", options.format, async () => {
28732
- const fields2 = checkFields(options.fields, columns2);
28733
- const project = await openProject(process.cwd());
28734
- const { documents, problems } = await project.cache.documents.list();
28735
- const agents2 = { path: AGENTS, exists: !!(await lstat10(join15(project.root, AGENTS)).catch(() => void 0))?.isFile() };
28736
- const historyNeeded = options.sort === "updated" || options.author !== void 0 || !!fields2?.includes("updated");
29650
+ async function listInstructions(source2, options, request) {
29651
+ const listed3 = await source2.cache.documents.list();
29652
+ const problems = listed3.problems;
29653
+ const states = await documentStates(source2, listed3.documents);
29654
+ const documents = states.documents;
29655
+ const agents2 = { path: AGENTS, exists: !!(await lstat12(join17(source2.root, AGENTS)).catch(() => void 0))?.isFile() };
29656
+ const historyNeeded = options.sort === "updated" || options.author !== void 0 || !!options.withUpdated;
28737
29657
  const latest = /* @__PURE__ */ new Map();
28738
29658
  const newest = /* @__PURE__ */ new Map();
28739
29659
  let touched;
28740
29660
  if (historyNeeded) {
28741
- const head = await project.head();
28742
- const changes2 = head ? await project.cache.history.changesOf(head) : [];
29661
+ const head = await source2.head();
29662
+ const changes2 = head ? await source2.cache.history.changesOf(head) : [];
28743
29663
  changes2.forEach((c, rank) => {
28744
29664
  if (!latest.has(c.id)) {
28745
29665
  latest.set(c.id, { commit: c.commit, date: c.date, author: c.author, email: c.email });
@@ -28751,35 +29671,60 @@ var runInstructionsList = (options) => runCommand("instructions", options.format
28751
29671
  touched = new Set(changes2.filter(by).map((c) => c.id));
28752
29672
  }
28753
29673
  }
28754
- const matched = options.q !== void 0 ? await project.cache.documents.matching(options.q) : void 0;
29674
+ const matched = options.q !== void 0 ? await source2.cache.documents.matching(options.q) : void 0;
28755
29675
  const instructions2 = documents.filter((d) => d.kind === "instruction").filter((d) => (!touched || touched.has(d.id)) && (!matched || matched.has(d.id))).sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0);
28756
- const rows = await Promise.all(instructions2.map(async (d) => ({
28757
- id: d.id,
28758
- name: d.name,
28759
- path: d.path,
28760
- title: d.title,
28761
- description: d.description,
28762
- ...d.draft ? { draft: true } : {},
28763
- files: (await listInstructionFiles(project.root, d.path)).files.length,
28764
- ...historyNeeded ? { updated: latest.get(d.id) ?? null } : {},
28765
- ...matched ? { line: matched.get(d.id) } : {}
28766
- })));
29676
+ const rows = await Promise.all(instructions2.map(async (d) => {
29677
+ const state2 = states.state(d.id);
29678
+ const { files, limited } = state2 === "deleted" ? { files: [], limited: false } : await readInstructionFiles(source2.root, d.path);
29679
+ return {
29680
+ id: d.id,
29681
+ name: d.name,
29682
+ path: d.path,
29683
+ title: d.title,
29684
+ description: d.description,
29685
+ ...d.draft ? { draft: true } : {},
29686
+ state: state2,
29687
+ ...states.previousPath(d.id) ? { previousPath: states.previousPath(d.id) } : {},
29688
+ files,
29689
+ ...limited ? { filesLimited: true } : {},
29690
+ ...historyNeeded ? { updated: latest.get(d.id) ?? null } : {},
29691
+ ...matched ? { line: matched.get(d.id) } : {}
29692
+ };
29693
+ }));
28767
29694
  if (options.sort === "updated") rows.sort((a, b) => (newest.get(a.id) ?? Infinity) - (newest.get(b.id) ?? Infinity));
28768
- const shown = options.limit === void 0 ? rows : rows.slice(0, options.limit);
29695
+ const page = pageOf(rows, (r) => r.id, request);
29696
+ if (!page) return void 0;
29697
+ return { agents: agents2, problems, page };
29698
+ }
29699
+
29700
+ // src/commands/instructions.ts
29701
+ var instructionSorts = ["name", "updated"];
29702
+ var columns2 = ["id", "name", "path", "title", "description", "draft", "state", "previousPath", "files", "updated", "line"];
29703
+ var runInstructionsList = (options) => runCommand("instructions", options.format, async () => {
29704
+ const fields2 = checkFields(options.fields, columns2);
29705
+ const project = await openProject(process.cwd());
29706
+ const listed3 = await listInstructions(project, { ...options, withUpdated: !!fields2?.includes("updated") }, options) ?? cursorGone(options);
29707
+ const { agents: agents2, problems, page } = listed3;
29708
+ const shown = page.rows;
29709
+ const paging = { total: page.total, next: page.next, unit: "instruction" };
28769
29710
  const unreadable2 = problems.filter((p) => p.path.startsWith(INSTRUCTIONS_ROOT + "/"));
28770
29711
  if (fields2) {
28771
29712
  const picked = selected(shown, fields2);
28772
- return { json: { agents: agents2, instructions: picked.json, problems: unreadable2 }, text: picked.text };
29713
+ return {
29714
+ json: { agents: agents2, instructions: picked.json, problems: unreadable2, page: paging },
29715
+ text: selected(shown.map((r) => ({ ...r, files: r.files.map((f) => f.path) })), fields2).text + aside(options.format, pageLine(page))
29716
+ };
28773
29717
  }
28774
29718
  const out = [agents2.exists ? t2("instructions.agents") : t2("instructions.noAgents")];
28775
29719
  for (const r of shown) {
28776
- const files = r.files ? " \xB7 " + t2("instructions.files", { count: r.files }) : "";
28777
29720
  const when = r.updated ? ` \xB7 ${r.updated.date.slice(0, 10)} ${r.updated.author}` : "";
28778
- out.push(`${r.id} ${r.title}${draftMark(r)} (${r.name}) \u2014 ${r.description}${files}${when}`, ...r.line ? [" " + r.line] : []);
29721
+ out.push(`${r.id} ${r.title}${draftMark(r)}${stateMark(r.state)} (${r.name}) \u2014 ${r.description}${when}`, ...r.line ? [" " + r.line] : []);
29722
+ out.push(...r.files.map((f) => " " + fileLine(f)), ...r.filesLimited ? [" " + t2("instructions.filesLimited", { count: r.files.length })] : []);
28779
29723
  }
28780
29724
  if (!shown.length) out.push(options.q !== void 0 || options.author !== void 0 ? t2("docs.noMatch") : t2("instructions.empty"));
29725
+ out.push(...pageLine(page));
28781
29726
  if (unreadable2.length) out.push(t2("docs.unreadable", { count: unreadable2.length }));
28782
- return { json: { agents: agents2, instructions: shown, problems: unreadable2 }, text: text2(out) };
29727
+ return { json: { agents: agents2, instructions: shown, problems: unreadable2, page: paging }, text: text2(out) };
28783
29728
  });
28784
29729
  async function resolveInstructions(project, targets, at) {
28785
29730
  const view = at === void 0 ? await working(project) : await committed(project, at);
@@ -28805,9 +29750,9 @@ var runInstructionsShow = (targets, options) => runCommand("instructions", optio
28805
29750
  var runInstructionsNew = (name2, options) => runCommand("instructions", options.format, async () => createDocument(await openProject(process.cwd()), "instruction", name2, options));
28806
29751
 
28807
29752
  // src/adapters/filesystem/document-warnings.ts
28808
- import { lstat as lstat11, readdir as readdir6 } from "node:fs/promises";
28809
- import { join as join16 } from "node:path";
28810
- var info2 = async (path3) => lstat11(path3).catch((e) => {
29753
+ import { lstat as lstat13, readdir as readdir6 } from "node:fs/promises";
29754
+ import { join as join18 } from "node:path";
29755
+ var info2 = async (path3) => lstat13(path3).catch((e) => {
28811
29756
  if (e.code === "ENOENT") return void 0;
28812
29757
  throw e;
28813
29758
  });
@@ -28815,11 +29760,11 @@ var posix = (path3) => path3.split("\\").join("/");
28815
29760
  async function listAssets(root) {
28816
29761
  const out = [];
28817
29762
  async function visit2(relative, depth) {
28818
- const stat3 = await info2(join16(root, relative));
29763
+ const stat3 = await info2(join18(root, relative));
28819
29764
  if (!stat3 || stat3.isSymbolicLink()) return;
28820
29765
  if (stat3.isDirectory()) {
28821
29766
  if (depth > 8 || out.length > 4e3) return;
28822
- for (const name2 of (await readdir6(join16(root, relative))).sort()) await visit2(relative + "/" + name2, depth + 1);
29767
+ for (const name2 of (await readdir6(join18(root, relative))).sort()) await visit2(relative + "/" + name2, depth + 1);
28823
29768
  } else if (stat3.isFile()) out.push({ path: posix(relative), bytes: stat3.size });
28824
29769
  }
28825
29770
  await visit2(ASSETS_DIR, 0);
@@ -28828,7 +29773,7 @@ async function listAssets(root) {
28828
29773
  async function readDocumentWarnings(root, documents) {
28829
29774
  const outside = [...new Set(documentBodyLinks(documents).map((l) => l.target).filter((t3) => !t3.startsWith(".gitifact/") || t3.startsWith(INSTRUCTIONS_ROOT + "/")))];
28830
29775
  const existing = /* @__PURE__ */ new Set();
28831
- for (const target of outside) if ((await info2(join16(root, ...target.split("/"))))?.isFile()) existing.add(target);
29776
+ for (const target of outside) if ((await info2(join18(root, ...target.split("/"))))?.isFile()) existing.add(target);
28832
29777
  return documentWarnings(documents, await listAssets(root), existing);
28833
29778
  }
28834
29779
 
@@ -28852,30 +29797,30 @@ var runCheck = (options) => runCommand("check", options.format, async () => {
28852
29797
 
28853
29798
  // src/commands/changes.ts
28854
29799
  import { randomUUID as randomUUID5 } from "node:crypto";
28855
- import { mkdir as mkdir6, open as open4, readFile as readFile16, rename as rename3, rmdir, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
28856
- import { join as join19 } from "node:path";
29800
+ import { mkdir as mkdir6, open as open5, readFile as readFile16, rename as rename3, rmdir, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
29801
+ import { join as join21 } from "node:path";
28857
29802
 
28858
29803
  // src/adapters/filesystem/agent-inputs.ts
28859
- import { createHash as createHash3, randomBytes as randomBytes2 } from "node:crypto";
28860
- import { lstat as lstat12, mkdir as mkdir5, readdir as readdir7, realpath as realpath3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
29804
+ import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
29805
+ import { lstat as lstat14, mkdir as mkdir5, readdir as readdir7, realpath as realpath3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
28861
29806
  import { tmpdir } from "node:os";
28862
- import { basename, dirname as dirname2, join as join17 } from "node:path";
29807
+ import { basename, dirname as dirname2, join as join19 } from "node:path";
28863
29808
  var maxAge = 7 * 24 * 60 * 60 * 1e3;
28864
- var info3 = (path3) => lstat12(path3).catch((e) => {
29809
+ var info3 = (path3) => lstat14(path3).catch((e) => {
28865
29810
  if (e.code === "ENOENT") return void 0;
28866
29811
  throw e;
28867
29812
  });
28868
29813
  var comparable = (path3) => process.platform === "win32" ? path3.toLowerCase() : path3;
28869
29814
  async function candidates(root, controls) {
28870
- const key = createHash3("sha256").update(comparable(await realpath3(root))).digest("hex").slice(0, 16);
28871
- return { system: join17((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join17(root, ".gitifact", "tmp") };
29815
+ const key = createHash4("sha256").update(comparable(await realpath3(root))).digest("hex").slice(0, 16);
29816
+ return { system: join19((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join19(root, ".gitifact", "tmp") };
28872
29817
  }
28873
29818
  async function writable(directory) {
28874
29819
  try {
28875
29820
  await mkdir5(directory, { recursive: true });
28876
29821
  const stat3 = await info3(directory);
28877
29822
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) return false;
28878
- const probe = join17(directory, ".probe-" + randomBytes2(8).toString("hex"));
29823
+ const probe = join19(directory, ".probe-" + randomBytes2(8).toString("hex"));
28879
29824
  await writeFile3(probe, "", { flag: "wx" });
28880
29825
  await unlink3(probe);
28881
29826
  return true;
@@ -28889,7 +29834,7 @@ async function prepareAgentInputs(root, controls = {}) {
28889
29834
  if (await writable(system)) directory = system;
28890
29835
  else if (await writable(project)) {
28891
29836
  directory = project;
28892
- await writeFile3(join17(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
29837
+ await writeFile3(join19(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
28893
29838
  if (e.code !== "EEXIST") throw e;
28894
29839
  });
28895
29840
  }
@@ -28897,10 +29842,10 @@ async function prepareAgentInputs(root, controls = {}) {
28897
29842
  const now = (controls.now ?? Date.now)();
28898
29843
  for (const name2 of await readdir7(directory).catch(() => [])) {
28899
29844
  if (name2 === ".gitignore") continue;
28900
- const stat3 = await info3(join17(directory, name2)).catch(() => void 0);
28901
- if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink3(join17(directory, name2)).catch(() => void 0);
29845
+ const stat3 = await info3(join19(directory, name2)).catch(() => void 0);
29846
+ if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink3(join19(directory, name2)).catch(() => void 0);
28902
29847
  }
28903
- return { commit: join17(directory, "commit.json") };
29848
+ return { commit: join19(directory, "commit.json") };
28904
29849
  }
28905
29850
  async function discardAgentInput(root, file2, controls = {}) {
28906
29851
  try {
@@ -28921,18 +29866,18 @@ async function discardAgentInput(root, file2, controls = {}) {
28921
29866
  }
28922
29867
 
28923
29868
  // src/commands/commit-files.ts
28924
- import { createHash as createHash4 } from "node:crypto";
28925
- import { lstat as lstat13, readFile as readFile14 } from "node:fs/promises";
28926
- import { join as join18 } from "node:path";
29869
+ import { createHash as createHash5 } from "node:crypto";
29870
+ import { lstat as lstat15, readFile as readFile14 } from "node:fs/promises";
29871
+ import { join as join20 } from "node:path";
28927
29872
  var fail3 = (message) => {
28928
29873
  throw new CommandError("INVALID_COMMIT", message);
28929
29874
  };
28930
- var hash2 = (value) => createHash4("sha256").update(value).digest("hex");
29875
+ var hash2 = (value) => createHash5("sha256").update(value).digest("hex");
28931
29876
  var optional2 = (path3) => readFile14(path3).catch((e) => {
28932
29877
  if (e.code === "ENOENT") return null;
28933
29878
  throw e;
28934
29879
  });
28935
- var info4 = (path3) => lstat13(path3).catch((e) => {
29880
+ var info4 = (path3) => lstat15(path3).catch((e) => {
28936
29881
  if (e.code === "ENOENT") return void 0;
28937
29882
  throw e;
28938
29883
  });
@@ -28958,11 +29903,11 @@ async function fingerprint(root, path3) {
28958
29903
  validPath2(path3);
28959
29904
  const parts = path3.split("/");
28960
29905
  for (let i = 1; i <= parts.length; i++) {
28961
- const target = join18(root, ...parts.slice(0, i));
29906
+ const target = join20(root, ...parts.slice(0, i));
28962
29907
  const stat3 = await info4(target);
28963
29908
  if (!stat3) return null;
28964
29909
  if (stat3.isSymbolicLink() || (i < parts.length ? !stat3.isDirectory() : !stat3.isFile())) fail3(t2("commitFiles.linkOrDirectory"));
28965
- if (i < parts.length && await info4(join18(target, ".git"))) fail3(t2("commitFiles.nestedRepository"));
29910
+ if (i < parts.length && await info4(join20(target, ".git"))) fail3(t2("commitFiles.nestedRepository"));
28966
29911
  if (i === parts.length) {
28967
29912
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail3(t2("commitFiles.hardLinkOrSize"));
28968
29913
  return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile14(target)]));
@@ -29029,18 +29974,12 @@ async function readStdin(stream) {
29029
29974
  var fields = ["paths", "message", "authorization", "migration"];
29030
29975
  var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
29031
29976
  var MIGRATION_TARGET = "0.8.0";
29977
+ var PATH_LIMIT = 128;
29978
+ var MIGRATION_PATH_LIMIT = 5e3;
29032
29979
  var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
29033
29980
  var problemLines2 = (problems) => problems.map((p) => " " + p.code + " " + p.message);
29034
29981
  var underDocuments = (path3) => path3.startsWith(SPEC_ROOT + "/") || path3.startsWith(WIKI_ROOT + "/") || path3 === HISTORY_PATH;
29035
- async function pendingChanges(project, head) {
29036
- const headFiles = head ? await project.cache.history.filesAt(head) : /* @__PURE__ */ new Map();
29037
- const [working2, records2] = await Promise.all([project.cache.documents.files(), project.pendingRecords()]);
29038
- return { headFiles, working: working2, records: records2, ...compareDocumentSets(headFiles, working2.files) };
29039
- }
29040
- var uncovered = (changes2, records2) => {
29041
- const covered = new Set(records2.flatMap((r) => r.docs));
29042
- return changes2.filter((c) => c.types.some((type) => type !== "created") && !covered.has(c.id)).map((c) => c.id);
29043
- };
29982
+ var pendingChanges = (project, head) => workingChanges(project, head);
29044
29983
  var shared = (changes2, records2) => changes2.map((c) => c.id).filter((id) => records2.filter((r) => r.docs.includes(id)).length > 1);
29045
29984
  var changeLine = (c) => `${c.types.join(",").padEnd(9)} ${c.id} ${c.title} \u2014 ${c.path}${c.previousPath ? " (\u2190 " + c.previousPath + ")" : ""}`;
29046
29985
  var recordLine = (r, changes2) => {
@@ -29087,7 +30026,7 @@ var runChangesCommit = (options, controls = {}) => runCommand("changes", options
29087
30026
  async function commitChanges(cwd, input2, dryRun) {
29088
30027
  const request = object3(input2);
29089
30028
  if (Object.keys(request).some((k) => !fields.includes(k))) fail3(t2("commit.unknownField"));
29090
- const selected2 = paths(request.paths);
30029
+ const selected2 = paths(request.paths, request.migration === true ? MIGRATION_PATH_LIMIT : PATH_LIMIT);
29091
30030
  const authorization = object3(request.authorization);
29092
30031
  if (Object.keys(authorization).sort().join(",") !== "basis,evidence" || !["user-request", "project-policy"].includes(String(authorization.basis))) fail3(t2("commit.authorizationRequired"));
29093
30032
  text3(authorization.evidence, 2e3);
@@ -29118,7 +30057,7 @@ async function commitChanges(cwd, input2, dryRun) {
29118
30057
  ...stdin ? { input: stdin } : {}
29119
30058
  });
29120
30059
  const staged = async (index) => (await git(["diff", "--cached", "--ita-visible-in-index", "--name-only", "--no-renames", "-z"], index)).toString("utf8").split("\0").filter(Boolean).sort();
29121
- const busy = join19(gitDir, "gitifact-changes-commit.lock");
30060
+ const busy = join21(gitDir, "gitifact-changes-commit.lock");
29122
30061
  if (await info4(busy)) fail3(t2("commit.busy", { path: busy }));
29123
30062
  if ((await staged()).length) fail3(t2("commit.existingStaging"));
29124
30063
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
@@ -29157,7 +30096,7 @@ async function commitChanges(cwd, input2, dryRun) {
29157
30096
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
29158
30097
  return { outcome: "dry-run", committed: false, ...summary, commit: void 0 };
29159
30098
  }
29160
- const temporary = join19(gitDir, "gitifact-commit-index-" + randomUUID5());
30099
+ const temporary = join21(gitDir, "gitifact-commit-index-" + randomUUID5());
29161
30100
  let owned = false;
29162
30101
  let uncertain = false;
29163
30102
  let commitStarted = false;
@@ -29171,16 +30110,16 @@ async function commitChanges(cwd, input2, dryRun) {
29171
30110
  if (e.code === "EEXIST") fail3(t2("commit.busy", { path: busy }));
29172
30111
  throw e;
29173
30112
  }
29174
- const lock = indexLock = await open4(indexPath + ".lock", "wx", 384);
30113
+ const lock = indexLock = await open5(indexPath + ".lock", "wx", 384);
29175
30114
  const original = await optional2(indexPath);
29176
30115
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail3(t2("commit.indexChanged"));
29177
30116
  const before = await hashes([...started.keys()]);
29178
30117
  for (const [path3, value] of started) if (before.get(path3) !== value) fail3(t2("commit.fileChangedRunning", { path: path3 }));
29179
- await writeFile4(join19(busy, "recovery.json"), JSON.stringify({ before: base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected2, message }));
30118
+ await writeFile4(join21(busy, "recovery.json"), JSON.stringify({ before: base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected2, message }));
29180
30119
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected2, ...contextPaths])]);
29181
30120
  if (original) await writeFile4(temporary, original, { flag: "wx" });
29182
30121
  else await git(["read-tree", "--empty"], temporary);
29183
- await git(["add", "--", ...stageable], temporary);
30122
+ await git(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], temporary, Buffer.from(stageable.join("\0")));
29184
30123
  const actual = await staged(temporary);
29185
30124
  if (!actual.length || actual.some((p) => !selected2.includes(p))) fail3(t2("commit.unexpectedFiles"));
29186
30125
  const storeFiles = selected2.filter((p) => record3(p) && locked.get(p) !== null);
@@ -29193,18 +30132,18 @@ async function commitChanges(cwd, input2, dryRun) {
29193
30132
  const size = Number(header[2]);
29194
30133
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail3(t2("commit.stagedUnreadable", { path: path3 }));
29195
30134
  const blob = output2.subarray(end + 1, end + 1 + size);
29196
- const file2 = await readFile16(join19(root, path3));
30135
+ const file2 = await readFile16(join21(root, path3));
29197
30136
  if (!blob.equals(file2) && !blob.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail3(t2("commit.filterChanged"));
29198
30137
  offset = end + size + 2;
29199
30138
  }
29200
30139
  }
29201
30140
  const again = await hashes([...locked.keys()]);
29202
30141
  for (const [path3, value] of locked) if (again.get(path3) !== value) fail3(t2("commit.fileChangedPreparing", { path: path3 }));
29203
- for (const name2 of integration) if (await info4(join19(gitDir, name2))) fail3(t2("commit.integrationStarted"));
30142
+ for (const name2 of integration) if (await info4(join21(gitDir, name2))) fail3(t2("commit.integrationStarted"));
29204
30143
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
29205
30144
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
29206
30145
  commitStarted = true;
29207
- await git(["commit", "-m", message.trim() + (trailers.length ? "\n\n" + trailers.join("\n") : "")], temporary);
30146
+ await git(["commit", "-F", "-"], temporary, Buffer.from(message.trim() + (trailers.length ? "\n\n" + trailers.join("\n") : "") + "\n"));
29208
30147
  const after = await project.reader.baseline();
29209
30148
  if (!after.head) return fail3(t2("commit.resultUnknown"));
29210
30149
  const raw = (await git(["cat-file", "commit", after.head])).toString("utf8");
@@ -29240,7 +30179,7 @@ async function commitChanges(cwd, input2, dryRun) {
29240
30179
  await unlink4(temporary).catch(() => {
29241
30180
  });
29242
30181
  if (owned) {
29243
- await unlink4(join19(busy, "recovery.json")).catch(() => {
30182
+ await unlink4(join21(busy, "recovery.json")).catch(() => {
29244
30183
  });
29245
30184
  await rmdir(busy);
29246
30185
  }
@@ -29251,7 +30190,7 @@ async function commitChanges(cwd, input2, dryRun) {
29251
30190
 
29252
30191
  // src/commands/records.ts
29253
30192
  import { readFile as readFile17 } from "node:fs/promises";
29254
- import { join as join20 } from "node:path";
30193
+ import { join as join22 } from "node:path";
29255
30194
  var runRecordsNew = (options) => runCommand("records", options.format, async () => {
29256
30195
  if (/[\r\n]/.test(options.title)) throw new CommandError("INVALID_VALUE", t2("docs.singleLine", { field: "title" }));
29257
30196
  const docs = [...new Set(options.docs.flatMap((d) => d.split(",")).map((d) => d.trim()).filter(Boolean))];
@@ -29287,7 +30226,7 @@ var runRecordsShow = (ids, options) => runCommand("records", options.format, asy
29287
30226
  const shown = await Promise.all([...new Set(ids)].map(async (id) => {
29288
30227
  const path3 = await findRecord(project.root, id);
29289
30228
  if (!path3) return { id, missing: true };
29290
- const source2 = await readFile17(join20(project.root, ...path3.split("/")), "utf8");
30229
+ const source2 = await readFile17(join22(project.root, ...path3.split("/")), "utf8");
29291
30230
  const log = project.reader.decode(await project.reader.run(["log", "-1", "--diff-filter=A", "--format=%H%x00%aN%x00%aI", "--", path3])).trim();
29292
30231
  const [commit, author, date5] = log.split("\0");
29293
30232
  return { id, path: path3, text: source2, commit: commit ? { commit, author, date: date5 } : null };
@@ -29312,18 +30251,17 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29312
30251
  const fields2 = checkFields(options.fields, options.doc === void 0 ? recordColumns : eventColumns);
29313
30252
  const project = await openProject(process.cwd());
29314
30253
  const head = await project.head();
29315
- const after = await since(project, options.since, head);
29316
- const by = byAuthor(options.author);
29317
- const has = contains(options.q);
29318
- const limit = (rows2) => options.limit === void 0 ? rows2 : rows2.slice(0, options.limit);
29319
30254
  const sectionText = (sections2) => sections2.map((s) => s.body);
29320
30255
  if (options.doc !== void 0) {
30256
+ const after = await since(project, options.since, head);
30257
+ const by = byAuthor(options.author);
30258
+ const has2 = contains(options.q);
29321
30259
  const id = options.doc;
29322
30260
  const events = head ? await project.cache.history.ofDocument(head, id) : [];
29323
30261
  const { documents } = await project.cache.documents.list();
29324
30262
  const title = documents.find((d) => d.id === id)?.title ?? events.map((e) => (e.after ?? e.before)?.title).find(Boolean) ?? null;
29325
30263
  if (!title && !events.length) throw new CommandError("UNKNOWN_DOCUMENT", t2("docs.unknownDocument", { ids: id }));
29326
- const rows2 = limit(events.filter((e) => after(e) && by(e) && has(e.message, ...e.records.flatMap((r) => [r.title, ...sectionText(r.sections)]))).map((e) => ({
30264
+ const page = paginate(events.filter((e) => after(e) && by(e) && has2(e.message, ...e.records.flatMap((r) => [r.title, ...sectionText(r.sections)]))).map((e) => ({
29327
30265
  commit: e.commit,
29328
30266
  date: e.date,
29329
30267
  author: e.author,
@@ -29332,10 +30270,13 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29332
30270
  types: e.types,
29333
30271
  path: (e.after ?? e.before)?.path ?? null,
29334
30272
  records: e.records
29335
- })));
30273
+ })), (e) => e.commit, options);
30274
+ const rows2 = page.rows;
30275
+ const paging2 = { total: page.total, next: page.next, unit: "commit" };
30276
+ const more2 = pageLine(page, 0, t2("list.commits"));
29336
30277
  if (fields2) {
29337
30278
  const picked = selected(rows2, fields2);
29338
- return { json: { doc: { id, title }, events: picked.json }, text: picked.text };
30279
+ return { json: { doc: { id, title }, events: picked.json, page: paging2 }, text: picked.text + aside(options.format, more2) };
29339
30280
  }
29340
30281
  const out2 = [`${id} ${title ?? ""}`.trimEnd()];
29341
30282
  for (const e of rows2) {
@@ -29348,20 +30289,35 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29348
30289
  out2.push(" " + t2("docs.commit") + ": " + e.message);
29349
30290
  }
29350
30291
  if (!rows2.length) out2.push(events.length ? t2("docs.noMatch") : t2("docs.noHistory"));
29351
- return { json: { doc: { id, title }, events: rows2 }, text: text2(out2) };
30292
+ if (more2.length) out2.push("", ...more2);
30293
+ return { json: { doc: { id, title }, events: rows2, page: paging2 }, text: text2(out2) };
29352
30294
  }
29353
- const pending = options.author !== void 0 ? [] : [...(await project.pendingRecords()).files].flatMap(([path3, source2]) => {
30295
+ const has = contains(options.q);
30296
+ const pending = (options.author !== void 0 ? [] : [...(await project.pendingRecords()).files].flatMap(([path3, source2]) => {
29354
30297
  try {
29355
30298
  return [parseRecordFile(path3, source2)];
29356
30299
  } catch {
29357
30300
  return [];
29358
30301
  }
29359
- }).map((r) => ({ id: r.id, title: r.title, commit: null, date: null, author: null, docs: r.docs, sections: r.sections.map((s) => ({ key: s.key, body: s.body })) }));
29360
- const committed2 = (head ? await project.cache.history.recordsOf(head) : []).filter((r) => after(r) && by(r) && has(r.title, r.message, ...sectionText(r.sections))).map((r) => ({ id: r.id, title: r.title, commit: r.commit, date: r.date, author: r.author, docs: r.docs, sections: r.sections }));
29361
- const rows = limit([...pending.filter((r) => has(r.title, ...sectionText(r.sections))).sort((a, b) => a.id < b.id ? -1 : 1), ...committed2]);
30302
+ }).map((r) => ({ id: r.id, title: r.title, commit: null, date: null, author: null, docs: r.docs, sections: r.sections.map((s) => ({ key: s.key, body: s.body })) }))).filter((r) => has(r.title, ...sectionText(r.sections))).sort((a, b) => a.id < b.id ? -1 : 1);
30303
+ const size = options.all ? void 0 : options.limit ?? PAGE_SIZE;
30304
+ const inPending = options.after === void 0 ? 0 : pending.findIndex((r) => r.id === options.after) + 1;
30305
+ const afterCommitted = options.after !== void 0 && !inPending ? options.after : void 0;
30306
+ const pendingShown = afterCommitted !== void 0 ? [] : pending.slice(inPending, size === void 0 ? void 0 : inPending + size);
30307
+ const filter = { author: options.author, q: options.q, ...await sinceRange(project, options.since, head) };
30308
+ const room = size === void 0 ? void 0 : size - pendingShown.length;
30309
+ const found = head ? await project.cache.history.records(head, filter, afterCommitted, room) : { records: [], total: 0 };
30310
+ if (!found) throw new CommandError("INVALID_VALUE", t2("list.after", { cursor: options.after }));
30311
+ const committed2 = found.records.map((r) => ({ id: r.id, title: r.title, commit: r.commit, date: r.date, author: r.author, docs: r.docs, sections: r.sections }));
30312
+ const rows = [...pendingShown, ...committed2];
30313
+ const total = pending.length + found.total;
30314
+ const before = afterCommitted === void 0 ? inPending : pending.length + await readThrough(project, head, filter, afterCommitted);
30315
+ const next = before + rows.length < total && rows.length ? rows[rows.length - 1].id : null;
30316
+ const paging = { total, next, unit: "record" };
30317
+ const more = pageLine({ rows, total, next }, before, t2("list.records"));
29362
30318
  if (fields2) {
29363
30319
  const picked = selected(rows, fields2);
29364
- return { json: { records: picked.json }, text: picked.text };
30320
+ return { json: { records: picked.json, page: paging }, text: picked.text + aside(options.format, more) };
29365
30321
  }
29366
30322
  const docs = (ids) => ids.slice(0, 5).join(", ") + (ids.length > 5 ? " " + t2("records.moreDocs", { count: ids.length - 5 }) : "");
29367
30323
  const out = rows.flatMap((r) => [
@@ -29369,13 +30325,18 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29369
30325
  ` ${r.commit ? `${r.date.slice(0, 10)} ${r.commit.slice(0, 7)} ${r.author}` : t2("records.uncommitted")} \xB7 ${docs(r.docs)}`
29370
30326
  ]);
29371
30327
  const filtered = options.since !== void 0 || options.author !== void 0 || options.q !== void 0;
29372
- return { json: { records: rows }, text: text2(out.length ? out : [filtered ? t2("docs.noMatch") : t2("records.empty")]) };
30328
+ return { json: { records: rows, page: paging }, text: text2(out.length ? [...out, ...more] : [filtered ? t2("docs.noMatch") : t2("records.empty")]) };
29373
30329
  });
30330
+ async function readThrough(project, head, filter, id) {
30331
+ if (!head) return 0;
30332
+ const all = await project.cache.history.records(head, filter, void 0, void 0);
30333
+ return (all?.records.findIndex((r) => r.id === id) ?? -1) + 1;
30334
+ }
29374
30335
 
29375
30336
  // src/commands/feedback.ts
29376
30337
  import { readFile as readFile18 } from "node:fs/promises";
29377
30338
  import { release as release3 } from "node:os";
29378
- import { dirname as dirname3, join as join21 } from "node:path";
30339
+ import { dirname as dirname3, join as join23 } from "node:path";
29379
30340
 
29380
30341
  // src/adapters/github/issue.ts
29381
30342
  import { spawn as spawn2 } from "node:child_process";
@@ -29442,7 +30403,7 @@ function parseFeedback(input2) {
29442
30403
  }
29443
30404
  async function schemaVersion(from) {
29444
30405
  for (let folder = from; ; folder = dirname3(folder)) {
29445
- const config2 = await readFile18(join21(folder, ".gitifact", "config.json"), "utf8").catch(() => void 0);
30406
+ const config2 = await readFile18(join23(folder, ".gitifact", "config.json"), "utf8").catch(() => void 0);
29446
30407
  if (config2 !== void 0) {
29447
30408
  try {
29448
30409
  const value = JSON.parse(config2).schemaVersion;
@@ -29520,7 +30481,7 @@ function agentDocsGit(root, inherited = process.env) {
29520
30481
 
29521
30482
  // src/commands/update.ts
29522
30483
  var blockCommitMessage = (version2) => "chore(gitifact): refresh GITIFACT block to v" + version2;
29523
- var commitState = (state, extra = {}) => ({ state, commit: null, paths: [], message: null, reason: null, detail: null, ...extra });
30484
+ var commitState = (state2, extra = {}) => ({ state: state2, commit: null, paths: [], message: null, reason: null, detail: null, ...extra });
29524
30485
  async function refreshBlocks(cwd, version2, env, controls) {
29525
30486
  const repo = initRepository(cwd, env);
29526
30487
  let first;
@@ -29650,10 +30611,10 @@ async function runUpdateCheck(format2, version2) {
29650
30611
  process.stdout.write(JSON.stringify(dto) + "\n");
29651
30612
  return;
29652
30613
  }
29653
- const state = dto.update;
30614
+ const state2 = dto.update;
29654
30615
  const lines = [
29655
30616
  t2("update.text.current", { version: dto.cliVersion }),
29656
- state.status === "available" ? t2("update.text.available", { version: state.latestVersion }) : state.status === "up-to-date" ? t2("update.text.upToDate") : state.status === "disabled" ? t2("update.text.disabled") : t2("update.text.unavailable")
30617
+ state2.status === "available" ? t2("update.text.available", { version: state2.latestVersion }) : state2.status === "up-to-date" ? t2("update.text.upToDate") : state2.status === "disabled" ? t2("update.text.disabled") : t2("update.text.unavailable")
29657
30618
  ];
29658
30619
  if (dto.command) lines.push(t2("update.text.install", { command: dto.command }));
29659
30620
  process.stdout.write(lines.join("\n") + "\n");
@@ -29668,15 +30629,15 @@ for (let i = 0; i < languageArgs.length; i++) {
29668
30629
  if (value === "ko" || value === "en") selectedLanguage = value;
29669
30630
  }
29670
30631
  configureLanguage(selectedLanguage ?? environmentLanguage(), selectedLanguage);
29671
- var program2 = new Command().name("gitifact").addOption(new Option("--lang <language>", t2("help.lang")).choices(["ko", "en"])).description(t2("help.program")).version("0.8.0").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
30632
+ var program2 = new Command().name("gitifact").addOption(new Option("--lang <language>", t2("help.lang")).choices(["ko", "en"])).description(t2("help.program")).version("0.8.2").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
29672
30633
  var format = () => new Option("--format <format>", t2("help.format")).choices(formats).default("text");
29673
- program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).action((options) => runBrowser(options, "0.8.0"));
29674
- program2.command("init").description(t2("help.init")).allowExcessArguments(false).option("--dry-run", t2("help.initDryRun")).addOption(new Option("--agent <agent>", t2("help.initAgent")).choices([...agentPresetNames])).addOption(new Option("--remove-agents", t2("help.initRemoveAgents")).conflicts("skipAgents")).option("--skip-agents", t2("help.initSkipAgents")).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.8.0"));
29675
- program2.command("update").description(t2("help.update")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).option("--commit", t2("help.updateCommit")).addOption(new Option("--check", t2("help.updateCheck")).conflicts("commit")).action((options) => options.check ? runUpdateCheck(options.format, "0.8.0") : runUpdate(options, "0.8.0"));
30634
+ program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).action((options) => runBrowser(options, "0.8.2"));
30635
+ program2.command("init").description(t2("help.init")).allowExcessArguments(false).option("--dry-run", t2("help.initDryRun")).addOption(new Option("--agent <agent>", t2("help.initAgent")).choices([...agentPresetNames])).addOption(new Option("--remove-agents", t2("help.initRemoveAgents")).conflicts("skipAgents")).option("--skip-agents", t2("help.initSkipAgents")).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.8.2"));
30636
+ program2.command("update").description(t2("help.update")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).option("--commit", t2("help.updateCommit")).addOption(new Option("--check", t2("help.updateCheck")).conflicts("commit")).action((options) => options.check ? runUpdateCheck(options.format, "0.8.2") : runUpdate(options, "0.8.2"));
29676
30637
  var listed2 = (command, sorts) => {
29677
30638
  command.allowExcessArguments(false).option("--q <words>", t2("help.listQ")).option("--author <name>", t2("help.listAuthor"));
29678
30639
  if (sorts) command.addOption(new Option("--sort <key>", t2("help.listSort")).choices([...sorts]).default(sorts[0]));
29679
- return command.option("--limit <n>", t2("help.listLimit"), parseLimit).option("--fields <names>", t2("help.listFields"), parseFields).addOption(format());
30640
+ return command.addOption(new Option("--limit <n>", t2("help.listLimit")).argParser(parseLimit).conflicts("all")).option("--all", t2("help.listAll")).option("--after <cursor>", t2("help.listAfter")).option("--fields <names>", t2("help.listFields"), parseFields).addOption(format());
29680
30641
  };
29681
30642
  var specs = program2.command("specs").description(t2("help.specs"));
29682
30643
  listed2(specs.command("list").description(t2("help.specsList")).addOption(new Option("--type <kind>", t2("help.specsListType")).choices([...specKinds])).option("--feature <folder>", t2("help.specsListFeature")).option("--without-design", t2("help.specsListWithoutDesign")).option("--uncovered", t2("help.specsListUncovered")).option("--draft", t2("help.specsListDraft")).option("--changed-since <date|commit>", t2("help.specsListChangedSince")), specSorts).action((o) => runSpecsList(o));
@@ -29694,7 +30655,7 @@ program2.command("check").description(t2("help.check")).allowExcessArguments(fal
29694
30655
  var changes = program2.command("changes").description(t2("help.changes"));
29695
30656
  changes.command("list").description(t2("help.changesList")).allowExcessArguments(false).addOption(format()).action((o) => runChangesList(o));
29696
30657
  changes.command("commit").description(t2("help.changesCommit")).allowExcessArguments(false).requiredOption("--file <path>", t2("help.changesCommitFile")).option("--dry-run", t2("help.changesCommitDryRun")).addOption(format()).action((o) => runChangesCommit(o));
29697
- program2.command("feedback").description(t2("help.feedback")).allowExcessArguments(false).requiredOption("--file <path>", t2("help.feedbackFile")).option("--dry-run", t2("help.feedbackDryRun")).addOption(format()).action((o) => runFeedback(o, "0.8.0"));
30658
+ program2.command("feedback").description(t2("help.feedback")).allowExcessArguments(false).requiredOption("--file <path>", t2("help.feedbackFile")).option("--dry-run", t2("help.feedbackDryRun")).addOption(format()).action((o) => runFeedback(o, "0.8.2"));
29698
30659
  var guide = program2.command("guide").description(t2("help.guide"));
29699
30660
  guide.command("list").description(t2("help.guideList")).allowExcessArguments(false).addOption(format()).action((o) => runGuideList(o));
29700
30661
  guide.command("show").description(t2("help.guideShow")).argument("<topic>", t2("help.guideTopic")).allowExcessArguments(false).addOption(format()).action((topic, o) => runGuideShow(topic, o));