gitifact 0.8.4 → 0.8.5

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 (77) hide show
  1. package/README.md +1 -0
  2. package/dist/browser/assets/{HgiRefresh-CAJ0Xdvi.js → HgiRefresh-BSPkIVFZ.js} +1 -1
  3. package/dist/browser/assets/{Markdown-ev4hMr67.js → Markdown-B1P3lpA0.js} +1 -1
  4. package/dist/browser/assets/Selector-BG3Gd_OR.js +2 -0
  5. package/dist/browser/assets/{Tab-BwJj0rUr.js → Tab-RKLZrCiK.js} +1 -1
  6. package/dist/browser/assets/{Table-XuC9zK9w.js → Table-DjTkmoR7.js} +2 -2
  7. package/dist/browser/assets/Token-CKcN25HT.css +1 -0
  8. package/dist/browser/assets/Token-D337a1e3.js +2 -0
  9. package/dist/browser/assets/{about-vlKWfG7J.js → about-D06ipt4C.js} +2 -2
  10. package/dist/browser/assets/activity-timeline-Bl6a55GR.js +2 -0
  11. package/dist/browser/assets/changelog-DCvEVceR.js +1 -0
  12. package/dist/browser/assets/commit-Cj7mcn9u.js +4 -0
  13. package/dist/browser/assets/{contributor-Bs3RWHQv.js → contributor-D1TZSDq9.js} +1 -1
  14. package/dist/browser/assets/contributors-lkpVuCXS.js +1 -0
  15. package/dist/browser/assets/{contributors._email-BR9sLgCs.js → contributors._email-B7ATmxN1.js} +1 -1
  16. package/dist/browser/assets/{contributors.index-FoYwG7VE.js → contributors.index-Cc0QRJwQ.js} +1 -1
  17. package/dist/browser/assets/dashboard.index-BNTyn3_V.js +1 -0
  18. package/dist/browser/assets/document-7jVYmB_Z.js +2 -0
  19. package/dist/browser/assets/{document-DPtVyR5y.js → document-kGFspd5u.js} +4 -4
  20. package/dist/browser/assets/features-CBeEOrok.js +2 -0
  21. package/dist/browser/assets/features-FKxsPKab.css +1 -0
  22. package/dist/browser/assets/{features._featureId-xxpihTEs.js → features._featureId-DGUBnzjL.js} +1 -1
  23. package/dist/browser/assets/{features.index-CO7APsrh.js → features.index-Dgw4kiMG.js} +1 -1
  24. package/dist/browser/assets/getting-started-B_LeBSZE.js +1 -0
  25. package/dist/browser/assets/git-DW-pzDOm.js +1 -0
  26. package/dist/browser/assets/index-CswY1wJ4.css +1 -0
  27. package/dist/browser/assets/index-IML0_BCy.js +49 -0
  28. package/dist/browser/assets/instructions-Qhbd8_KG.js +1 -0
  29. package/dist/browser/assets/{instructions._instructionId-Bp9u9xx9.js → instructions._instructionId-CnX6OyFZ.js} +1 -1
  30. package/dist/browser/assets/instructions.agents-0tKcPahQ.js +1 -0
  31. package/dist/browser/assets/instructions.index-B3v6vtEg.js +1 -0
  32. package/dist/browser/assets/{lazyRouteComponent-D4lq6X5s.js → lazyRouteComponent-tJOK5sxC.js} +1 -1
  33. package/dist/browser/assets/load-more-DVsjzCxE.js +1 -0
  34. package/dist/browser/assets/page-header-AS8jZ1ve.js +248 -0
  35. package/dist/browser/assets/records._recordId-C2qxnLKN.js +1 -0
  36. package/dist/browser/assets/records.commits._commit-CSa3sQ8C.js +1 -0
  37. package/dist/browser/assets/records.docs._docId-SIPsGTwH.js +1 -0
  38. package/dist/browser/assets/records.index-DoE_3GrP.js +1 -0
  39. package/dist/browser/assets/records.working-Cp5ualrr.js +1 -0
  40. package/dist/browser/assets/search-CbtiuqNx.js +1 -0
  41. package/dist/browser/assets/{settings-BNIy3TaP.js → settings-tVQIx1Uf.js} +1 -1
  42. package/dist/browser/assets/{useInfiniteQuery-03AIcFvA.js → useInfiniteQuery-C0rB-9Ci.js} +1 -1
  43. package/dist/browser/assets/{useKeyboardHint-DN1hAi8g.js → useKeyboardHint-D5jGavym.js} +1 -1
  44. package/dist/browser/index.html +24 -24
  45. package/dist/i18n/en/changelog.md +15 -0
  46. package/dist/i18n/en/docs/design.md +2 -0
  47. package/dist/i18n/en/docs/spec.md +30 -5
  48. package/dist/i18n/ko/changelog.md +15 -0
  49. package/dist/i18n/ko/docs/design.md +2 -0
  50. package/dist/i18n/ko/docs/spec.md +30 -5
  51. package/dist/main.js +272 -125
  52. package/package.json +3 -3
  53. package/dist/browser/assets/Selector-CGOdL2eo.js +0 -2
  54. package/dist/browser/assets/Token-BV_4bSkY.js +0 -2
  55. package/dist/browser/assets/Token-Bqkc8eyB.css +0 -1
  56. package/dist/browser/assets/activity-timeline-Dmc-97N6.js +0 -2
  57. package/dist/browser/assets/changelog-6QcNKW8u.js +0 -1
  58. package/dist/browser/assets/commit-BcdbBdP4.js +0 -4
  59. package/dist/browser/assets/contributors-B_bVSY4J.js +0 -1
  60. package/dist/browser/assets/dashboard.index-BFp6Hqsf.js +0 -1
  61. package/dist/browser/assets/document-Q3lvgyX8.js +0 -2
  62. package/dist/browser/assets/features-CmvypKgf.js +0 -2
  63. package/dist/browser/assets/features-DUHxz0_L.css +0 -1
  64. package/dist/browser/assets/getting-started-11zYPwlJ.js +0 -1
  65. package/dist/browser/assets/git-CVY8qpaV.js +0 -1
  66. package/dist/browser/assets/index-BeeP8ZFW.js +0 -49
  67. package/dist/browser/assets/index-D1JeDnJc.css +0 -1
  68. package/dist/browser/assets/instructions-D3-EfbZG.js +0 -1
  69. package/dist/browser/assets/instructions.agents-DvFHo8x8.js +0 -1
  70. package/dist/browser/assets/instructions.index-zHzGHq7D.js +0 -1
  71. package/dist/browser/assets/load-more-CymfxLt7.js +0 -1
  72. package/dist/browser/assets/page-header-CVCjLFQo.js +0 -246
  73. package/dist/browser/assets/records._recordId-Be8aM0F-.js +0 -1
  74. package/dist/browser/assets/records.commits._commit-Dm3gX-bQ.js +0 -1
  75. package/dist/browser/assets/records.index-BgfJnGAl.js +0 -1
  76. package/dist/browser/assets/records.working-Eeag2L5N.js +0 -1
  77. package/dist/browser/assets/search-CSSuO-jR.js +0 -1
package/dist/main.js CHANGED
@@ -3517,6 +3517,9 @@ var messages_default = {
3517
3517
  "warn.ASSET_EXTENSION": "\uBE0C\uB77C\uC6B0\uC800\uAC00 \uBBF8\uB9AC \uBCF4\uC5EC \uC8FC\uC9C0 \uC54A\uB294 \uD655\uC7A5\uC790\uB77C \uB0B4\uB824\uBC1B\uAE30\uB85C\uB9CC \uC5F4\uB9BD\uB2C8\uB2E4: {path}",
3518
3518
  "warn.UNREFERENCED_ASSET": "\uC5B4\uB5A4 \uBB38\uC11C\uB3C4 \uAC00\uB9AC\uD0A4\uC9C0 \uC54A\uB294 \uC5D0\uC14B\uC785\uB2C8\uB2E4: {path}",
3519
3519
  "warn.ASSETS_TOTAL_SIZE": "\uC5D0\uC14B \uC804\uCCB4\uAC00 \uAD8C\uC7A5 \uD06C\uAE30(50MB)\uB97C \uB118\uC2B5\uB2C8\uB2E4: {bytes}\uBC14\uC774\uD2B8",
3520
+ "warn.DESIGN_OVERVIEW_LARGE": "\uC124\uACC4 overview.md\uC758 \uC808\uC774 {sections}\uAC1C\uC785\uB2C8\uB2E4. \uB367\uBD99\uC774\uAE30 \uC804\uC5D0 \uCD95 \uD30C\uC77C\uB85C \uB098\uB20C\uC9C0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3C\uC73C\uC138\uC694(guide show design\uC758 \uAC1C\uC815): {path}",
3521
+ "warn.REQUIREMENT_SCOPE_FORMAT": "\uBC94\uC704\uC640 \uC81C\uC57D\uC740 \uC218\uC6A9 \uC870\uAC74 \uC55E\uC5D0 - \uBAA9\uB85D\uC73C\uB85C\uB9CC \uC501\uB2C8\uB2E4(guide show spec): {path}",
3522
+ "warn.REQUIREMENT_CRITERIA_FORMAT": "\uC218\uC6A9 \uC870\uAC74 \uC544\uB798\uC5D0\uB294 \uBC88\uD638 \uD56D\uBAA9\uB9CC \uB461\uB2C8\uB2E4. \uADDC\uCE59\uC740 \uBC94\uC704\uC640 \uC81C\uC57D\uC73C\uB85C \uC62E\uAE30\uC138\uC694(guide show spec): {path}",
3520
3523
  "config.migrationRequired": "\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 0.7 \uC800\uC7A5 \uADDC\uC57D(schemaVersion 2)\uC785\uB2C8\uB2E4. 0.8.0 CLI\uB294 \uC61B \uD615\uC2DD\uC744 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. gitifact guide show migrate\uC758 \uC808\uCC28\uB85C \uBB38\uC11C\uB97C \uC0C8 \uAD6C\uC870\uB85C \uC62E\uAE30\uC138\uC694."
3521
3524
  };
3522
3525
 
@@ -3636,6 +3639,9 @@ var messages_default2 = {
3636
3639
  "warn.ASSET_EXTENSION": "The browser does not preview this extension and offers it only as a download: {path}",
3637
3640
  "warn.UNREFERENCED_ASSET": "No document refers to this asset: {path}",
3638
3641
  "warn.ASSETS_TOTAL_SIZE": "All assets together are over the recommended size (50 MB): {bytes} bytes",
3642
+ "warn.DESIGN_OVERVIEW_LARGE": "The design overview.md has {sections} sections. Before adding to it, ask the user whether to split it into axis files (revising, in guide show design): {path}",
3643
+ "warn.REQUIREMENT_SCOPE_FORMAT": "Scope and constraints come before the acceptance criteria, as - items only (guide show spec): {path}",
3644
+ "warn.REQUIREMENT_CRITERIA_FORMAT": "The acceptance criteria hold numbered items only; move rules to scope and constraints (guide show spec): {path}",
3639
3645
  "config.migrationRequired": "This project uses the 0.7 storage convention (schemaVersion 2). The 0.8.0 CLI does not read the old format. Move the documents to the new structure by following gitifact guide show migrate."
3640
3646
  };
3641
3647
 
@@ -3860,20 +3866,20 @@ var scalar = (value) => {
3860
3866
  fail(t("frontmatter.scalar", { value }));
3861
3867
  return value;
3862
3868
  };
3863
- function parseFrontmatter(lines, kind2, legacyIds) {
3864
- const legacy = new RegExp(`^<!-- ${marker}-(?:${kind2 === "wiki" ? "product|guide" : kind2}): (${legacyIds}-${token}) -->$`).exec(lines[0] ?? "");
3869
+ function parseFrontmatter(lines2, kind2, legacyIds) {
3870
+ const legacy = new RegExp(`^<!-- ${marker}-(?:${kind2 === "wiki" ? "product|guide" : kind2}): (${legacyIds}-${token}) -->$`).exec(lines2[0] ?? "");
3865
3871
  if (legacy) {
3866
- lines.shift();
3872
+ lines2.shift();
3867
3873
  return { id: legacy[1] };
3868
3874
  }
3869
- if (lines[0] !== "---")
3875
+ if (lines2[0] !== "---")
3870
3876
  fail(t("frontmatter.required"));
3871
- lines.shift();
3872
- const end = lines.indexOf("---");
3877
+ lines2.shift();
3878
+ const end = lines2.indexOf("---");
3873
3879
  if (end === -1)
3874
3880
  fail(t("frontmatter.unclosed"));
3875
- const block = lines.splice(0, end);
3876
- lines.shift();
3881
+ const block = lines2.splice(0, end);
3882
+ lines2.shift();
3877
3883
  const fields2 = /* @__PURE__ */ new Map();
3878
3884
  for (let i = 0; i < block.length; i++) {
3879
3885
  const line2 = block[i];
@@ -3938,17 +3944,17 @@ function validateSource(value) {
3938
3944
  function parseDesign(source2, specId) {
3939
3945
  if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3940
3946
  fail(t("design.invalidCharacters"));
3941
- const lines = normalized(source2).split("\n");
3942
- const front = parseFrontmatter(lines, "design", "S");
3947
+ const lines2 = normalized(source2).split("\n");
3948
+ const front = parseFrontmatter(lines2, "design", "S");
3943
3949
  if (front.id !== specId)
3944
3950
  fail(t("design.idMismatch"));
3945
- while (lines[0] === "")
3946
- lines.shift();
3947
- const heading = lines.shift();
3951
+ while (lines2[0] === "")
3952
+ lines2.shift();
3953
+ const heading = lines2.shift();
3948
3954
  if (!heading?.startsWith("# ") || !heading.slice(2).trim())
3949
3955
  fail(t("design.titleRequired"));
3950
3956
  const title = heading.slice(2).trim();
3951
- const raw = normalized(lines.join("\n"));
3957
+ const raw = normalized(lines2.join("\n"));
3952
3958
  if (!raw)
3953
3959
  fail(t("design.bodyRequired"));
3954
3960
  const references = /* @__PURE__ */ new Set();
@@ -3986,15 +3992,15 @@ function parseDocument(path3, source2) {
3986
3992
  validateDocumentRelativePath(match[1]);
3987
3993
  if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3988
3994
  fail(t("document.invalidCharacters", { path: path3 }));
3989
- const lines = normalized(source2).split("\n");
3990
- const { id } = parseFrontmatter(lines, "wiki", "W");
3991
- while (lines[0] === "")
3992
- lines.shift();
3993
- const heading = lines.shift();
3995
+ const lines2 = normalized(source2).split("\n");
3996
+ const { id } = parseFrontmatter(lines2, "wiki", "W");
3997
+ while (lines2[0] === "")
3998
+ lines2.shift();
3999
+ const heading = lines2.shift();
3994
4000
  if (!heading?.startsWith("# ") || !heading.slice(2).trim())
3995
4001
  fail(t("document.titleRequired", { path: path3 }));
3996
4002
  const title = heading.slice(2).trim();
3997
- const body2 = normalized(lines.join("\n"));
4003
+ const body2 = normalized(lines2.join("\n"));
3998
4004
  if (!body2)
3999
4005
  fail(t("document.bodyRequired", { path: path3 }));
4000
4006
  let fence;
@@ -4081,8 +4087,8 @@ function parseSpec(path3, source2, history = "", designSource) {
4081
4087
  fail(t("spec.unsupportedPath", { path: path3 }));
4082
4088
  if (source2.includes("\0") || source2.includes("\r") && /\r(?!\n)/.test(source2))
4083
4089
  fail(t("spec.invalidCharacters", { path: path3 }));
4084
- const lines = normalized(source2).split("\n");
4085
- const specId = parseFrontmatter(lines, "spec", "S").id;
4090
+ const lines2 = normalized(source2).split("\n");
4091
+ const specId = parseFrontmatter(lines2, "spec", "S").id;
4086
4092
  let title = "";
4087
4093
  let description = "";
4088
4094
  let current2;
@@ -4095,8 +4101,8 @@ function parseSpec(path3, source2, history = "", designSource) {
4095
4101
  else
4096
4102
  description += line2 + "\n";
4097
4103
  };
4098
- for (let i = 0; i < lines.length; i++) {
4099
- const line2 = lines[i];
4104
+ for (let i = 0; i < lines2.length; i++) {
4105
+ const line2 = lines2[i];
4100
4106
  if (waitingId) {
4101
4107
  const match = new RegExp(`^<!-- ${marker}-req: (R-${token}) -->$`).exec(line2);
4102
4108
  if (!match || !current2)
@@ -4336,13 +4342,13 @@ function quoteScalar(value) {
4336
4342
  return plain2 ? value : '"' + value.replace(/[\\"]/g, "\\$&") + '"';
4337
4343
  }
4338
4344
  function parseFrontmatterBlock(source2, path3) {
4339
- const lines = source2.split("\n");
4340
- if (lines[0] !== "---")
4345
+ const lines2 = source2.split("\n");
4346
+ if (lines2[0] !== "---")
4341
4347
  throw new DocumentError("FRONTMATTER_REQUIRED", path3, t("doc.FRONTMATTER_REQUIRED", { path: path3 }));
4342
- const end = lines.indexOf("---", 1);
4348
+ const end = lines2.indexOf("---", 1);
4343
4349
  if (end === -1)
4344
4350
  throw new DocumentError("FRONTMATTER_UNCLOSED", path3, t("doc.FRONTMATTER_UNCLOSED", { path: path3 }));
4345
- const block = lines.slice(1, end);
4351
+ const block = lines2.slice(1, end);
4346
4352
  const fields2 = /* @__PURE__ */ new Map();
4347
4353
  const bad = (line2) => new DocumentError("FRONTMATTER_LINE", path3, t("doc.FRONTMATTER_LINE", { path: path3, line: line2 }));
4348
4354
  for (let i = 0; i < block.length; i++) {
@@ -4385,7 +4391,7 @@ function parseFrontmatterBlock(source2, path3) {
4385
4391
  throw bad(line2);
4386
4392
  fields2.set(key, scalars.length ? { type: "list", items: scalars } : { type: "maps", items: maps });
4387
4393
  }
4388
- return { fields: fields2, body: lines.slice(end + 1).join("\n") };
4394
+ return { fields: fields2, body: lines2.slice(end + 1).join("\n") };
4389
4395
  }
4390
4396
  function renderFrontmatterBlock(fields2) {
4391
4397
  const out = ["---"];
@@ -4929,6 +4935,7 @@ function compareDocumentSets(before, after) {
4929
4935
  }
4930
4936
 
4931
4937
  // ../../packages/core/dist/use-cases/document-warnings.js
4938
+ var OVERVIEW_SECTION_LIMIT = 7;
4932
4939
  function documentBodyLinks(documents) {
4933
4940
  return documents.flatMap((doc) => extractLinks(doc.body).flatMap((link3) => {
4934
4941
  const target = resolveLink(doc.path, link3);
@@ -4963,6 +4970,35 @@ function documentWarnings(documents, assets, existing) {
4963
4970
  }
4964
4971
  if (total > ASSETS_TOTAL_LIMIT)
4965
4972
  warnings.push(docWarning("ASSETS_TOTAL_SIZE", ASSETS_DIR, { bytes: total }));
4973
+ for (const doc of documents) {
4974
+ if (doc.kind !== "design" || !doc.path.endsWith("/design/overview.md"))
4975
+ continue;
4976
+ const sections2 = lines(doc.body).filter((line2) => /^##\s/.test(line2)).length;
4977
+ if (sections2 >= OVERVIEW_SECTION_LIMIT)
4978
+ warnings.push(docWarning("DESIGN_OVERVIEW_LARGE", doc.path, { sections: sections2 }));
4979
+ }
4980
+ return warnings;
4981
+ }
4982
+ var lines = (body2) => body2.replace(/\r\n/g, "\n").split("\n");
4983
+ var scopeHeading = /^###\s+(?:범위와 제약|scope and constraints)\s*$/i;
4984
+ var acceptanceHeading = /^###\s+(?:수용 조건|acceptance criteria)\s*$/i;
4985
+ function requirementFormatWarnings(doc) {
4986
+ if (doc.kind !== "requirement")
4987
+ return [];
4988
+ const body2 = lines(doc.body);
4989
+ const scope = body2.findIndex((line2) => scopeHeading.test(line2.trim()));
4990
+ const acceptance = body2.findIndex((line2) => acceptanceHeading.test(line2.trim()));
4991
+ const sectionOf = (start) => {
4992
+ const rest = body2.slice(start + 1);
4993
+ const end = rest.findIndex((line2) => /^#{1,3}\s/.test(line2));
4994
+ return end < 0 ? rest : rest.slice(0, end);
4995
+ };
4996
+ const outside = (section2, item) => section2.some((line2) => line2.trim() !== "" && !item.test(line2) && !/^\s+\S/.test(line2));
4997
+ const warnings = [];
4998
+ if (scope >= 0 && (acceptance >= 0 && scope > acceptance || outside(sectionOf(scope), /^- /)))
4999
+ warnings.push(docWarning("REQUIREMENT_SCOPE_FORMAT", doc.path));
5000
+ if (acceptance >= 0 && (outside(sectionOf(acceptance), /^\d+\.\s/) || body2.slice(acceptance + 1).some((line2) => /^#{1,3}\s/.test(line2) && !scopeHeading.test(line2.trim()))))
5001
+ warnings.push(docWarning("REQUIREMENT_CRITERIA_FORMAT", doc.path));
4966
5002
  return warnings;
4967
5003
  }
4968
5004
 
@@ -4981,12 +5017,12 @@ function parseChangelog(text4) {
4981
5017
  let entry2;
4982
5018
  let section2;
4983
5019
  const fail4 = (line2, key, values = {}) => new InitError("INVALID_CHANGELOG", t("changelog.atLine", { line: line2, reason: t(key, values) }));
4984
- const lines = text4.replace(/\r\n/g, "\n").split("\n");
5020
+ const lines2 = text4.replace(/\r\n/g, "\n").split("\n");
4985
5021
  const close = (line2) => {
4986
5022
  if (entry2 && changelogSections.every((name2) => entry2[name2].length === 0))
4987
5023
  throw fail4(line2, "changelog.emptyVersion", { version: entry2.version });
4988
5024
  };
4989
- lines.forEach((raw, index) => {
5025
+ lines2.forEach((raw, index) => {
4990
5026
  const line2 = index + 1;
4991
5027
  if (raw.trim() === "")
4992
5028
  return;
@@ -5024,7 +5060,7 @@ function parseChangelog(text4) {
5024
5060
  }
5025
5061
  throw fail4(line2, "changelog.unknownLine", { text: raw });
5026
5062
  });
5027
- close(lines.length);
5063
+ close(lines2.length);
5028
5064
  if (entries.length === 0)
5029
5065
  throw fail4(1, "changelog.noVersions");
5030
5066
  return entries;
@@ -6997,14 +7033,14 @@ function toDotPath(_path) {
6997
7033
  return segs.join("");
6998
7034
  }
6999
7035
  function prettifyError(error62) {
7000
- const lines = [];
7036
+ const lines2 = [];
7001
7037
  const issues = [...error62.issues].sort((a, b) => (a.path ?? []).length - (b.path ?? []).length);
7002
7038
  for (const issue2 of issues) {
7003
- lines.push(`\u2716 ${issue2.message}`);
7039
+ lines2.push(`\u2716 ${issue2.message}`);
7004
7040
  if (issue2.path?.length)
7005
- lines.push(` \u2192 at ${toDotPath(issue2.path)}`);
7041
+ lines2.push(` \u2192 at ${toDotPath(issue2.path)}`);
7006
7042
  }
7007
- return lines.join("\n");
7043
+ return lines2.join("\n");
7008
7044
  }
7009
7045
 
7010
7046
  // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/parse.js
@@ -7839,9 +7875,9 @@ var Doc = class {
7839
7875
  return;
7840
7876
  }
7841
7877
  const content = arg;
7842
- const lines = content.split("\n").filter((x) => x);
7843
- const minIndent = Math.min(...lines.map((x) => x.length - x.trimStart().length));
7844
- const dedented = lines.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
7878
+ const lines2 = content.split("\n").filter((x) => x);
7879
+ const minIndent = Math.min(...lines2.map((x) => x.length - x.trimStart().length));
7880
+ const dedented = lines2.map((x) => x.slice(minIndent)).map((x) => " ".repeat(this.indent * 2) + x);
7845
7881
  for (const line2 of dedented) {
7846
7882
  this.content.push(line2);
7847
7883
  }
@@ -25198,16 +25234,25 @@ var event = external_exports.strictObject({
25198
25234
  after: reference,
25199
25235
  records: external_exports.array(record2)
25200
25236
  });
25201
- var browserHistoryV6 = external_exports.strictObject({
25237
+ var browserHistoryV7 = external_exports.strictObject({
25202
25238
  contract: external_exports.literal("browser-history"),
25203
- version: external_exports.literal(6),
25239
+ version: external_exports.literal(7),
25204
25240
  sessionId: external_exports.string(),
25205
25241
  head: oid2,
25206
25242
  // Matching changes and the commits that hold them in all of history, not in this page.
25207
25243
  total: external_exports.number().int().nonnegative(),
25208
25244
  commits: external_exports.number().int().nonnegative(),
25209
25245
  next: oid2.nullable(),
25210
- events: external_exports.array(event)
25246
+ events: external_exports.array(event),
25247
+ // Every document and those no record explains, and each record with how many documents it explains, in commit order.
25248
+ whole: external_exports.array(external_exports.strictObject({
25249
+ commit: oid2,
25250
+ documents: external_exports.number().int().nonnegative(),
25251
+ unrecorded: external_exports.number().int().nonnegative(),
25252
+ records: external_exports.array(external_exports.strictObject({ id: external_exports.string(), documents: external_exports.number().int().positive() }))
25253
+ })),
25254
+ // The kinds of document the history holds at all, so the filter offers a kind only when it can find something.
25255
+ kinds: external_exports.array(kind)
25211
25256
  });
25212
25257
  var browserHistorySummaryV5 = external_exports.strictObject({
25213
25258
  contract: external_exports.literal("browser-history-summary"),
@@ -25243,13 +25288,14 @@ var browserSearchV3 = external_exports.strictObject({
25243
25288
  });
25244
25289
  var headQuery = oid2;
25245
25290
  var count = (max) => external_exports.string().regex(/^(0|[1-9]\d{0,6})$/).transform(Number).pipe(external_exports.number().int().min(0).max(max));
25246
- var browserHistoryQueryV4 = external_exports.strictObject({
25291
+ var browserHistoryQueryV5 = external_exports.strictObject({
25247
25292
  head: headQuery,
25248
25293
  after: oid2.optional(),
25249
25294
  limit: count(50).pipe(external_exports.number().min(1)).optional(),
25250
- kind: changeType.optional(),
25295
+ // A feature or an instruction; `record=missing` keeps the changes no record explains.
25296
+ target: external_exports.string().regex(/^[SI]-[a-z2-7]{10}$/).optional(),
25251
25297
  document: kind.optional(),
25252
- feature: external_exports.string().regex(/^S-[a-z2-7]{10}$/).optional(),
25298
+ record: external_exports.literal("missing").optional(),
25253
25299
  author: external_exports.string().min(1).max(320).optional(),
25254
25300
  q: external_exports.string().max(200).optional()
25255
25301
  });
@@ -25345,6 +25391,24 @@ var browserRecordV2 = external_exports.strictObject({
25345
25391
  others: external_exports.number().int().nonnegative()
25346
25392
  });
25347
25393
  var browserRecordQueryV1 = external_exports.strictObject({ head: oid2, id: recordId });
25394
+ var browserDocumentHistoryV1 = external_exports.strictObject({
25395
+ contract: external_exports.literal("browser-document-history"),
25396
+ version: external_exports.literal(1),
25397
+ sessionId: external_exports.string(),
25398
+ head: oid2,
25399
+ doc: external_exports.strictObject({ id: external_exports.string(), title: external_exports.string().nullable(), kind: kind.nullable(), path: external_exports.string().nullable() }),
25400
+ total: external_exports.number().int().nonnegative(),
25401
+ recorded: external_exports.number().int().nonnegative(),
25402
+ withoutRecord: external_exports.number().int().nonnegative(),
25403
+ next: oid2.nullable(),
25404
+ events: external_exports.array(event)
25405
+ });
25406
+ var browserDocumentHistoryQueryV1 = external_exports.strictObject({
25407
+ head: oid2,
25408
+ id: external_exports.string().regex(/^[SRDWI]-[a-z2-7]{10}$/),
25409
+ after: oid2.optional(),
25410
+ limit: count(50).pipe(external_exports.number().min(1)).optional()
25411
+ });
25348
25412
  var browserInstructionFileV1 = external_exports.strictObject({
25349
25413
  contract: external_exports.literal("browser-instruction-file"),
25350
25414
  version: external_exports.literal(1),
@@ -25635,6 +25699,7 @@ var messages_default3 = {
25635
25699
  "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.",
25636
25700
  "server.workingChangeNotFound": "\uCEE4\uBC0B \uC804 \uBCC0\uACBD\uC5D0 \uADF8 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
25637
25701
  "server.recordNotFound": "\uC774 \uC774\uB825\uC5D0 \uADF8 \uACB0\uC815\uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
25702
+ "server.documentNotFound": "\uC774 \uC774\uB825\uACFC \uC791\uC5C5 \uD3F4\uB354\uC5D0 \uADF8 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
25638
25703
  "server.commitFileNotFound": "\uADF8 \uCEE4\uBC0B\uC774 \uBC14\uAFBC \uD30C\uC77C\uC774 \uC544\uB2D9\uB2C8\uB2E4.",
25639
25704
  "server.instructionFileNotFound": "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
25640
25705
  "server.specsUnreadable": "\uBA85\uC138\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
@@ -25769,7 +25834,7 @@ var messages_default3 = {
25769
25834
  "docs.pathExample.design": "payment/api (.gitifact/spec/payment/design/api.md)",
25770
25835
  "docs.pathExample.instruction": "cli-architecture (.gitifact/instructions/cli-architecture/index.md)",
25771
25836
  "docs.skeleton.feature": "(\uC774 \uAE30\uB2A5\uC774 \uBB34\uC5C7\uC774\uACE0 \uC5B4\uB514\uAE4C\uC9C0\uC778\uC9C0)",
25772
- "docs.skeleton.requirement": "(\uC0AC\uC6A9\uC790)\uB85C\uC11C, (\uBAA9\uC801)\uC744 \uC704\uD574 (\uB3D9\uC791)\uC744 \uD558\uACE0 \uC2F6\uB2E4.\n\n### \uC218\uC6A9 \uC870\uAC74\n\n1. \uC870\uAC74: (\uC0C1\uD669)\n \uAE30\uB300 \uB3D9\uC791: (\uACB0\uACFC)",
25837
+ "docs.skeleton.requirement": "(\uC0AC\uC6A9\uC790)\uB85C\uC11C, (\uBAA9\uC801)\uC744 \uC704\uD574 (\uB3D9\uC791)\uC744 \uD558\uACE0 \uC2F6\uB2E4.\n\n### \uBC94\uC704\uC640 \uC81C\uC57D\n\n- (\uCC38\xB7\uAC70\uC9D3\uC744 \uAC00\uB9B4 \uC218 \uC788\uB294 \uADDC\uCE59 \uD55C \uBB38\uC7A5. \uC5C6\uC73C\uBA74 \uC774 \uC808\uC744 \uC9C0\uC6B4\uB2E4)\n\n### \uC218\uC6A9 \uC870\uAC74\n\n1. \uC870\uAC74: (\uC0C1\uD669)\n \uAE30\uB300 \uB3D9\uC791: (\uACB0\uACFC)",
25773
25838
  "docs.skeleton.design": "(\uC774 \uC124\uACC4\uAC00 \uB2E4\uB8E8\uB294 \uAD6C\uC870\uC640 \uCC98\uB9AC \uBC29\uC2DD)",
25774
25839
  "docs.skeleton.instruction": "(\uC774 \uC791\uC5C5\uC744 \uD560 \uB54C \uC9C0\uD0AC \uADDC\uCE59\uACFC \uC808\uCC28. \uC790\uC138\uD55C \uB0B4\uC6A9\uC740 references/ \uC544\uB798 \uD30C\uC77C\uB85C \uB098\uB204\uACE0 \uC5EC\uAE30\uC11C \uB9C1\uD06C\uD55C\uB2E4)",
25775
25840
  "docs.exists": "\uC774\uBBF8 \uC788\uB294 \uD30C\uC77C\uC785\uB2C8\uB2E4: {path}",
@@ -25794,6 +25859,7 @@ var messages_default3 = {
25794
25859
  "docs.singleLine": "{field}\uC740 \uD55C \uC904\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
25795
25860
  "changes.problems": "\uBB38\uC11C \uAC80\uC0AC: \uBB38\uC81C {count}\uAC1C (check\uB85C \uD655\uC778)",
25796
25861
  "changes.warnings": "\uBB38\uC11C \uACBD\uACE0: {count}\uAC1C (check\uB85C \uD655\uC778, \uCEE4\uBC0B\uC740 \uB9C9\uC9C0 \uC54A\uC74C)",
25862
+ "changes.formatWarnings": "\uD615\uC2DD \uACBD\uACE0 {count}\uAC1C (\uC774\uBC88\uC5D0 \uBC14\uB010 \uC694\uAD6C\uC0AC\uD56D, \uCEE4\uBC0B\uC740 \uB9C9\uC9C0 \uC54A\uC74C)",
25797
25863
  "changes.clean": "\uBB38\uC11C \uAC80\uC0AC: \uBB38\uC81C \uC5C6\uC74C",
25798
25864
  "changes.pendingRecords": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC740 \uACB0\uC815\uAE30\uB85D {count}\uAC1C (*\uB294 \uC774\uBC88\uC5D0 \uBC14\uB00C\uC9C0 \uC54A\uC740 \uBB38\uC11C)",
25799
25865
  "changes.withoutRecord": "\uACB0\uC815\uAE30\uB85D\uC774 \uC5C6\uB294 \uC218\uC815\xB7\uC774\uB3D9\xB7\uC0AD\uC81C: {ids}",
@@ -25949,6 +26015,7 @@ var messages_default4 = {
25949
26015
  "server.cursorNotFound": "The place to read on from is no longer in the list. Load it again from the start.",
25950
26016
  "server.workingChangeNotFound": "No uncommitted change of that document.",
25951
26017
  "server.recordNotFound": "This history has no such decision record.",
26018
+ "server.documentNotFound": "Neither this history nor the working tree has that document.",
25952
26019
  "server.commitFileNotFound": "That commit did not change this file.",
25953
26020
  "server.instructionFileNotFound": "The instruction file was not found.",
25954
26021
  "server.specsUnreadable": "Could not read specifications.",
@@ -26083,7 +26150,7 @@ var messages_default4 = {
26083
26150
  "docs.pathExample.design": "payment/api (.gitifact/spec/payment/design/api.md)",
26084
26151
  "docs.pathExample.instruction": "cli-architecture (.gitifact/instructions/cli-architecture/index.md)",
26085
26152
  "docs.skeleton.feature": "(What this feature is and where it ends)",
26086
- "docs.skeleton.requirement": "As (a user), I want to (act) so that (purpose).\n\n### Acceptance criteria\n\n1. Condition: (situation)\n Expected: (result)",
26153
+ "docs.skeleton.requirement": "As (a user), I want to (act) so that (purpose).\n\n### Scope and constraints\n\n- (One rule that is either true or false. Remove this section if there is none)\n\n### Acceptance criteria\n\n1. Condition: (situation)\n Expected: (result)",
26087
26154
  "docs.skeleton.design": "(The structure and processing this design covers)",
26088
26155
  "docs.skeleton.instruction": "(Rules and steps to follow for this kind of work. Put details in files under references/ and link them from here)",
26089
26156
  "docs.exists": "The file already exists: {path}",
@@ -26108,6 +26175,7 @@ var messages_default4 = {
26108
26175
  "docs.singleLine": "The {field} must be a single line.",
26109
26176
  "changes.problems": "Document check: {count} problems (see check)",
26110
26177
  "changes.warnings": "Document warnings: {count} (see check; they do not block a commit)",
26178
+ "changes.formatWarnings": "Format warnings: {count} (requirements changed here; they do not block a commit)",
26111
26179
  "changes.clean": "Document check: no problems",
26112
26180
  "changes.pendingRecords": "{count} uncommitted record(s) (* marks a document this change does not touch)",
26113
26181
  "changes.withoutRecord": "Changed, moved or deleted without a record: {ids}",
@@ -26711,6 +26779,10 @@ function snippet(text4, query) {
26711
26779
  return (from > 0 ? "\u2026" : "") + text4.slice(from, at + query.length + 70).trim() + (at + query.length + 70 < text4.length ? "\u2026" : "");
26712
26780
  }
26713
26781
  var containing = (query) => "%" + query.replace(/[\\%_]/g, (c) => "\\" + c) + "%";
26782
+ function hashPrefix(query) {
26783
+ const wanted = query?.trim().toLowerCase();
26784
+ return wanted && /^[0-9a-f]{7,64}$/.test(wanted) ? wanted : void 0;
26785
+ }
26714
26786
 
26715
26787
  // src/adapters/cache/documents.ts
26716
26788
  var FILE_LIMIT = 1024 * 1024;
@@ -27103,10 +27175,10 @@ function createCommitChanges(git) {
27103
27175
  }
27104
27176
  };
27105
27177
  for (const chunk of patch.split("").filter(Boolean)) {
27106
- const lines = chunk.split("\n");
27107
- const commit = lines[0].trim();
27108
- const removed = new Set(lines.filter((l) => l.startsWith("-") && !l.startsWith("---")).flatMap((l) => read(l.slice(1))).map((r) => r.id));
27109
- found.set(commit, lines.filter((l) => l.startsWith("+") && !l.startsWith("+++")).flatMap((l) => read(l.slice(1))).filter((r) => !removed.has(r.id)).map((r) => reasonRecord(r.id, r.docs, r.reason)));
27178
+ const lines2 = chunk.split("\n");
27179
+ const commit = lines2[0].trim();
27180
+ const removed = new Set(lines2.filter((l) => l.startsWith("-") && !l.startsWith("---")).flatMap((l) => read(l.slice(1))).map((r) => r.id));
27181
+ found.set(commit, lines2.filter((l) => l.startsWith("+") && !l.startsWith("+++")).flatMap((l) => read(l.slice(1))).filter((r) => !removed.has(r.id)).map((r) => reasonRecord(r.id, r.docs, r.reason)));
27110
27182
  }
27111
27183
  return found;
27112
27184
  }
@@ -27415,25 +27487,30 @@ function createHistory(database, git, originals) {
27415
27487
  function where(filter) {
27416
27488
  const clauses = [];
27417
27489
  const params = [];
27418
- if (filter.kind) {
27419
- clauses.push("instr(',' || c.types || ',', ?) > 0");
27420
- params.push("," + filter.kind + ",");
27490
+ if (filter.target?.startsWith("I-")) {
27491
+ clauses.push("c.id = ?");
27492
+ params.push(filter.target);
27493
+ } else if (filter.target) {
27494
+ clauses.push("(c.before_spec = ? OR c.after_spec = ?)");
27495
+ params.push(filter.target, filter.target);
27421
27496
  }
27422
27497
  if (filter.document) {
27423
27498
  clauses.push("c.kind = ?");
27424
27499
  params.push(filter.document);
27425
27500
  }
27426
- if (filter.feature) {
27427
- clauses.push("(c.before_spec = ? OR c.after_spec = ?)");
27428
- params.push(filter.feature, filter.feature);
27429
- }
27501
+ if (filter.unrecorded) clauses.push("json_array_length(c.row, '$.records') = 0");
27430
27502
  if (filter.author) {
27431
27503
  clauses.push("c.email = ?");
27432
27504
  params.push(filter.author);
27433
27505
  }
27434
- if (filter.q?.trim()) {
27435
- clauses.push("instr(c.needle, ?) > 0");
27436
- params.push(filter.q.trim().toLowerCase());
27506
+ const q = filter.q?.trim().toLowerCase();
27507
+ if (q) {
27508
+ const record4 = `EXISTS (SELECT 1 FROM json_each(c.row, '$.records') j JOIN records r ON r.oid = c.oid AND r.id = j.value
27509
+ WHERE instr(lower(r.title), ?) > 0 OR instr(lower(r.id), ?) > 0
27510
+ OR instr(lower((SELECT group_concat(json_extract(s.value, '$.body'), ' ') FROM json_each(r.sections) s)), ?) > 0)`;
27511
+ const hash3 = hashPrefix(q) ? " OR substr(c.oid, 1, length(?)) = ?" : "";
27512
+ clauses.push(`(instr(c.needle, ?) > 0 OR ${record4}${hash3})`);
27513
+ params.push(q, q, q, q, ...hash3 ? [q, q] : []);
27437
27514
  }
27438
27515
  return { sql: clauses.map((c) => " AND " + c).join(""), params };
27439
27516
  }
@@ -27461,11 +27538,28 @@ function createHistory(database, git, originals) {
27461
27538
  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);
27462
27539
  const shown = commits.slice(0, limit);
27463
27540
  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);
27541
+ const oids = JSON.stringify(shown.map((c) => c.oid));
27542
+ const sizes = db.prepare(`SELECT oid, count(*) AS documents, sum(json_array_length(row, '$.records') = 0) AS unrecorded
27543
+ FROM changes WHERE oid IN (SELECT value FROM json_each(?)) GROUP BY oid`).all(oids);
27544
+ const explained = db.prepare(`SELECT c.oid AS oid, j.value AS id, count(*) AS documents FROM changes c, json_each(c.row, '$.records') j
27545
+ WHERE c.oid IN (SELECT value FROM json_each(?)) GROUP BY c.oid, j.value ORDER BY c.oid, min(c.ord)`).all(oids);
27546
+ const whole = shown.map(({ oid: oid3 }) => {
27547
+ const size = sizes.find((s) => s.oid === oid3);
27548
+ return {
27549
+ commit: oid3,
27550
+ documents: Number(size?.documents ?? 0),
27551
+ unrecorded: Number(size?.unrecorded ?? 0),
27552
+ records: explained.filter((r) => r.oid === oid3).map((r) => ({ id: r.id, documents: Number(r.documents) }))
27553
+ };
27554
+ });
27555
+ const kinds = db.prepare("SELECT DISTINCT c.kind AS kind FROM lineage l JOIN changes c ON c.oid = l.oid WHERE l.head = ?").all(head).map((k) => k.kind);
27464
27556
  return {
27465
27557
  total: Number(counted.n),
27466
27558
  commits: Number(counted.commits),
27467
27559
  next: commits.length > limit ? shown[shown.length - 1].oid : null,
27468
- events: hydrate(db, rows.map((r) => r.row))
27560
+ events: hydrate(db, rows.map((r) => r.row)),
27561
+ whole,
27562
+ kinds
27469
27563
  };
27470
27564
  });
27471
27565
  },
@@ -27515,9 +27609,11 @@ function createHistory(database, git, originals) {
27515
27609
  clauses.push("(lower(json_extract(row, '$.author')) = ? OR lower(json_extract(row, '$.email')) = ?)");
27516
27610
  params.push(filter.author.toLowerCase(), filter.author.toLowerCase());
27517
27611
  }
27518
- if (filter.q?.trim()) {
27519
- clauses.push("instr(lower(title || ' ' || body || ' ' || json_extract(row, '$.message')), ?) > 0");
27520
- params.push(filter.q.trim().toLowerCase());
27612
+ const q = filter.q?.trim().toLowerCase();
27613
+ const hash3 = hashPrefix(q);
27614
+ if (q) {
27615
+ clauses.push(`(instr(lower(title || ' ' || id || ' ' || coalesce(body, '')), ?) > 0${hash3 ? " OR substr(oid, 1, length(?)) = ?" : ""})`);
27616
+ params.push(q, ...hash3 ? [hash3, hash3] : []);
27521
27617
  }
27522
27618
  if (filter.from !== void 0) {
27523
27619
  clauses.push("unixepoch(json_extract(row, '$.date')) * 1000 >= ?");
@@ -27725,8 +27821,8 @@ function createCommitLog(database, git, root) {
27725
27821
  * prints it. Anything shorter or not a hash names none. Newest first, a page after the commit `after`.
27726
27822
  */
27727
27823
  async commitsNamed(head, raw, after, limit) {
27728
- const prefix = raw.trim().toLowerCase();
27729
- if (!/^[0-9a-f]{7,64}$/.test(prefix)) return { total: 0, next: null, hits: [] };
27824
+ const prefix = hashPrefix(raw);
27825
+ if (!prefix) return { total: 0, next: null, hits: [] };
27730
27826
  await ensure(head);
27731
27827
  return database.with((db) => {
27732
27828
  const rows = db.prepare("SELECT oid, name, date, subject FROM log WHERE reach = 1 AND substr(oid, 1, length(?)) = ? ORDER BY time DESC, oid").all(prefix, prefix);
@@ -28077,6 +28173,8 @@ function createStampReader(root, inherited = process.env) {
28077
28173
  const info5 = await lstat8(join9(root, ...path3.split("/"))).catch(() => void 0);
28078
28174
  return path3 + " " + (info5 ? info5.mtimeMs + ":" + info5.size : "-");
28079
28175
  }));
28176
+ const mailmap = await lstat8(join9(root, ".mailmap")).catch(() => void 0);
28177
+ seen.push(".mailmap " + (mailmap ? mailmap.mtimeMs + ":" + mailmap.size : "-"));
28080
28178
  return createHash2("sha256").update(head.trim() + "\n" + seen.sort().join("\n")).digest("hex").slice(0, 20);
28081
28179
  };
28082
28180
  }
@@ -28152,23 +28250,23 @@ function createCheckoutReader(root, cache, inherited = process.env) {
28152
28250
  return head || null;
28153
28251
  };
28154
28252
  const pending = /* @__PURE__ */ new Map();
28253
+ const last = /* @__PURE__ */ new Map();
28155
28254
  const readStamp = createStampReader(root, inherited);
28156
- async function read() {
28255
+ async function read(stamp) {
28157
28256
  const raw = await readConfigFile(root);
28158
28257
  if (!raw) throw new StoreError(t2("specReader.schemaRequired"));
28159
28258
  parseManagedConfig(raw);
28160
28259
  const head = await readHead();
28161
- const stamp = await readStamp();
28162
- const [current2, dirty, authors, people] = await settled([
28260
+ const [current3, dirty, authors, people] = await settled([
28163
28261
  cache.documents.list(),
28164
28262
  head ? git(["status", "--porcelain=v1", "--", ".gitifact/spec", INSTRUCTIONS_ROOT]) : Promise.resolve(""),
28165
28263
  // Who touched each folder and who committed at all, counted over the commit log in the cache.
28166
28264
  head ? cache.log.folders(head) : Promise.resolve(/* @__PURE__ */ new Map()),
28167
28265
  head ? cache.log.contributors(head) : Promise.resolve([])
28168
28266
  ]);
28169
- const states = await documentStates({ head: async () => head, reader }, current2.documents);
28267
+ const states = await documentStates({ head: async () => head, reader }, current3.documents);
28170
28268
  const { features, instructions: instructions2, orphans } = checkoutDocuments(states, authors);
28171
- const problems = [...current2.problems, ...orphans.map((feature2) => docProblem("FEATURE_INDEX_REQUIRED", `.gitifact/spec/${feature2}/index.md`, { feature: feature2 }))];
28269
+ const problems = [...current3.problems, ...orphans.map((feature2) => docProblem("FEATURE_INDEX_REQUIRED", `.gitifact/spec/${feature2}/index.md`, { feature: feature2 }))];
28172
28270
  if (await readHead() !== head) throw new StoreError(t2("specReader.projectChanged"));
28173
28271
  return {
28174
28272
  head,
@@ -28182,11 +28280,19 @@ function createCheckoutReader(root, cache, inherited = process.env) {
28182
28280
  folders: authors
28183
28281
  };
28184
28282
  }
28283
+ async function current2(language) {
28284
+ const stamp = await readStamp();
28285
+ const known = last.get(language);
28286
+ if (known?.stamp === stamp) return { ...known, observedAt: (/* @__PURE__ */ new Date()).toISOString() };
28287
+ const fresh = await read(stamp);
28288
+ last.set(language, fresh);
28289
+ return fresh;
28290
+ }
28185
28291
  const base = () => {
28186
28292
  const language = getLanguage();
28187
28293
  let value = pending.get(language);
28188
28294
  if (!value) {
28189
- value = read().finally(() => pending.delete(language));
28295
+ value = current2(language).finally(() => pending.delete(language));
28190
28296
  pending.set(language, value);
28191
28297
  }
28192
28298
  return value;
@@ -28240,15 +28346,15 @@ function createCommitFiles(git) {
28240
28346
  const revs = parent ? [parent, commit] : [commit];
28241
28347
  const names = (await text4([...base, "--name-status", ...revs, "--"])).split("\0");
28242
28348
  const counts = (await text4([...base, "--numstat", ...revs, "--"])).split("\0");
28243
- const lines = /* @__PURE__ */ new Map();
28349
+ const lines2 = /* @__PURE__ */ new Map();
28244
28350
  for (let i = 0; i < counts.length; i++) {
28245
28351
  const [added, deleted, path3] = counts[i].split(" ");
28246
28352
  if (added === void 0 || deleted === void 0) continue;
28247
28353
  const count2 = { additions: added === "-" ? null : Number(added), deletions: deleted === "-" ? null : Number(deleted) };
28248
28354
  if (path3 === "") {
28249
- lines.set(counts[i + 2], count2);
28355
+ lines2.set(counts[i + 2], count2);
28250
28356
  i += 2;
28251
- } else if (path3 !== void 0) lines.set(path3, count2);
28357
+ } else if (path3 !== void 0) lines2.set(path3, count2);
28252
28358
  }
28253
28359
  const files = [];
28254
28360
  for (let i = 0; i < names.length - 1; i++) {
@@ -28258,7 +28364,7 @@ function createCommitFiles(git) {
28258
28364
  const previousPath = renamed ? names[++i] : void 0;
28259
28365
  const path3 = names[++i];
28260
28366
  if (!isSource(path3) && (!previousPath || !isSource(previousPath))) continue;
28261
- files.push({ path: path3, ...renamed && code.startsWith("R") ? { previousPath } : {}, status: statusOf(code), ...lines.get(path3) ?? { additions: null, deletions: null } });
28367
+ files.push({ path: path3, ...renamed && code.startsWith("R") ? { previousPath } : {}, status: statusOf(code), ...lines2.get(path3) ?? { additions: null, deletions: null } });
28262
28368
  }
28263
28369
  return { commit, parent, total: files.length, files };
28264
28370
  }
@@ -28400,6 +28506,25 @@ async function recent(source2, head) {
28400
28506
  return { group: "recent", total: hits.length, next: null, hits };
28401
28507
  }
28402
28508
 
28509
+ // src/queries/document-history.ts
28510
+ async function documentHistory(source2, head, id) {
28511
+ const events = head ? await source2.cache.history.ofDocument(head, id) : [];
28512
+ const current2 = (await source2.cache.documents.list()).documents.find((d) => d.id === id);
28513
+ const last = events.map((e) => e.after ?? e.before).find(Boolean);
28514
+ if (!current2 && !events.length) return void 0;
28515
+ const doc = { id, title: current2?.title ?? last?.title ?? null, kind: current2?.kind ?? events[0]?.kind ?? null, path: current2?.path ?? last?.path ?? null };
28516
+ return { doc, events };
28517
+ }
28518
+ async function documentHistoryPage(source2, head, id, request) {
28519
+ const found = await documentHistory(source2, head, id);
28520
+ if (!found) return void 0;
28521
+ const page = pageOf(found.events, (e) => e.commit, request);
28522
+ if (!page) return null;
28523
+ const recorded = found.events.filter((e) => e.records.length).length;
28524
+ const withoutRecord = found.events.filter((e) => !e.records.length && e.types.some((type) => type !== "created")).length;
28525
+ return { doc: found.doc, total: page.total, recorded, withoutRecord, next: page.next, events: page.rows };
28526
+ }
28527
+
28403
28528
  // src/queries/checkout.ts
28404
28529
  var SHOWN_REQUIREMENTS = 12;
28405
28530
  function checkoutIndex(base) {
@@ -28416,13 +28541,13 @@ function checkoutIndex(base) {
28416
28541
  people: base.people.map((p) => ({ name: p.name, email: p.email }))
28417
28542
  };
28418
28543
  }
28419
- var acceptanceHeading = /^###\s+(?:수용 조건|acceptance criteria)\s*$/i;
28544
+ var acceptanceHeading2 = /^###\s+(?:수용 조건|acceptance criteria)\s*$/i;
28420
28545
  function acceptanceCount(body2) {
28421
- const lines = body2.replace(/\r\n/g, "\n").split("\n");
28422
- const start = lines.findIndex((line2) => acceptanceHeading.test(line2.trim()));
28546
+ const lines2 = body2.replace(/\r\n/g, "\n").split("\n");
28547
+ const start = lines2.findIndex((line2) => acceptanceHeading2.test(line2.trim()));
28423
28548
  if (start < 0) return null;
28424
28549
  let count2 = 0;
28425
- for (const line2 of lines.slice(start + 1)) {
28550
+ for (const line2 of lines2.slice(start + 1)) {
28426
28551
  if (/^#{1,6}\s/.test(line2)) break;
28427
28552
  if (/^\d+\.\s/.test(line2)) count2++;
28428
28553
  }
@@ -28544,10 +28669,16 @@ function recordRoutes(root, sessionId, env) {
28544
28669
  } }),
28545
28670
  // Whether the screen is behind, asked when the reader comes back to the tab; cheap enough to ask every time.
28546
28671
  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() })) }),
28547
- route({ method: "GET", path: "/api/v1/history", session: true, query: browserHistoryQueryV4, unreadable: unreadable2, handle: async ({ query }) => {
28548
- 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);
28672
+ route({ method: "GET", path: "/api/v1/history", session: true, query: browserHistoryQueryV5, unreadable: unreadable2, handle: async ({ query }) => {
28673
+ const page = await cache.history.commits(query.head, {
28674
+ target: query.target,
28675
+ document: query.document,
28676
+ unrecorded: query.record === "missing",
28677
+ author: query.author,
28678
+ q: query.q
28679
+ }, query.after, query.limit ?? PAGE);
28549
28680
  if (!page) throw cursorGone2();
28550
- return ok(browserHistoryV6.parse({ contract: "browser-history", version: 6, sessionId, head: query.head, ...page }));
28681
+ return ok(browserHistoryV7.parse({ contract: "browser-history", version: 7, sessionId, head: query.head, ...page }));
28551
28682
  } }),
28552
28683
  route({ method: "GET", path: "/api/v1/history/summary", session: true, query: browserHistorySummaryQueryV1, unreadable: unreadable2, handle: async ({ query }) => ok(browserHistorySummaryV5.parse({
28553
28684
  contract: "browser-history-summary",
@@ -28592,6 +28723,13 @@ function recordRoutes(root, sessionId, env) {
28592
28723
  if (!found) throw new HttpError(404, "NOT_FOUND", t2("server.recordNotFound"));
28593
28724
  return ok(browserRecordV2.parse({ contract: "browser-record", version: 2, sessionId, head: query.head, id: query.id, ...found }));
28594
28725
  } }),
28726
+ // One document's decision flow in the HEAD the reader is on: the commits that changed it, a page of them at a time.
28727
+ route({ method: "GET", path: "/api/v1/document-history", session: true, query: browserDocumentHistoryQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
28728
+ const found = await documentHistoryPage(source2, query.head, query.id, { after: query.after, limit: query.limit ?? PAGE });
28729
+ if (found === void 0) throw new HttpError(404, "NOT_FOUND", t2("server.documentNotFound"));
28730
+ if (found === null) throw cursorGone2();
28731
+ return ok(browserDocumentHistoryV1.parse({ contract: "browser-document-history", version: 1, sessionId, head: query.head, ...found }));
28732
+ } }),
28595
28733
  // What is not committed yet, worked out again on every request: the uncommitted entry of the records list.
28596
28734
  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) })) }),
28597
28735
  route({ method: "GET", path: "/api/v1/working/change", session: true, query: browserWorkingChangeQueryV1, unreadable: unreadable2, handle: async ({ query }) => {
@@ -28973,11 +29111,11 @@ import { readFile as readFile11 } from "node:fs/promises";
28973
29111
  // src/commands/output.ts
28974
29112
  var formats = ["text", "json"];
28975
29113
  var CommandError = class extends Error {
28976
- constructor(code, message, details = {}, lines = []) {
29114
+ constructor(code, message, details = {}, lines2 = []) {
28977
29115
  super(message);
28978
29116
  this.code = code;
28979
29117
  this.details = details;
28980
- this.lines = lines;
29118
+ this.lines = lines2;
28981
29119
  }
28982
29120
  code;
28983
29121
  details;
@@ -28994,13 +29132,13 @@ async function runCommand(contract, format2, run) {
28994
29132
  const code = known ? error62.code : contract.toUpperCase() + "_FAILED";
28995
29133
  const message = known ? error62.message : t2("command.failed");
28996
29134
  const details = error62 instanceof CommandError ? error62.details : {};
28997
- const lines = error62 instanceof CommandError ? error62.lines : [];
28998
- process.stderr.write(format2 === "json" ? JSON.stringify({ ...envelope, ok: false, error: { code, message, ...details } }) + "\n" : [code + ": " + message, ...lines].join("\n") + "\n");
29135
+ const lines2 = error62 instanceof CommandError ? error62.lines : [];
29136
+ process.stderr.write(format2 === "json" ? JSON.stringify({ ...envelope, ok: false, error: { code, message, ...details } }) + "\n" : [code + ": " + message, ...lines2].join("\n") + "\n");
28999
29137
  process.exitCode = 1;
29000
29138
  }
29001
29139
  }
29002
- var section = (label, lines, indent = " ") => lines.length ? [label, ...lines.map((line2) => indent + line2)] : [];
29003
- var text2 = (lines) => lines.join("\n") + "\n";
29140
+ var section = (label, lines2, indent = " ") => lines2.length ? [label, ...lines2.map((line2) => indent + line2)] : [];
29141
+ var text2 = (lines2) => lines2.join("\n") + "\n";
29004
29142
 
29005
29143
  // src/commands/guide.ts
29006
29144
  var guideTopics = ["workflow", "spec", "design", "instructions", "records", "writing", "commit", "migrate"];
@@ -29071,8 +29209,8 @@ function findBlock(text4) {
29071
29209
  function isWrapperFile(text4) {
29072
29210
  const found = findBlock(text4);
29073
29211
  const own2 = found ? text4.slice(0, found.start) + text4.slice(found.end) : text4;
29074
- const lines = own2.split(/\r?\n/).map((line2) => line2.trim()).filter(Boolean);
29075
- return lines.length > 0 && lines.every((line2) => /^@(?:\.\/)?[^\s]+$/.test(line2) && candidatePaths.includes(line2.replace(/^@(?:\.\/)?/, "")));
29212
+ const lines2 = own2.split(/\r?\n/).map((line2) => line2.trim()).filter(Boolean);
29213
+ return lines2.length > 0 && lines2.every((line2) => /^@(?:\.\/)?[^\s]+$/.test(line2) && candidatePaths.includes(line2.replace(/^@(?:\.\/)?/, "")));
29076
29214
  }
29077
29215
  var CLAUDE_WRAPPER_PATH = "CLAUDE.md";
29078
29216
  function claudeWrapperFor(agents2) {
@@ -29732,8 +29870,8 @@ function cursorGone(options) {
29732
29870
  throw new CommandError("INVALID_VALUE", t2("list.after", { cursor: options.after ?? "" }));
29733
29871
  }
29734
29872
  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 })];
29735
- function aside(format2, lines) {
29736
- if (format2 === "text" && lines.length) process.stderr.write(lines.join("\n") + "\n");
29873
+ function aside(format2, lines2) {
29874
+ if (format2 === "text" && lines2.length) process.stderr.write(lines2.join("\n") + "\n");
29737
29875
  return "";
29738
29876
  }
29739
29877
 
@@ -30136,6 +30274,10 @@ var recordLine = (r, changes2) => {
30136
30274
  const changed = new Set(changes2.map((c) => c.id));
30137
30275
  return `${r.id} ${r.title}${r.draft ? " (" + t2("docs.draft") + ")" : ""} \u2014 ${r.docs.map((d) => changed.has(d) ? d : d + "*").join(", ")}`;
30138
30276
  };
30277
+ var formatWarnings = (changes2, documents) => {
30278
+ const touched = new Set(changes2.map((c) => c.id));
30279
+ return documents.filter((d) => touched.has(d.id)).flatMap(requirementFormatWarnings);
30280
+ };
30139
30281
  var recordSummary = (r) => ({ id: r.id, title: r.title, docs: r.docs, path: r.path, ...r.draft ? { draft: true } : {} });
30140
30282
  var runChangesList = (options, controls = {}) => runCommand("changes", options.format, async () => {
30141
30283
  const project = await openProject(process.cwd());
@@ -30146,7 +30288,8 @@ var runChangesList = (options, controls = {}) => runCommand("changes", options.f
30146
30288
  const withoutRecord = uncovered(changes2, records2);
30147
30289
  const sharedDocuments = shared(changes2, records2);
30148
30290
  const problems = [...working2.problems, ...pending.problems, ...checked.problems];
30149
- const warnings = await readDocumentWarnings(project.root, checked.documents);
30291
+ const formats2 = formatWarnings(changes2, checked.documents);
30292
+ const warnings = [...await readDocumentWarnings(project.root, checked.documents), ...formats2];
30150
30293
  const inputs = await prepareAgentInputs(project.root, controls).catch(() => void 0);
30151
30294
  const out = changes2.length ? [t2("changes.changed", { count: changes2.length }), ...changes2.map((c) => " " + changeLine(c))] : [t2("changes.none")];
30152
30295
  out.push(...section(t2("changes.pendingRecords", { count: records2.length }), records2.map((r) => recordLine(r, changes2))));
@@ -30154,7 +30297,8 @@ var runChangesList = (options, controls = {}) => runCommand("changes", options.f
30154
30297
  if (sharedDocuments.length) out.push(t2("changes.sharedDocuments", { ids: sharedDocuments.join(", ") }));
30155
30298
  if (pending.altered.length) out.push(t2("changes.recordAltered", { paths: pending.altered.join(", ") }));
30156
30299
  out.push(problems.length ? t2("changes.problems", { count: problems.length }) : t2("changes.clean"));
30157
- if (warnings.length) out.push(t2("changes.warnings", { count: warnings.length }));
30300
+ if (warnings.length > formats2.length) out.push(t2("changes.warnings", { count: warnings.length - formats2.length }));
30301
+ out.push(...section(t2("changes.formatWarnings", { count: formats2.length }), formats2.map((w) => w.code + " " + w.message)));
30158
30302
  if (inputs) out.push(t2("changes.input", { path: inputs.commit }));
30159
30303
  return { json: { head, changes: changes2, pendingRecords: records2.map(recordSummary), withoutRecord, sharedDocuments, alteredRecords: pending.altered, problems, warnings, inputs: inputs ?? null }, text: text2(out) };
30160
30304
  });
@@ -30169,6 +30313,7 @@ var runChangesCommit = (options, controls = {}) => runCommand("changes", options
30169
30313
  ...result.changes.map((c) => " " + changeLine(c)),
30170
30314
  ...result.records.map((r) => " " + r.id + " " + r.title),
30171
30315
  ...result.withoutRecord.length ? [t2("changes.withoutRecord", { ids: result.withoutRecord.join(", ") })] : [],
30316
+ ...section(t2("changes.formatWarnings", { count: result.warnings.length }), result.warnings.map((w) => w.code + " " + w.message)),
30172
30317
  ...result.trailers
30173
30318
  ];
30174
30319
  return { json: { ...json3, ...inputRemoved === void 0 ? {} : { inputRemoved } }, text: text2(out) };
@@ -30225,6 +30370,7 @@ async function commitChanges(cwd, input2, dryRun) {
30225
30370
  const split = changes2.filter((c) => c.previousPath !== void 0 && !(selected2.includes(c.path) && selected2.includes(c.previousPath)));
30226
30371
  if (split.length) fail3(t2("commit.selectMove", { ids: split.map((c) => c.id).join(", ") }));
30227
30372
  const withoutRecord = uncovered(changes2, records2);
30373
+ const warnings = formatWarnings(changes2, checked.documents);
30228
30374
  const status = (await git(["status", "--porcelain=v1", "-z", "--untracked-files=all", "--no-renames", "--", ".gitifact"])).toString("utf8").split("\0").filter(Boolean);
30229
30375
  const removed = new Set(status.filter((entry2) => entry2.slice(0, 2).includes("D")).map((entry2) => entry2.slice(3)));
30230
30376
  checkStoreSelection(selected2.filter((p) => !(underDocuments(p) && removed.has(p))));
@@ -30241,7 +30387,7 @@ async function commitChanges(cwd, input2, dryRun) {
30241
30387
  const contextPaths = policyPaths(selected2);
30242
30388
  const hashes = async (list) => new Map(await Promise.all(list.map(async (p) => [p, await fingerprint(root, p)])));
30243
30389
  const started = await hashes([.../* @__PURE__ */ new Set([...selected2, ...contextPaths])]);
30244
- const summary = { root, head: base.head, message, paths: selected2, changes: changes2, records: records2.map(recordSummary), withoutRecord, trailers };
30390
+ const summary = { root, head: base.head, message, paths: selected2, changes: changes2, records: records2.map(recordSummary), withoutRecord, warnings, trailers };
30245
30391
  if (dryRun) {
30246
30392
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
30247
30393
  return { outcome: "dry-run", committed: false, ...summary, commit: void 0 };
@@ -30406,12 +30552,13 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
30406
30552
  const after = await since(project, options.since, head);
30407
30553
  const by = byAuthor(options.author);
30408
30554
  const has2 = contains(options.q);
30555
+ const hash3 = hashPrefix(options.q);
30409
30556
  const id = options.doc;
30410
- const events = head ? await project.cache.history.ofDocument(head, id) : [];
30411
- const { documents } = await project.cache.documents.list();
30412
- const title = documents.find((d) => d.id === id)?.title ?? events.map((e) => (e.after ?? e.before)?.title).find(Boolean) ?? null;
30413
- if (!title && !events.length) throw new CommandError("UNKNOWN_DOCUMENT", t2("docs.unknownDocument", { ids: id }));
30414
- const page = paginate(events.filter((e) => after(e) && by(e) && has2(e.message, ...e.records.flatMap((r) => [r.title, ...sectionText(r.sections)]))).map((e) => ({
30557
+ const found2 = await documentHistory(project, head, id);
30558
+ if (!found2) throw new CommandError("UNKNOWN_DOCUMENT", t2("docs.unknownDocument", { ids: id }));
30559
+ const { events } = found2;
30560
+ const title = found2.doc.title;
30561
+ const page = paginate(events.filter((e) => after(e) && by(e) && (has2(...e.records.flatMap((r) => [r.title, r.id, ...sectionText(r.sections)])) || !!hash3 && e.commit.startsWith(hash3))).map((e) => ({
30415
30562
  commit: e.commit,
30416
30563
  date: e.date,
30417
30564
  author: e.author,
@@ -30449,7 +30596,7 @@ var runRecordsList = (options) => runCommand("records", options.format, async ()
30449
30596
  } catch {
30450
30597
  return [];
30451
30598
  }
30452
- }).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);
30599
+ }).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, r.id, ...sectionText(r.sections))).sort((a, b) => a.id < b.id ? -1 : 1);
30453
30600
  const size = options.all ? void 0 : options.limit ?? PAGE_SIZE;
30454
30601
  const inPending = options.after === void 0 ? 0 : pending.findIndex((r) => r.id === options.after) + 1;
30455
30602
  const afterCommitted = options.after !== void 0 && !inPending ? options.after : void 0;
@@ -30759,23 +30906,23 @@ async function runUpdate(options, version2) {
30759
30906
  process.stdout.write(JSON.stringify(dto) + "\n");
30760
30907
  return;
30761
30908
  }
30762
- const lines = [t2("update.text.current", { version: dto.cliVersion })];
30763
- lines.push(dto.update.status === "available" ? t2("update.text.available", { version: dto.update.latestVersion }) : dto.update.status === "up-to-date" ? t2("update.text.upToDate") : dto.update.status === "disabled" ? t2("update.text.disabled") : t2("update.text.unavailable"));
30764
- if (dto.install) lines.push(t2("update.text.install", { command: dto.install.npmGlobal }), t2("update.text.afterInstall"));
30765
- lines.push(dto.agentDocs.state === "refreshed" ? t2("update.text.blockRefreshed", { paths: dto.agentDocs.paths.join(", ") }) : dto.agentDocs.state === "current" ? t2("update.text.blockCurrent", { paths: dto.agentDocs.paths.join(", ") }) : dto.agentDocs.state === "kept" ? t2("update.text.blockKept", { paths: dto.agentDocs.paths.join(", ") || "-" }) : dto.agentDocs.state === "no-block" ? t2("update.text.noBlock") : t2("update.text.notInitialized"));
30766
- if (dto.agentDocs.missing.length) lines.push(t2("update.text.missing", { paths: dto.agentDocs.missing.join(", ") }));
30767
- if (dto.project.state === "written") lines.push(t2("update.text.projectWritten", { path: CONFIG_PATH, version: dto.project.cli ?? dto.cliVersion }));
30768
- if (dto.project.cli && isNewerRelease(dto.project.cli, dto.cliVersion)) lines.push(t2("update.text.projectAhead", { project: dto.project.cli, version: dto.cliVersion, command: npmGlobalInstall(dto.project.cli) }));
30769
- if (dto.migrationRequired) lines.push(t2("update.text.migrationRequired", { version: dto.cliVersion }));
30909
+ const lines2 = [t2("update.text.current", { version: dto.cliVersion })];
30910
+ lines2.push(dto.update.status === "available" ? t2("update.text.available", { version: dto.update.latestVersion }) : dto.update.status === "up-to-date" ? t2("update.text.upToDate") : dto.update.status === "disabled" ? t2("update.text.disabled") : t2("update.text.unavailable"));
30911
+ if (dto.install) lines2.push(t2("update.text.install", { command: dto.install.npmGlobal }), t2("update.text.afterInstall"));
30912
+ lines2.push(dto.agentDocs.state === "refreshed" ? t2("update.text.blockRefreshed", { paths: dto.agentDocs.paths.join(", ") }) : dto.agentDocs.state === "current" ? t2("update.text.blockCurrent", { paths: dto.agentDocs.paths.join(", ") }) : dto.agentDocs.state === "kept" ? t2("update.text.blockKept", { paths: dto.agentDocs.paths.join(", ") || "-" }) : dto.agentDocs.state === "no-block" ? t2("update.text.noBlock") : t2("update.text.notInitialized"));
30913
+ if (dto.agentDocs.missing.length) lines2.push(t2("update.text.missing", { paths: dto.agentDocs.missing.join(", ") }));
30914
+ if (dto.project.state === "written") lines2.push(t2("update.text.projectWritten", { path: CONFIG_PATH, version: dto.project.cli ?? dto.cliVersion }));
30915
+ if (dto.project.cli && isNewerRelease(dto.project.cli, dto.cliVersion)) lines2.push(t2("update.text.projectAhead", { project: dto.project.cli, version: dto.cliVersion, command: npmGlobalInstall(dto.project.cli) }));
30916
+ if (dto.migrationRequired) lines2.push(t2("update.text.migrationRequired", { version: dto.cliVersion }));
30770
30917
  const { commit } = dto;
30771
- if (commit.state === "committed") lines.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
30772
- else if (commit.state === "nothing") lines.push(t2("update.text.commitNothing"));
30918
+ if (commit.state === "committed") lines2.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
30919
+ else if (commit.state === "nothing") lines2.push(t2("update.text.commitNothing"));
30773
30920
  else if (commit.state === "skipped") {
30774
30921
  const paths2 = commit.paths.join(", ");
30775
30922
  const text4 = commit.reason === "untracked" ? t2("update.text.commitSkipped.untracked", { paths: paths2 }) : commit.reason === "other-changes" ? t2("update.text.commitSkipped.other-changes", { paths: paths2 }) : t2("update.text.commitSkipped.commit-failed", { paths: paths2 });
30776
- lines.push(text4 + (commit.detail ? " (" + commit.detail + ")" : ""));
30923
+ lines2.push(text4 + (commit.detail ? " (" + commit.detail + ")" : ""));
30777
30924
  }
30778
- process.stdout.write(lines.join("\n") + "\n");
30925
+ process.stdout.write(lines2.join("\n") + "\n");
30779
30926
  } catch (error62) {
30780
30927
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
30781
30928
  const failure2 = { code: known ? error62.code : "UPDATE_FAILED", message: known ? error62.message : t2("update.failed") };
@@ -30804,12 +30951,12 @@ async function runUpdateCheck(format2, version2) {
30804
30951
  return;
30805
30952
  }
30806
30953
  const state2 = dto.update;
30807
- const lines = [
30954
+ const lines2 = [
30808
30955
  t2("update.text.current", { version: dto.cliVersion }),
30809
30956
  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")
30810
30957
  ];
30811
- if (state2.status === "available") lines.push(t2("update.text.install", { command: npmGlobalInstall(state2.latestVersion) }), t2("update.text.afterInstall"));
30812
- process.stdout.write(lines.join("\n") + "\n");
30958
+ if (state2.status === "available") lines2.push(t2("update.text.install", { command: npmGlobalInstall(state2.latestVersion) }), t2("update.text.afterInstall"));
30959
+ process.stdout.write(lines2.join("\n") + "\n");
30813
30960
  }
30814
30961
 
30815
30962
  // src/commands/version-notice.ts
@@ -30884,17 +31031,17 @@ for (let i = 0; i < languageArgs.length; i++) {
30884
31031
  if (value === "ko" || value === "en") selectedLanguage = value;
30885
31032
  }
30886
31033
  configureLanguage(selectedLanguage ?? environmentLanguage(), selectedLanguage);
30887
- var program2 = new Command().name("gitifact").addOption(new Option("--lang <language>", t2("help.lang")).choices(["ko", "en"])).description(t2("help.program")).version("0.8.4").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
31034
+ var program2 = new Command().name("gitifact").addOption(new Option("--lang <language>", t2("help.lang")).choices(["ko", "en"])).description(t2("help.program")).version("0.8.5").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
30888
31035
  program2.hook("preAction", async (_program, action) => {
30889
31036
  let top = action;
30890
31037
  while (top.parent && top.parent !== program2) top = top.parent;
30891
- if (top.name() !== REFRESH_COMMAND) await noticeVersion("0.8.4", top.name());
31038
+ if (top.name() !== REFRESH_COMMAND) await noticeVersion("0.8.5", top.name());
30892
31039
  });
30893
- program2.command(REFRESH_COMMAND, { hidden: true }).allowExcessArguments(false).action(() => refreshUpdateCache("0.8.4"));
31040
+ program2.command(REFRESH_COMMAND, { hidden: true }).allowExcessArguments(false).action(() => refreshUpdateCache("0.8.5"));
30894
31041
  var format = () => new Option("--format <format>", t2("help.format")).choices(formats).default("text");
30895
- 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.4"));
30896
- 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.4"));
30897
- 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")).addOption(new Option("--later", t2("help.updateLater")).conflicts(["check", "commit"])).action((options) => options.later ? runUpdateLater(options.format, "0.8.4") : options.check ? runUpdateCheck(options.format, "0.8.4") : runUpdate(options, "0.8.4"));
31042
+ 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.5"));
31043
+ 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.5"));
31044
+ 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")).addOption(new Option("--later", t2("help.updateLater")).conflicts(["check", "commit"])).action((options) => options.later ? runUpdateLater(options.format, "0.8.5") : options.check ? runUpdateCheck(options.format, "0.8.5") : runUpdate(options, "0.8.5"));
30898
31045
  var listed2 = (command, sorts) => {
30899
31046
  command.allowExcessArguments(false).option("--q <words>", t2("help.listQ")).option("--author <name>", t2("help.listAuthor"));
30900
31047
  if (sorts) command.addOption(new Option("--sort <key>", t2("help.listSort")).choices([...sorts]).default(sorts[0]));
@@ -30916,7 +31063,7 @@ program2.command("check").description(t2("help.check")).allowExcessArguments(fal
30916
31063
  var changes = program2.command("changes").description(t2("help.changes"));
30917
31064
  changes.command("list").description(t2("help.changesList")).allowExcessArguments(false).addOption(format()).action((o) => runChangesList(o));
30918
31065
  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));
30919
- 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.4"));
31066
+ 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.5"));
30920
31067
  var guide = program2.command("guide").description(t2("help.guide"));
30921
31068
  guide.command("list").description(t2("help.guideList")).allowExcessArguments(false).addOption(format()).action((o) => runGuideList(o));
30922
31069
  guide.command("show").description(t2("help.guideShow")).argument("<topic>", t2("help.guideTopic")).allowExcessArguments(false).addOption(format()).action((topic, o) => runGuideShow(topic, o));