gitifact 0.8.0 → 0.8.1

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 (100) hide show
  1. package/dist/browser/assets/Banner-c1hFCLs8.js +1 -0
  2. package/dist/browser/assets/HoverCard-D6keobP0.js +1 -0
  3. package/dist/browser/assets/{Markdown-K4Mne1wf.js → Markdown-Droxhk-G.js} +1 -1
  4. package/dist/browser/assets/{Selector-DuT8AsMB.js → Selector-D_xYW7R_.js} +1 -1
  5. package/dist/browser/assets/Tab-VVHagF2Z.js +1 -0
  6. package/dist/browser/assets/{Table-8qCDrsKI.js → Table-D4v8xCrw.js} +2 -2
  7. package/dist/browser/assets/TimestampHoverCard-qfGbCkoP.js +1 -0
  8. package/dist/browser/assets/{Token-Dmavo-NE.js → Token-BLd8-jfF.js} +1 -1
  9. package/dist/browser/assets/{about-B8tYN8_7.js → about-DeUi-_2z.js} +2 -2
  10. package/dist/browser/assets/activity-timeline-BEeyw2GA.css +1 -0
  11. package/dist/browser/assets/activity-timeline-bFoqFwVu.js +2 -0
  12. package/dist/browser/assets/changelog-Brd1Ninm.js +2 -0
  13. package/dist/browser/assets/commit-CiMtFlZl.js +4 -0
  14. package/dist/browser/assets/{commit-45NumYDT.css → commit-DVyOH45R.css} +1 -1
  15. package/dist/browser/assets/contributor-2mkEa2Wo.css +1 -0
  16. package/dist/browser/assets/contributor-C_IskSHq.js +1 -0
  17. package/dist/browser/assets/contributors-CbwXLc7M.js +1 -0
  18. package/dist/browser/assets/{contributors._email-CW4qlA9y.js → contributors._email-BRfre3dw.js} +1 -1
  19. package/dist/browser/assets/contributors.index-CV84owW7.js +1 -0
  20. package/dist/browser/assets/dashboard.index-CQdr7hsf.js +1 -0
  21. package/dist/browser/assets/document-BZmiLx-e.js +2 -0
  22. package/dist/browser/assets/document-DEGaf2yT.css +1 -0
  23. package/dist/browser/assets/{document-D4osdtS6.js → document-JLY2-z8S.js} +4 -4
  24. package/dist/browser/assets/features-Cj41kcXf.js +4 -0
  25. package/dist/browser/assets/features-g3j2elTj.css +1 -0
  26. package/dist/browser/assets/features._featureId-BgFHsRcy.js +1 -0
  27. package/dist/browser/assets/features.index-Ca0rzFW6.js +1 -0
  28. package/dist/browser/assets/getting-started-CgL3Vi9o.js +1 -0
  29. package/dist/browser/assets/git-B7oxggGB.js +1 -0
  30. package/dist/browser/assets/{index-BER0M7UG.css → index-CmU7dwFQ.css} +1 -1
  31. package/dist/browser/assets/index-DWRGm9YO.js +48 -0
  32. package/dist/browser/assets/instructions-2OUMP-vh.css +1 -0
  33. package/dist/browser/assets/instructions-D3cdAPD3.js +1 -0
  34. package/dist/browser/assets/instructions._instructionId-BkT14646.js +1 -0
  35. package/dist/browser/assets/instructions.agents-B3jLRl1u.js +1 -0
  36. package/dist/browser/assets/instructions.index-8qlhSXoy.js +1 -0
  37. package/dist/browser/assets/{lazyRouteComponent-Dbwmw-_u.js → lazyRouteComponent-JkRa2CHo.js} +1 -1
  38. package/dist/browser/assets/{page-header-PYFVxi9j.js → page-header-CPL7myjo.js} +1 -1
  39. package/dist/browser/assets/records-Dk3shbYl.css +1 -0
  40. package/dist/browser/assets/records-page-B4Al3fIQ.js +1 -0
  41. package/dist/browser/assets/records-page-D2F1WJrh.css +1 -0
  42. package/dist/browser/assets/records._recordId-Chn1SO5R.js +1 -0
  43. package/dist/browser/assets/records.commits._commit-D4Wk4i_m.js +1 -0
  44. package/dist/browser/assets/records.index-CViTeD08.js +1 -0
  45. package/dist/browser/assets/records.working-BsHKMIz-.js +1 -0
  46. package/dist/browser/assets/request-state-oiWyP9c-.js +1 -0
  47. package/dist/browser/assets/{search-palette-C-XxJJ6I.js → search-palette-D0ctICJy.js} +5 -5
  48. package/dist/browser/assets/{settings-BdZ_rM9V.js → settings-BDVKHrS8.js} +1 -1
  49. package/dist/browser/assets/{useInfiniteQuery-Cs8d91AB.js → useInfiniteQuery-Bn_R13Ih.js} +1 -1
  50. package/dist/browser/assets/{useKeyboardHint-CuvkDYsZ.js → useKeyboardHint-lSxUw5Qj.js} +1 -1
  51. package/dist/browser/index.html +12 -11
  52. package/dist/i18n/en/block.md +23 -23
  53. package/dist/i18n/en/changelog.md +20 -0
  54. package/dist/i18n/en/docs/commit.md +1 -1
  55. package/dist/i18n/en/docs/instructions.md +16 -3
  56. package/dist/i18n/en/docs/migrate.md +3 -2
  57. package/dist/i18n/en/docs/spec.md +1 -1
  58. package/dist/i18n/en/docs/workflow.md +1 -1
  59. package/dist/i18n/ko/block.md +25 -25
  60. package/dist/i18n/ko/changelog.md +20 -0
  61. package/dist/i18n/ko/docs/commit.md +1 -1
  62. package/dist/i18n/ko/docs/instructions.md +16 -3
  63. package/dist/i18n/ko/docs/migrate.md +3 -2
  64. package/dist/i18n/ko/docs/spec.md +1 -1
  65. package/dist/i18n/ko/docs/workflow.md +1 -1
  66. package/dist/main.js +919 -417
  67. package/package.json +1 -1
  68. package/dist/browser/assets/HgiRefresh-DVzwwzGM.js +0 -1
  69. package/dist/browser/assets/TimestampHoverCard-_VKbn1Py.js +0 -1
  70. package/dist/browser/assets/activity-timeline-CiFzANgu.css +0 -1
  71. package/dist/browser/assets/activity-timeline-USDjdupU.js +0 -2
  72. package/dist/browser/assets/changelog-BwEMGKor.js +0 -2
  73. package/dist/browser/assets/commit-C0G_pkoH.js +0 -4
  74. package/dist/browser/assets/contributors-FNt-eYzY.js +0 -1
  75. package/dist/browser/assets/contributors.index-BMT_fvWV.js +0 -1
  76. package/dist/browser/assets/dashboard.index-IeAMhDp4.js +0 -1
  77. package/dist/browser/assets/document-BbpOg7j8.js +0 -1
  78. package/dist/browser/assets/document-DpkFXhPm.css +0 -1
  79. package/dist/browser/assets/features-D91CUmI3.css +0 -1
  80. package/dist/browser/assets/features-DUe_HTbz.js +0 -4
  81. package/dist/browser/assets/features._featureId-CPuJxCrI.js +0 -1
  82. package/dist/browser/assets/features.index-BqKMq6DZ.js +0 -1
  83. package/dist/browser/assets/getting-started-DG-Skl34.js +0 -1
  84. package/dist/browser/assets/git-B2XLYoEb.js +0 -1
  85. package/dist/browser/assets/index-BoqBsl1i.js +0 -48
  86. package/dist/browser/assets/instructions-CX6doP-p.css +0 -1
  87. package/dist/browser/assets/instructions-CmMSgO5v.js +0 -1
  88. package/dist/browser/assets/instructions._instructionId-Biq4h3Db.js +0 -1
  89. package/dist/browser/assets/instructions.agents-C1LXUm7K.js +0 -1
  90. package/dist/browser/assets/instructions.index-DCiLsKxg.js +0 -1
  91. package/dist/browser/assets/records-CiGcLzEi.css +0 -1
  92. package/dist/browser/assets/records-page-BSx-Bxmm.css +0 -1
  93. package/dist/browser/assets/records-page-D8qxMJNY.js +0 -1
  94. package/dist/browser/assets/records._recordId-B6sdT5ro.js +0 -1
  95. package/dist/browser/assets/records.commits._commit-goSqW9tl.js +0 -1
  96. package/dist/browser/assets/records.index-CxubA4G5.js +0 -1
  97. package/dist/browser/assets/related-list-CU-kUhYj.js +0 -2
  98. package/dist/browser/assets/related-list-Rp3yvN_G.css +0 -1
  99. package/dist/browser/assets/request-state-rD3dhp0I.js +0 -1
  100. package/dist/browser/assets/useCollapsible-D7UZAUy-.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,11 +10402,11 @@ 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
  }
@@ -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
  }
@@ -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,13 +25064,14 @@ 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({
25067
+ var browserSpecsV7 = external_exports.strictObject({
25048
25068
  contract: external_exports.literal("browser-specs"),
25049
- version: external_exports.literal(6),
25069
+ version: external_exports.literal(7),
25050
25070
  sessionId: external_exports.string(),
25051
25071
  head: oid2.nullable(),
25052
25072
  observedAt: external_exports.string(),
25053
25073
  working: external_exports.boolean(),
25074
+ stamp: external_exports.string(),
25054
25075
  features: external_exports.array(feature),
25055
25076
  instructions: external_exports.array(instruction),
25056
25077
  agents: agents.nullable(),
@@ -25090,14 +25111,15 @@ var event = external_exports.strictObject({
25090
25111
  after: reference,
25091
25112
  records: external_exports.array(record2)
25092
25113
  });
25093
- var browserHistoryV5 = external_exports.strictObject({
25114
+ var browserHistoryV6 = external_exports.strictObject({
25094
25115
  contract: external_exports.literal("browser-history"),
25095
- version: external_exports.literal(5),
25116
+ version: external_exports.literal(6),
25096
25117
  sessionId: external_exports.string(),
25097
25118
  head: oid2,
25098
- // Matching changes in all of history, not in this page; `offset` is where this page starts among them.
25119
+ // Matching changes and the commits that hold them in all of history, not in this page.
25099
25120
  total: external_exports.number().int().nonnegative(),
25100
- offset: external_exports.number().int().nonnegative(),
25121
+ commits: external_exports.number().int().nonnegative(),
25122
+ next: oid2.nullable(),
25101
25123
  events: external_exports.array(event)
25102
25124
  });
25103
25125
  var browserHistorySummaryV4 = external_exports.strictObject({
@@ -25131,10 +25153,10 @@ var browserSearchV2 = external_exports.strictObject({
25131
25153
  });
25132
25154
  var headQuery = oid2;
25133
25155
  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({
25156
+ var browserHistoryQueryV4 = external_exports.strictObject({
25135
25157
  head: headQuery,
25136
- offset: count(1e6).optional(),
25137
- limit: count(100).pipe(external_exports.number().min(1)).optional(),
25158
+ after: oid2.optional(),
25159
+ limit: count(50).pipe(external_exports.number().min(1)).optional(),
25138
25160
  kind: changeType.optional(),
25139
25161
  document: kind.optional(),
25140
25162
  feature: external_exports.string().regex(/^S-[a-z2-7]{10}$/).optional(),
@@ -25176,9 +25198,9 @@ var browserCommitFileV1 = external_exports.strictObject({
25176
25198
  });
25177
25199
  var browserCommitFilesQueryV1 = external_exports.strictObject({ commit: oid2 });
25178
25200
  var browserCommitFileQueryV1 = external_exports.strictObject({ commit: oid2, path: external_exports.string().min(1).max(4096) });
25179
- var browserCommitV3 = external_exports.strictObject({
25201
+ var browserCommitV4 = external_exports.strictObject({
25180
25202
  contract: external_exports.literal("browser-commit"),
25181
- version: external_exports.literal(3),
25203
+ version: external_exports.literal(4),
25182
25204
  sessionId: external_exports.string(),
25183
25205
  commit: oid2,
25184
25206
  author: external_exports.string(),
@@ -25186,9 +25208,21 @@ var browserCommitV3 = external_exports.strictObject({
25186
25208
  committer: external_exports.string(),
25187
25209
  date: external_exports.string(),
25188
25210
  message: external_exports.string(),
25189
- changes: external_exports.array(external_exports.strictObject({ event, before: snapshot, after: snapshot }))
25211
+ total: external_exports.number().int().nonnegative(),
25212
+ next: external_exports.string().nullable(),
25213
+ changes: external_exports.array(event)
25214
+ });
25215
+ var browserCommitQueryV2 = external_exports.strictObject({ commit: oid2, after: external_exports.string().min(1).max(200).optional(), limit: count(500).pipe(external_exports.number().min(1)).optional() });
25216
+ var browserCommitChangeV1 = external_exports.strictObject({
25217
+ contract: external_exports.literal("browser-commit-change"),
25218
+ version: external_exports.literal(1),
25219
+ sessionId: external_exports.string(),
25220
+ commit: oid2,
25221
+ event,
25222
+ before: snapshot,
25223
+ after: snapshot
25190
25224
  });
25191
- var browserCommitQueryV1 = external_exports.strictObject({ commit: oid2 });
25225
+ var browserCommitChangeQueryV1 = external_exports.strictObject({ commit: oid2, id: external_exports.string().min(1).max(100) });
25192
25226
  var recordId = external_exports.string().regex(/^(?:DR|H)-[A-Za-z0-9_-]{1,64}$/);
25193
25227
  var browserRecordV1 = external_exports.strictObject({
25194
25228
  contract: external_exports.literal("browser-record"),
@@ -25210,6 +25244,27 @@ var browserInstructionFileV1 = external_exports.strictObject({
25210
25244
  binary: external_exports.boolean(),
25211
25245
  tooLarge: external_exports.boolean()
25212
25246
  });
25247
+ 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) });
25248
+ 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() });
25249
+ var browserWorkingV1 = external_exports.strictObject({
25250
+ contract: external_exports.literal("browser-working"),
25251
+ version: external_exports.literal(1),
25252
+ sessionId: external_exports.string(),
25253
+ head: oid2.nullable(),
25254
+ records: external_exports.array(pendingRecord),
25255
+ changes: external_exports.array(workingChange),
25256
+ withoutRecord: external_exports.array(external_exports.string())
25257
+ });
25258
+ var browserWorkingChangeV1 = external_exports.strictObject({
25259
+ contract: external_exports.literal("browser-working-change"),
25260
+ version: external_exports.literal(1),
25261
+ sessionId: external_exports.string(),
25262
+ change: workingChange,
25263
+ before: snapshot,
25264
+ after: snapshot
25265
+ });
25266
+ var browserWorkingChangeQueryV1 = external_exports.strictObject({ id: external_exports.string().regex(/^[SRDWI]-[a-z2-7]{10}$/) });
25267
+ 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
25268
 
25214
25269
  // ../../packages/contracts/dist/versions/changelog-v1.js
25215
25270
  var items = external_exports.array(external_exports.string().min(1));
@@ -25243,11 +25298,11 @@ var updateStateV1 = external_exports.strictObject({
25243
25298
  latestVersion: release.nullable()
25244
25299
  }).refine((value) => (value.status === "available" || value.status === "up-to-date") === (value.latestVersion !== null), "latestVersion is set exactly when the check produced a result.");
25245
25300
 
25246
- // ../../packages/contracts/dist/versions/project-init-v7.js
25247
- var projectInitV7 = external_exports.discriminatedUnion("ok", [
25301
+ // ../../packages/contracts/dist/versions/project-init-v8.js
25302
+ var projectInitV8 = external_exports.discriminatedUnion("ok", [
25248
25303
  external_exports.strictObject({
25249
25304
  contract: external_exports.literal("project-init"),
25250
- version: external_exports.literal(7),
25305
+ version: external_exports.literal(8),
25251
25306
  ok: external_exports.literal(true),
25252
25307
  outcome: external_exports.enum(["planned", "created", "already-initialized"]),
25253
25308
  rootPath: external_exports.string(),
@@ -25258,11 +25313,13 @@ var projectInitV7 = external_exports.discriminatedUnion("ok", [
25258
25313
  external_exports.strictObject({ kind: external_exports.literal("commit"), objectFormat: external_exports.enum(["sha1", "sha256"]), commit: external_exports.string() })
25259
25314
  ]),
25260
25315
  agentDocs: external_exports.strictObject({ mode: external_exports.enum(["install", "remove", "skip"]), paths: external_exports.array(external_exports.string()) }),
25316
+ // The file that keeps documents in LF on every OS. `created` is false when it was already there or the run is planned.
25317
+ lineEndings: external_exports.strictObject({ path: external_exports.literal(".gitifact/.gitattributes"), created: external_exports.boolean() }),
25261
25318
  update: updateStateV1,
25262
25319
  // Present only when a newer release exists. npx refreshes the project blocks without a global install; npmGlobal remains an alternative.
25263
25320
  install: external_exports.strictObject({ npx: external_exports.string().min(1), npmGlobal: external_exports.string().min(1) }).nullable()
25264
25321
  }),
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() }) })
25322
+ 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
25323
  ]);
25267
25324
 
25268
25325
  // ../../packages/contracts/dist/versions/update-v5.js
@@ -25448,6 +25505,8 @@ var messages_default3 = {
25448
25505
  "server.sessionChanged": "\uC11C\uBC84 \uC138\uC158\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.",
25449
25506
  "server.badRange": "\uC870\uD68C \uBC94\uC704\uB97C \uD655\uC778\uD558\uC138\uC694.",
25450
25507
  "server.commitNotFound": "\uC774 \uC800\uC7A5\uC18C\uC5D0 \uADF8 \uCEE4\uBC0B\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25508
+ "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.",
25509
+ "server.workingChangeNotFound": "\uCEE4\uBC0B \uC804 \uBCC0\uACBD\uC5D0 \uADF8 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
25451
25510
  "server.recordNotFound": "\uC774 \uC774\uB825\uC5D0 \uADF8 \uACB0\uC815\uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25452
25511
  "server.commitFileNotFound": "\uADF8 \uCEE4\uBC0B\uC774 \uBC14\uAFBC \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
25453
25512
  "server.instructionFileNotFound": "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -25491,6 +25550,7 @@ var messages_default3 = {
25491
25550
  "init.text.blockUpdated": "\uBE14\uB85D \uAC31\uC2E0",
25492
25551
  "init.text.storage": "\uC800\uC7A5 \uADDC\uC57D",
25493
25552
  "init.text.agentDocs": "\uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68",
25553
+ "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
25554
  "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
25555
  "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
25556
  "config.storeNotDirectory": ".gitifact\uB294 \uB9C1\uD06C\uAC00 \uC544\uB2CC \uC77C\uBC18 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
@@ -25534,7 +25594,7 @@ var messages_default3 = {
25534
25594
  "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
25595
  "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
25596
  "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.",
25597
+ "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
25598
  "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
25539
25599
  "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25540
25600
  "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 +25621,9 @@ var messages_default3 = {
25561
25621
  "input.size": "\uC785\uB825 \uD06C\uAE30\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4.",
25562
25622
  "input.json": "\uC785\uB825 \uD30C\uC77C\uC740 UTF-8 JSON\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
25563
25623
  "docs.draft": "\uCD08\uC548",
25624
+ "docs.state.added": "\uCEE4\uBC0B \uC804: \uCD94\uAC00",
25625
+ "docs.state.modified": "\uCEE4\uBC0B \uC804: \uBCC0\uACBD",
25626
+ "docs.state.deleted": "\uCEE4\uBC0B \uC804: \uC0AD\uC81C \uC608\uC815",
25564
25627
  "docs.feature": "\uAE30\uB2A5",
25565
25628
  "docs.requirements": "\uC694\uAD6C\uC0AC\uD56D",
25566
25629
  "docs.designs": "\uC124\uACC4",
@@ -25644,7 +25707,7 @@ var messages_default3 = {
25644
25707
  "help.newTitle": "\uBB38\uC11C \uC81C\uBAA9",
25645
25708
  "help.newDescription": "\uD55C \uC904 \uC124\uBA85",
25646
25709
  "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)",
25710
+ "help.instructionsList": "AGENTS.md\uC640 \uC9C0\uCE68 \uBAA9\uB85D (\uC9C0\uCE68\uB9C8\uB2E4 \uB538\uB9B0 \uD30C\uC77C\uC758 \uC81C\uBAA9\uACFC \uC124\uBA85)",
25648
25711
  "help.instructionsShow": "\uC9C0\uCE68 index.md \uC6D0\uBB38, \uB530\uB974\uB294 \uC124\uACC4, \uD3F4\uB354 \uD30C\uC77C \uBAA9\uB85D",
25649
25712
  "help.instructionsShowTargets": "\uC9C0\uCE68 \uD3F4\uB354 \uC774\uB984\uC774\uB098 I- ID (\uC5EC\uB7EC \uAC1C \uAC00\uB2A5)",
25650
25713
  "help.instructionsShowFile": "\uC9C0\uCE68 \uD3F4\uB354 \uC548\uC758 \uD30C\uC77C \uD558\uB098\uB97C \uC77D\uC74C (\uC608: references/code-style.md)",
@@ -25657,18 +25720,26 @@ var messages_default3 = {
25657
25720
  "help.listQ": "\uC81C\uBAA9\xB7\uC124\uBA85\xB7\uBCF8\uBB38\uC5D0 \uC774 \uB9D0\uC774 \uC788\uB294 \uAC83\uB9CC",
25658
25721
  "help.listAuthor": "\uC774 Git \uC791\uC131\uC790(\uC774\uB984\uC774\uB098 \uC774\uBA54\uC77C)\uAC00 \uBC14\uAFBC \uAC83\uB9CC",
25659
25722
  "help.listSort": "\uC815\uB82C \uAE30\uC900",
25660
- "help.listLimit": "\uC55E\uC758 n\uAC1C\uB9CC",
25723
+ "help.listLimit": "\uD55C \uD398\uC774\uC9C0\uC5D0 n\uAC1C (\uAE30\uBCF8 20)",
25724
+ "help.listAll": "\uD398\uC774\uC9C0\uB85C \uB098\uB204\uC9C0 \uC54A\uACE0 \uC804\uBD80",
25725
+ "help.listAfter": "\uC774 \uD56D\uBAA9 \uB2E4\uC74C\uBD80\uD130 (\uC55E \uD398\uC774\uC9C0 \uB05D\uC5D0 \uB098\uC628 \uAC12)",
25661
25726
  "help.listFields": "\uC774 \uC5F4\uB9CC \uC27C\uD45C\uB85C (\uD14D\uC2A4\uD2B8\uB294 \uD0ED\uC73C\uB85C \uB098\uB208 \uD55C \uC904)",
25662
25727
  "list.limit": "1 \uC774\uC0C1\uC758 \uC815\uC218\uB97C \uC801\uC73C\uC138\uC694.",
25663
25728
  "list.unknownFields": "\uC5C6\uB294 \uC5F4\uC785\uB2C8\uB2E4: {fields}. \uC4F8 \uC218 \uC788\uB294 \uC5F4: {columns}",
25664
25729
  "list.since": "\uB0A0\uC9DC(YYYY-MM-DD)\uB098 \uCEE4\uBC0B\uC774 \uC544\uB2D9\uB2C8\uB2E4: {value}",
25730
+ "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.",
25731
+ "list.more": "{unit} {shown}/{total}\uAC1C. \uB2E4\uC74C \uD398\uC774\uC9C0: \uAC19\uC740 \uBA85\uB839\uC5D0 --after {cursor}",
25732
+ "list.items": "\uD56D\uBAA9",
25733
+ "list.features": "\uAE30\uB2A5",
25734
+ "list.commits": "\uCEE4\uBC0B",
25735
+ "list.records": "\uACB0\uC815\uAE30\uB85D",
25665
25736
  "docs.otherResource": "\uC774 \uBA85\uB839\uC774 \uB2E4\uB8E8\uB294 \uBB38\uC11C\uAC00 \uC544\uB2D9\uB2C8\uB2E4: {ids}. {command}\uB85C \uC77D\uC73C\uC138\uC694.",
25666
25737
  "docs.noMatch": "\uC870\uAC74\uC5D0 \uB9DE\uB294 \uAC83\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25667
25738
  "docs.requirement": "\uC694\uAD6C\uC0AC\uD56D",
25668
25739
  "docs.design": "\uC124\uACC4",
25669
25740
  "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
25741
  "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",
25742
+ "instructions.filesLimited": "(\uD30C\uC77C\uC774 \uB354 \uC788\uC5B4 \uC55E {count}\uAC1C\uB9CC \uBCF4\uC785\uB2C8\uB2E4)",
25672
25743
  "instructions.empty": "\uC9C0\uCE68\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. instructions new\uB85C \uB9CC\uB4DC\uC138\uC694.",
25673
25744
  "instructions.fileWithOne": "--file\uC740 \uC9C0\uCE68 \uD558\uB098\uC640 \uD568\uAED8 \uC4F0\uBA70 --ref\uC640 \uD568\uAED8 \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
25674
25745
  "instructions.unknownFile": "\uC9C0\uCE68 {name} \uD3F4\uB354\uC5D0 \uADF8 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
@@ -25738,6 +25809,8 @@ var messages_default4 = {
25738
25809
  "server.sessionChanged": "The server session changed. Reconnect to continue.",
25739
25810
  "server.badRange": "Check the requested range.",
25740
25811
  "server.commitNotFound": "This repository has no such commit.",
25812
+ "server.cursorNotFound": "The place to read on from is no longer in the list. Load it again from the start.",
25813
+ "server.workingChangeNotFound": "No uncommitted change of that document.",
25741
25814
  "server.recordNotFound": "This history has no such decision record.",
25742
25815
  "server.commitFileNotFound": "That commit did not change this file.",
25743
25816
  "server.instructionFileNotFound": "The instruction file was not found.",
@@ -25781,6 +25854,7 @@ var messages_default4 = {
25781
25854
  "init.text.blockUpdated": "Block refreshed",
25782
25855
  "init.text.storage": "Storage convention",
25783
25856
  "init.text.agentDocs": "Agent instructions",
25857
+ "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
25858
  "init.text.updateAvailable": "Version {version} is available. Run {command} to use it and refresh the project instructions.",
25785
25859
  "init.failed": "Initialization failed. Check file permissions and the generated configuration and temporary files.",
25786
25860
  "config.storeNotDirectory": ".gitifact must be a regular directory, not a link.",
@@ -25824,7 +25898,7 @@ var messages_default4 = {
25824
25898
  "update.text.noBlock": "No instruction files contain a GITIFACT block. Run npx --yes gitifact@{version} init to install one.",
25825
25899
  "update.text.notInitialized": "Only the version was checked because this project is not initialized.",
25826
25900
  "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.",
25901
+ "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
25902
  "update.text.committed": "Instruction blocks committed: {commit} ({paths})",
25829
25903
  "update.text.commitNothing": "No instruction block changes to commit.",
25830
25904
  "update.text.commitSkipped.untracked": "Not committed. Instruction files are untracked: {paths}",
@@ -25851,6 +25925,9 @@ var messages_default4 = {
25851
25925
  "input.size": "The input is too large.",
25852
25926
  "input.json": "The input file must be UTF-8 JSON.",
25853
25927
  "docs.draft": "draft",
25928
+ "docs.state.added": "uncommitted: added",
25929
+ "docs.state.modified": "uncommitted: modified",
25930
+ "docs.state.deleted": "uncommitted: to be deleted",
25854
25931
  "docs.feature": "Feature",
25855
25932
  "docs.requirements": "Requirements",
25856
25933
  "docs.designs": "Design",
@@ -25934,7 +26011,7 @@ var messages_default4 = {
25934
26011
  "help.newTitle": "Document title",
25935
26012
  "help.newDescription": "One-line description",
25936
26013
  "help.instructions": "List, read and create project instructions",
25937
- "help.instructionsList": "AGENTS.md and the instructions (with how many files each folder holds)",
26014
+ "help.instructionsList": "AGENTS.md and the instructions (with the title and description of each file in their folders)",
25938
26015
  "help.instructionsShow": "An instruction's index.md as written, the designs that follow it and its folder's files",
25939
26016
  "help.instructionsShowTargets": "Instruction folder names or I- IDs (one or more)",
25940
26017
  "help.instructionsShowFile": "Read one file of the instruction folder (e.g. references/code-style.md)",
@@ -25947,18 +26024,26 @@ var messages_default4 = {
25947
26024
  "help.listQ": "Only those whose title, description or text holds these words",
25948
26025
  "help.listAuthor": "Only those this Git author (name or email) changed",
25949
26026
  "help.listSort": "Sort key",
25950
- "help.listLimit": "Only the first n",
26027
+ "help.listLimit": "n per page (20 by default)",
26028
+ "help.listAll": "Everything, not split into pages",
26029
+ "help.listAfter": "Start after this item (the value at the end of the previous page)",
25951
26030
  "help.listFields": "Only these columns, comma-separated (text prints one tab-separated line each)",
25952
26031
  "list.limit": "Give a whole number of 1 or more.",
25953
26032
  "list.unknownFields": "Unknown columns: {fields}. Available columns: {columns}",
25954
26033
  "list.since": "Not a date (YYYY-MM-DD) or a commit: {value}",
26034
+ "list.after": "No such cursor in the list: {cursor}. Read again from the start without --after.",
26035
+ "list.more": "{unit}: {shown} of {total}. Next page: the same command with --after {cursor}",
26036
+ "list.items": "Items",
26037
+ "list.features": "Features",
26038
+ "list.commits": "Commits",
26039
+ "list.records": "Records",
25955
26040
  "docs.otherResource": "Not a document this command reads: {ids}. Use {command}.",
25956
26041
  "docs.noMatch": "Nothing matches.",
25957
26042
  "docs.requirement": "requirement",
25958
26043
  "docs.design": "design",
25959
26044
  "instructions.agents": "AGENTS.md \u2014 the index outside the block says which instruction to read for which work",
25960
26045
  "instructions.noAgents": "No AGENTS.md \u2014 nothing tells when to read which instruction",
25961
- "instructions.files": "{count} files",
26046
+ "instructions.filesLimited": "(the folder holds more; only the first {count} are shown)",
25962
26047
  "instructions.empty": "No instructions yet. Create one with instructions new.",
25963
26048
  "instructions.fileWithOne": "--file takes one instruction and does not go with --ref.",
25964
26049
  "instructions.unknownFile": "The folder of instruction {name} has no such file: {path}",
@@ -26069,9 +26154,9 @@ async function dispatch(routes, sessionId, url2, path3, request, response, allow
26069
26154
  if (found.query) {
26070
26155
  const keys2 = [...url2.searchParams.keys()];
26071
26156
  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;
26157
+ const parsed2 = found.query.safeParse(Object.fromEntries(url2.searchParams));
26158
+ if (!parsed2.success) return fail2(response, 400, "BAD_REQUEST", t2("server.badRange"));
26159
+ query = parsed2.data;
26075
26160
  } else if (url2.search) return fail2(response, 400, "BAD_REQUEST", t2("server.noQuery"));
26076
26161
  if (found.session && request.headers["x-gitifact-session"] !== sessionId) return fail2(response, 409, "SESSION_CHANGED", t2("server.sessionChanged"));
26077
26162
  try {
@@ -26329,7 +26414,7 @@ function storeReader(cwd) {
26329
26414
  import { DatabaseSync } from "node:sqlite";
26330
26415
  import { lstat, mkdir, rm, writeFile } from "node:fs/promises";
26331
26416
  import { join as join2 } from "node:path";
26332
- var CACHE_FORMAT = 8;
26417
+ var CACHE_FORMAT = 9;
26333
26418
  var CACHE_DIR = ".gitifact/cache";
26334
26419
  var SCHEMA = `
26335
26420
  -- Working-tree files already read: the modification time and size decide whether a file is read again.
@@ -26344,13 +26429,17 @@ var SCHEMA = `
26344
26429
  CREATE INDEX doc_references_to ON doc_references (to_id);
26345
26430
  -- Commits already read, and with which parser: 'current', 'legacy' (0.7, before a migration) or 'migration' (hidden).
26346
26431
  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.
26432
+ -- One row per change. 'row' is the list shape as JSON with its records by ID; 'detail' where each side's text is read
26433
+ -- again from Git (commit and path), or the text itself for 0.7 history, which has no files to read again.
26348
26434
  CREATE TABLE changes (
26349
26435
  key TEXT PRIMARY KEY, oid TEXT NOT NULL, ord INTEGER NOT NULL,
26350
26436
  id TEXT NOT NULL, kind TEXT NOT NULL, types TEXT NOT NULL, email TEXT NOT NULL, date TEXT NOT NULL,
26351
26437
  before_spec TEXT, after_spec TEXT, needle TEXT NOT NULL, row TEXT NOT NULL, detail TEXT NOT NULL);
26352
26438
  CREATE INDEX changes_by_commit ON changes (oid, ord);
26353
26439
  CREATE INDEX changes_by_id ON changes (id);
26440
+ -- The records each commit added, once each; change rows name them by ID.
26441
+ CREATE TABLE records (oid TEXT NOT NULL, id TEXT NOT NULL, title TEXT NOT NULL, sections TEXT NOT NULL, PRIMARY KEY (oid, id));
26442
+ CREATE INDEX records_by_id ON records (id);
26354
26443
  -- All reachable record commits of one HEAD, children before parents (pos 0 is newest).
26355
26444
  CREATE TABLE lineage (head TEXT NOT NULL, pos INTEGER NOT NULL, oid TEXT NOT NULL, PRIMARY KEY (head, pos));
26356
26445
  CREATE INDEX lineage_by_commit ON lineage (head, oid);
@@ -26377,6 +26466,10 @@ function prepare(db) {
26377
26466
  db.exec("ROLLBACK");
26378
26467
  throw error62;
26379
26468
  }
26469
+ try {
26470
+ db.exec("VACUUM");
26471
+ } catch {
26472
+ }
26380
26473
  }
26381
26474
  function createCacheDatabase(root) {
26382
26475
  let path3;
@@ -26472,24 +26565,23 @@ var place = (doc) => doc.path.replace(/^\.gitifact\//, "");
26472
26565
  function createDocumentCache(root, database) {
26473
26566
  let syncing;
26474
26567
  async function walk() {
26475
- const found = /* @__PURE__ */ new Map();
26568
+ const found = [];
26476
26569
  async function visit2(relative, depth) {
26477
26570
  const info5 = await lstat2(join3(root, ...relative.split("/"))).catch((error62) => {
26478
26571
  if (error62.code === "ENOENT") return void 0;
26479
26572
  throw error62;
26480
26573
  });
26481
- if (!info5 || found.size >= COUNT_LIMIT) return;
26574
+ if (!info5 || found.length >= 2 * COUNT_LIMIT) return;
26482
26575
  if (info5.isDirectory() && !info5.isSymbolicLink()) {
26483
26576
  if (depth > 10) return;
26484
- for (const name2 of (await readdir2(join3(root, ...relative.split("/")))).sort()) await visit2(relative + "/" + name2, depth + 1);
26577
+ await Promise.all((await readdir2(join3(root, ...relative.split("/")))).map((name2) => visit2(relative + "/" + name2, depth + 1)));
26485
26578
  return;
26486
26579
  }
26487
- found.set(relative, { mtime: info5.mtimeMs, size: info5.size, link: info5.isSymbolicLink() || !info5.isFile() });
26580
+ found.push([relative, { mtime: info5.mtimeMs, size: info5.size, link: info5.isSymbolicLink() || !info5.isFile() }]);
26488
26581
  }
26489
- await visit2(SPEC_ROOT, 0);
26490
- await visit2(WIKI_ROOT, 0);
26491
- await visit2(INSTRUCTIONS_ROOT, 0);
26492
- return found;
26582
+ await Promise.all([visit2(SPEC_ROOT, 0), visit2(WIKI_ROOT, 0), visit2(INSTRUCTIONS_ROOT, 0)]);
26583
+ found.sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0);
26584
+ return new Map(found.slice(0, COUNT_LIMIT));
26493
26585
  }
26494
26586
  async function read(path3, seen) {
26495
26587
  try {
@@ -26513,7 +26605,7 @@ function createDocumentCache(root, database) {
26513
26605
  });
26514
26606
  const gone = [...known.keys()].filter((path3) => !seen.has(path3));
26515
26607
  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) })));
26608
+ const parsed2 = await Promise.all(changed.map(async ([path3, s]) => ({ path: path3, seen: s, ...await read(path3, s) })));
26517
26609
  await database.with((db) => transaction(db, () => {
26518
26610
  const forget = (path3) => {
26519
26611
  const row = db.prepare("SELECT id FROM documents WHERE path = ?").get(path3);
@@ -26530,7 +26622,7 @@ function createDocumentCache(root, database) {
26530
26622
  const document = db.prepare("INSERT INTO documents (path, id, kind, feature, title, description, ord, doc) VALUES (?, ?, ?, ?, ?, ?, ?, ?)");
26531
26623
  const reference2 = db.prepare("INSERT INTO doc_references (from_id, to_id, type) VALUES (?, ?, ?)");
26532
26624
  const search = db.prepare("INSERT INTO search (scope, kind, ref, oid, payload, title, place, body) VALUES (?,?,?,?,?,?,?,?)");
26533
- for (const p of parsed) {
26625
+ for (const p of parsed2) {
26534
26626
  forget(p.path);
26535
26627
  file2.run(p.path, p.seen.mtime, p.seen.size, p.problem ? JSON.stringify(p.problem) : null);
26536
26628
  const doc = p.doc;
@@ -26589,28 +26681,23 @@ function createDocumentCache(root, database) {
26589
26681
  throw error62;
26590
26682
  });
26591
26683
  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
- }
26684
+ const read2 = await Promise.all([...seen].map(async ([path3, s]) => {
26685
+ if (!readable(path3)) return { path: path3 };
26686
+ if (instructionFile3(path3) && !path3.endsWith(".md")) return { path: path3, text: "" };
26687
+ if (s.link) return { path: path3, problem: docProblem("PATH_UNSUPPORTED", path3) };
26688
+ if (s.size > (path3 === HISTORY_PATH ? 64 * FILE_LIMIT : FILE_LIMIT)) return { path: path3, problem: docProblem("FILE_TOO_LARGE", path3) };
26608
26689
  try {
26609
- files.set(path3, decode3(await readFile2(join3(root, ...path3.split("/")))));
26690
+ return { path: path3, text: decode3(await readFile2(join3(root, ...path3.split("/")))) };
26610
26691
  } catch (error62) {
26611
- if (error62 instanceof TypeError) problems.push(docProblem("INVALID_CHARACTERS", path3));
26612
- else throw error62;
26692
+ if (error62 instanceof TypeError) return { path: path3, problem: docProblem("INVALID_CHARACTERS", path3) };
26693
+ throw error62;
26613
26694
  }
26695
+ }));
26696
+ const files = /* @__PURE__ */ new Map();
26697
+ const problems = [];
26698
+ for (const r of read2) {
26699
+ if (r.text !== void 0) files.set(r.path, r.text);
26700
+ if (r.problem) problems.push(r.problem);
26614
26701
  }
26615
26702
  return { files, problems };
26616
26703
  },
@@ -26866,7 +26953,8 @@ function createCommitChanges(git) {
26866
26953
  types: change2.types,
26867
26954
  before: change2.before,
26868
26955
  after: change2.after,
26869
- records: recordsFor(records2, change2.id)
26956
+ records: recordsFor(records2, change2.id),
26957
+ source: { before: c.parent ?? null, after: c.commit }
26870
26958
  }));
26871
26959
  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
26960
  const recordsAt = (blobs, rev, paths2) => paths2.flatMap((path3) => {
@@ -26951,6 +27039,21 @@ function createCommitChanges(git) {
26951
27039
  return {
26952
27040
  /** Every document and reason file of a commit (path → text): what `specs show --ref` and `changes` compare against. */
26953
27041
  tree,
27042
+ /**
27043
+ * Documents read again at the commits and paths a change was read at, by one `cat-file --batch`: the text a
27044
+ * change's detail shows, which the cache does not keep. A place that no longer reads is null.
27045
+ */
27046
+ async sides(places) {
27047
+ const blobs = await readBlobs([...new Set(places.map((p) => p.rev + ":" + p.path))]);
27048
+ return places.map((p) => {
27049
+ const text4 = blobs.get(p.rev + ":" + p.path);
27050
+ try {
27051
+ return text4 === void 0 ? null : snapshot3(parseDocumentFile(p.path, text4), p.specId);
27052
+ } catch {
27053
+ return null;
27054
+ }
27055
+ });
27056
+ },
26954
27057
  /** All reachable record commits, children before parents even when author clocks differ. */
26955
27058
  async lineage(head) {
26956
27059
  return git.decode(await git.run(["rev-list", "--full-history", "--date-order", head, "--", ...HISTORY_PATHSPECS])).split("\n").filter(Boolean);
@@ -26989,8 +27092,10 @@ function createCommitChanges(git) {
26989
27092
  // src/adapters/cache/history.ts
26990
27093
  var listed = (e) => {
26991
27094
  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) };
27095
+ const { source: _source, ...event2 } = e;
27096
+ return { ...event2, before: reference2(e.before), after: reference2(e.after) };
26993
27097
  };
27098
+ var sideOf3 = (snapshot5, rev) => !snapshot5 ? null : rev ? { rev, path: snapshot5.path } : snapshot5;
26994
27099
  var titleOf = (e) => (e.after ?? e.before)?.title ?? e.id;
26995
27100
  var DAY = 864e5;
26996
27101
  var KEPT_HEADS = 4;
@@ -27003,11 +27108,15 @@ function createHistory(database, git) {
27003
27108
  function insert(db, commits, readers) {
27004
27109
  const commit = db.prepare("INSERT OR IGNORE INTO commits (oid, reader) VALUES (?, ?)");
27005
27110
  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 (?,?,?,?,?,?,?,?,?,?,?,?,?)");
27111
+ const record4 = db.prepare("INSERT OR IGNORE INTO records (oid, id, title, sections) VALUES (?, ?, ?, ?)");
27006
27112
  const search = db.prepare("INSERT INTO search (scope, kind, ref, oid, payload, title, place, body) VALUES (?,?,?,?,?,?,?,?)");
27007
27113
  transaction(db, () => {
27008
27114
  for (const c of commits) {
27009
27115
  if (!Number(commit.run(c.commit, readers.get(c.commit) ?? "current").changes)) continue;
27010
27116
  c.events.forEach((e, ord) => {
27117
+ for (const r of e.records) record4.run(e.commit, r.id, r.title, JSON.stringify(r.sections));
27118
+ const row = { ...listed(e), records: e.records.map((r) => r.id) };
27119
+ const detail = { before: sideOf3(e.before, e.source?.before), after: sideOf3(e.after, e.source?.after) };
27011
27120
  change2.run(
27012
27121
  e.key,
27013
27122
  e.commit,
@@ -27020,8 +27129,8 @@ function createHistory(database, git) {
27020
27129
  e.before?.specId ?? null,
27021
27130
  e.after?.specId ?? null,
27022
27131
  [e.id, e.before?.title ?? "", e.after?.title ?? ""].join(" ").toLowerCase(),
27023
- JSON.stringify(listed(e)),
27024
- JSON.stringify({ before: e.before, after: e.after })
27132
+ JSON.stringify(row),
27133
+ JSON.stringify(detail)
27025
27134
  );
27026
27135
  const line2 = e.records.length ? e.records.map((r) => [r.title, ...r.sections.map((s) => s.body)].join(" ")).join(" \xB7 ") : e.message;
27027
27136
  const where2 = e.commit.slice(0, 7) + " \xB7 " + e.author;
@@ -27048,6 +27157,7 @@ function createHistory(database, git) {
27048
27157
  for (const oid3 of stale) {
27049
27158
  db.prepare("DELETE FROM commits WHERE oid = ?").run(oid3);
27050
27159
  db.prepare("DELETE FROM changes WHERE oid = ?").run(oid3);
27160
+ db.prepare("DELETE FROM records WHERE oid = ?").run(oid3);
27051
27161
  db.prepare("DELETE FROM search WHERE scope = 'history' AND oid = ?").run(oid3);
27052
27162
  }
27053
27163
  }));
@@ -27078,6 +27188,39 @@ function createHistory(database, git) {
27078
27188
  }
27079
27189
  return pending;
27080
27190
  }
27191
+ async function readCommit(commit) {
27192
+ const known = await database.with((db) => !!db.prepare("SELECT 1 FROM commits WHERE oid = ?").get(commit));
27193
+ if (known) return;
27194
+ const read = await changes2.of([commit]).catch(() => void 0);
27195
+ if (read) await database.with((db) => insert(db, read, /* @__PURE__ */ new Map()));
27196
+ }
27197
+ function hydrate(db, rows) {
27198
+ const events = rows.map((row) => JSON.parse(row));
27199
+ const read = db.prepare("SELECT id, title, sections FROM records WHERE oid = ?");
27200
+ const byCommit = /* @__PURE__ */ new Map();
27201
+ for (const e of events) if (e.records.length && !byCommit.has(e.commit)) {
27202
+ byCommit.set(e.commit, new Map(read.all(e.commit).map((r) => [r.id, { id: r.id, title: r.title, sections: JSON.parse(r.sections) }])));
27203
+ }
27204
+ return events.map((e) => ({ ...e, records: e.records.flatMap((id) => {
27205
+ const r = byCommit.get(e.commit)?.get(id);
27206
+ return r ? [r] : [];
27207
+ }) }));
27208
+ }
27209
+ async function detailOf(rows) {
27210
+ const events = await database.with((db) => hydrate(db, rows.map((r) => r.row)));
27211
+ const sides = rows.map((r) => JSON.parse(r.detail));
27212
+ const places = [];
27213
+ sides.forEach((side, i) => {
27214
+ for (const key of ["before", "after"]) {
27215
+ const value = side[key];
27216
+ if (value && "rev" in value) places.push({ rev: value.rev, path: value.path, specId: events[i][key]?.specId ?? "" });
27217
+ }
27218
+ });
27219
+ const read = places.length ? await changes2.sides(places) : [];
27220
+ const text4 = new Map(places.map((p, i) => [p.rev + ":" + p.path, read[i] ?? null]));
27221
+ const resolve = (value) => !value ? null : "rev" in value ? text4.get(value.rev + ":" + value.path) ?? null : value;
27222
+ return events.map((event2, i) => ({ event: event2, before: resolve(sides[i].before), after: resolve(sides[i].after) }));
27223
+ }
27081
27224
  function where(filter) {
27082
27225
  const clauses = [];
27083
27226
  const params = [];
@@ -27107,21 +27250,55 @@ function createHistory(database, git) {
27107
27250
  ensure,
27108
27251
  /** The document and reason files of one commit, read from Git and not kept. */
27109
27252
  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) {
27253
+ /**
27254
+ * The changes that match, newest first, a page of whole commits: `limit` commits after the commit `after`, each
27255
+ * with all of its matching changes, and how many changes and commits match in all. `next` is the last commit of the
27256
+ * page when more follow. A commit `after` that is not in `head`'s matching history answers undefined.
27257
+ */
27258
+ async commits(head, filter, after, limit) {
27112
27259
  await ensure(head);
27113
27260
  return database.with((db) => {
27114
27261
  const { sql, params } = where(filter);
27115
27262
  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)) };
27263
+ const counted = db.prepare("SELECT count(*) AS n, count(DISTINCT c.oid) AS commits " + from).get(head, ...params);
27264
+ let start = -1;
27265
+ if (after !== void 0) {
27266
+ const found = db.prepare("SELECT l.pos AS pos " + from + " AND l.oid = ? LIMIT 1").get(head, ...params, after);
27267
+ if (!found) return void 0;
27268
+ start = found.pos;
27269
+ }
27270
+ 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);
27271
+ const shown = commits.slice(0, limit);
27272
+ 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);
27273
+ return {
27274
+ total: Number(counted.n),
27275
+ commits: Number(counted.commits),
27276
+ next: commits.length > limit ? shown[shown.length - 1].oid : null,
27277
+ events: hydrate(db, rows.map((r) => r.row))
27278
+ };
27279
+ });
27280
+ },
27281
+ /** The changes of one commit without their text, a page at a time after the change `after` (a key): the commit page's list. */
27282
+ async commitChanges(commit, after, limit) {
27283
+ await readCommit(commit);
27284
+ return database.with((db) => {
27285
+ const all = db.prepare("SELECT key, row FROM changes WHERE oid = ? ORDER BY ord").all(commit);
27286
+ const start = after === void 0 ? 0 : all.findIndex((r) => r.key === after) + 1;
27287
+ if (after !== void 0 && !start) return void 0;
27288
+ const shown = all.slice(start, start + limit);
27289
+ 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
27290
  });
27120
27291
  },
27292
+ /** One change of a commit with the text on both sides, read again from Git: the change the reader opened. */
27293
+ async commitChange(commit, id) {
27294
+ await readCommit(commit);
27295
+ 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));
27296
+ return rows.length ? (await detailOf(rows))[0] : void 0;
27297
+ },
27121
27298
  /** Every change of one document, newest first: `records list --doc`. */
27122
27299
  async ofDocument(head, id) {
27123
27300
  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)));
27301
+ 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
27302
  },
27126
27303
  /** Who changed which document in which commit of `head`'s history, newest first: what the lists filter and sort by. */
27127
27304
  async changesOf(head) {
@@ -27130,25 +27307,60 @@ function createHistory(database, git) {
27130
27307
  FROM lineage l JOIN changes c ON c.oid = l.oid WHERE l.head = ? ORDER BY l.pos, c.ord`).all(head));
27131
27308
  },
27132
27309
  /**
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.
27310
+ * The records of `head`'s history that match, newest first, a page at a time: what `records list` shows after the
27311
+ * records not committed yet. The filters and the page are SQL over the records table, so a page reads only its
27312
+ * rows. `after` is the ID of the last record of the previous page; `limit` undefined is every match.
27135
27313
  */
27136
- async recordsOf(head) {
27314
+ async records(head, filter, after, limit) {
27137
27315
  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;
27316
+ return database.with((db) => {
27317
+ const clauses = [];
27318
+ const params = [];
27319
+ if (filter.author) {
27320
+ clauses.push("(lower(json_extract(row, '$.author')) = ? OR lower(json_extract(row, '$.email')) = ?)");
27321
+ params.push(filter.author.toLowerCase(), filter.author.toLowerCase());
27148
27322
  }
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()];
27323
+ if (filter.q?.trim()) {
27324
+ clauses.push("instr(lower(title || ' ' || body || ' ' || json_extract(row, '$.message')), ?) > 0");
27325
+ params.push(filter.q.trim().toLowerCase());
27326
+ }
27327
+ if (filter.from !== void 0) {
27328
+ clauses.push("unixepoch(json_extract(row, '$.date')) * 1000 >= ?");
27329
+ params.push(filter.from);
27330
+ }
27331
+ if (filter.commits) {
27332
+ clauses.push("oid IN (SELECT value FROM json_each(?))");
27333
+ params.push(JSON.stringify(filter.commits));
27334
+ }
27335
+ 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,
27336
+ (SELECT group_concat(json_extract(s.value, '$.body'), ' ') FROM json_each(r.sections) s) AS body,
27337
+ (SELECT c.row FROM changes c WHERE c.oid = r.oid ORDER BY c.ord LIMIT 1) AS row
27338
+ FROM lineage l JOIN records r ON r.oid = l.oid WHERE l.head = ?)
27339
+ SELECT * FROM meta WHERE 1 = 1${clauses.map((c) => " AND " + c).join("")}`;
27340
+ const total = Number(db.prepare(`SELECT count(*) AS n FROM (${matching})`).get(head, ...params).n);
27341
+ let from;
27342
+ if (after !== void 0) {
27343
+ from = db.prepare(`SELECT pos, id FROM (${matching}) WHERE id = ?`).get(head, ...params, after);
27344
+ if (!from) return void 0;
27345
+ }
27346
+ 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);
27347
+ 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");
27348
+ const records2 = rows.map((r) => {
27349
+ const commit = JSON.parse(r.row);
27350
+ return {
27351
+ id: r.id,
27352
+ title: r.title,
27353
+ sections: JSON.parse(r.sections),
27354
+ commit: r.oid,
27355
+ date: commit.date,
27356
+ author: commit.author,
27357
+ email: commit.email,
27358
+ message: commit.message,
27359
+ docs: docsOf.all(r.oid, r.id).map((d) => d.id)
27360
+ };
27361
+ });
27362
+ return { records: records2, total };
27363
+ });
27152
27364
  },
27153
27365
  /** Counts over all of history and its newest commits, for the overview. */
27154
27366
  async summary(head) {
@@ -27163,7 +27375,7 @@ function createHistory(database, git) {
27163
27375
  const recent = commits.slice(0, RECENT_COMMITS).map((c) => ({
27164
27376
  commit: c.oid,
27165
27377
  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))
27378
+ 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
27379
  }));
27168
27380
  return { total, byType: { created: count2("created"), modified: count2("modified"), moved: count2("moved"), deleted: count2("deleted") }, pulse, recent };
27169
27381
  });
@@ -27171,19 +27383,7 @@ function createHistory(database, git) {
27171
27383
  /** The commit of `head`'s history that added a record, found through the records its changes carry. */
27172
27384
  async commitOfRecord(head, id) {
27173
27385
  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) })));
27386
+ return database.with((db) => db.prepare("SELECT l.oid AS oid 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)?.oid);
27187
27387
  },
27188
27388
  /**
27189
27389
  * Documents whose title, place or text holds the query — a title match before a place match before a text match —
@@ -27242,28 +27442,89 @@ function openCache(root, git) {
27242
27442
  };
27243
27443
  }
27244
27444
 
27245
- // 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";
27445
+ // src/adapters/git/pending-records.ts
27446
+ import { lstat as lstat3, readdir as readdir3, readFile as readFile3 } from "node:fs/promises";
27247
27447
  import { join as join4 } from "node:path";
27448
+ var decoder = new TextDecoder("utf-8", { fatal: true, ignoreBOM: true });
27449
+ var info = (path3) => lstat3(path3).catch((error62) => {
27450
+ if (error62.code === "ENOENT") return void 0;
27451
+ throw error62;
27452
+ });
27453
+ async function recordFolders(root) {
27454
+ return (await readdir3(join4(root, ...RECORDS_ROOT.split("/"))).catch((error62) => {
27455
+ if (error62.code === "ENOENT") return [];
27456
+ throw error62;
27457
+ })).filter(isRecordFolder);
27458
+ }
27459
+ async function findRecord(root, id) {
27460
+ for (const folder of await recordFolders(root)) {
27461
+ const path3 = `${RECORDS_ROOT}/${folder}/${id}.md`;
27462
+ if ((await info(join4(root, ...path3.split("/"))))?.isFile()) return path3;
27463
+ }
27464
+ return void 0;
27465
+ }
27466
+ async function readPendingRecords(root, run) {
27467
+ const status = (await run(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", RECORDS_ROOT])).toString("utf8").split("\0").filter(Boolean);
27468
+ const files = /* @__PURE__ */ new Map();
27469
+ const altered = [];
27470
+ const problems = [];
27471
+ for (const entry2 of status) {
27472
+ const state2 = entry2.slice(0, 2);
27473
+ const path3 = entry2.slice(3);
27474
+ if (state2 !== "??" && !state2.includes("A")) {
27475
+ altered.push(path3);
27476
+ continue;
27477
+ }
27478
+ const file2 = join4(root, ...path3.split("/"));
27479
+ const stat3 = await info(file2);
27480
+ if (!stat3?.isFile() || stat3.isSymbolicLink()) {
27481
+ problems.push(docProblem("PATH_UNSUPPORTED", path3));
27482
+ continue;
27483
+ }
27484
+ if (stat3.size > 1024 * 1024) {
27485
+ problems.push(docProblem("FILE_TOO_LARGE", path3));
27486
+ continue;
27487
+ }
27488
+ try {
27489
+ files.set(path3, decoder.decode(await readFile3(file2)));
27490
+ } catch {
27491
+ problems.push(docProblem("INVALID_CHARACTERS", path3));
27492
+ }
27493
+ }
27494
+ const folders = await recordFolders(root);
27495
+ for (const path3 of files.keys()) {
27496
+ const id = recordIdOf(path3);
27497
+ if (!id) continue;
27498
+ for (const folder of folders) {
27499
+ const other = `${RECORDS_ROOT}/${folder}/${id}.md`;
27500
+ if (other !== path3 && !files.has(other) && (await info(join4(root, ...other.split("/"))))?.isFile()) problems.push(docProblem("DUPLICATE_RECORD_ID", path3, { id, other }));
27501
+ }
27502
+ }
27503
+ return { files, altered: altered.sort(), problems };
27504
+ }
27505
+
27506
+ // src/adapters/filesystem/config-file.ts
27507
+ import { lstat as lstat4, readFile as readFile4, realpath, mkdir as mkdir2, open as open2, link, rename, unlink } from "node:fs/promises";
27508
+ import { join as join5 } from "node:path";
27248
27509
  import { randomUUID as randomUUID2 } from "node:crypto";
27249
27510
  var missing = (error62) => error62.code === "ENOENT";
27250
- var fileInfo = (path3) => lstat3(path3).catch((error62) => {
27511
+ var fileInfo = (path3) => lstat4(path3).catch((error62) => {
27251
27512
  if (missing(error62)) return void 0;
27252
27513
  throw error62;
27253
27514
  });
27254
27515
  async function configDirectory(root) {
27255
- const path3 = join4(root, ".gitifact");
27516
+ const path3 = join5(root, ".gitifact");
27256
27517
  const info5 = await fileInfo(path3);
27257
27518
  if (info5 && (!info5.isDirectory() || info5.isSymbolicLink())) throw new InitError("PATH_CONFLICT", t2("config.storeNotDirectory"));
27258
27519
  return info5;
27259
27520
  }
27260
27521
  async function readConfigFile(root) {
27261
27522
  await configDirectory(root);
27262
- const path3 = join4(root, ".gitifact", "config.json");
27523
+ const path3 = join5(root, ".gitifact", "config.json");
27263
27524
  const info5 = await fileInfo(path3);
27264
27525
  if (!info5) return void 0;
27265
27526
  if (!info5.isFile() || info5.isSymbolicLink() || info5.size > 65536) throw new InitError("PATH_CONFLICT", t2("config.fileShape"));
27266
- const bytes = await readFile3(path3);
27527
+ const bytes = await readFile4(path3);
27267
27528
  const after = await fileInfo(path3);
27268
27529
  await configDirectory(root);
27269
27530
  if (!after || after.ino !== info5.ino || after.dev !== info5.dev || after.mtimeMs !== info5.mtimeMs || bytes.length > 65536) {
@@ -27276,7 +27537,7 @@ async function readConfigFile(root) {
27276
27537
  }
27277
27538
  }
27278
27539
  async function publishConfig(root, text4, recheck, beforePublish, replace = false) {
27279
- const directory = join4(root, ".gitifact");
27540
+ const directory = join5(root, ".gitifact");
27280
27541
  await configDirectory(root);
27281
27542
  await mkdir2(directory).catch((error62) => {
27282
27543
  if (error62.code !== "EEXIST") throw error62;
@@ -27289,7 +27550,7 @@ async function publishConfig(root, text4, recheck, beforePublish, replace = fals
27289
27550
  throw new InitError("INPUT_CHANGED", t2("config.directoryChanged"));
27290
27551
  }
27291
27552
  };
27292
- const temporary = join4(directory, ".init-" + randomUUID2() + ".tmp");
27553
+ const temporary = join5(directory, ".init-" + randomUUID2() + ".tmp");
27293
27554
  const handle = await open2(temporary, "wx", 384);
27294
27555
  const tempOwner = await handle.stat();
27295
27556
  try {
@@ -27303,11 +27564,11 @@ async function publishConfig(root, text4, recheck, beforePublish, replace = fals
27303
27564
  await unchanged();
27304
27565
  await recheck();
27305
27566
  await unchanged();
27306
- const bytes = await readFile3(temporary, "utf8");
27567
+ const bytes = await readFile4(temporary, "utf8");
27307
27568
  if (bytes !== text4) throw new InitError("INPUT_CHANGED", t2("config.temporaryChanged"));
27308
27569
  parseManagedConfig(bytes);
27309
- if (replace) await rename(temporary, join4(directory, "config.json"));
27310
- else await link(temporary, join4(directory, "config.json"));
27570
+ if (replace) await rename(temporary, join5(directory, "config.json"));
27571
+ else await link(temporary, join5(directory, "config.json"));
27311
27572
  } finally {
27312
27573
  await unchanged();
27313
27574
  const current2 = await fileInfo(temporary);
@@ -27316,42 +27577,64 @@ async function publishConfig(root, text4, recheck, beforePublish, replace = fals
27316
27577
  }
27317
27578
 
27318
27579
  // 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";
27580
+ import { lstat as lstat5, open as open3, readdir as readdir4, readFile as readFile5 } from "node:fs/promises";
27581
+ import { join as join6 } from "node:path";
27321
27582
  var INSTRUCTION_FILE_LIMIT = 500;
27322
27583
  var INSTRUCTION_TEXT_LIMIT = 512 * 1024;
27323
- var folderOf2 = (root, instructionPath) => join5(root, ...instructionPath.split("/").slice(0, -1));
27584
+ var folderOf2 = (root, instructionPath) => join6(root, ...instructionPath.split("/").slice(0, -1));
27324
27585
  async function listInstructionFiles(root, instructionPath) {
27325
27586
  const files = [];
27326
27587
  let limited = false;
27327
27588
  async function visit2(folder, prefix, depth) {
27328
27589
  if (depth > 10) return;
27329
- const entries = await readdir3(folder, { withFileTypes: true }).catch(() => []);
27590
+ const entries = await readdir4(folder, { withFileTypes: true }).catch(() => []);
27330
27591
  for (const entry2 of entries.sort((a, b) => a.name < b.name ? -1 : a.name > b.name ? 1 : 0)) {
27331
27592
  if (files.length >= INSTRUCTION_FILE_LIMIT) {
27332
27593
  limited = true;
27333
27594
  return;
27334
27595
  }
27335
27596
  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 });
27597
+ if (entry2.isDirectory()) await visit2(join6(folder, entry2.name), path3 + "/", depth + 1);
27598
+ else if (entry2.isFile() && path3 !== INSTRUCTION_FILE) files.push({ path: path3, size: (await lstat5(join6(folder, entry2.name))).size });
27338
27599
  }
27339
27600
  }
27340
27601
  await visit2(folderOf2(root, instructionPath), "", 0);
27341
27602
  return { files, limited };
27342
27603
  }
27604
+ var FRONTMATTER_WINDOW = 8 * 1024;
27605
+ async function describeFiles(root, instructionPath, files) {
27606
+ const folder = folderOf2(root, instructionPath);
27607
+ return Promise.all(files.map(async (file2) => {
27608
+ if (!isInstructionReference(file2.path)) return file2;
27609
+ let handle;
27610
+ try {
27611
+ handle = await open3(join6(folder, ...file2.path.split("/")), "r");
27612
+ const buffer = Buffer.alloc(Math.min(FRONTMATTER_WINDOW, file2.size));
27613
+ const { bytesRead } = await handle.read(buffer, 0, buffer.length, 0);
27614
+ return { ...file2, ...parseInstructionFile(file2.path, buffer.subarray(0, bytesRead).toString("utf8")) };
27615
+ } catch {
27616
+ return file2;
27617
+ } finally {
27618
+ await handle?.close();
27619
+ }
27620
+ }));
27621
+ }
27622
+ async function readInstructionFiles(root, instructionPath) {
27623
+ const { files, limited } = await listInstructionFiles(root, instructionPath);
27624
+ return { files: await describeFiles(root, instructionPath, files), limited };
27625
+ }
27343
27626
  async function readInstructionFile(root, instructionPath, path3) {
27344
27627
  const parts = path3.split("/");
27345
27628
  if (parts.some((p) => !p || p === "." || p === ".." || /[\\:\0]/.test(p))) return void 0;
27346
27629
  let full = folderOf2(root, instructionPath);
27347
27630
  for (const [index, part] of parts.entries()) {
27348
- full = join5(full, part);
27349
- const info5 = await lstat4(full).catch(() => void 0);
27631
+ full = join6(full, part);
27632
+ const info5 = await lstat5(full).catch(() => void 0);
27350
27633
  if (!info5 || info5.isSymbolicLink() || (index < parts.length - 1 ? !info5.isDirectory() : !info5.isFile())) return void 0;
27351
27634
  }
27352
- const size = (await lstat4(full)).size;
27635
+ const size = (await lstat5(full)).size;
27353
27636
  if (size > INSTRUCTION_TEXT_LIMIT) return { text: null, size, binary: false, tooLarge: true };
27354
- const bytes = await readFile4(full);
27637
+ const bytes = await readFile5(full);
27355
27638
  try {
27356
27639
  if (bytes.includes(0)) throw new TypeError("binary");
27357
27640
  return { text: new TextDecoder("utf-8", { fatal: true }).decode(bytes), size, binary: false, tooLarge: false };
@@ -27361,20 +27644,108 @@ async function readInstructionFile(root, instructionPath, path3) {
27361
27644
  }
27362
27645
 
27363
27646
  // 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";
27647
+ import { lstat as lstat6, readFile as readFile6 } from "node:fs/promises";
27648
+ import { join as join7 } from "node:path";
27366
27649
  var LIMIT = 1024 * 1024;
27367
27650
  async function readAgentsFile(root) {
27368
- const file2 = join6(root, "AGENTS.md");
27369
- const info5 = await lstat5(file2).catch(() => void 0);
27651
+ const file2 = join7(root, "AGENTS.md");
27652
+ const info5 = await lstat6(file2).catch(() => void 0);
27370
27653
  if (!info5?.isFile() || info5.size > LIMIT) return null;
27371
27654
  try {
27372
- return new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(await readFile5(file2));
27655
+ return new TextDecoder("utf-8", { fatal: true, ignoreBOM: false }).decode(await readFile6(file2));
27373
27656
  } catch {
27374
27657
  return null;
27375
27658
  }
27376
27659
  }
27377
27660
 
27661
+ // src/queries/document-states.ts
27662
+ var isDocument = (path3) => {
27663
+ try {
27664
+ return classifyDocPath(path3).type === "doc";
27665
+ } catch {
27666
+ return false;
27667
+ }
27668
+ };
27669
+ async function documentStates(project, working2) {
27670
+ const head = await project.head();
27671
+ const states = /* @__PURE__ */ new Map();
27672
+ const previous = /* @__PURE__ */ new Map();
27673
+ const result = (documents) => ({ documents, state: (id) => states.get(id) ?? "committed", previousPath: (id) => previous.get(id) });
27674
+ if (!head) {
27675
+ for (const doc of working2) states.set(doc.id, "added");
27676
+ return result(working2);
27677
+ }
27678
+ 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);
27679
+ const byPath2 = new Map(working2.map((d) => [d.path, d]));
27680
+ const added = [];
27681
+ const deletedPaths = [];
27682
+ for (const entry2 of status) {
27683
+ const [x, y] = [entry2[0], entry2[1]];
27684
+ const path3 = entry2.slice(3);
27685
+ if (!isDocument(path3)) continue;
27686
+ const doc = byPath2.get(path3);
27687
+ const atHead = x !== "?" && x !== "A";
27688
+ if (!doc) {
27689
+ if (atHead && (x === "D" || y === "D")) deletedPaths.push(path3);
27690
+ continue;
27691
+ }
27692
+ if (!atHead) {
27693
+ added.push(doc);
27694
+ continue;
27695
+ }
27696
+ if (x !== " " || y !== " ") states.set(doc.id, "modified");
27697
+ }
27698
+ const gone = await documentsAtHead(project, head, deletedPaths);
27699
+ const goneById = new Map(gone.map((d) => [d.id, d]));
27700
+ for (const doc of added) {
27701
+ const before = goneById.get(doc.id);
27702
+ if (before) {
27703
+ states.set(doc.id, "modified");
27704
+ previous.set(doc.id, before.path);
27705
+ goneById.delete(doc.id);
27706
+ } else states.set(doc.id, "added");
27707
+ }
27708
+ const ids = new Set(working2.map((d) => d.id));
27709
+ const deleted = [...goneById.values()].filter((d) => !ids.has(d.id));
27710
+ for (const doc of deleted) states.set(doc.id, "deleted");
27711
+ return result([...working2, ...deleted]);
27712
+ }
27713
+ async function documentsAtHead(project, head, paths2) {
27714
+ if (!paths2.length) return [];
27715
+ 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] }));
27716
+ const blobs = await project.reader.blobs([...new Set(listing.map((l) => l.blob))]).catch(() => /* @__PURE__ */ new Map());
27717
+ return listing.flatMap(({ blob, path: path3 }) => {
27718
+ const text4 = blobs.get(blob);
27719
+ try {
27720
+ return text4 === void 0 ? [] : [parseDocumentFile(path3, text4)];
27721
+ } catch {
27722
+ return [];
27723
+ }
27724
+ });
27725
+ }
27726
+
27727
+ // src/server/checkout/stamp.ts
27728
+ import { createHash as createHash2 } from "node:crypto";
27729
+ import { lstat as lstat7 } from "node:fs/promises";
27730
+ import { join as join8 } from "node:path";
27731
+ function createStampReader(root, inherited = process.env) {
27732
+ const runner = createGitRunner();
27733
+ const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_REPLACE_OBJECTS: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "0", LC_ALL: "C" };
27734
+ 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");
27735
+ return async () => {
27736
+ const [head, status] = await Promise.all([
27737
+ git(["rev-parse", "--verify", "--quiet", "HEAD"], [0, 1]),
27738
+ git(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", ":(top).gitifact", ":(top)AGENTS.md"])
27739
+ ]);
27740
+ const paths2 = status.split("\0").filter(Boolean).map((entry2) => entry2.slice(3)).filter((path3) => !path3.startsWith(".gitifact/cache/"));
27741
+ const seen = await Promise.all(paths2.map(async (path3) => {
27742
+ const info5 = await lstat7(join8(root, ...path3.split("/"))).catch(() => void 0);
27743
+ return path3 + " " + (info5 ? info5.mtimeMs + ":" + info5.size : "-");
27744
+ }));
27745
+ return createHash2("sha256").update(head.trim() + "\n" + seen.sort().join("\n")).digest("hex").slice(0, 20);
27746
+ };
27747
+ }
27748
+
27378
27749
  // src/server/checkout/checkout-reader.ts
27379
27750
  var tally = (people, name2, email3, latest) => {
27380
27751
  const person = people.get(email3);
@@ -27388,7 +27759,12 @@ async function settled(reads) {
27388
27759
  return results.map((r) => r.value);
27389
27760
  }
27390
27761
  var folderOf3 = (path3) => path3.split("/").slice(0, 3).join("/");
27391
- function checkoutDocuments(documents) {
27762
+ function checkoutDocuments(states) {
27763
+ const documents = states.documents;
27764
+ const standing2 = (id) => {
27765
+ const previousPath = states.previousPath(id);
27766
+ return { state: states.state(id), ...previousPath ? { previousPath } : {} };
27767
+ };
27392
27768
  const byId = new Map(documents.map((d) => [d.id, d]));
27393
27769
  const source2 = (s) => {
27394
27770
  if (!("id" in s)) return { title: s.title, url: s.url, ...s.note ? { note: s.note } : {} };
@@ -27402,7 +27778,8 @@ function checkoutDocuments(documents) {
27402
27778
  title: index.title,
27403
27779
  description: index.description,
27404
27780
  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 })),
27781
+ ...standing2(index.id),
27782
+ 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) })),
27406
27783
  designs: designs.map((d) => ({
27407
27784
  id: d.id,
27408
27785
  path: d.path,
@@ -27411,10 +27788,11 @@ function checkoutDocuments(documents) {
27411
27788
  order: d.order,
27412
27789
  body: d.body,
27413
27790
  requirements: d.requirements,
27414
- sources: d.sources.map(source2)
27791
+ sources: d.sources.map(source2),
27792
+ ...standing2(d.id)
27415
27793
  }))
27416
27794
  }));
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 }));
27795
+ const instructions2 = arranged.instructions.map((k) => ({ id: k.id, name: k.name, path: k.path, title: k.title, description: k.description, body: k.body, ...standing2(k.id) }));
27418
27796
  return { features, instructions: instructions2, orphans: arranged.orphans };
27419
27797
  }
27420
27798
  function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
@@ -27429,6 +27807,7 @@ function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
27429
27807
  return head || null;
27430
27808
  };
27431
27809
  const pending = /* @__PURE__ */ new Map();
27810
+ const readStamp = createStampReader(root, inherited);
27432
27811
  const notMigration = ["-E", "--invert-grep", `--grep=^${MIGRATION_TRAILER}: `];
27433
27812
  async function storeAuthors(head) {
27434
27813
  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"]);
@@ -27463,6 +27842,7 @@ function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
27463
27842
  if (!raw) throw new StoreError(t2("specReader.schemaRequired"));
27464
27843
  parseManagedConfig(raw);
27465
27844
  const head = await readHead();
27845
+ const stamp = await readStamp();
27466
27846
  const [current2, dirty, authors, everyone] = await settled([
27467
27847
  cache.documents.list(),
27468
27848
  head ? git(["status", "--porcelain=v1", "--", ".gitifact/spec", INSTRUCTIONS_ROOT]) : Promise.resolve(""),
@@ -27477,25 +27857,27 @@ function createCheckoutReader(root, sessionId, cache, inherited = process.env) {
27477
27857
  if (!name2 || !email3 || !latest) throw new StoreError(t2("specReader.authorUnreadable"));
27478
27858
  tally(people, name2, email3, latest);
27479
27859
  }
27480
- const arranged = checkoutDocuments(current2.documents);
27860
+ const states = await documentStates({ head: async () => head, reader }, current2.documents);
27861
+ const arranged = checkoutDocuments(states);
27481
27862
  const features = arranged.features.map((feature2) => {
27482
27863
  const entry2 = authors?.folders.get(folderOf3(feature2.path));
27483
27864
  return { ...feature2, contributors: entry2 ? [...entry2.people.values()].sort((a, b) => b.commits - a.commits) : [], updatedAt: entry2?.latest ?? null };
27484
27865
  });
27485
27866
  const instructions2 = await Promise.all(arranged.instructions.map(async (instruction2) => {
27486
- const { files, limited } = await listInstructionFiles(root, instruction2.path);
27867
+ const { files, limited } = instruction2.state === "deleted" ? { files: [], limited: false } : await readInstructionFiles(root, instruction2.path);
27487
27868
  return { ...instruction2, files, filesLimited: limited, updatedAt: authors?.instructions.get(instruction2.path.split("/").slice(0, 3).join("/")) ?? null };
27488
27869
  }));
27489
27870
  const agentsText = await readAgentsFile(root);
27490
27871
  const agents2 = agentsText === null ? null : { path: "AGENTS.md", body: agentsText, updatedAt: authors?.instructions.get("AGENTS.md") ?? null };
27491
27872
  const problems = [...current2.problems, ...arranged.orphans.map((feature2) => docProblem("FEATURE_INDEX_REQUIRED", `.gitifact/spec/${feature2}/index.md`, { feature: feature2 }))];
27492
27873
  if (await readHead() !== head) throw new StoreError(t2("specReader.projectChanged"));
27493
- const checkout = browserSpecsV6.parse({
27874
+ const checkout = browserSpecsV7.parse({
27494
27875
  contract: "browser-specs",
27495
- version: 6,
27876
+ version: 7,
27496
27877
  sessionId,
27497
27878
  head,
27498
27879
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
27880
+ stamp,
27499
27881
  working: head ? !!dirty.trim() : features.length > 0 || instructions2.length > 0,
27500
27882
  features,
27501
27883
  instructions: instructions2,
@@ -27606,30 +27988,108 @@ function isUtf8(bytes) {
27606
27988
  }
27607
27989
  }
27608
27990
 
27991
+ // src/queries/working-changes.ts
27992
+ async function workingChanges(source2, known) {
27993
+ const head = known !== void 0 ? known : await source2.head();
27994
+ const headFiles = head ? await source2.cache.history.filesAt(head) : /* @__PURE__ */ new Map();
27995
+ const [working2, records2] = await Promise.all([source2.cache.documents.files(), source2.pendingRecords()]);
27996
+ return { head, headFiles, working: working2, records: records2, ...compareDocumentSets(headFiles, working2.files) };
27997
+ }
27998
+ var uncovered = (changes2, records2) => {
27999
+ const covered = new Set(records2.flatMap((r) => r.docs));
28000
+ return changes2.filter((c) => c.types.some((type) => type !== "created") && !covered.has(c.id)).map((c) => c.id);
28001
+ };
28002
+ async function workingOverview(source2) {
28003
+ const found = await workingChanges(source2);
28004
+ const records2 = checkDocuments(new Map([...found.working.files, ...found.records.files])).records;
28005
+ return {
28006
+ head: found.head,
28007
+ 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 })),
28008
+ 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 })),
28009
+ withoutRecord: uncovered(found.changes, records2)
28010
+ };
28011
+ }
28012
+ function snapshot4(doc, docs) {
28013
+ const folder = doc.path.split("/").slice(0, 3).join("/");
28014
+ const specId = doc.kind === "wiki" || doc.kind === "instruction" ? doc.kind : docs.find((d) => d.kind === "feature" && d.path === folder + "/index.md")?.id ?? "";
28015
+ const base = { id: doc.id, kind: doc.kind, title: doc.title, description: doc.description, body: doc.body, specId, path: doc.path };
28016
+ if (doc.kind === "requirement") return { ...base, order: doc.order };
28017
+ if (doc.kind === "design") return { ...base, order: doc.order, requirements: doc.requirements, sources: doc.sources };
28018
+ return base;
28019
+ }
28020
+ var parsed = (files) => [...files].flatMap(([path3, text4]) => {
28021
+ try {
28022
+ return classifyDocPath(path3).type === "doc" ? [parseDocumentFile(path3, text4)] : [];
28023
+ } catch {
28024
+ return [];
28025
+ }
28026
+ });
28027
+ async function workingChange2(source2, id) {
28028
+ const found = await workingChanges(source2);
28029
+ const change2 = found.changes.find((c) => c.id === id);
28030
+ if (!change2) return void 0;
28031
+ const before = parsed(found.headFiles);
28032
+ const after = parsed(found.working.files);
28033
+ const side = (docs) => {
28034
+ const doc = docs.find((d) => d.id === id);
28035
+ return doc ? snapshot4(doc, docs) : null;
28036
+ };
28037
+ return {
28038
+ change: { id: change2.id, kind: change2.kind, title: change2.title, path: change2.path, ...change2.previousPath ? { previousPath: change2.previousPath } : {}, types: change2.types },
28039
+ before: side(before),
28040
+ after: side(after)
28041
+ };
28042
+ }
28043
+
27609
28044
  // src/server/routes/record-routes.ts
27610
- var PAGE = 50;
28045
+ var PAGE = 20;
27611
28046
  function recordRoutes(root, sessionId, env) {
27612
28047
  const git = storeReader(root);
27613
28048
  const cache = openCache(root, { run: (args, input2) => git.run(args, input2), decode: git.decode, legacyBundles: (oids) => git.readBundles(oids) });
27614
28049
  const readCheckout = createCheckoutReader(root, sessionId, cache, env);
28050
+ const readStamp = createStampReader(root, env);
27615
28051
  const commitFiles = createCommitFiles(git);
28052
+ const working2 = { head: async () => (await git.baseline()).head, cache, pendingRecords: () => readPendingRecords(root, (args) => git.run(args)) };
27616
28053
  const unreadable2 = () => t2("server.specsUnreadable");
28054
+ const cursorGone = () => new HttpError(404, "NOT_FOUND", t2("server.cursorNotFound"));
27617
28055
  return [
27618
28056
  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 }));
28057
+ // Whether the screen is behind, asked when the reader comes back to the tab; cheap enough to ask every time.
28058
+ 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() })) }),
28059
+ route({ method: "GET", path: "/api/v1/history", session: true, query: browserHistoryQueryV4, unreadable: unreadable2, handle: async ({ query }) => {
28060
+ 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);
28061
+ if (!page) throw cursorGone();
28062
+ return ok(browserHistoryV6.parse({ contract: "browser-history", version: 6, sessionId, head: query.head, ...page }));
27623
28063
  } }),
27624
28064
  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;
28065
+ // One commit as its page reads it: who made it and a page of the documents it changed, even when it changed none.
28066
+ route({ method: "GET", path: "/api/v1/commit", session: true, query: browserCommitQueryV2, unreadable: unreadable2, handle: async ({ query }) => {
28067
+ const page = await cache.history.commitChanges(query.commit, query.after, query.limit ?? PAGE);
28068
+ if (!page) throw cursorGone();
28069
+ const first = page.events[0];
27629
28070
  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
28071
  if (!first && line2.length < 5) throw new HttpError(404, "NOT_FOUND", t2("server.commitNotFound"));
27631
28072
  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 }));
28073
+ return ok(browserCommitV4.parse({
28074
+ contract: "browser-commit",
28075
+ version: 4,
28076
+ sessionId,
28077
+ commit: query.commit,
28078
+ author,
28079
+ email: email3,
28080
+ committer,
28081
+ date: date5,
28082
+ message,
28083
+ total: page.total,
28084
+ next: page.next,
28085
+ changes: page.events
28086
+ }));
28087
+ } }),
28088
+ // The text on both sides of one change, read from Git when the reader opens that document.
28089
+ route({ method: "GET", path: "/api/v1/commit/change", session: true, query: browserCommitChangeQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28090
+ const change2 = await cache.history.commitChange(query.commit, query.id);
28091
+ if (!change2) throw new HttpError(404, "NOT_FOUND", t2("server.commitNotFound"));
28092
+ return ok(browserCommitChangeV1.parse({ contract: "browser-commit-change", version: 1, sessionId, commit: query.commit, event: change2.event, before: change2.before, after: change2.after }));
27633
28093
  } }),
27634
28094
  // A record's page names the record; which commit added it is read from the history of the HEAD the reader is on.
27635
28095
  route({ method: "GET", path: "/api/v1/record", session: true, query: browserRecordQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
@@ -27637,6 +28097,13 @@ function recordRoutes(root, sessionId, env) {
27637
28097
  if (!commit) throw new HttpError(404, "NOT_FOUND", t2("server.recordNotFound"));
27638
28098
  return ok(browserRecordV1.parse({ contract: "browser-record", version: 1, sessionId, head: query.head, id: query.id, commit }));
27639
28099
  } }),
28100
+ // What is not committed yet, worked out again on every request: the uncommitted entry of the records list.
28101
+ 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) })) }),
28102
+ route({ method: "GET", path: "/api/v1/working/change", session: true, query: browserWorkingChangeQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28103
+ const change2 = await workingChange2(working2, query.id);
28104
+ if (!change2) throw new HttpError(404, "NOT_FOUND", t2("server.workingChangeNotFound"));
28105
+ return ok(browserWorkingChangeV1.parse({ contract: "browser-working-change", version: 1, sessionId, ...change2 }));
28106
+ } }),
27640
28107
  // The source a commit changed beside its documents, read from Git when the commit or record page asks for it.
27641
28108
  route({ method: "GET", path: "/api/v1/commit/files", session: true, query: browserCommitFilesQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
27642
28109
  const found = await commitFiles.files(query.commit);
@@ -27660,18 +28127,18 @@ function recordRoutes(root, sessionId, env) {
27660
28127
  }
27661
28128
 
27662
28129
  // 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";
28130
+ import { lstat as lstat8, readFile as readFile7 } from "node:fs/promises";
28131
+ import { join as join9 } from "node:path";
27665
28132
  function assetRoutes(root) {
27666
28133
  return [
27667
28134
  route({ method: "GET", path: "/api/v1/assets/*", session: false, handle: async ({ path: path3, response }) => {
27668
28135
  const relative = path3.slice("/api/v1/assets/".length);
27669
28136
  const assetPath = ASSETS_DIR + "/" + relative;
27670
28137
  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);
28138
+ const file2 = join9(root, ...assetPath.split("/"));
28139
+ const stat3 = await lstat8(file2).catch(() => void 0);
27673
28140
  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);
28141
+ const bytes = await readFile7(file2);
27675
28142
  const extension = assetExtension(relative);
27676
28143
  const inline = ["png", "jpg", "jpeg", "gif", "webp", "svg"].includes(extension);
27677
28144
  response.writeHead(200, {
@@ -27688,7 +28155,7 @@ function assetRoutes(root) {
27688
28155
  }
27689
28156
 
27690
28157
  // src/server/browser-server.ts
27691
- var readBundledChangelog = (language) => readFile7(new URL("./i18n/" + language + "/changelog.md", import.meta.url), "utf8").catch((error62) => {
28158
+ var readBundledChangelog = (language) => readFile8(new URL("./i18n/" + language + "/changelog.md", import.meta.url), "utf8").catch((error62) => {
27692
28159
  if (error62.code === "ENOENT") return null;
27693
28160
  throw error62;
27694
28161
  });
@@ -27818,14 +28285,14 @@ async function runBrowser(options, version2) {
27818
28285
  }
27819
28286
 
27820
28287
  // src/commands/spec-init.ts
27821
- import { readdir as readdir4 } from "node:fs/promises";
27822
- import { join as join10 } from "node:path";
28288
+ import { readdir as readdir5 } from "node:fs/promises";
28289
+ import { join as join13 } from "node:path";
27823
28290
 
27824
28291
  // 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) => {
28292
+ import { createHash as createHash3 } from "node:crypto";
28293
+ import { lstat as lstat9, readFile as readFile9 } from "node:fs/promises";
28294
+ import { join as join10 } from "node:path";
28295
+ var markerPresent = (path3) => lstat9(path3).then(() => true, (error62) => {
27829
28296
  if (error62.code === "ENOENT") return false;
27830
28297
  throw error62;
27831
28298
  });
@@ -27842,27 +28309,27 @@ function initRepository(cwd, inherited = process.env) {
27842
28309
  });
27843
28310
  return {
27844
28311
  async inspect(observedState, includeTrackedConfig = true) {
27845
- const state = observedState ?? await createRepositoryReader(cwd, { env }).read();
27846
- const root = state.repository.rootPath;
28312
+ const state2 = observedState ?? await createRepositoryReader(cwd, { env }).read();
28313
+ const root = state2.repository.rootPath;
27847
28314
  const locations = decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--absolute-git-dir", "--git-path", "index"], root)).split("\n");
27848
28315
  const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
27849
28316
  const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
27850
28317
  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 }));
28318
+ if (await markerPresent(join10(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
27852
28319
  }
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) => {
28320
+ if (state2.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
28321
+ const index = await readFile9(indexPath).catch((error62) => {
27855
28322
  if (error62.code === "ENOENT") return Buffer.alloc(0);
27856
28323
  throw error62;
27857
28324
  });
27858
28325
  const [staged, committed2] = includeTrackedConfig ? await Promise.all([
27859
28326
  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)
28327
+ state2.head.commit ? git(["ls-tree", "-z", state2.head.commit, "--", ".gitifact/config.json"], root) : Buffer.alloc(0)
27861
28328
  ]) : [Buffer.alloc(0), Buffer.alloc(0)];
27862
28329
  return {
27863
- state,
28330
+ state: state2,
27864
28331
  trackedConfig: staged.length > 0 || committed2.length > 0,
27865
- stamp: JSON.stringify({ repository: state.repository, head: state.head, index: createHash2("sha256").update(index).digest("hex") })
28332
+ stamp: JSON.stringify({ repository: state2.repository, head: state2.head, index: createHash3("sha256").update(index).digest("hex") })
27866
28333
  };
27867
28334
  },
27868
28335
  async checkIgnore(root, path3 = ".gitifact/config.json") {
@@ -27884,19 +28351,52 @@ function initRepository(cwd, inherited = process.env) {
27884
28351
  };
27885
28352
  }
27886
28353
 
28354
+ // src/adapters/filesystem/document-file.ts
28355
+ import { randomBytes } from "node:crypto";
28356
+ import { lstat as lstat10, mkdir as mkdir3, writeFile as writeFile2 } from "node:fs/promises";
28357
+ import { join as join11 } from "node:path";
28358
+ var generateId = (prefix) => prefix + "-" + [...randomBytes(10)].map((n) => "abcdefghijklmnopqrstuvwxyz234567"[n & 31]).join("");
28359
+ async function createDocumentFile(root, path3, text4) {
28360
+ const parts = path3.split("/");
28361
+ for (let i = 1; i < parts.length; i++) {
28362
+ const folder = join11(root, ...parts.slice(0, i));
28363
+ const info5 = await lstat10(folder).catch((error62) => {
28364
+ if (error62.code === "ENOENT") return void 0;
28365
+ throw error62;
28366
+ });
28367
+ if (!info5) {
28368
+ await mkdir3(folder);
28369
+ continue;
28370
+ }
28371
+ if (!info5.isDirectory() || info5.isSymbolicLink()) throw Object.assign(new Error(folder), { code: "ENOTDIR" });
28372
+ }
28373
+ await writeFile2(join11(root, ...parts), text4, { flag: "wx" });
28374
+ }
28375
+ var LINE_ENDINGS_PATH = ".gitifact/.gitattributes";
28376
+ 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";
28377
+ async function writeLineEndings(root) {
28378
+ try {
28379
+ await createDocumentFile(root, LINE_ENDINGS_PATH, LINE_ENDINGS);
28380
+ return true;
28381
+ } catch (error62) {
28382
+ if (error62.code === "EEXIST") return false;
28383
+ throw error62;
28384
+ }
28385
+ }
28386
+
27887
28387
  // 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";
28388
+ 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";
28389
+ import { join as join12, dirname } from "node:path";
27890
28390
  import { randomUUID as randomUUID4 } from "node:crypto";
27891
28391
  var DEFAULT_LIMIT = 1024 * 1024;
27892
28392
  async function managedPath(root, path3, create = false) {
27893
28393
  let full = root;
27894
28394
  const parts = path3.split("/");
27895
28395
  for (let i = 0; i < parts.length; i++) {
27896
- full = join9(full, parts[i]);
28396
+ full = join12(full, parts[i]);
27897
28397
  let info5 = await fileInfo(full);
27898
28398
  if (!info5 && create && i < parts.length - 1) {
27899
- await mkdir3(full).catch((error62) => {
28399
+ await mkdir4(full).catch((error62) => {
27900
28400
  if (error62.code !== "EEXIST") throw error62;
27901
28401
  });
27902
28402
  info5 = await fileInfo(full);
@@ -27912,7 +28412,7 @@ async function managedRead(root, path3, limit = DEFAULT_LIMIT) {
27912
28412
  const before = await fileInfo(full);
27913
28413
  if (!before) return null;
27914
28414
  if (before.size > limit) throw new InitError("FILE_TOO_LARGE", t2("managedFile.tooLarge", { kib: limit / 1024, path: path3 }));
27915
- const bytes = await readFile9(full);
28415
+ const bytes = await readFile10(full);
27916
28416
  await managedPath(root, path3);
27917
28417
  const after = await fileInfo(full);
27918
28418
  if (!after || before.ino !== after.ino || before.dev !== after.dev || before.mtimeMs !== after.mtimeMs || bytes.length > limit) {
@@ -27941,8 +28441,8 @@ async function managedWrite(root, path3, previous, next, recheck) {
27941
28441
  if (previous !== null) await unlink2(full);
27942
28442
  return;
27943
28443
  }
27944
- const temp = join9(directory, ".gitifact-managed-" + randomUUID4() + ".tmp");
27945
- const handle = await open3(temp, "wx", 384);
28444
+ const temp = join12(directory, ".gitifact-managed-" + randomUUID4() + ".tmp");
28445
+ const handle = await open4(temp, "wx", 384);
27946
28446
  const tempOwner = await handle.stat();
27947
28447
  try {
27948
28448
  try {
@@ -27953,7 +28453,7 @@ async function managedWrite(root, path3, previous, next, recheck) {
27953
28453
  }
27954
28454
  await recheck();
27955
28455
  await unchanged();
27956
- if (await managedRead(root, path3) !== previous || await readFile9(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", t2("managedFile.publishTargetChanged", { path: path3 }));
28456
+ if (await managedRead(root, path3) !== previous || await readFile10(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", t2("managedFile.publishTargetChanged", { path: path3 }));
27957
28457
  if (previous === null) await link2(temp, full);
27958
28458
  else await rename2(temp, full);
27959
28459
  } finally {
@@ -27964,10 +28464,10 @@ async function managedWrite(root, path3, previous, next, recheck) {
27964
28464
  }
27965
28465
 
27966
28466
  // src/commands/agent-block.ts
27967
- import { readFile as readFile11 } from "node:fs/promises";
28467
+ import { readFile as readFile12 } from "node:fs/promises";
27968
28468
 
27969
28469
  // src/commands/guide.ts
27970
- import { readFile as readFile10 } from "node:fs/promises";
28470
+ import { readFile as readFile11 } from "node:fs/promises";
27971
28471
 
27972
28472
  // src/commands/output.ts
27973
28473
  var formats = ["text", "json"];
@@ -28004,7 +28504,7 @@ var text2 = (lines) => lines.join("\n") + "\n";
28004
28504
  // src/commands/guide.ts
28005
28505
  var guideTopics = ["workflow", "spec", "design", "instructions", "records", "writing", "commit", "migrate"];
28006
28506
  var isGuideTopic = (value) => guideTopics.includes(value);
28007
- var readBundledGuide = (name2, lang) => readFile10(new URL("./i18n/" + lang + "/docs/" + name2 + ".md", import.meta.url), "utf8");
28507
+ var readBundledGuide = (name2, lang) => readFile11(new URL("./i18n/" + lang + "/docs/" + name2 + ".md", import.meta.url), "utf8");
28008
28508
  function guideSummary(source2) {
28009
28509
  const { fields: fields2 } = parseFrontmatterBlock(source2.replace(/\r\n/g, "\n"), "guide");
28010
28510
  const value = (key) => {
@@ -28045,7 +28545,7 @@ function boilerplateFor(path3) {
28045
28545
  const name2 = path3 === "AGENTS.md" ? path3 : path3.split("/").pop().replace(/^\./, "").replace(/\.md$/, "");
28046
28546
  return "# " + name2 + "\n\nProject-specific guidance for AI coding agents.\n";
28047
28547
  }
28048
- var readBundledBlock = (lang) => readFile11(new URL("./i18n/" + lang + "/block.md", import.meta.url), "utf8");
28548
+ var readBundledBlock = (lang) => readFile12(new URL("./i18n/" + lang + "/block.md", import.meta.url), "utf8");
28049
28549
  async function renderAgentBlock(version2, controls = {}, lang = getLanguage()) {
28050
28550
  const body2 = await (controls.readBlock ?? readBundledBlock)(lang);
28051
28551
  const filled = body2.trimEnd().replace(/\{(version|language|topics)\}/g, (_whole, name2) => name2 === "version" ? version2 : name2 === "language" ? lang : guideTopics.join(", "));
@@ -28226,11 +28726,11 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28226
28726
  const first = await repo.inspect();
28227
28727
  const root = first.state.repository.rootPath;
28228
28728
  const docsPlan = agentDocs ? await planAgentDocs(root, agentDocs) : skippedAgentDocs;
28229
- const result = async (config3, outcome) => {
28729
+ const result = async (config3, outcome, lineEndings = false) => {
28230
28730
  const checked = await update;
28231
- return projectInitV7.parse({
28731
+ return projectInitV8.parse({
28232
28732
  contract: "project-init",
28233
- version: 7,
28733
+ version: 8,
28234
28734
  ok: true,
28235
28735
  outcome,
28236
28736
  rootPath: root,
@@ -28238,6 +28738,7 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28238
28738
  schemaVersion: config3.schemaVersion,
28239
28739
  baseline: config3.baseline,
28240
28740
  agentDocs: { mode: docsPlan.mode, paths: docsPlan.paths },
28741
+ lineEndings: { path: LINE_ENDINGS_PATH, created: lineEndings },
28241
28742
  update: checked,
28242
28743
  install: checked.status === "available" ? { npx: npxUpdate(checked.latestVersion), npmGlobal: npmGlobalInstall(checked.latestVersion) } : null
28243
28744
  });
@@ -28249,8 +28750,9 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28249
28750
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text5) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
28250
28751
  };
28251
28752
  await unchanged();
28252
- if (!dryRun) await applyAgentDocs(root, docsPlan, unchanged);
28253
- return result(config3, "already-initialized");
28753
+ if (dryRun) return result(config3, "already-initialized");
28754
+ await applyAgentDocs(root, docsPlan, unchanged);
28755
+ return result(config3, "already-initialized", await writeLineEndings(root));
28254
28756
  };
28255
28757
  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
28758
  const text4 = JSON.stringify(config2, null, 2) + "\n";
@@ -28260,13 +28762,13 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
28260
28762
  };
28261
28763
  await published();
28262
28764
  await applyAgentDocs(root, docsPlan, published);
28263
- return result(config2, outcome);
28765
+ return result(config2, outcome, await writeLineEndings(root));
28264
28766
  };
28265
28767
  const old = await readConfigFile(root);
28266
28768
  if (old !== void 0) return existing(old);
28267
28769
  if (first.trackedConfig) throw new InitError("CONFIG_DELETED", t2("init.configDeleted"));
28268
28770
  const checkRecords = async () => {
28269
- const entries = await readdir4(join10(root, ".gitifact")).catch((e) => {
28771
+ const entries = await readdir5(join13(root, ".gitifact")).catch((e) => {
28270
28772
  if (e.code === "ENOENT") return [];
28271
28773
  throw e;
28272
28774
  });
@@ -28318,117 +28820,33 @@ async function runInit(options, version2) {
28318
28820
  );
28319
28821
  if (!dto.ok) throw new Error("unreachable");
28320
28822
  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") + ")");
28823
+ const lineEndings = dto.lineEndings.created ? t2("init.text.lineEndings", { path: dto.lineEndings.path }) + "\n" : "";
28321
28824
  const newer = dto.install ? t2("init.text.updateAvailable", { version: dto.update.latestVersion, command: dto.install.npx }) + "\n" : "";
28322
28825
  process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
28323
28826
  ${t2("init.text.storage")}: schemaVersion ${dto.schemaVersion}
28324
28827
  ${t2("init.text.agentDocs")}: ${docs}
28325
- ${newer}` : JSON.stringify(dto) + "\n");
28828
+ ${lineEndings}${newer}` : JSON.stringify(dto) + "\n");
28326
28829
  } catch (error62) {
28327
28830
  cancel.abort();
28328
28831
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
28329
28832
  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");
28833
+ 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
28834
  process.exitCode = 1;
28332
28835
  }
28333
28836
  }
28334
28837
 
28335
28838
  // src/commands/documents.ts
28336
28839
  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
- }
28840
+ import { join as join15 } from "node:path";
28424
28841
 
28425
28842
  // src/commands/project.ts
28843
+ import { join as join14 } from "node:path";
28426
28844
  var operations = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
28427
28845
  async function openProject(cwd, options = {}) {
28428
28846
  const reader = storeReader(cwd);
28429
28847
  const location = await reader.location();
28430
28848
  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 }));
28849
+ for (const marker2 of operations) if (await fileInfo(join14(location.gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
28432
28850
  if (await reader.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
28433
28851
  }
28434
28852
  const raw = await readConfigFile(location.root);
@@ -28461,13 +28879,20 @@ function documentsOf(files) {
28461
28879
 
28462
28880
  // src/commands/documents.ts
28463
28881
  var draftMark = (doc) => doc.draft ? " (" + t2("docs.draft") + ")" : "";
28882
+ var stateLabel = {
28883
+ added: () => t2("docs.state.added"),
28884
+ modified: () => t2("docs.state.modified"),
28885
+ deleted: () => t2("docs.state.deleted")
28886
+ };
28887
+ var stateMark = (state2) => state2 === "committed" ? "" : " (" + stateLabel[state2]() + ")";
28464
28888
  var line = (doc) => `${doc.id} ${doc.title}${draftMark(doc)} \u2014 ${doc.description}`;
28889
+ var fileLine = (f) => f.title ? `${f.path} ${f.title} \u2014 ${f.description}` : f.path;
28465
28890
  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);
28466
28891
  async function working(project) {
28467
28892
  const { documents } = await project.cache.documents.list();
28468
28893
  return {
28469
28894
  byId: new Map(documents.map((d) => [d.id, d])),
28470
- read: (path3) => readFile13(join14(project.root, ...path3.split("/")), "utf8"),
28895
+ read: (path3) => readFile13(join15(project.root, ...path3.split("/")), "utf8"),
28471
28896
  referencing: async (id) => [...new Set((await project.cache.documents.referencing(id)).map((r) => r.from))]
28472
28897
  };
28473
28898
  }
@@ -28493,7 +28918,7 @@ async function showDocuments(project, ids, at, kinds, otherResource) {
28493
28918
  ...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
28919
  ] : [];
28495
28920
  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) } : {};
28921
+ const files = doc.kind === "instruction" && at === void 0 ? { files: (await readInstructionFiles(project.root, doc.path)).files } : {};
28497
28922
  return { id, kind: doc.kind, path: doc.path, title: doc.title, text: await view.read(doc.path), references, referencedBy, ...files };
28498
28923
  }));
28499
28924
  const named = (r) => r.id ? r.id + " " + (r.title ?? "(" + t2("docs.missing") + ")") : r.title + " <" + r.url + ">";
@@ -28502,7 +28927,7 @@ async function showDocuments(project, ids, at, kinds, otherResource) {
28502
28927
  d.text.replace(/\n$/, ""),
28503
28928
  ...section("-- " + t2("docs.references"), d.references.map(named)),
28504
28929
  ...section("-- " + t2("docs.referencedBy"), d.referencedBy.map(named)),
28505
- ...section("-- " + t2("docs.files"), d.files ?? [])
28930
+ ...section("-- " + t2("docs.files"), (d.files ?? []).map(fileLine))
28506
28931
  ].join("\n"));
28507
28932
  return { json: { ...at ? { commit: at } : {}, documents: shown }, text: blocks.join("\n\n") + "\n" };
28508
28933
  }
@@ -28598,6 +29023,22 @@ async function since(project, value, head) {
28598
29023
  const after = new Set(project.reader.decode(await project.reader.run(["rev-list", `${commit}..${head}`, "--"])).split("\n").filter(Boolean));
28599
29024
  return (change2) => after.has(change2.commit);
28600
29025
  }
29026
+ async function sinceRange(project, value, head) {
29027
+ if (value === void 0) return {};
29028
+ if (/^\d{4}-\d\d-\d\d/.test(value)) {
29029
+ const from = Date.parse(value);
29030
+ if (!Number.isFinite(from)) throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
29031
+ return { from };
29032
+ }
29033
+ let commit;
29034
+ try {
29035
+ commit = await project.reader.resolve(value);
29036
+ } catch {
29037
+ throw new CommandError("INVALID_VALUE", t2("list.since", { value }));
29038
+ }
29039
+ if (!head) return { commits: [] };
29040
+ return { commits: project.reader.decode(await project.reader.run(["rev-list", `${commit}..${head}`, "--"])).split("\n").filter(Boolean) };
29041
+ }
28601
29042
  var contains = (query) => {
28602
29043
  const wanted = query?.trim().toLowerCase();
28603
29044
  return (...texts) => !wanted || texts.some((value) => value?.toLowerCase().includes(wanted));
@@ -28620,16 +29061,37 @@ function selected(rows, fields2) {
28620
29061
  text: rows.length ? text2(rows.map((row) => fields2.map((f) => cell(row[f])).join(" "))) : ""
28621
29062
  };
28622
29063
  }
29064
+ var PAGE_SIZE = 20;
29065
+ function paginate(rows, keyOf, options) {
29066
+ let start = 0;
29067
+ if (options.after !== void 0) {
29068
+ const at = rows.findIndex((row) => keyOf(row) === options.after);
29069
+ if (at < 0) throw new CommandError("INVALID_VALUE", t2("list.after", { cursor: options.after }));
29070
+ start = at + 1;
29071
+ }
29072
+ const size = options.all ? rows.length : options.limit ?? PAGE_SIZE;
29073
+ const shown = rows.slice(start, start + size);
29074
+ const more = start + shown.length < rows.length && shown.length > 0;
29075
+ return { rows: shown, total: rows.length, next: more ? keyOf(shown[shown.length - 1]) : null };
29076
+ }
29077
+ 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 })];
29078
+ function aside(format2, lines) {
29079
+ if (format2 === "text" && lines.length) process.stderr.write(lines.join("\n") + "\n");
29080
+ return "";
29081
+ }
28623
29082
 
28624
29083
  // src/commands/specs.ts
28625
29084
  var specKinds = ["feature", "requirement", "design"];
28626
29085
  var specSorts = ["order", "title", "updated"];
28627
- var columns = ["id", "kind", "feature", "path", "title", "description", "order", "draft", "requirements", "sources", "designs", "updated", "line"];
29086
+ var columns = ["id", "kind", "feature", "path", "title", "description", "order", "draft", "state", "previousPath", "requirements", "sources", "designs", "updated", "line"];
28628
29087
  var kindLabel = { feature: () => t2("docs.feature"), requirement: () => t2("docs.requirement"), design: () => t2("docs.design") };
28629
29088
  var runSpecsList = (options) => runCommand("specs", options.format, async () => {
28630
29089
  const fields2 = checkFields(options.fields, columns);
28631
29090
  const project = await openProject(process.cwd());
28632
- const { documents, problems } = await project.cache.documents.list();
29091
+ const listed3 = await project.cache.documents.list();
29092
+ const problems = listed3.problems;
29093
+ const states = await documentStates(project, listed3.documents);
29094
+ const documents = states.documents;
28633
29095
  const byId = new Map(documents.map((d) => [d.id, d]));
28634
29096
  const arranged = arrangeDocuments(documents);
28635
29097
  let features = arranged.features;
@@ -28671,6 +29133,8 @@ var runSpecsList = (options) => runCommand("specs", options.format, async () =>
28671
29133
  description: doc.description,
28672
29134
  ...doc.kind === "requirement" || doc.kind === "design" ? { order: doc.order } : {},
28673
29135
  ...doc.draft ? { draft: true } : {},
29136
+ state: states.state(doc.id),
29137
+ ...states.previousPath(doc.id) ? { previousPath: states.previousPath(doc.id) } : {},
28674
29138
  ...doc.kind === "design" ? { requirements: doc.requirements.map(titled), sources: titledSources(doc, byId) } : {},
28675
29139
  ...doc.kind === "requirement" ? { designs: designsOf.get(doc.id) ?? [] } : {},
28676
29140
  ...historyNeeded ? { updated: latest.get(doc.id) ?? null } : {},
@@ -28678,11 +29142,16 @@ var runSpecsList = (options) => runCommand("specs", options.format, async () =>
28678
29142
  })));
28679
29143
  if (options.sort === "title") rows.sort((a, b) => a.title.localeCompare(b.title));
28680
29144
  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);
29145
+ const grouped = options.sort === "order";
29146
+ const groups = features.map((f) => ({ id: f.index.id, rows: rows.filter((r) => r.feature === f.index.feature) })).filter((g) => g.rows.length);
29147
+ const page = grouped ? paginate(groups, (g) => g.id, options) : paginate(rows, (r) => r.id, options);
29148
+ const shown = grouped ? page.rows.flatMap((g) => g.rows) : page.rows;
29149
+ const more = pageLine(page, 0, grouped ? t2("list.features") : t2("list.items"));
29150
+ const paging = { total: page.total, next: page.next, unit: grouped ? "feature" : "document" };
28682
29151
  const orphans = options.feature === void 0 ? arranged.orphans : [];
28683
29152
  if (fields2) {
28684
29153
  const picked = selected(shown, fields2);
28685
- return { json: { documents: picked.json, orphans, problems }, text: picked.text };
29154
+ return { json: { documents: picked.json, orphans, problems, page: paging }, text: picked.text + aside(options.format, more) };
28686
29155
  }
28687
29156
  const refName = (r) => r.id + " " + (r.title ?? "(" + t2("docs.missing") + ")");
28688
29157
  const excerpt = (row) => row.line ? [" " + row.line] : [];
@@ -28691,29 +29160,30 @@ var runSpecsList = (options) => runCommand("specs", options.format, async () =>
28691
29160
  for (const f of features) {
28692
29161
  const own2 = shown.filter((r) => r.feature === f.index.feature);
28693
29162
  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}`);
29163
+ 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
29164
  const index = own2.find((r) => r.kind === "feature");
28696
29165
  if (index) out.push(...excerpt(index));
28697
- const requirement2 = (r) => [`${r.order} ${line(byId.get(r.id))}`, ...excerpt(r)];
29166
+ const requirement2 = (r) => [`${r.order} ${line(byId.get(r.id))}${stateMark(r.state)}`, ...excerpt(r)];
28698
29167
  out.push(...section(" " + t2("docs.requirements"), own2.filter((r) => r.kind === "requirement").flatMap(requirement2), " "));
28699
29168
  const designs = own2.filter((r) => r.kind === "design").flatMap((r) => {
28700
29169
  const refs = r.requirements.length ? " [" + r.requirements.map(refName).join(", ") + "]" : "";
28701
29170
  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)];
29171
+ 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
29172
  });
28704
29173
  out.push(...section(" " + t2("docs.designs"), designs, " "));
28705
29174
  }
28706
29175
  } else {
28707
29176
  for (const r of shown) {
28708
29177
  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));
29178
+ out.push(`${r.id} ${r.title}${draftMark(r)}${stateMark(r.state)} (${kindLabel[r.kind]()} \xB7 ${r.feature}) \u2014 ${r.description}${when}`, ...excerpt(r));
28710
29179
  }
28711
29180
  }
28712
29181
  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
29182
  if (!out.length) out.push(filtered ? t2("docs.noMatch") : t2("docs.empty"));
29183
+ out.push(...more);
28714
29184
  if (orphans.length) out.push(t2("docs.orphans", { folders: orphans.join(", ") }));
28715
29185
  if (problems.length) out.push(t2("docs.unreadable", { count: problems.length }));
28716
- return { json: { documents: shown, orphans, problems }, text: text2(out) };
29186
+ return { json: { documents: shown, orphans, problems, page: paging }, text: text2(out) };
28717
29187
  });
28718
29188
  var runSpecsShow = (ids, options) => runCommand("specs", options.format, async () => {
28719
29189
  const project = await openProject(process.cwd());
@@ -28723,16 +29193,19 @@ var runSpecsShow = (ids, options) => runCommand("specs", options.format, async (
28723
29193
  var runSpecsNew = (kind2, path3, options) => runCommand("specs", options.format, async () => createDocument(await openProject(process.cwd()), kind2, path3, options));
28724
29194
 
28725
29195
  // src/commands/instructions.ts
28726
- import { lstat as lstat10 } from "node:fs/promises";
28727
- import { join as join15 } from "node:path";
29196
+ import { lstat as lstat11 } from "node:fs/promises";
29197
+ import { join as join16 } from "node:path";
28728
29198
  var instructionSorts = ["name", "updated"];
28729
- var columns2 = ["id", "name", "path", "title", "description", "draft", "files", "updated", "line"];
29199
+ var columns2 = ["id", "name", "path", "title", "description", "draft", "state", "previousPath", "files", "updated", "line"];
28730
29200
  var AGENTS = "AGENTS.md";
28731
29201
  var runInstructionsList = (options) => runCommand("instructions", options.format, async () => {
28732
29202
  const fields2 = checkFields(options.fields, columns2);
28733
29203
  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() };
29204
+ const listed3 = await project.cache.documents.list();
29205
+ const problems = listed3.problems;
29206
+ const states = await documentStates(project, listed3.documents);
29207
+ const documents = states.documents;
29208
+ const agents2 = { path: AGENTS, exists: !!(await lstat11(join16(project.root, AGENTS)).catch(() => void 0))?.isFile() };
28736
29209
  const historyNeeded = options.sort === "updated" || options.author !== void 0 || !!fields2?.includes("updated");
28737
29210
  const latest = /* @__PURE__ */ new Map();
28738
29211
  const newest = /* @__PURE__ */ new Map();
@@ -28753,33 +29226,46 @@ var runInstructionsList = (options) => runCommand("instructions", options.format
28753
29226
  }
28754
29227
  const matched = options.q !== void 0 ? await project.cache.documents.matching(options.q) : void 0;
28755
29228
  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
- })));
29229
+ const rows = await Promise.all(instructions2.map(async (d) => {
29230
+ const state2 = states.state(d.id);
29231
+ const { files, limited } = state2 === "deleted" ? { files: [], limited: false } : await readInstructionFiles(project.root, d.path);
29232
+ return {
29233
+ id: d.id,
29234
+ name: d.name,
29235
+ path: d.path,
29236
+ title: d.title,
29237
+ description: d.description,
29238
+ ...d.draft ? { draft: true } : {},
29239
+ state: state2,
29240
+ ...states.previousPath(d.id) ? { previousPath: states.previousPath(d.id) } : {},
29241
+ files,
29242
+ ...limited ? { filesLimited: true } : {},
29243
+ ...historyNeeded ? { updated: latest.get(d.id) ?? null } : {},
29244
+ ...matched ? { line: matched.get(d.id) } : {}
29245
+ };
29246
+ }));
28767
29247
  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);
29248
+ const page = paginate(rows, (r) => r.id, options);
29249
+ const shown = page.rows;
29250
+ const paging = { total: page.total, next: page.next, unit: "instruction" };
28769
29251
  const unreadable2 = problems.filter((p) => p.path.startsWith(INSTRUCTIONS_ROOT + "/"));
28770
29252
  if (fields2) {
28771
29253
  const picked = selected(shown, fields2);
28772
- return { json: { agents: agents2, instructions: picked.json, problems: unreadable2 }, text: picked.text };
29254
+ return {
29255
+ json: { agents: agents2, instructions: picked.json, problems: unreadable2, page: paging },
29256
+ text: selected(shown.map((r) => ({ ...r, files: r.files.map((f) => f.path) })), fields2).text + aside(options.format, pageLine(page))
29257
+ };
28773
29258
  }
28774
29259
  const out = [agents2.exists ? t2("instructions.agents") : t2("instructions.noAgents")];
28775
29260
  for (const r of shown) {
28776
- const files = r.files ? " \xB7 " + t2("instructions.files", { count: r.files }) : "";
28777
29261
  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] : []);
29262
+ out.push(`${r.id} ${r.title}${draftMark(r)}${stateMark(r.state)} (${r.name}) \u2014 ${r.description}${when}`, ...r.line ? [" " + r.line] : []);
29263
+ out.push(...r.files.map((f) => " " + fileLine(f)), ...r.filesLimited ? [" " + t2("instructions.filesLimited", { count: r.files.length })] : []);
28779
29264
  }
28780
29265
  if (!shown.length) out.push(options.q !== void 0 || options.author !== void 0 ? t2("docs.noMatch") : t2("instructions.empty"));
29266
+ out.push(...pageLine(page));
28781
29267
  if (unreadable2.length) out.push(t2("docs.unreadable", { count: unreadable2.length }));
28782
- return { json: { agents: agents2, instructions: shown, problems: unreadable2 }, text: text2(out) };
29268
+ return { json: { agents: agents2, instructions: shown, problems: unreadable2, page: paging }, text: text2(out) };
28783
29269
  });
28784
29270
  async function resolveInstructions(project, targets, at) {
28785
29271
  const view = at === void 0 ? await working(project) : await committed(project, at);
@@ -28805,9 +29291,9 @@ var runInstructionsShow = (targets, options) => runCommand("instructions", optio
28805
29291
  var runInstructionsNew = (name2, options) => runCommand("instructions", options.format, async () => createDocument(await openProject(process.cwd()), "instruction", name2, options));
28806
29292
 
28807
29293
  // 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) => {
29294
+ import { lstat as lstat12, readdir as readdir6 } from "node:fs/promises";
29295
+ import { join as join17 } from "node:path";
29296
+ var info2 = async (path3) => lstat12(path3).catch((e) => {
28811
29297
  if (e.code === "ENOENT") return void 0;
28812
29298
  throw e;
28813
29299
  });
@@ -28815,11 +29301,11 @@ var posix = (path3) => path3.split("\\").join("/");
28815
29301
  async function listAssets(root) {
28816
29302
  const out = [];
28817
29303
  async function visit2(relative, depth) {
28818
- const stat3 = await info2(join16(root, relative));
29304
+ const stat3 = await info2(join17(root, relative));
28819
29305
  if (!stat3 || stat3.isSymbolicLink()) return;
28820
29306
  if (stat3.isDirectory()) {
28821
29307
  if (depth > 8 || out.length > 4e3) return;
28822
- for (const name2 of (await readdir6(join16(root, relative))).sort()) await visit2(relative + "/" + name2, depth + 1);
29308
+ for (const name2 of (await readdir6(join17(root, relative))).sort()) await visit2(relative + "/" + name2, depth + 1);
28823
29309
  } else if (stat3.isFile()) out.push({ path: posix(relative), bytes: stat3.size });
28824
29310
  }
28825
29311
  await visit2(ASSETS_DIR, 0);
@@ -28828,7 +29314,7 @@ async function listAssets(root) {
28828
29314
  async function readDocumentWarnings(root, documents) {
28829
29315
  const outside = [...new Set(documentBodyLinks(documents).map((l) => l.target).filter((t3) => !t3.startsWith(".gitifact/") || t3.startsWith(INSTRUCTIONS_ROOT + "/")))];
28830
29316
  const existing = /* @__PURE__ */ new Set();
28831
- for (const target of outside) if ((await info2(join16(root, ...target.split("/"))))?.isFile()) existing.add(target);
29317
+ for (const target of outside) if ((await info2(join17(root, ...target.split("/"))))?.isFile()) existing.add(target);
28832
29318
  return documentWarnings(documents, await listAssets(root), existing);
28833
29319
  }
28834
29320
 
@@ -28852,30 +29338,30 @@ var runCheck = (options) => runCommand("check", options.format, async () => {
28852
29338
 
28853
29339
  // src/commands/changes.ts
28854
29340
  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";
29341
+ import { mkdir as mkdir6, open as open5, readFile as readFile16, rename as rename3, rmdir, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
29342
+ import { join as join20 } from "node:path";
28857
29343
 
28858
29344
  // 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";
29345
+ import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
29346
+ import { lstat as lstat13, mkdir as mkdir5, readdir as readdir7, realpath as realpath3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
28861
29347
  import { tmpdir } from "node:os";
28862
- import { basename, dirname as dirname2, join as join17 } from "node:path";
29348
+ import { basename, dirname as dirname2, join as join18 } from "node:path";
28863
29349
  var maxAge = 7 * 24 * 60 * 60 * 1e3;
28864
- var info3 = (path3) => lstat12(path3).catch((e) => {
29350
+ var info3 = (path3) => lstat13(path3).catch((e) => {
28865
29351
  if (e.code === "ENOENT") return void 0;
28866
29352
  throw e;
28867
29353
  });
28868
29354
  var comparable = (path3) => process.platform === "win32" ? path3.toLowerCase() : path3;
28869
29355
  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") };
29356
+ const key = createHash4("sha256").update(comparable(await realpath3(root))).digest("hex").slice(0, 16);
29357
+ return { system: join18((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join18(root, ".gitifact", "tmp") };
28872
29358
  }
28873
29359
  async function writable(directory) {
28874
29360
  try {
28875
29361
  await mkdir5(directory, { recursive: true });
28876
29362
  const stat3 = await info3(directory);
28877
29363
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) return false;
28878
- const probe = join17(directory, ".probe-" + randomBytes2(8).toString("hex"));
29364
+ const probe = join18(directory, ".probe-" + randomBytes2(8).toString("hex"));
28879
29365
  await writeFile3(probe, "", { flag: "wx" });
28880
29366
  await unlink3(probe);
28881
29367
  return true;
@@ -28889,7 +29375,7 @@ async function prepareAgentInputs(root, controls = {}) {
28889
29375
  if (await writable(system)) directory = system;
28890
29376
  else if (await writable(project)) {
28891
29377
  directory = project;
28892
- await writeFile3(join17(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
29378
+ await writeFile3(join18(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
28893
29379
  if (e.code !== "EEXIST") throw e;
28894
29380
  });
28895
29381
  }
@@ -28897,10 +29383,10 @@ async function prepareAgentInputs(root, controls = {}) {
28897
29383
  const now = (controls.now ?? Date.now)();
28898
29384
  for (const name2 of await readdir7(directory).catch(() => [])) {
28899
29385
  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);
29386
+ const stat3 = await info3(join18(directory, name2)).catch(() => void 0);
29387
+ if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink3(join18(directory, name2)).catch(() => void 0);
28902
29388
  }
28903
- return { commit: join17(directory, "commit.json") };
29389
+ return { commit: join18(directory, "commit.json") };
28904
29390
  }
28905
29391
  async function discardAgentInput(root, file2, controls = {}) {
28906
29392
  try {
@@ -28921,18 +29407,18 @@ async function discardAgentInput(root, file2, controls = {}) {
28921
29407
  }
28922
29408
 
28923
29409
  // 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";
29410
+ import { createHash as createHash5 } from "node:crypto";
29411
+ import { lstat as lstat14, readFile as readFile14 } from "node:fs/promises";
29412
+ import { join as join19 } from "node:path";
28927
29413
  var fail3 = (message) => {
28928
29414
  throw new CommandError("INVALID_COMMIT", message);
28929
29415
  };
28930
- var hash2 = (value) => createHash4("sha256").update(value).digest("hex");
29416
+ var hash2 = (value) => createHash5("sha256").update(value).digest("hex");
28931
29417
  var optional2 = (path3) => readFile14(path3).catch((e) => {
28932
29418
  if (e.code === "ENOENT") return null;
28933
29419
  throw e;
28934
29420
  });
28935
- var info4 = (path3) => lstat13(path3).catch((e) => {
29421
+ var info4 = (path3) => lstat14(path3).catch((e) => {
28936
29422
  if (e.code === "ENOENT") return void 0;
28937
29423
  throw e;
28938
29424
  });
@@ -28958,11 +29444,11 @@ async function fingerprint(root, path3) {
28958
29444
  validPath2(path3);
28959
29445
  const parts = path3.split("/");
28960
29446
  for (let i = 1; i <= parts.length; i++) {
28961
- const target = join18(root, ...parts.slice(0, i));
29447
+ const target = join19(root, ...parts.slice(0, i));
28962
29448
  const stat3 = await info4(target);
28963
29449
  if (!stat3) return null;
28964
29450
  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"));
29451
+ if (i < parts.length && await info4(join19(target, ".git"))) fail3(t2("commitFiles.nestedRepository"));
28966
29452
  if (i === parts.length) {
28967
29453
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail3(t2("commitFiles.hardLinkOrSize"));
28968
29454
  return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile14(target)]));
@@ -29029,18 +29515,12 @@ async function readStdin(stream) {
29029
29515
  var fields = ["paths", "message", "authorization", "migration"];
29030
29516
  var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
29031
29517
  var MIGRATION_TARGET = "0.8.0";
29518
+ var PATH_LIMIT = 128;
29519
+ var MIGRATION_PATH_LIMIT = 5e3;
29032
29520
  var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
29033
29521
  var problemLines2 = (problems) => problems.map((p) => " " + p.code + " " + p.message);
29034
29522
  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
- };
29523
+ var pendingChanges = (project, head) => workingChanges(project, head);
29044
29524
  var shared = (changes2, records2) => changes2.map((c) => c.id).filter((id) => records2.filter((r) => r.docs.includes(id)).length > 1);
29045
29525
  var changeLine = (c) => `${c.types.join(",").padEnd(9)} ${c.id} ${c.title} \u2014 ${c.path}${c.previousPath ? " (\u2190 " + c.previousPath + ")" : ""}`;
29046
29526
  var recordLine = (r, changes2) => {
@@ -29087,7 +29567,7 @@ var runChangesCommit = (options, controls = {}) => runCommand("changes", options
29087
29567
  async function commitChanges(cwd, input2, dryRun) {
29088
29568
  const request = object3(input2);
29089
29569
  if (Object.keys(request).some((k) => !fields.includes(k))) fail3(t2("commit.unknownField"));
29090
- const selected2 = paths(request.paths);
29570
+ const selected2 = paths(request.paths, request.migration === true ? MIGRATION_PATH_LIMIT : PATH_LIMIT);
29091
29571
  const authorization = object3(request.authorization);
29092
29572
  if (Object.keys(authorization).sort().join(",") !== "basis,evidence" || !["user-request", "project-policy"].includes(String(authorization.basis))) fail3(t2("commit.authorizationRequired"));
29093
29573
  text3(authorization.evidence, 2e3);
@@ -29118,7 +29598,7 @@ async function commitChanges(cwd, input2, dryRun) {
29118
29598
  ...stdin ? { input: stdin } : {}
29119
29599
  });
29120
29600
  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");
29601
+ const busy = join20(gitDir, "gitifact-changes-commit.lock");
29122
29602
  if (await info4(busy)) fail3(t2("commit.busy", { path: busy }));
29123
29603
  if ((await staged()).length) fail3(t2("commit.existingStaging"));
29124
29604
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
@@ -29157,7 +29637,7 @@ async function commitChanges(cwd, input2, dryRun) {
29157
29637
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
29158
29638
  return { outcome: "dry-run", committed: false, ...summary, commit: void 0 };
29159
29639
  }
29160
- const temporary = join19(gitDir, "gitifact-commit-index-" + randomUUID5());
29640
+ const temporary = join20(gitDir, "gitifact-commit-index-" + randomUUID5());
29161
29641
  let owned = false;
29162
29642
  let uncertain = false;
29163
29643
  let commitStarted = false;
@@ -29171,16 +29651,16 @@ async function commitChanges(cwd, input2, dryRun) {
29171
29651
  if (e.code === "EEXIST") fail3(t2("commit.busy", { path: busy }));
29172
29652
  throw e;
29173
29653
  }
29174
- const lock = indexLock = await open4(indexPath + ".lock", "wx", 384);
29654
+ const lock = indexLock = await open5(indexPath + ".lock", "wx", 384);
29175
29655
  const original = await optional2(indexPath);
29176
29656
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail3(t2("commit.indexChanged"));
29177
29657
  const before = await hashes([...started.keys()]);
29178
29658
  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 }));
29659
+ await writeFile4(join20(busy, "recovery.json"), JSON.stringify({ before: base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected2, message }));
29180
29660
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected2, ...contextPaths])]);
29181
29661
  if (original) await writeFile4(temporary, original, { flag: "wx" });
29182
29662
  else await git(["read-tree", "--empty"], temporary);
29183
- await git(["add", "--", ...stageable], temporary);
29663
+ await git(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], temporary, Buffer.from(stageable.join("\0")));
29184
29664
  const actual = await staged(temporary);
29185
29665
  if (!actual.length || actual.some((p) => !selected2.includes(p))) fail3(t2("commit.unexpectedFiles"));
29186
29666
  const storeFiles = selected2.filter((p) => record3(p) && locked.get(p) !== null);
@@ -29193,18 +29673,18 @@ async function commitChanges(cwd, input2, dryRun) {
29193
29673
  const size = Number(header[2]);
29194
29674
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail3(t2("commit.stagedUnreadable", { path: path3 }));
29195
29675
  const blob = output2.subarray(end + 1, end + 1 + size);
29196
- const file2 = await readFile16(join19(root, path3));
29676
+ const file2 = await readFile16(join20(root, path3));
29197
29677
  if (!blob.equals(file2) && !blob.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail3(t2("commit.filterChanged"));
29198
29678
  offset = end + size + 2;
29199
29679
  }
29200
29680
  }
29201
29681
  const again = await hashes([...locked.keys()]);
29202
29682
  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"));
29683
+ for (const name2 of integration) if (await info4(join20(gitDir, name2))) fail3(t2("commit.integrationStarted"));
29204
29684
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
29205
29685
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
29206
29686
  commitStarted = true;
29207
- await git(["commit", "-m", message.trim() + (trailers.length ? "\n\n" + trailers.join("\n") : "")], temporary);
29687
+ await git(["commit", "-F", "-"], temporary, Buffer.from(message.trim() + (trailers.length ? "\n\n" + trailers.join("\n") : "") + "\n"));
29208
29688
  const after = await project.reader.baseline();
29209
29689
  if (!after.head) return fail3(t2("commit.resultUnknown"));
29210
29690
  const raw = (await git(["cat-file", "commit", after.head])).toString("utf8");
@@ -29240,7 +29720,7 @@ async function commitChanges(cwd, input2, dryRun) {
29240
29720
  await unlink4(temporary).catch(() => {
29241
29721
  });
29242
29722
  if (owned) {
29243
- await unlink4(join19(busy, "recovery.json")).catch(() => {
29723
+ await unlink4(join20(busy, "recovery.json")).catch(() => {
29244
29724
  });
29245
29725
  await rmdir(busy);
29246
29726
  }
@@ -29251,7 +29731,7 @@ async function commitChanges(cwd, input2, dryRun) {
29251
29731
 
29252
29732
  // src/commands/records.ts
29253
29733
  import { readFile as readFile17 } from "node:fs/promises";
29254
- import { join as join20 } from "node:path";
29734
+ import { join as join21 } from "node:path";
29255
29735
  var runRecordsNew = (options) => runCommand("records", options.format, async () => {
29256
29736
  if (/[\r\n]/.test(options.title)) throw new CommandError("INVALID_VALUE", t2("docs.singleLine", { field: "title" }));
29257
29737
  const docs = [...new Set(options.docs.flatMap((d) => d.split(",")).map((d) => d.trim()).filter(Boolean))];
@@ -29287,7 +29767,7 @@ var runRecordsShow = (ids, options) => runCommand("records", options.format, asy
29287
29767
  const shown = await Promise.all([...new Set(ids)].map(async (id) => {
29288
29768
  const path3 = await findRecord(project.root, id);
29289
29769
  if (!path3) return { id, missing: true };
29290
- const source2 = await readFile17(join20(project.root, ...path3.split("/")), "utf8");
29770
+ const source2 = await readFile17(join21(project.root, ...path3.split("/")), "utf8");
29291
29771
  const log = project.reader.decode(await project.reader.run(["log", "-1", "--diff-filter=A", "--format=%H%x00%aN%x00%aI", "--", path3])).trim();
29292
29772
  const [commit, author, date5] = log.split("\0");
29293
29773
  return { id, path: path3, text: source2, commit: commit ? { commit, author, date: date5 } : null };
@@ -29312,18 +29792,17 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29312
29792
  const fields2 = checkFields(options.fields, options.doc === void 0 ? recordColumns : eventColumns);
29313
29793
  const project = await openProject(process.cwd());
29314
29794
  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
29795
  const sectionText = (sections2) => sections2.map((s) => s.body);
29320
29796
  if (options.doc !== void 0) {
29797
+ const after = await since(project, options.since, head);
29798
+ const by = byAuthor(options.author);
29799
+ const has2 = contains(options.q);
29321
29800
  const id = options.doc;
29322
29801
  const events = head ? await project.cache.history.ofDocument(head, id) : [];
29323
29802
  const { documents } = await project.cache.documents.list();
29324
29803
  const title = documents.find((d) => d.id === id)?.title ?? events.map((e) => (e.after ?? e.before)?.title).find(Boolean) ?? null;
29325
29804
  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) => ({
29805
+ 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
29806
  commit: e.commit,
29328
29807
  date: e.date,
29329
29808
  author: e.author,
@@ -29332,10 +29811,13 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29332
29811
  types: e.types,
29333
29812
  path: (e.after ?? e.before)?.path ?? null,
29334
29813
  records: e.records
29335
- })));
29814
+ })), (e) => e.commit, options);
29815
+ const rows2 = page.rows;
29816
+ const paging2 = { total: page.total, next: page.next, unit: "commit" };
29817
+ const more2 = pageLine(page, 0, t2("list.commits"));
29336
29818
  if (fields2) {
29337
29819
  const picked = selected(rows2, fields2);
29338
- return { json: { doc: { id, title }, events: picked.json }, text: picked.text };
29820
+ return { json: { doc: { id, title }, events: picked.json, page: paging2 }, text: picked.text + aside(options.format, more2) };
29339
29821
  }
29340
29822
  const out2 = [`${id} ${title ?? ""}`.trimEnd()];
29341
29823
  for (const e of rows2) {
@@ -29348,20 +29830,35 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29348
29830
  out2.push(" " + t2("docs.commit") + ": " + e.message);
29349
29831
  }
29350
29832
  if (!rows2.length) out2.push(events.length ? t2("docs.noMatch") : t2("docs.noHistory"));
29351
- return { json: { doc: { id, title }, events: rows2 }, text: text2(out2) };
29833
+ if (more2.length) out2.push("", ...more2);
29834
+ return { json: { doc: { id, title }, events: rows2, page: paging2 }, text: text2(out2) };
29352
29835
  }
29353
- const pending = options.author !== void 0 ? [] : [...(await project.pendingRecords()).files].flatMap(([path3, source2]) => {
29836
+ const has = contains(options.q);
29837
+ const pending = (options.author !== void 0 ? [] : [...(await project.pendingRecords()).files].flatMap(([path3, source2]) => {
29354
29838
  try {
29355
29839
  return [parseRecordFile(path3, source2)];
29356
29840
  } catch {
29357
29841
  return [];
29358
29842
  }
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]);
29843
+ }).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);
29844
+ const size = options.all ? void 0 : options.limit ?? PAGE_SIZE;
29845
+ const inPending = options.after === void 0 ? 0 : pending.findIndex((r) => r.id === options.after) + 1;
29846
+ const afterCommitted = options.after !== void 0 && !inPending ? options.after : void 0;
29847
+ const pendingShown = afterCommitted !== void 0 ? [] : pending.slice(inPending, size === void 0 ? void 0 : inPending + size);
29848
+ const filter = { author: options.author, q: options.q, ...await sinceRange(project, options.since, head) };
29849
+ const room = size === void 0 ? void 0 : size - pendingShown.length;
29850
+ const found = head ? await project.cache.history.records(head, filter, afterCommitted, room) : { records: [], total: 0 };
29851
+ if (!found) throw new CommandError("INVALID_VALUE", t2("list.after", { cursor: options.after }));
29852
+ 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 }));
29853
+ const rows = [...pendingShown, ...committed2];
29854
+ const total = pending.length + found.total;
29855
+ const before = afterCommitted === void 0 ? inPending : pending.length + await readThrough(project, head, filter, afterCommitted);
29856
+ const next = before + rows.length < total && rows.length ? rows[rows.length - 1].id : null;
29857
+ const paging = { total, next, unit: "record" };
29858
+ const more = pageLine({ rows, total, next }, before, t2("list.records"));
29362
29859
  if (fields2) {
29363
29860
  const picked = selected(rows, fields2);
29364
- return { json: { records: picked.json }, text: picked.text };
29861
+ return { json: { records: picked.json, page: paging }, text: picked.text + aside(options.format, more) };
29365
29862
  }
29366
29863
  const docs = (ids) => ids.slice(0, 5).join(", ") + (ids.length > 5 ? " " + t2("records.moreDocs", { count: ids.length - 5 }) : "");
29367
29864
  const out = rows.flatMap((r) => [
@@ -29369,13 +29866,18 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
29369
29866
  ` ${r.commit ? `${r.date.slice(0, 10)} ${r.commit.slice(0, 7)} ${r.author}` : t2("records.uncommitted")} \xB7 ${docs(r.docs)}`
29370
29867
  ]);
29371
29868
  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")]) };
29869
+ return { json: { records: rows, page: paging }, text: text2(out.length ? [...out, ...more] : [filtered ? t2("docs.noMatch") : t2("records.empty")]) };
29373
29870
  });
29871
+ async function readThrough(project, head, filter, id) {
29872
+ if (!head) return 0;
29873
+ const all = await project.cache.history.records(head, filter, void 0, void 0);
29874
+ return (all?.records.findIndex((r) => r.id === id) ?? -1) + 1;
29875
+ }
29374
29876
 
29375
29877
  // src/commands/feedback.ts
29376
29878
  import { readFile as readFile18 } from "node:fs/promises";
29377
29879
  import { release as release3 } from "node:os";
29378
- import { dirname as dirname3, join as join21 } from "node:path";
29880
+ import { dirname as dirname3, join as join22 } from "node:path";
29379
29881
 
29380
29882
  // src/adapters/github/issue.ts
29381
29883
  import { spawn as spawn2 } from "node:child_process";
@@ -29442,7 +29944,7 @@ function parseFeedback(input2) {
29442
29944
  }
29443
29945
  async function schemaVersion(from) {
29444
29946
  for (let folder = from; ; folder = dirname3(folder)) {
29445
- const config2 = await readFile18(join21(folder, ".gitifact", "config.json"), "utf8").catch(() => void 0);
29947
+ const config2 = await readFile18(join22(folder, ".gitifact", "config.json"), "utf8").catch(() => void 0);
29446
29948
  if (config2 !== void 0) {
29447
29949
  try {
29448
29950
  const value = JSON.parse(config2).schemaVersion;
@@ -29520,7 +30022,7 @@ function agentDocsGit(root, inherited = process.env) {
29520
30022
 
29521
30023
  // src/commands/update.ts
29522
30024
  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 });
30025
+ var commitState = (state2, extra = {}) => ({ state: state2, commit: null, paths: [], message: null, reason: null, detail: null, ...extra });
29524
30026
  async function refreshBlocks(cwd, version2, env, controls) {
29525
30027
  const repo = initRepository(cwd, env);
29526
30028
  let first;
@@ -29650,10 +30152,10 @@ async function runUpdateCheck(format2, version2) {
29650
30152
  process.stdout.write(JSON.stringify(dto) + "\n");
29651
30153
  return;
29652
30154
  }
29653
- const state = dto.update;
30155
+ const state2 = dto.update;
29654
30156
  const lines = [
29655
30157
  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")
30158
+ 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
30159
  ];
29658
30160
  if (dto.command) lines.push(t2("update.text.install", { command: dto.command }));
29659
30161
  process.stdout.write(lines.join("\n") + "\n");
@@ -29668,15 +30170,15 @@ for (let i = 0; i < languageArgs.length; i++) {
29668
30170
  if (value === "ko" || value === "en") selectedLanguage = value;
29669
30171
  }
29670
30172
  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());
30173
+ var program2 = new Command().name("gitifact").addOption(new Option("--lang <language>", t2("help.lang")).choices(["ko", "en"])).description(t2("help.program")).version("0.8.1").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
29672
30174
  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"));
30175
+ 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.1"));
30176
+ 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.1"));
30177
+ 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.1") : runUpdate(options, "0.8.1"));
29676
30178
  var listed2 = (command, sorts) => {
29677
30179
  command.allowExcessArguments(false).option("--q <words>", t2("help.listQ")).option("--author <name>", t2("help.listAuthor"));
29678
30180
  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());
30181
+ 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
30182
  };
29681
30183
  var specs = program2.command("specs").description(t2("help.specs"));
29682
30184
  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 +30196,7 @@ program2.command("check").description(t2("help.check")).allowExcessArguments(fal
29694
30196
  var changes = program2.command("changes").description(t2("help.changes"));
29695
30197
  changes.command("list").description(t2("help.changesList")).allowExcessArguments(false).addOption(format()).action((o) => runChangesList(o));
29696
30198
  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"));
30199
+ 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.1"));
29698
30200
  var guide = program2.command("guide").description(t2("help.guide"));
29699
30201
  guide.command("list").description(t2("help.guideList")).allowExcessArguments(false).addOption(format()).action((o) => runGuideList(o));
29700
30202
  guide.command("show").description(t2("help.guideShow")).argument("<topic>", t2("help.guideTopic")).allowExcessArguments(false).addOption(format()).action((topic, o) => runGuideShow(topic, o));