gitifact 0.8.2 → 0.8.4

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 (106) hide show
  1. package/README.md +47 -37
  2. package/dist/THIRD_PARTY_NOTICES.txt +26 -0
  3. package/dist/browser/assets/HgiRefresh-CAJ0Xdvi.js +1 -0
  4. package/dist/browser/assets/{Markdown-B9QJ2Ao-.js → Markdown-ev4hMr67.js} +1 -1
  5. package/dist/browser/assets/Selector-CGOdL2eo.js +2 -0
  6. package/dist/browser/assets/Tab-BwJj0rUr.js +1 -0
  7. package/dist/browser/assets/{Table-LdzcI4YS.js → Table-XuC9zK9w.js} +2 -2
  8. package/dist/browser/assets/Token-BV_4bSkY.js +2 -0
  9. package/dist/browser/assets/Token-Bqkc8eyB.css +1 -0
  10. package/dist/browser/assets/about-vlKWfG7J.js +3 -0
  11. package/dist/browser/assets/activity-timeline-Dmc-97N6.js +2 -0
  12. package/dist/browser/assets/{appearance-DgDbwXAm.js → appearance-DyJA06C4.js} +1 -1
  13. package/dist/browser/assets/changelog-6QcNKW8u.js +1 -0
  14. package/dist/browser/assets/commit-BcdbBdP4.js +4 -0
  15. package/dist/browser/assets/contributor-Bs3RWHQv.js +1 -0
  16. package/dist/browser/assets/contributors-B_bVSY4J.js +1 -0
  17. package/dist/browser/assets/contributors._email-BR9sLgCs.js +1 -0
  18. package/dist/browser/assets/contributors.index-FoYwG7VE.js +1 -0
  19. package/dist/browser/assets/dashboard.index-BFp6Hqsf.js +1 -0
  20. package/dist/browser/assets/document-Cmx0e6kz.css +1 -0
  21. package/dist/browser/assets/document-DPtVyR5y.js +19 -0
  22. package/dist/browser/assets/document-Q3lvgyX8.js +2 -0
  23. package/dist/browser/assets/features-CmvypKgf.js +2 -0
  24. package/dist/browser/assets/features-DUHxz0_L.css +1 -0
  25. package/dist/browser/assets/features._featureId-xxpihTEs.js +1 -0
  26. package/dist/browser/assets/features.index-CO7APsrh.js +1 -0
  27. package/dist/browser/assets/getting-started-11zYPwlJ.js +1 -0
  28. package/dist/browser/assets/git-CVY8qpaV.js +1 -0
  29. package/dist/browser/assets/index-BeeP8ZFW.js +49 -0
  30. package/dist/browser/assets/index-D1JeDnJc.css +1 -0
  31. package/dist/browser/assets/instructions-D3-EfbZG.js +1 -0
  32. package/dist/browser/assets/instructions._instructionId-Bp9u9xx9.js +1 -0
  33. package/dist/browser/assets/instructions.agents-DvFHo8x8.js +1 -0
  34. package/dist/browser/assets/instructions.index-zHzGHq7D.js +1 -0
  35. package/dist/browser/assets/lazyRouteComponent-D4lq6X5s.js +1 -0
  36. package/dist/browser/assets/load-more-CymfxLt7.js +1 -0
  37. package/dist/browser/assets/lottie_light-DSIvQuvH.js +2 -0
  38. package/dist/browser/assets/page-header-CVCjLFQo.js +246 -0
  39. package/dist/browser/assets/{search-palette-CDmYAo36.css → page-header-DTEyUWBS.css} +1 -1
  40. package/dist/browser/assets/records._recordId-Be8aM0F-.js +1 -0
  41. package/dist/browser/assets/records.commits._commit-Dm3gX-bQ.js +1 -0
  42. package/dist/browser/assets/records.index-BgfJnGAl.js +1 -0
  43. package/dist/browser/assets/records.working-Eeag2L5N.js +1 -0
  44. package/dist/browser/assets/rocket-loader-CMSP4Jri.js +1 -0
  45. package/dist/browser/assets/settings-BNIy3TaP.js +1 -0
  46. package/dist/browser/assets/useInfiniteQuery-03AIcFvA.js +1 -0
  47. package/dist/browser/assets/useKeyboardHint-DN1hAi8g.js +1 -0
  48. package/dist/browser/index.html +34 -13
  49. package/dist/browser/licenses/rocket-in-space-lottie.txt +29 -0
  50. package/dist/i18n/en/block.md +4 -6
  51. package/dist/i18n/en/changelog.md +24 -0
  52. package/dist/i18n/en/docs/commit.md +1 -1
  53. package/dist/i18n/en/docs/workflow.md +7 -7
  54. package/dist/i18n/ko/block.md +4 -6
  55. package/dist/i18n/ko/changelog.md +24 -0
  56. package/dist/i18n/ko/docs/commit.md +1 -1
  57. package/dist/i18n/ko/docs/workflow.md +5 -5
  58. package/dist/main.js +411 -150
  59. package/package.json +1 -1
  60. package/dist/browser/assets/Banner-DQ1G6Jkv.js +0 -1
  61. package/dist/browser/assets/Selector-BlDYK5Fb.js +0 -2
  62. package/dist/browser/assets/Tab-BoCyIuQw.js +0 -1
  63. package/dist/browser/assets/Token-CYBSUyAu.js +0 -1
  64. package/dist/browser/assets/about-ClxCgIMf.js +0 -3
  65. package/dist/browser/assets/activity-timeline-i0lmNZ1o.js +0 -2
  66. package/dist/browser/assets/changelog-CxmMo2wk.css +0 -1
  67. package/dist/browser/assets/changelog-bGtJx397.js +0 -2
  68. package/dist/browser/assets/commit-5TIiQrYh.js +0 -4
  69. package/dist/browser/assets/contributor-C1R6Q471.js +0 -1
  70. package/dist/browser/assets/contributors-DjNETDIr.js +0 -1
  71. package/dist/browser/assets/contributors._email-DXxCGNZx.js +0 -1
  72. package/dist/browser/assets/contributors.index-ByVvGuYC.js +0 -1
  73. package/dist/browser/assets/dashboard-CMSj2wu3.css +0 -1
  74. package/dist/browser/assets/dashboard.index-RoAwRn4H.js +0 -1
  75. package/dist/browser/assets/document-BwoITy_-.js +0 -2
  76. package/dist/browser/assets/document-CwA2dxtP.js +0 -19
  77. package/dist/browser/assets/document-DEGaf2yT.css +0 -1
  78. package/dist/browser/assets/features-CttwIYBf.js +0 -2
  79. package/dist/browser/assets/features-ZYCEDoiE.css +0 -1
  80. package/dist/browser/assets/features._featureId-DPctVYNz.js +0 -1
  81. package/dist/browser/assets/features.index-lcNtlMn8.js +0 -1
  82. package/dist/browser/assets/getting-started-Chwezama.js +0 -1
  83. package/dist/browser/assets/git-CtsTwkaq.js +0 -1
  84. package/dist/browser/assets/git-D_wcK2vC.css +0 -1
  85. package/dist/browser/assets/index-CmU7dwFQ.css +0 -1
  86. package/dist/browser/assets/index-iSt1IRzY.js +0 -48
  87. package/dist/browser/assets/instructions-DULmclHB.js +0 -1
  88. package/dist/browser/assets/instructions._instructionId-CBz40hpF.js +0 -1
  89. package/dist/browser/assets/instructions.agents-CDpXylSu.js +0 -1
  90. package/dist/browser/assets/instructions.index-BZ5tCMZJ.js +0 -1
  91. package/dist/browser/assets/lazyRouteComponent-DhTs_UEV.js +0 -1
  92. package/dist/browser/assets/load-more-DxscRoh6.js +0 -1
  93. package/dist/browser/assets/page-header-B9LQWl7J.js +0 -1
  94. package/dist/browser/assets/page-header-atX8Nsmd.css +0 -1
  95. package/dist/browser/assets/records-Dk3shbYl.css +0 -1
  96. package/dist/browser/assets/records-page-B4tDip2H.css +0 -1
  97. package/dist/browser/assets/records-page-BjyFUmMr.js +0 -1
  98. package/dist/browser/assets/records._recordId-vwjWdPRX.js +0 -1
  99. package/dist/browser/assets/records.commits._commit-CYYgrOxv.js +0 -1
  100. package/dist/browser/assets/records.index-N49ep6ay.js +0 -1
  101. package/dist/browser/assets/records.working-CcYY8eyI.js +0 -1
  102. package/dist/browser/assets/request-state-BeiYxf95.js +0 -1
  103. package/dist/browser/assets/search-palette-BCEUlRVv.js +0 -561
  104. package/dist/browser/assets/settings-I5Mn86wq.js +0 -1
  105. package/dist/browser/assets/useInfiniteQuery-tqfloUvn.js +0 -1
  106. package/dist/browser/assets/useKeyboardHint-HHpn4XUs.js +0 -1
package/dist/main.js CHANGED
@@ -3407,6 +3407,8 @@ var messages_default = {
3407
3407
  "config.invalidFields": "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815\uC758 \uD544\uB4DC\uB098 \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3408
3408
  "config.jsonUnreadableShort": "\uC124\uC815 JSON\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
3409
3409
  "config.schemaFields": "schemaVersion\uACFC baseline \uC124\uC815 \uD544\uB4DC\uB97C \uD655\uC778\uD558\uC138\uC694.",
3410
+ "config.cliVersion": "\uC124\uC815\uC758 cli\uB294 0.8.3 \uAC19\uC740 \uBC30\uD3EC \uBC84\uC804\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
3411
+ "config.language": "\uC124\uC815\uC758 language\uB294 {languages} \uC911 \uD558\uB098\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
3410
3412
  "document.pathTooLong": "\uC704\uD0A4 \uD398\uC774\uC9C0 \uACBD\uB85C \uAE4A\uC774 \uB610\uB294 \uAE38\uC774 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: {path}",
3411
3413
  "document.fileName": "\uC704\uD0A4 \uD398\uC774\uC9C0 \uD30C\uC77C \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uACFC .md\uC5EC\uC57C \uD569\uB2C8\uB2E4(\uB8E8\uD2B8\uC758 README.md\xB7ARCHITECTURE.md \uAC19\uC740 \uB300\uBB38\uC790 \uC774\uB984 \uC608\uC678): {path}",
3412
3414
  "document.folderName": "\uC704\uD0A4 \uD3F4\uB354 \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: {path}",
@@ -3524,6 +3526,8 @@ var messages_default2 = {
3524
3526
  "config.invalidFields": "Invalid project configuration fields or values.",
3525
3527
  "config.jsonUnreadableShort": "Could not read configuration JSON.",
3526
3528
  "config.schemaFields": "Check the schemaVersion and baseline fields.",
3529
+ "config.cliVersion": "The config field cli must be a release version such as 0.8.3.",
3530
+ "config.language": "The config field language must be one of {languages}.",
3527
3531
  "document.pathTooLong": "Wiki page path exceeds the depth or length limit: {path}",
3528
3532
  "document.fileName": "Wiki filenames must use lowercase letters, digits, hyphens and .md; uppercase root names such as README.md and ARCHITECTURE.md are allowed: {path}",
3529
3533
  "document.folderName": "Wiki folder names must use lowercase letters, digits and hyphens: {path}",
@@ -3682,6 +3686,8 @@ var InitError = class extends Error {
3682
3686
  };
3683
3687
  var SCHEMA_VERSION = 3;
3684
3688
  var PREVIOUS_SCHEMA_VERSION = 2;
3689
+ var PROJECT_LANGUAGES = ["ko", "en"];
3690
+ var CLI_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
3685
3691
  var object = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
3686
3692
  var keys = (value, expected) => Object.keys(value).sort().join(",") === expected.sort().join(",");
3687
3693
  function parseBaseline(value) {
@@ -3714,7 +3720,7 @@ function parseManagedConfig(text4) {
3714
3720
  }
3715
3721
  if (!object(value) || !("schemaVersion" in value))
3716
3722
  throw new InitError("UNSUPPORTED_FORMAT", t("config.unsupportedFormat"));
3717
- if (!keys(value, ["baseline", "schemaVersion"]))
3723
+ if (!("baseline" in value))
3718
3724
  throw new InitError("INVALID_CONFIG", t("config.schemaFields"));
3719
3725
  if (value.schemaVersion !== SCHEMA_VERSION) {
3720
3726
  const found = value.schemaVersion;
@@ -3723,7 +3729,35 @@ function parseManagedConfig(text4) {
3723
3729
  throw new InitError("UNSUPPORTED_SCHEMA", t("config.migrationRequired"));
3724
3730
  throw new InitError("UNSUPPORTED_SCHEMA", newer ? t("config.newerSchema", { version: String(found) }) : t("config.unsupportedSchema", { version: String(found) }));
3725
3731
  }
3726
- return { schemaVersion: SCHEMA_VERSION, baseline: parseBaseline(value.baseline) };
3732
+ const config2 = { schemaVersion: SCHEMA_VERSION, baseline: parseBaseline(value.baseline) };
3733
+ if ("cli" in value) {
3734
+ if (typeof value.cli !== "string" || !CLI_VERSION_PATTERN.test(value.cli))
3735
+ throw new InitError("INVALID_CONFIG", t("config.cliVersion"));
3736
+ config2.cli = value.cli;
3737
+ }
3738
+ if ("language" in value) {
3739
+ if (!PROJECT_LANGUAGES.includes(value.language))
3740
+ throw new InitError("INVALID_CONFIG", t("config.language", { languages: PROJECT_LANGUAGES.join(", ") }));
3741
+ config2.language = value.language;
3742
+ }
3743
+ return config2;
3744
+ }
3745
+ function formatManagedConfig(config2, previous) {
3746
+ let unknown2 = {};
3747
+ if (previous !== void 0) {
3748
+ const value = JSON.parse(previous);
3749
+ if (object(value)) {
3750
+ const { schemaVersion: _s, baseline: _b, cli: _c, language: _l, ...rest } = value;
3751
+ unknown2 = rest;
3752
+ }
3753
+ }
3754
+ const known = {
3755
+ schemaVersion: config2.schemaVersion,
3756
+ baseline: config2.baseline,
3757
+ ...config2.cli === void 0 ? {} : { cli: config2.cli },
3758
+ ...config2.language === void 0 ? {} : { language: config2.language }
3759
+ };
3760
+ return JSON.stringify({ ...known, ...unknown2 }, null, 2) + "\n";
3727
3761
  }
3728
3762
 
3729
3763
  // ../../packages/core/dist/formats/links.js
@@ -25400,9 +25434,9 @@ var projectInitV8 = external_exports.discriminatedUnion("ok", [
25400
25434
  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() }) })
25401
25435
  ]);
25402
25436
 
25403
- // ../../packages/contracts/dist/versions/update-v5.js
25437
+ // ../../packages/contracts/dist/versions/update-v6.js
25404
25438
  var commitReason = external_exports.enum(["untracked", "other-changes", "commit-failed"]);
25405
- var agentDocsCommit = external_exports.strictObject({
25439
+ var updateCommit = external_exports.strictObject({
25406
25440
  state: external_exports.enum(["not-requested", "nothing", "committed", "skipped"]),
25407
25441
  commit: external_exports.string().nullable(),
25408
25442
  paths: external_exports.array(external_exports.string()),
@@ -25410,27 +25444,40 @@ var agentDocsCommit = external_exports.strictObject({
25410
25444
  reason: commitReason.nullable(),
25411
25445
  detail: external_exports.string().nullable()
25412
25446
  }).refine((value) => value.state === "committed" === (value.commit !== null) && value.state === "skipped" === (value.reason !== null), "commit is set exactly when committed, reason exactly when skipped.");
25413
- var updateV5 = external_exports.discriminatedUnion("ok", [
25447
+ var updateV6 = external_exports.discriminatedUnion("ok", [
25414
25448
  external_exports.strictObject({
25415
25449
  contract: external_exports.literal("update"),
25416
- version: external_exports.literal(5),
25450
+ version: external_exports.literal(6),
25417
25451
  ok: external_exports.literal(true),
25418
25452
  cliVersion: external_exports.string().min(1),
25419
25453
  update: updateStateV1,
25420
- // Present only when a newer release exists. npx refreshes the project blocks without a global install; npmGlobal remains an alternative.
25421
- install: external_exports.strictObject({ npx: external_exports.string().min(1), npmGlobal: external_exports.string().min(1) }).nullable(),
25422
- // refreshed: blocks whose version line differed were rewritten. current: every block already matched.
25454
+ // Present only when a newer release exists; after installing it, `gitifact update` sets the project to it.
25455
+ install: external_exports.strictObject({ npmGlobal: external_exports.string().min(1) }).nullable(),
25456
+ // refreshed: blocks whose text differed were rewritten. current: every block already matched. kept: the project is on a
25457
+ // release newer than this CLI, so its blocks were left as they are.
25423
25458
  // not-initialized: no .gitifact config here, so only the version check ran. no-block: no file carries a block.
25424
25459
  // missing: files `gitifact init` would add and update does not create, e.g. CLAUDE.md when only AGENTS.md has the block.
25425
- agentDocs: external_exports.strictObject({ state: external_exports.enum(["refreshed", "current", "not-initialized", "no-block"]), paths: external_exports.array(external_exports.string()), missing: external_exports.array(external_exports.string()) }),
25426
- commit: agentDocsCommit,
25460
+ agentDocs: external_exports.strictObject({ state: external_exports.enum(["refreshed", "current", "kept", "not-initialized", "no-block"]), paths: external_exports.array(external_exports.string()), missing: external_exports.array(external_exports.string()) }),
25461
+ // written: .gitifact/config.json now names this release as `cli` and the block language. current: it already did.
25462
+ // cli is the project's release afterwards; one newer than cliVersion was kept, since an older CLI does not lower it.
25463
+ project: external_exports.strictObject({ state: external_exports.enum(["written", "current", "not-initialized"]), cli: external_exports.string().min(1).nullable() }),
25464
+ commit: updateCommit,
25427
25465
  // The project still uses the 0.7 storage convention (schemaVersion 2): the blocks now describe commands that will refuse it
25428
25466
  // until the documents are moved with `gitifact guide show migrate`.
25429
25467
  migrationRequired: external_exports.boolean()
25430
25468
  }),
25431
- external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(5), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
25469
+ external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(6), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
25432
25470
  ]);
25433
25471
 
25472
+ // ../../packages/contracts/dist/versions/update-later-v1.js
25473
+ var updateLaterV1 = external_exports.strictObject({
25474
+ contract: external_exports.literal("update-later"),
25475
+ version: external_exports.literal(1),
25476
+ ok: external_exports.literal(true),
25477
+ cliVersion: external_exports.string().min(1),
25478
+ later: external_exports.strictObject({ version: external_exports.string().min(1), until: external_exports.iso.datetime() }).nullable()
25479
+ });
25480
+
25434
25481
  // ../../packages/contracts/dist/versions/browser-session-v3.js
25435
25482
  var browserSessionV3 = external_exports.strictObject({
25436
25483
  contract: external_exports.literal("browser-session"),
@@ -25631,7 +25678,7 @@ var messages_default3 = {
25631
25678
  "init.text.storage": "\uC800\uC7A5 \uADDC\uC57D",
25632
25679
  "init.text.agentDocs": "\uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68",
25633
25680
  "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.",
25634
- "init.text.updateAvailable": "\uC0C8 \uBC84\uC804 {version}\uC774 \uC788\uC2B5\uB2C8\uB2E4. {command}\uB85C \uC0C8 \uBC84\uC804\uC744 \uC2E4\uD589\uD558\uACE0 \uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68\uC744 \uAC31\uC2E0\uD558\uC138\uC694.",
25681
+ "init.text.updateAvailable": "\uC0C8 \uBC84\uC804 {version}\uC774 \uC788\uC2B5\uB2C8\uB2E4. {command}\uB85C \uC124\uCE58\uD55C \uB4A4 gitifact update\uB85C \uD504\uB85C\uC81D\uD2B8\uB97C \uC0C8 \uBC84\uC804\uC5D0 \uB9DE\uCD94\uC138\uC694.",
25635
25682
  "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.",
25636
25683
  "config.storeNotDirectory": ".gitifact\uB294 \uB9C1\uD06C\uAC00 \uC544\uB2CC \uC77C\uBC18 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
25637
25684
  "config.fileShape": "config.json\uC740 64 KiB \uC774\uD558\uC758 \uC77C\uBC18 \uD30C\uC77C\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
@@ -25666,13 +25713,13 @@ var messages_default3 = {
25666
25713
  "update.text.upToDate": "\uCD5C\uC2E0 \uBC84\uC804\uC744 \uC0AC\uC6A9 \uC911\uC785\uB2C8\uB2E4.",
25667
25714
  "update.text.disabled": "\uC0C8 \uBC84\uC804 \uD655\uC778\uC774 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4.",
25668
25715
  "update.text.unavailable": "\uC0C8 \uBC84\uC804\uC744 \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694.",
25669
- "update.text.install": "\uC5C5\uB370\uC774\uD2B8 \uBA85\uB839: {command}",
25670
- "update.text.afterInstall": "\uC704 \uBA85\uB839\uC740 \uC804\uC5ED \uC124\uCE58 \uC5C6\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uC9C0\uCE68\uC744 \uAC31\uC2E0\uD569\uB2C8\uB2E4. \uD504\uB85C\uC81D\uD2B8 \uC758\uC874\uC131\uC73C\uB85C \uAD00\uB9AC\uD55C\uB2E4\uBA74 \uD574\uB2F9 \uBC84\uC804\uC744 \uBA3C\uC800 \uAC31\uC2E0\uD558\uC138\uC694. \uBE0C\uB77C\uC6B0\uC800\uB3C4 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
25716
+ "update.text.install": "\uC124\uCE58 \uBA85\uB839: {command}",
25717
+ "update.text.afterInstall": "\uC124\uCE58\uD55C \uB4A4 gitifact update\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uACFC \uC9C0\uCE68\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uBC14\uB01D\uB2C8\uB2E4. \uBE0C\uB77C\uC6B0\uC800\uB3C4 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.",
25671
25718
  "update.text.blockRefreshed": "\uC9C0\uCE68 \uBE14\uB85D \uAC31\uC2E0: {paths}",
25672
25719
  "update.text.blockCurrent": "\uC9C0\uCE68 \uBE14\uB85D\uC740 \uC774\uBBF8 \uD604\uC7AC \uBC84\uC804\uC785\uB2C8\uB2E4: {paths}",
25673
- "update.text.noBlock": "GITIFACT \uBE14\uB85D\uC774 \uC788\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. npx --yes gitifact@{version} init\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694.",
25720
+ "update.text.noBlock": "GITIFACT \uBE14\uB85D\uC774 \uC788\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC774 \uC5C6\uC2B5\uB2C8\uB2E4. gitifact init\uC73C\uB85C \uC124\uCE58\uD558\uC138\uC694.",
25674
25721
  "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.",
25675
- "update.text.missing": "{paths} \uD30C\uC77C\uC774 \uC5C6\uC5B4 Claude Code\uAC00 AGENTS.md\uC758 \uC9C0\uCE68\uC744 \uC77D\uC9C0 \uBABB\uD569\uB2C8\uB2E4. npx --yes gitifact@{version} init\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 AGENTS.md\uB97C \uAC00\uC838\uC624\uB294 \uD30C\uC77C\uC744 \uB9CC\uB4ED\uB2C8\uB2E4.",
25722
+ "update.text.missing": "{paths} \uD30C\uC77C\uC774 \uC5C6\uC5B4 Claude Code\uAC00 AGENTS.md\uC758 \uC9C0\uCE68\uC744 \uC77D\uC9C0 \uBABB\uD569\uB2C8\uB2E4. gitifact init\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 AGENTS.md\uB97C \uAC00\uC838\uC624\uB294 \uD30C\uC77C\uC744 \uB9CC\uB4ED\uB2C8\uB2E4.",
25676
25723
  "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.",
25677
25724
  "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
25678
25725
  "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -25684,7 +25731,7 @@ var messages_default3 = {
25684
25731
  "server.assetNotFound": "\uC5D0\uC14B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
25685
25732
  "help.lang": "\uCD9C\uB825 \uC5B8\uC5B4 (ko \uB610\uB294 en)",
25686
25733
  "help.updateCheck": "\uD30C\uC77C\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uC0C8 \uBC84\uC804\uB9CC \uD655\uC778",
25687
- "commitFiles.notRecord": ".gitifact \uC548\uC5D0\uC11C\uB294 \uAE30\uB85D\xB7\uC124\uC815\xB7\uC5D0\uC14B\uB9CC \uCEE4\uBC0B\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4: {path}",
25734
+ "commitFiles.notRecord": ".gitifact \uC548\uC5D0\uC11C\uB294 \uAE30\uB85D\xB7\uC124\uC815(config.json\xB7.gitattributes)\xB7\uC5D0\uC14B\uB9CC \uCEE4\uBC0B\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4: {path}",
25688
25735
  "help.changes": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC740 \uBB38\uC11C \uBCC0\uACBD \uC870\uD68C\uC640 \uCEE4\uBC0B",
25689
25736
  "help.changesList": "HEAD \uB300\uBE44 \uBC14\uB010 \uBB38\uC11C, \uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC740 \uACB0\uC815\uAE30\uB85D, \uAE30\uB85D\uC774 \uC5C6\uB294 \uBCC0\uACBD, \uCEE4\uBC0B \uC785\uB825 \uD30C\uC77C \uACBD\uB85C",
25690
25737
  "help.changesCommit": "check\uC640 \uAC19\uC740 \uAC80\uC0AC \uB4A4 \uC120\uD0DD\uD55C \uD30C\uC77C\uACFC \uACB0\uC815\uAE30\uB85D\uC744 \uCEE4\uBC0B",
@@ -25837,7 +25884,16 @@ var messages_default3 = {
25837
25884
  "feedback.openUrl": "gh\uB97C \uC4F8 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4. \uC544\uB798 \uC8FC\uC18C\uB97C \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5B4 \uC774\uC288\uB97C \uC81C\uCD9C\uD558\uC138\uC694.",
25838
25885
  "feedback.ghFailed": "gh\uB85C \uC774\uC288\uB97C \uB9CC\uB4E4\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uC544\uB798 \uC8FC\uC18C\uB97C \uBE0C\uB77C\uC6B0\uC800\uC5D0\uC11C \uC5F4\uC5B4 \uC774\uC288\uB97C \uC81C\uCD9C\uD558\uC138\uC694.",
25839
25886
  "feedback.truncated": "\uBCF8\uBB38\uC774 \uAE38\uC5B4 \uC8FC\uC18C\uC5D0\uB294 \uC55E\uBD80\uBD84\uB9CC \uB2F4\uACBC\uC2B5\uB2C8\uB2E4. \uC544\uB798 \uC804\uCCB4 \uBCF8\uBB38\uC744 \uC791\uC131 \uD398\uC774\uC9C0\uC5D0 \uBD99\uC5EC \uB123\uC73C\uC138\uC694.",
25840
- "feedback.title": "\uC81C\uBAA9: {title}"
25887
+ "feedback.title": "\uC81C\uBAA9: {title}",
25888
+ "update.text.projectWritten": "{path}\uC758 \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC744 gitifact {version}\uC73C\uB85C \uC801\uC5C8\uC2B5\uB2C8\uB2E4.",
25889
+ "update.text.projectAhead": "\uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC740 gitifact {project}\uC785\uB2C8\uB2E4. \uC774 CLI({version})\uAC00 \uB354 \uC624\uB798\uB418\uC5B4 \uB0AE\uCD94\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. {command}\uB85C \uC62C\uB9AC\uC138\uC694.",
25890
+ "update.later.postponed": "{latest} \uC548\uB0B4\uB97C {until}\uAE4C\uC9C0 \uC27D\uB2C8\uB2E4. \uB354 \uC0C8 \uBC84\uC804\uC774 \uB098\uC624\uBA74 \uBC14\uB85C \uB2E4\uC2DC \uC548\uB0B4\uD569\uB2C8\uB2E4.",
25891
+ "update.later.nothing": "\uBBF8\uB8F0 \uC0C8 \uBC84\uC804 \uC548\uB0B4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4(\uC9C0\uAE08 {version}).",
25892
+ "notice.release": "\uC0C8 \uBC84\uC804 {latest}\uAC00 \uC788\uC2B5\uB2C8\uB2E4(\uC9C0\uAE08 {version}). \uC5C5\uB370\uC774\uD2B8: {command} \uD6C4 gitifact update \xB7 \uB098\uC911\uC5D0: gitifact update --later",
25893
+ "notice.projectNewer": "\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 gitifact {project} \uAE30\uC900\uC785\uB2C8\uB2E4(\uC2E4\uD589 \uC911 {version}). {command}\uB85C \uC62C\uB9AC\uC138\uC694.",
25894
+ "notice.projectOlder": "\uC774 CLI({version})\uAC00 \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900({project})\uBCF4\uB2E4 \uC0C8 \uBC84\uC804\uC785\uB2C8\uB2E4. gitifact update\uB85C \uD504\uB85C\uC81D\uD2B8 \uAE30\uC900\uC744 \uC62C\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
25895
+ "help.updateLater": "\uC9C0\uAE08 \uC548\uB0B4\uB41C \uC0C8 \uBC84\uC804\uC744 24\uC2DC\uAC04 \uB3D9\uC548 \uC548\uB0B4\uD558\uC9C0 \uC54A\uC74C. \uB354 \uC0C8 \uBC84\uC804\uC740 \uBC14\uB85C \uC548\uB0B4",
25896
+ "update.text.blockKept": "\uD504\uB85C\uC81D\uD2B8\uAC00 \uC774 CLI\uBCF4\uB2E4 \uC0C8 \uBC84\uC804 \uAE30\uC900\uC774\uB77C \uC9C0\uCE68 \uBE14\uB85D\uC744 \uADF8\uB300\uB85C \uB450\uC5C8\uC2B5\uB2C8\uB2E4: {paths}"
25841
25897
  };
25842
25898
 
25843
25899
  // src/shared/i18n/en/messages.json
@@ -25936,7 +25992,7 @@ var messages_default4 = {
25936
25992
  "init.text.storage": "Storage convention",
25937
25993
  "init.text.agentDocs": "Agent instructions",
25938
25994
  "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.",
25939
- "init.text.updateAvailable": "Version {version} is available. Run {command} to use it and refresh the project instructions.",
25995
+ "init.text.updateAvailable": "Version {version} is available. Install it with {command}, then run gitifact update to move the project to it.",
25940
25996
  "init.failed": "Initialization failed. Check file permissions and the generated configuration and temporary files.",
25941
25997
  "config.storeNotDirectory": ".gitifact must be a regular directory, not a link.",
25942
25998
  "config.fileShape": "config.json must be a regular file no larger than 64 KiB.",
@@ -25971,13 +26027,13 @@ var messages_default4 = {
25971
26027
  "update.text.upToDate": "You are using the latest version.",
25972
26028
  "update.text.disabled": "Update checks are disabled.",
25973
26029
  "update.text.unavailable": "Could not check for updates. Check your network connection.",
25974
- "update.text.install": "Update command: {command}",
25975
- "update.text.afterInstall": "This command refreshes the instructions with the new version without a global installation. If you manage Gitifact as a project dependency, update that dependency first. Restart the browser with the new version.",
26030
+ "update.text.install": "Install command: {command}",
26031
+ "update.text.afterInstall": "After installing, run gitifact update again to move the project and its instructions to the new version. Restart the browser with the new version too.",
25976
26032
  "update.text.blockRefreshed": "Instruction blocks refreshed: {paths}",
25977
26033
  "update.text.blockCurrent": "Instruction blocks are already current: {paths}",
25978
- "update.text.noBlock": "No instruction files contain a GITIFACT block. Run npx --yes gitifact@{version} init to install one.",
26034
+ "update.text.noBlock": "No instruction files contain a GITIFACT block. Run gitifact init to install one.",
25979
26035
  "update.text.notInitialized": "Only the version was checked because this project is not initialized.",
25980
- "update.text.missing": "{paths} is missing, so Claude Code cannot load the AGENTS.md instructions. Rerun npx --yes gitifact@{version} init to create the import file.",
26036
+ "update.text.missing": "{paths} is missing, so Claude Code cannot load the AGENTS.md instructions. Rerun gitifact init to create the import file.",
25981
26037
  "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.",
25982
26038
  "update.text.committed": "Instruction blocks committed: {commit} ({paths})",
25983
26039
  "update.text.commitNothing": "No instruction block changes to commit.",
@@ -25989,7 +26045,7 @@ var messages_default4 = {
25989
26045
  "server.assetNotFound": "Asset not found.",
25990
26046
  "help.lang": "Output language (ko or en)",
25991
26047
  "help.updateCheck": "Check for a newer version without changing files",
25992
- "commitFiles.notRecord": "Inside .gitifact only records, the configuration and assets can be committed: {path}",
26048
+ "commitFiles.notRecord": "Inside .gitifact only records, the configuration (config.json, .gitattributes) and assets can be committed: {path}",
25993
26049
  "help.changes": "Inspect and commit uncommitted document changes",
25994
26050
  "help.changesList": "Documents changed since HEAD, uncommitted records, changes without a record, and the commit input path",
25995
26051
  "help.changesCommit": "Check like the check command, then commit the selected files and records",
@@ -26142,7 +26198,16 @@ var messages_default4 = {
26142
26198
  "feedback.openUrl": "gh is not available. Open the address below in a browser and submit the issue.",
26143
26199
  "feedback.ghFailed": "gh could not create the issue. Open the address below in a browser and submit the issue.",
26144
26200
  "feedback.truncated": "The body is long, so the address holds only its beginning. Paste the full body below into the page.",
26145
- "feedback.title": "Title: {title}"
26201
+ "feedback.title": "Title: {title}",
26202
+ "update.text.projectWritten": "Set the project to gitifact {version} in {path}.",
26203
+ "update.text.projectAhead": "The project is set to gitifact {project}. This CLI ({version}) is older, so it was not lowered. Install it with {command}.",
26204
+ "update.later.postponed": "The notice for {latest} is quiet until {until}. A newer release is announced right away.",
26205
+ "update.later.nothing": "No newer release to postpone (running {version}).",
26206
+ "notice.release": "Version {latest} is available (running {version}). Update: {command}, then gitifact update \xB7 Later: gitifact update --later",
26207
+ "notice.projectNewer": "This project is set to gitifact {project} (running {version}). Install it with {command}.",
26208
+ "notice.projectOlder": "This CLI ({version}) is newer than the project ({project}). Run gitifact update to move the project to it.",
26209
+ "help.updateLater": "Quiet the announced newer release for 24 hours; a release newer than it is announced right away",
26210
+ "update.text.blockKept": "The project is set to a newer release than this CLI, so the instruction blocks were left as they are: {paths}"
26146
26211
  };
26147
26212
 
26148
26213
  // src/shared/i18n/index.ts
@@ -28982,15 +29047,15 @@ function boilerplateFor(path3) {
28982
29047
  return "# " + name2 + "\n\nProject-specific guidance for AI coding agents.\n";
28983
29048
  }
28984
29049
  var readBundledBlock = (lang) => readFile12(new URL("./i18n/" + lang + "/block.md", import.meta.url), "utf8");
28985
- async function renderAgentBlock(version2, controls = {}, lang = getLanguage()) {
29050
+ async function renderAgentBlock(controls = {}, lang = getLanguage()) {
28986
29051
  const body2 = await (controls.readBlock ?? readBundledBlock)(lang);
28987
- const filled = body2.trimEnd().replace(/\{(version|language|topics)\}/g, (_whole, name2) => name2 === "version" ? version2 : name2 === "language" ? lang : guideTopics.join(", "));
28988
- return AGENT_START + "\n" + filled + "\n" + AGENT_END;
29052
+ return AGENT_START + "\n" + body2.trimEnd().replace(/\{topics\}/g, guideTopics.join(", ")) + "\n" + AGENT_END;
28989
29053
  }
28990
- function parseAgentBlock(text4) {
28991
- const match = /^gitifact v(\S+)(?: · ([a-z][a-z-]*))? · .*schemaVersion (\d+)$/m.exec(text4);
28992
- if (!match) return null;
28993
- return { version: match[1], language: match[2] ?? "ko", schemaVersion: Number(match[3]) };
29054
+ function legacyBlockLanguage(text4) {
29055
+ const match = /^gitifact v\S+(?: · ([a-z][a-z-]*))? · .*schemaVersion \d+$/m.exec(text4);
29056
+ if (!match) return void 0;
29057
+ const language = match[1] ?? "ko";
29058
+ return language === "ko" || language === "en" ? language : void 0;
28994
29059
  }
28995
29060
  function findBlock(text4) {
28996
29061
  const start = text4.indexOf(AGENT_START);
@@ -29052,14 +29117,26 @@ function removeBlock(text4, boilerplate) {
29052
29117
  }
29053
29118
 
29054
29119
  // src/commands/agent-docs.ts
29055
- var skippedAgentDocs = { mode: "skip", paths: [], writes: [], missing: [] };
29120
+ var skippedAgentDocs = (language = explicitLanguage() ?? getLanguage()) => ({ mode: "skip", paths: [], writes: [], missing: [], language });
29121
+ var blockOf = (text4) => {
29122
+ try {
29123
+ const found = findBlock(text4);
29124
+ return found ? text4.slice(found.start, found.end) : void 0;
29125
+ } catch {
29126
+ return void 0;
29127
+ }
29128
+ };
29056
29129
  async function planAgentDocs(root, options) {
29057
- if (options.skip) return skippedAgentDocs;
29130
+ if (options.skip) return skippedAgentDocs(explicitLanguage() ?? options.language ?? getLanguage());
29058
29131
  const existing = /* @__PURE__ */ new Map();
29059
29132
  for (const path3 of candidatePaths) {
29060
29133
  const text4 = await managedRead(root, path3);
29061
29134
  if (text4 !== null) existing.set(path3, text4);
29062
29135
  }
29136
+ const language = explicitLanguage() ?? options.language ?? [...existing.values()].map((text4) => {
29137
+ const block2 = blockOf(text4);
29138
+ return block2 === void 0 ? void 0 : legacyBlockLanguage(block2);
29139
+ }).find(Boolean) ?? getLanguage();
29063
29140
  const writes = [];
29064
29141
  const paths2 = [];
29065
29142
  if (options.remove) {
@@ -29075,19 +29152,14 @@ async function planAgentDocs(root, options) {
29075
29152
  paths2.push(CLAUDE_WRAPPER_PATH);
29076
29153
  writes.push({ path: CLAUDE_WRAPPER_PATH, previous: wrapper, next: null });
29077
29154
  }
29078
- return { mode: "remove", paths: paths2, writes, missing: [] };
29155
+ return { mode: "remove", paths: paths2, writes, missing: [], language };
29079
29156
  }
29080
- const blocks = /* @__PURE__ */ new Map();
29081
- const blockFor = async (previous) => {
29082
- const stored = previous ? parseAgentBlock(previous)?.language : void 0;
29083
- const language = explicitLanguage() ?? (stored === "ko" || stored === "en" ? stored : getLanguage());
29084
- if (!blocks.has(language)) blocks.set(language, await renderAgentBlock(options.version, options, language));
29085
- return blocks.get(language);
29086
- };
29157
+ let block;
29158
+ const blockFor = async () => block ??= await renderAgentBlock(options, language);
29087
29159
  const { inject, create } = options.onlyExisting ? { inject: candidatePaths.filter((path3) => existing.has(path3) && findBlock(existing.get(path3)) && !isWrapperFile(existing.get(path3))), create: null } : resolveAgentPaths(options.agent, existing);
29088
29160
  for (const path3 of inject) {
29089
29161
  const previous = existing.get(path3);
29090
- const next = injectBlock(previous, await blockFor(previous), boilerplateFor(path3));
29162
+ const next = injectBlock(previous, await blockFor(), boilerplateFor(path3));
29091
29163
  paths2.push(path3);
29092
29164
  if (next !== previous) writes.push({ path: path3, previous, next });
29093
29165
  }
@@ -29107,7 +29179,7 @@ async function planAgentDocs(root, options) {
29107
29179
  if (paths2.includes(path3) || !isWrapperFile(text4) || !findBlock(text4)) continue;
29108
29180
  writes.push({ path: path3, previous: text4, next: removeBlock(text4, boilerplateFor(path3)) });
29109
29181
  }
29110
- return { mode: "install", paths: paths2, writes, missing: missing2 };
29182
+ return { mode: "install", paths: paths2, writes, missing: missing2, language };
29111
29183
  }
29112
29184
  async function applyAgentDocs(root, plan, recheck) {
29113
29185
  for (const write of plan.writes) await managedWrite(root, write.path, write.previous, write.next, recheck);
@@ -29156,12 +29228,32 @@ async function resolveUpdate(current2, fetchLatest, signal, timeoutMs = updateCh
29156
29228
  var npmGlobalInstall = (version2) => "npm install -g gitifact@" + version2;
29157
29229
  var npxUpdate = (version2) => "npx --yes gitifact@" + version2 + " update";
29158
29230
 
29231
+ // src/commands/project-settings.ts
29232
+ function settleConfig(config2, version2, language) {
29233
+ const cli = !CLI_VERSION_PATTERN.test(version2) || config2.cli !== void 0 && !isNewerRelease(version2, config2.cli) ? config2.cli : version2;
29234
+ const next = { ...config2, ...cli === void 0 ? {} : { cli }, language };
29235
+ return { config: next, changed: next.cli !== config2.cli || next.language !== config2.language };
29236
+ }
29237
+ function projectSettings(text4) {
29238
+ if (text4 === void 0) return {};
29239
+ try {
29240
+ const value = JSON.parse(text4);
29241
+ return {
29242
+ ...typeof value.cli === "string" && CLI_VERSION_PATTERN.test(value.cli) ? { cli: value.cli } : {},
29243
+ ...value.language === "ko" || value.language === "en" ? { language: value.language } : {}
29244
+ };
29245
+ } catch {
29246
+ return {};
29247
+ }
29248
+ }
29249
+
29159
29250
  // src/commands/spec-init.ts
29160
29251
  async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs, update = disabledUpdate) {
29161
29252
  const repo = initRepository(cwd, env);
29162
29253
  const first = await repo.inspect();
29163
29254
  const root = first.state.repository.rootPath;
29164
- const docsPlan = agentDocs ? await planAgentDocs(root, agentDocs) : skippedAgentDocs;
29255
+ const language = projectSettings(await readConfigFile(root).catch(() => void 0)).language;
29256
+ let docsPlan = agentDocs ? await planAgentDocs(root, { ...agentDocs, language }) : skippedAgentDocs(language);
29165
29257
  const result = async (config3, outcome, lineEndings = false) => {
29166
29258
  const checked = await update;
29167
29259
  return projectInitV8.parse({
@@ -29182,16 +29274,25 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
29182
29274
  const existing = async (text5) => {
29183
29275
  const config3 = parseManagedConfig(text5);
29184
29276
  await repo.validateBaseline(config3, root, first.state.head.commit, first.state.repository.objectFormat);
29277
+ let current2 = text5;
29185
29278
  const unchanged = async () => {
29186
- if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text5) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
29279
+ if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== current2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
29187
29280
  };
29188
29281
  await unchanged();
29282
+ if (agentDocs && config3.cli && isNewerRelease(config3.cli, agentDocs.version) && docsPlan.mode === "install") docsPlan = skippedAgentDocs(docsPlan.language);
29189
29283
  if (dryRun) return result(config3, "already-initialized");
29284
+ const settled2 = agentDocs ? settleConfig(config3, agentDocs.version, docsPlan.language) : { config: config3, changed: false };
29285
+ if (settled2.changed) {
29286
+ const next = formatManagedConfig(settled2.config, text5);
29287
+ await publishConfig(root, next, unchanged, void 0, true);
29288
+ current2 = next;
29289
+ }
29190
29290
  await applyAgentDocs(root, docsPlan, unchanged);
29191
- return result(config3, "already-initialized", await writeLineEndings(root));
29291
+ return result(settled2.config, "already-initialized", await writeLineEndings(root));
29192
29292
  };
29193
- const config2 = { schemaVersion: SCHEMA_VERSION, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
29194
- const text4 = JSON.stringify(config2, null, 2) + "\n";
29293
+ const base = { schemaVersion: SCHEMA_VERSION, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
29294
+ const config2 = agentDocs ? settleConfig(base, agentDocs.version, docsPlan.language).config : base;
29295
+ const text4 = formatManagedConfig(config2);
29195
29296
  const finish = async (outcome) => {
29196
29297
  const published = async () => {
29197
29298
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text4) throw new InitError("INPUT_CHANGED_AFTER_WRITE", t2("init.changedAfterWrite"));
@@ -29241,6 +29342,53 @@ var fetchLatestVersion = async (signal) => {
29241
29342
  return latest;
29242
29343
  };
29243
29344
 
29345
+ // src/adapters/filesystem/user-cache.ts
29346
+ import { mkdir as mkdir5, readFile as readFile13, rename as rename3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
29347
+ import { homedir } from "node:os";
29348
+ import { join as join15 } from "node:path";
29349
+ import { randomUUID as randomUUID5 } from "node:crypto";
29350
+ function userCacheDirectory(env = process.env, platform = process.platform, home = homedir()) {
29351
+ if (env.GITIFACT_CACHE_DIR) return env.GITIFACT_CACHE_DIR;
29352
+ if (platform === "win32") return join15(env.LOCALAPPDATA || join15(home, "AppData", "Local"), "gitifact");
29353
+ if (platform === "darwin") return join15(home, "Library", "Caches", "gitifact");
29354
+ return join15(env.XDG_CACHE_HOME || join15(home, ".cache"), "gitifact");
29355
+ }
29356
+ var cacheFile = (env) => join15(userCacheDirectory(env), "update.json");
29357
+ var isTime = (value) => typeof value === "string" && !Number.isNaN(Date.parse(value));
29358
+ async function readUpdateCache(env = process.env) {
29359
+ let value;
29360
+ try {
29361
+ value = JSON.parse(await readFile13(cacheFile(env), "utf8"));
29362
+ } catch {
29363
+ return {};
29364
+ }
29365
+ if (typeof value !== "object" || value === null) return {};
29366
+ const later = value.later;
29367
+ return {
29368
+ ...isTime(value.checkedAt) ? { checkedAt: value.checkedAt } : {},
29369
+ ...typeof value.latest === "string" ? { latest: value.latest } : {},
29370
+ ...isTime(value.checkingSince) ? { checkingSince: value.checkingSince } : {},
29371
+ ...later && typeof later.version === "string" && isTime(later.until) ? { later: { version: later.version, until: later.until } } : {}
29372
+ };
29373
+ }
29374
+ async function changeUpdateCache(env, change2) {
29375
+ const directory = userCacheDirectory(env);
29376
+ const temporary = join15(directory, "update-" + randomUUID5() + ".tmp");
29377
+ try {
29378
+ const next = change2(await readUpdateCache(env));
29379
+ await mkdir5(directory, { recursive: true });
29380
+ await writeFile3(temporary, JSON.stringify(next) + "\n", "utf8");
29381
+ await rename3(temporary, cacheFile(env));
29382
+ } catch {
29383
+ await unlink3(temporary).catch(() => {
29384
+ });
29385
+ }
29386
+ }
29387
+ async function recordUpdateCheck(env, update, now = Date.now()) {
29388
+ if (update.status === "disabled" || update.status === "checking") return;
29389
+ await changeUpdateCache(env, ({ checkingSince: _done, ...cache }) => ({ ...cache, checkedAt: new Date(now).toISOString(), ...update.latestVersion ? { latest: update.latestVersion } : {} }));
29390
+ }
29391
+
29244
29392
  // src/commands/init.ts
29245
29393
  async function runInit(options, version2) {
29246
29394
  const cancel = new AbortController();
@@ -29255,9 +29403,10 @@ async function runInit(options, version2) {
29255
29403
  update
29256
29404
  );
29257
29405
  if (!dto.ok) throw new Error("unreachable");
29406
+ await recordUpdateCheck(process.env, dto.update);
29258
29407
  const docs = dto.agentDocs.mode === "skip" ? t2("init.text.skipped") : dto.agentDocs.paths.join(", ") + (dto.agentDocs.mode === "remove" ? " (" + t2("init.text.blockRemoved") + ")" : dto.outcome === "planned" ? " (" + t2("init.text.blockPlanned") + ")" : " (" + t2("init.text.blockUpdated") + ")");
29259
29408
  const lineEndings = dto.lineEndings.created ? t2("init.text.lineEndings", { path: dto.lineEndings.path }) + "\n" : "";
29260
- const newer = dto.install ? t2("init.text.updateAvailable", { version: dto.update.latestVersion, command: dto.install.npx }) + "\n" : "";
29409
+ const newer = dto.install ? t2("init.text.updateAvailable", { version: dto.update.latestVersion, command: dto.install.npmGlobal }) + "\n" : "";
29261
29410
  process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
29262
29411
  ${t2("init.text.storage")}: schemaVersion ${dto.schemaVersion}
29263
29412
  ${t2("init.text.agentDocs")}: ${docs}
@@ -29272,17 +29421,17 @@ ${lineEndings}${newer}` : JSON.stringify(dto) + "\n");
29272
29421
  }
29273
29422
 
29274
29423
  // src/commands/documents.ts
29275
- import { readFile as readFile13 } from "node:fs/promises";
29276
- import { join as join16 } from "node:path";
29424
+ import { readFile as readFile14 } from "node:fs/promises";
29425
+ import { join as join17 } from "node:path";
29277
29426
 
29278
29427
  // src/commands/project.ts
29279
- import { join as join15 } from "node:path";
29428
+ import { join as join16 } from "node:path";
29280
29429
  var operations = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
29281
29430
  async function openProject(cwd, options = {}) {
29282
29431
  const reader = storeReader(cwd);
29283
29432
  const location = await reader.location();
29284
29433
  if (options.writing) {
29285
- for (const marker2 of operations) if (await fileInfo(join15(location.gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
29434
+ for (const marker2 of operations) if (await fileInfo(join16(location.gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
29286
29435
  if (await reader.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
29287
29436
  }
29288
29437
  const raw = await readConfigFile(location.root);
@@ -29446,7 +29595,7 @@ async function working(project) {
29446
29595
  const { documents } = await project.cache.documents.list();
29447
29596
  return {
29448
29597
  byId: new Map(documents.map((d) => [d.id, d])),
29449
- read: (path3) => readFile13(join16(project.root, ...path3.split("/")), "utf8"),
29598
+ read: (path3) => readFile14(join17(project.root, ...path3.split("/")), "utf8"),
29450
29599
  referencing: async (id) => [...new Set((await project.cache.documents.referencing(id)).map((r) => r.from))]
29451
29600
  };
29452
29601
  }
@@ -29645,14 +29794,14 @@ var runSpecsNew = (kind2, path3, options) => runCommand("specs", options.format,
29645
29794
 
29646
29795
  // src/queries/instructions.ts
29647
29796
  import { lstat as lstat12 } from "node:fs/promises";
29648
- import { join as join17 } from "node:path";
29797
+ import { join as join18 } from "node:path";
29649
29798
  var AGENTS = "AGENTS.md";
29650
29799
  async function listInstructions(source2, options, request) {
29651
29800
  const listed3 = await source2.cache.documents.list();
29652
29801
  const problems = listed3.problems;
29653
29802
  const states = await documentStates(source2, listed3.documents);
29654
29803
  const documents = states.documents;
29655
- const agents2 = { path: AGENTS, exists: !!(await lstat12(join17(source2.root, AGENTS)).catch(() => void 0))?.isFile() };
29804
+ const agents2 = { path: AGENTS, exists: !!(await lstat12(join18(source2.root, AGENTS)).catch(() => void 0))?.isFile() };
29656
29805
  const historyNeeded = options.sort === "updated" || options.author !== void 0 || !!options.withUpdated;
29657
29806
  const latest = /* @__PURE__ */ new Map();
29658
29807
  const newest = /* @__PURE__ */ new Map();
@@ -29751,7 +29900,7 @@ var runInstructionsNew = (name2, options) => runCommand("instructions", options.
29751
29900
 
29752
29901
  // src/adapters/filesystem/document-warnings.ts
29753
29902
  import { lstat as lstat13, readdir as readdir6 } from "node:fs/promises";
29754
- import { join as join18 } from "node:path";
29903
+ import { join as join19 } from "node:path";
29755
29904
  var info2 = async (path3) => lstat13(path3).catch((e) => {
29756
29905
  if (e.code === "ENOENT") return void 0;
29757
29906
  throw e;
@@ -29760,11 +29909,11 @@ var posix = (path3) => path3.split("\\").join("/");
29760
29909
  async function listAssets(root) {
29761
29910
  const out = [];
29762
29911
  async function visit2(relative, depth) {
29763
- const stat3 = await info2(join18(root, relative));
29912
+ const stat3 = await info2(join19(root, relative));
29764
29913
  if (!stat3 || stat3.isSymbolicLink()) return;
29765
29914
  if (stat3.isDirectory()) {
29766
29915
  if (depth > 8 || out.length > 4e3) return;
29767
- for (const name2 of (await readdir6(join18(root, relative))).sort()) await visit2(relative + "/" + name2, depth + 1);
29916
+ for (const name2 of (await readdir6(join19(root, relative))).sort()) await visit2(relative + "/" + name2, depth + 1);
29768
29917
  } else if (stat3.isFile()) out.push({ path: posix(relative), bytes: stat3.size });
29769
29918
  }
29770
29919
  await visit2(ASSETS_DIR, 0);
@@ -29773,7 +29922,7 @@ async function listAssets(root) {
29773
29922
  async function readDocumentWarnings(root, documents) {
29774
29923
  const outside = [...new Set(documentBodyLinks(documents).map((l) => l.target).filter((t3) => !t3.startsWith(".gitifact/") || t3.startsWith(INSTRUCTIONS_ROOT + "/")))];
29775
29924
  const existing = /* @__PURE__ */ new Set();
29776
- for (const target of outside) if ((await info2(join18(root, ...target.split("/"))))?.isFile()) existing.add(target);
29925
+ for (const target of outside) if ((await info2(join19(root, ...target.split("/"))))?.isFile()) existing.add(target);
29777
29926
  return documentWarnings(documents, await listAssets(root), existing);
29778
29927
  }
29779
29928
 
@@ -29796,15 +29945,15 @@ var runCheck = (options) => runCommand("check", options.format, async () => {
29796
29945
  });
29797
29946
 
29798
29947
  // src/commands/changes.ts
29799
- import { randomUUID as randomUUID5 } from "node:crypto";
29800
- import { mkdir as mkdir6, open as open5, readFile as readFile16, rename as rename3, rmdir, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
29801
- import { join as join21 } from "node:path";
29948
+ import { randomUUID as randomUUID6 } from "node:crypto";
29949
+ import { mkdir as mkdir7, open as open5, readFile as readFile17, rename as rename4, rmdir, unlink as unlink5, writeFile as writeFile5 } from "node:fs/promises";
29950
+ import { join as join22 } from "node:path";
29802
29951
 
29803
29952
  // src/adapters/filesystem/agent-inputs.ts
29804
29953
  import { createHash as createHash4, randomBytes as randomBytes2 } from "node:crypto";
29805
- import { lstat as lstat14, mkdir as mkdir5, readdir as readdir7, realpath as realpath3, unlink as unlink3, writeFile as writeFile3 } from "node:fs/promises";
29954
+ import { lstat as lstat14, mkdir as mkdir6, readdir as readdir7, realpath as realpath3, unlink as unlink4, writeFile as writeFile4 } from "node:fs/promises";
29806
29955
  import { tmpdir } from "node:os";
29807
- import { basename, dirname as dirname2, join as join19 } from "node:path";
29956
+ import { basename, dirname as dirname2, join as join20 } from "node:path";
29808
29957
  var maxAge = 7 * 24 * 60 * 60 * 1e3;
29809
29958
  var info3 = (path3) => lstat14(path3).catch((e) => {
29810
29959
  if (e.code === "ENOENT") return void 0;
@@ -29813,16 +29962,16 @@ var info3 = (path3) => lstat14(path3).catch((e) => {
29813
29962
  var comparable = (path3) => process.platform === "win32" ? path3.toLowerCase() : path3;
29814
29963
  async function candidates(root, controls) {
29815
29964
  const key = createHash4("sha256").update(comparable(await realpath3(root))).digest("hex").slice(0, 16);
29816
- return { system: join19((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join19(root, ".gitifact", "tmp") };
29965
+ return { system: join20((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join20(root, ".gitifact", "tmp") };
29817
29966
  }
29818
29967
  async function writable(directory) {
29819
29968
  try {
29820
- await mkdir5(directory, { recursive: true });
29969
+ await mkdir6(directory, { recursive: true });
29821
29970
  const stat3 = await info3(directory);
29822
29971
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) return false;
29823
- const probe = join19(directory, ".probe-" + randomBytes2(8).toString("hex"));
29824
- await writeFile3(probe, "", { flag: "wx" });
29825
- await unlink3(probe);
29972
+ const probe = join20(directory, ".probe-" + randomBytes2(8).toString("hex"));
29973
+ await writeFile4(probe, "", { flag: "wx" });
29974
+ await unlink4(probe);
29826
29975
  return true;
29827
29976
  } catch {
29828
29977
  return false;
@@ -29834,7 +29983,7 @@ async function prepareAgentInputs(root, controls = {}) {
29834
29983
  if (await writable(system)) directory = system;
29835
29984
  else if (await writable(project)) {
29836
29985
  directory = project;
29837
- await writeFile3(join19(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
29986
+ await writeFile4(join20(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
29838
29987
  if (e.code !== "EEXIST") throw e;
29839
29988
  });
29840
29989
  }
@@ -29842,10 +29991,10 @@ async function prepareAgentInputs(root, controls = {}) {
29842
29991
  const now = (controls.now ?? Date.now)();
29843
29992
  for (const name2 of await readdir7(directory).catch(() => [])) {
29844
29993
  if (name2 === ".gitignore") continue;
29845
- const stat3 = await info3(join19(directory, name2)).catch(() => void 0);
29846
- if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink3(join19(directory, name2)).catch(() => void 0);
29994
+ const stat3 = await info3(join20(directory, name2)).catch(() => void 0);
29995
+ if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink4(join20(directory, name2)).catch(() => void 0);
29847
29996
  }
29848
- return { commit: join19(directory, "commit.json") };
29997
+ return { commit: join20(directory, "commit.json") };
29849
29998
  }
29850
29999
  async function discardAgentInput(root, file2, controls = {}) {
29851
30000
  try {
@@ -29858,7 +30007,7 @@ async function discardAgentInput(root, file2, controls = {}) {
29858
30007
  if (await info3(directory) && comparable(await realpath3(directory)) === parent) managed = true;
29859
30008
  }
29860
30009
  if (!managed || basename(file2) === ".gitignore") return false;
29861
- await unlink3(file2);
30010
+ await unlink4(file2);
29862
30011
  return true;
29863
30012
  } catch {
29864
30013
  return false;
@@ -29867,13 +30016,13 @@ async function discardAgentInput(root, file2, controls = {}) {
29867
30016
 
29868
30017
  // src/commands/commit-files.ts
29869
30018
  import { createHash as createHash5 } from "node:crypto";
29870
- import { lstat as lstat15, readFile as readFile14 } from "node:fs/promises";
29871
- import { join as join20 } from "node:path";
30019
+ import { lstat as lstat15, readFile as readFile15 } from "node:fs/promises";
30020
+ import { join as join21 } from "node:path";
29872
30021
  var fail3 = (message) => {
29873
30022
  throw new CommandError("INVALID_COMMIT", message);
29874
30023
  };
29875
30024
  var hash2 = (value) => createHash5("sha256").update(value).digest("hex");
29876
- var optional2 = (path3) => readFile14(path3).catch((e) => {
30025
+ var optional2 = (path3) => readFile15(path3).catch((e) => {
29877
30026
  if (e.code === "ENOENT") return null;
29878
30027
  throw e;
29879
30028
  });
@@ -29903,14 +30052,14 @@ async function fingerprint(root, path3) {
29903
30052
  validPath2(path3);
29904
30053
  const parts = path3.split("/");
29905
30054
  for (let i = 1; i <= parts.length; i++) {
29906
- const target = join20(root, ...parts.slice(0, i));
30055
+ const target = join21(root, ...parts.slice(0, i));
29907
30056
  const stat3 = await info4(target);
29908
30057
  if (!stat3) return null;
29909
30058
  if (stat3.isSymbolicLink() || (i < parts.length ? !stat3.isDirectory() : !stat3.isFile())) fail3(t2("commitFiles.linkOrDirectory"));
29910
- if (i < parts.length && await info4(join20(target, ".git"))) fail3(t2("commitFiles.nestedRepository"));
30059
+ if (i < parts.length && await info4(join21(target, ".git"))) fail3(t2("commitFiles.nestedRepository"));
29911
30060
  if (i === parts.length) {
29912
30061
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail3(t2("commitFiles.hardLinkOrSize"));
29913
- return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile14(target)]));
30062
+ return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile15(target)]));
29914
30063
  }
29915
30064
  }
29916
30065
  return null;
@@ -29937,14 +30086,15 @@ function policyPaths(files) {
29937
30086
  }
29938
30087
  return [...all].sort();
29939
30088
  }
30089
+ var storeFiles = /* @__PURE__ */ new Set([".gitifact/config.json", LINE_ENDINGS_PATH]);
29940
30090
  function checkStoreSelection(selected2) {
29941
- for (const p of selected2.filter((p2) => p2.startsWith(".gitifact/") && !record3(p2) && p2 !== ".gitifact/config.json")) {
30091
+ for (const p of selected2.filter((p2) => p2.startsWith(".gitifact/") && !record3(p2) && !storeFiles.has(p2))) {
29942
30092
  if (!isAssetPath(p) && !instructionFile2(p)) fail3(t2("commitFiles.notRecord", { path: p }));
29943
30093
  }
29944
30094
  }
29945
30095
 
29946
30096
  // src/commands/input.ts
29947
- import { readFile as readFile15, stat as stat2 } from "node:fs/promises";
30097
+ import { readFile as readFile16, stat as stat2 } from "node:fs/promises";
29948
30098
  var LIMIT2 = 1024 * 1024;
29949
30099
  async function readJsonInput(file2, stdin = process.stdin) {
29950
30100
  const bytes = file2 === "-" ? await readStdin(stdin) : await readInputFile(file2);
@@ -29957,7 +30107,7 @@ async function readJsonInput(file2, stdin = process.stdin) {
29957
30107
  async function readInputFile(path3) {
29958
30108
  const source2 = await stat2(path3).catch(() => void 0);
29959
30109
  if (!source2?.isFile() || source2.size > LIMIT2) throw new CommandError("INVALID_INPUT", t2("input.file"));
29960
- return readFile15(path3);
30110
+ return readFile16(path3);
29961
30111
  }
29962
30112
  async function readStdin(stream) {
29963
30113
  const chunks = [];
@@ -30057,7 +30207,7 @@ async function commitChanges(cwd, input2, dryRun) {
30057
30207
  ...stdin ? { input: stdin } : {}
30058
30208
  });
30059
30209
  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();
30060
- const busy = join21(gitDir, "gitifact-changes-commit.lock");
30210
+ const busy = join22(gitDir, "gitifact-changes-commit.lock");
30061
30211
  if (await info4(busy)) fail3(t2("commit.busy", { path: busy }));
30062
30212
  if ((await staged()).length) fail3(t2("commit.existingStaging"));
30063
30213
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
@@ -30096,7 +30246,7 @@ async function commitChanges(cwd, input2, dryRun) {
30096
30246
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
30097
30247
  return { outcome: "dry-run", committed: false, ...summary, commit: void 0 };
30098
30248
  }
30099
- const temporary = join21(gitDir, "gitifact-commit-index-" + randomUUID5());
30249
+ const temporary = join22(gitDir, "gitifact-commit-index-" + randomUUID6());
30100
30250
  let owned = false;
30101
30251
  let uncertain = false;
30102
30252
  let commitStarted = false;
@@ -30104,7 +30254,7 @@ async function commitChanges(cwd, input2, dryRun) {
30104
30254
  let committed2;
30105
30255
  try {
30106
30256
  try {
30107
- await mkdir6(busy);
30257
+ await mkdir7(busy);
30108
30258
  owned = true;
30109
30259
  } catch (e) {
30110
30260
  if (e.code === "EEXIST") fail3(t2("commit.busy", { path: busy }));
@@ -30115,31 +30265,31 @@ async function commitChanges(cwd, input2, dryRun) {
30115
30265
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail3(t2("commit.indexChanged"));
30116
30266
  const before = await hashes([...started.keys()]);
30117
30267
  for (const [path3, value] of started) if (before.get(path3) !== value) fail3(t2("commit.fileChangedRunning", { path: path3 }));
30118
- await writeFile4(join21(busy, "recovery.json"), JSON.stringify({ before: base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected2, message }));
30268
+ await writeFile5(join22(busy, "recovery.json"), JSON.stringify({ before: base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected2, message }));
30119
30269
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected2, ...contextPaths])]);
30120
- if (original) await writeFile4(temporary, original, { flag: "wx" });
30270
+ if (original) await writeFile5(temporary, original, { flag: "wx" });
30121
30271
  else await git(["read-tree", "--empty"], temporary);
30122
30272
  await git(["add", "--pathspec-from-file=-", "--pathspec-file-nul"], temporary, Buffer.from(stageable.join("\0")));
30123
30273
  const actual = await staged(temporary);
30124
30274
  if (!actual.length || actual.some((p) => !selected2.includes(p))) fail3(t2("commit.unexpectedFiles"));
30125
- const storeFiles = selected2.filter((p) => record3(p) && locked.get(p) !== null);
30126
- if (storeFiles.length) {
30127
- const output2 = await git(["cat-file", "--batch"], temporary, Buffer.from(storeFiles.map((p) => ":" + p).join("\n") + "\n"));
30275
+ const storeFiles2 = selected2.filter((p) => record3(p) && locked.get(p) !== null);
30276
+ if (storeFiles2.length) {
30277
+ const output2 = await git(["cat-file", "--batch"], temporary, Buffer.from(storeFiles2.map((p) => ":" + p).join("\n") + "\n"));
30128
30278
  let offset = 0;
30129
- for (const path3 of storeFiles) {
30279
+ for (const path3 of storeFiles2) {
30130
30280
  const end = output2.indexOf(10, offset);
30131
30281
  const header = output2.subarray(offset, end).toString("utf8").split(" ");
30132
30282
  const size = Number(header[2]);
30133
30283
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail3(t2("commit.stagedUnreadable", { path: path3 }));
30134
30284
  const blob = output2.subarray(end + 1, end + 1 + size);
30135
- const file2 = await readFile16(join21(root, path3));
30285
+ const file2 = await readFile17(join22(root, path3));
30136
30286
  if (!blob.equals(file2) && !blob.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail3(t2("commit.filterChanged"));
30137
30287
  offset = end + size + 2;
30138
30288
  }
30139
30289
  }
30140
30290
  const again = await hashes([...locked.keys()]);
30141
30291
  for (const [path3, value] of locked) if (again.get(path3) !== value) fail3(t2("commit.fileChangedPreparing", { path: path3 }));
30142
- for (const name2 of integration) if (await info4(join21(gitDir, name2))) fail3(t2("commit.integrationStarted"));
30292
+ for (const name2 of integration) if (await info4(join22(gitDir, name2))) fail3(t2("commit.integrationStarted"));
30143
30293
  if (!same2(base, await project.reader.baseline())) fail3(t2("commit.headChanged"));
30144
30294
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
30145
30295
  commitStarted = true;
@@ -30154,10 +30304,10 @@ async function commitChanges(cwd, input2, dryRun) {
30154
30304
  if (headers[0] !== "tree " + tree || after.branch !== base.branch || !same2(parents, base.head ? [base.head] : []) || trailers.some((line2) => !body2.split(/\r?\n/).includes(line2))) fail3(t2("commit.resultAltered"));
30155
30305
  if (hash2(await optional2(indexPath) ?? Buffer.alloc(0)) !== indexHash) fail3(t2("commit.indexChangedAfter"));
30156
30306
  if ((await git(["write-tree"], temporary)).toString("utf8").trim() !== tree) fail3(t2("commit.hookChangedStaging"));
30157
- await lock.writeFile(await readFile16(temporary));
30307
+ await lock.writeFile(await readFile17(temporary));
30158
30308
  await lock.sync();
30159
30309
  await lock.close();
30160
- await rename3(indexPath + ".lock", indexPath);
30310
+ await rename4(indexPath + ".lock", indexPath);
30161
30311
  indexLock = void 0;
30162
30312
  committed2 = { commit: after.head, paths: actual };
30163
30313
  } catch (error62) {
@@ -30174,12 +30324,12 @@ async function commitChanges(cwd, input2, dryRun) {
30174
30324
  await indexLock?.close().catch(() => {
30175
30325
  });
30176
30326
  if (!uncertain) {
30177
- if (indexLock) await unlink4(indexPath + ".lock").catch(() => {
30327
+ if (indexLock) await unlink5(indexPath + ".lock").catch(() => {
30178
30328
  });
30179
- await unlink4(temporary).catch(() => {
30329
+ await unlink5(temporary).catch(() => {
30180
30330
  });
30181
30331
  if (owned) {
30182
- await unlink4(join21(busy, "recovery.json")).catch(() => {
30332
+ await unlink5(join22(busy, "recovery.json")).catch(() => {
30183
30333
  });
30184
30334
  await rmdir(busy);
30185
30335
  }
@@ -30189,8 +30339,8 @@ async function commitChanges(cwd, input2, dryRun) {
30189
30339
  }
30190
30340
 
30191
30341
  // src/commands/records.ts
30192
- import { readFile as readFile17 } from "node:fs/promises";
30193
- import { join as join22 } from "node:path";
30342
+ import { readFile as readFile18 } from "node:fs/promises";
30343
+ import { join as join23 } from "node:path";
30194
30344
  var runRecordsNew = (options) => runCommand("records", options.format, async () => {
30195
30345
  if (/[\r\n]/.test(options.title)) throw new CommandError("INVALID_VALUE", t2("docs.singleLine", { field: "title" }));
30196
30346
  const docs = [...new Set(options.docs.flatMap((d) => d.split(",")).map((d) => d.trim()).filter(Boolean))];
@@ -30226,7 +30376,7 @@ var runRecordsShow = (ids, options) => runCommand("records", options.format, asy
30226
30376
  const shown = await Promise.all([...new Set(ids)].map(async (id) => {
30227
30377
  const path3 = await findRecord(project.root, id);
30228
30378
  if (!path3) return { id, missing: true };
30229
- const source2 = await readFile17(join22(project.root, ...path3.split("/")), "utf8");
30379
+ const source2 = await readFile18(join23(project.root, ...path3.split("/")), "utf8");
30230
30380
  const log = project.reader.decode(await project.reader.run(["log", "-1", "--diff-filter=A", "--format=%H%x00%aN%x00%aI", "--", path3])).trim();
30231
30381
  const [commit, author, date5] = log.split("\0");
30232
30382
  return { id, path: path3, text: source2, commit: commit ? { commit, author, date: date5 } : null };
@@ -30334,9 +30484,7 @@ async function readThrough(project, head, filter, id) {
30334
30484
  }
30335
30485
 
30336
30486
  // src/commands/feedback.ts
30337
- import { readFile as readFile18 } from "node:fs/promises";
30338
30487
  import { release as release3 } from "node:os";
30339
- import { dirname as dirname3, join as join23 } from "node:path";
30340
30488
 
30341
30489
  // src/adapters/github/issue.ts
30342
30490
  import { spawn as spawn2 } from "node:child_process";
@@ -30390,6 +30538,17 @@ function newIssueUrl(title, body2, marker2) {
30390
30538
  return { url: address(characters.slice(0, low).join("") + marker2), truncated: true };
30391
30539
  }
30392
30540
 
30541
+ // src/shared/project-config.ts
30542
+ import { readFile as readFile19 } from "node:fs/promises";
30543
+ import { dirname as dirname3, join as join24 } from "node:path";
30544
+ async function findProjectConfig(from) {
30545
+ for (let folder = from; ; folder = dirname3(folder)) {
30546
+ const text4 = await readFile19(join24(folder, ".gitifact", "config.json"), "utf8").catch(() => void 0);
30547
+ if (text4 !== void 0) return { root: folder, text: text4 };
30548
+ if (dirname3(folder) === folder) return void 0;
30549
+ }
30550
+ }
30551
+
30393
30552
  // src/commands/feedback.ts
30394
30553
  var types = ["bug", "idea"];
30395
30554
  function parseFeedback(input2) {
@@ -30402,17 +30561,13 @@ function parseFeedback(input2) {
30402
30561
  return { type, title: title.trim(), body: body2.trim() };
30403
30562
  }
30404
30563
  async function schemaVersion(from) {
30405
- for (let folder = from; ; folder = dirname3(folder)) {
30406
- const config2 = await readFile18(join23(folder, ".gitifact", "config.json"), "utf8").catch(() => void 0);
30407
- if (config2 !== void 0) {
30408
- try {
30409
- const value = JSON.parse(config2).schemaVersion;
30410
- return typeof value === "number" ? String(value) : "none";
30411
- } catch {
30412
- return "none";
30413
- }
30414
- }
30415
- if (dirname3(folder) === folder) return "none";
30564
+ const config2 = (await findProjectConfig(from))?.text;
30565
+ if (config2 === void 0) return "none";
30566
+ try {
30567
+ const value = JSON.parse(config2).schemaVersion;
30568
+ return typeof value === "number" ? String(value) : "none";
30569
+ } catch {
30570
+ return "none";
30416
30571
  }
30417
30572
  }
30418
30573
  async function issueBody(feedback, version2, cwd) {
@@ -30449,6 +30604,11 @@ var runFeedback = (options, version2, run) => runCommand("feedback", options.for
30449
30604
  return { json: { ...sent, title: feedback.title, body: body2 }, text: text2(out) };
30450
30605
  });
30451
30606
 
30607
+ // src/commands/update.ts
30608
+ import { readFile as readFile20 } from "node:fs/promises";
30609
+ import { join as join25 } from "node:path";
30610
+ import { isDeepStrictEqual } from "node:util";
30611
+
30452
30612
  // src/adapters/git/agent-docs-commit.ts
30453
30613
  function agentDocsGit(root, inherited = process.env) {
30454
30614
  const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
@@ -30480,9 +30640,11 @@ function agentDocsGit(root, inherited = process.env) {
30480
30640
  }
30481
30641
 
30482
30642
  // src/commands/update.ts
30483
- var blockCommitMessage = (version2) => "chore(gitifact): refresh GITIFACT block to v" + version2;
30643
+ var CONFIG_PATH = ".gitifact/config.json";
30644
+ var updateCommitMessage = (version2) => "chore(gitifact): update project to gitifact v" + version2;
30484
30645
  var commitState = (state2, extra = {}) => ({ state: state2, commit: null, paths: [], message: null, reason: null, detail: null, ...extra });
30485
- async function refreshBlocks(cwd, version2, env, controls) {
30646
+ var notInitialized = { docs: { state: "not-initialized", paths: [], missing: [] }, project: { state: "not-initialized", cli: null } };
30647
+ async function refreshProject(cwd, version2, env, controls) {
30486
30648
  const repo = initRepository(cwd, env);
30487
30649
  let first;
30488
30650
  let config2;
@@ -30490,20 +30652,28 @@ async function refreshBlocks(cwd, version2, env, controls) {
30490
30652
  first = await repo.inspect();
30491
30653
  config2 = await readConfigFile(first.state.repository.rootPath);
30492
30654
  } catch (error62) {
30493
- if (error62 instanceof RepositoryReadError && error62.code === "NOT_A_REPOSITORY") return { state: "not-initialized", paths: [], missing: [] };
30655
+ if (error62 instanceof RepositoryReadError && error62.code === "NOT_A_REPOSITORY") return notInitialized;
30494
30656
  throw error62;
30495
30657
  }
30496
- if (config2 === void 0) return { state: "not-initialized", paths: [], missing: [] };
30658
+ if (config2 === void 0) return notInitialized;
30497
30659
  const root = first.state.repository.rootPath;
30498
- const plan = await planAgentDocs(root, { version: version2, onlyExisting: true, ...controls });
30660
+ const parsed2 = needsMigration(config2) ? void 0 : parseManagedConfig(config2);
30661
+ const plan = await planAgentDocs(root, { version: version2, onlyExisting: true, language: parsed2?.language, ...controls });
30499
30662
  const { missing: missing2 } = plan;
30500
- if (plan.paths.length === 0) return { state: "no-block", paths: [], missing: missing2, root };
30501
- if (plan.writes.length === 0) return { state: "current", paths: plan.paths, missing: missing2, root, candidates: plan.paths };
30663
+ if (parsed2?.cli && isNewerRelease(parsed2.cli, version2)) return { docs: { state: "kept", paths: plan.paths, missing: missing2, root }, project: { state: "current", cli: parsed2.cli } };
30664
+ let current2 = config2;
30502
30665
  const unchanged = async () => {
30503
- if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== config2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
30666
+ if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== current2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
30504
30667
  };
30505
- const applied = await applyAgentDocs(root, plan, unchanged);
30506
- return { state: "refreshed", paths: applied, missing: missing2, root, candidates: plan.paths };
30668
+ const docs = plan.paths.length === 0 ? { state: "no-block", paths: [], missing: missing2, root } : plan.writes.length === 0 ? { state: "current", paths: plan.paths, missing: missing2, root, candidates: plan.paths } : { state: "refreshed", paths: await applyAgentDocs(root, plan, unchanged), missing: missing2, root, candidates: plan.paths };
30669
+ if (!parsed2) return { docs, project: { state: "current", cli: null } };
30670
+ const settled2 = settleConfig(parsed2, version2, plan.language);
30671
+ if (settled2.changed) {
30672
+ const next = formatManagedConfig(settled2.config, config2);
30673
+ await publishConfig(root, next, unchanged, void 0, true);
30674
+ current2 = next;
30675
+ }
30676
+ return { docs: { ...docs, root }, project: { state: settled2.changed ? "written" : "current", cli: settled2.config.cli ?? null } };
30507
30677
  }
30508
30678
  function outsideBlock(text4) {
30509
30679
  const normalized2 = text4.replace(/\r\n/g, "\n");
@@ -30514,12 +30684,20 @@ function outsideBlock(text4) {
30514
30684
  return null;
30515
30685
  }
30516
30686
  }
30517
- async function commitBlocks(docs, version2, env) {
30518
- if (!docs.root || !docs.candidates?.length) return commitState("nothing");
30687
+ function withoutSettings(text4) {
30688
+ try {
30689
+ const { cli: _c, language: _l, ...rest } = JSON.parse(text4);
30690
+ return rest;
30691
+ } catch {
30692
+ return null;
30693
+ }
30694
+ }
30695
+ async function commitUpdate(docs, version2, env) {
30696
+ if (!docs.root) return commitState("nothing");
30519
30697
  const git = agentDocsGit(docs.root, env);
30520
30698
  const head = await git.head();
30521
30699
  const changed = [];
30522
- for (const path3 of docs.candidates) {
30700
+ for (const path3 of docs.candidates ?? []) {
30523
30701
  const working2 = await managedRead(docs.root, path3);
30524
30702
  const committed2 = head ? await git.committed(head, path3) : null;
30525
30703
  if (working2 === null) continue;
@@ -30529,8 +30707,16 @@ async function commitBlocks(docs, version2, env) {
30529
30707
  if (before === null || before !== outsideBlock(working2)) return commitState("skipped", { paths: [path3], reason: "other-changes" });
30530
30708
  changed.push(path3);
30531
30709
  }
30710
+ const workingConfig = await readFile20(join25(docs.root, CONFIG_PATH), "utf8").catch(() => null);
30711
+ const committedConfig = head ? await git.committed(head, CONFIG_PATH) : null;
30712
+ if (workingConfig !== null && committedConfig?.replace(/\r\n/g, "\n") !== workingConfig.replace(/\r\n/g, "\n")) {
30713
+ if (committedConfig === null) return commitState("skipped", { paths: [CONFIG_PATH], reason: "untracked" });
30714
+ const before = withoutSettings(committedConfig);
30715
+ if (before === null || !isDeepStrictEqual(before, withoutSettings(workingConfig))) return commitState("skipped", { paths: [CONFIG_PATH], reason: "other-changes" });
30716
+ changed.push(CONFIG_PATH);
30717
+ }
30532
30718
  if (changed.length === 0) return commitState("nothing");
30533
- const message = blockCommitMessage(version2);
30719
+ const message = updateCommitMessage(version2);
30534
30720
  let failure2;
30535
30721
  try {
30536
30722
  await git.commitOnly(changed, message);
@@ -30544,20 +30730,23 @@ async function commitBlocks(docs, version2, env) {
30544
30730
  return commitState("committed", { commit: after, paths: await git.changedPaths(after), message });
30545
30731
  }
30546
30732
  async function updateCommand(cwd, version2, env = process.env, controls = {}) {
30547
- const [update, agentDocs] = await Promise.all([
30733
+ const [update, refreshed] = await Promise.all([
30548
30734
  updateCheckDisabled(env) ? disabledUpdate : resolveUpdate(version2, controls.fetchLatest ?? fetchLatestVersion, void 0, controls.timeoutMs),
30549
- refreshBlocks(cwd, version2, env, controls)
30735
+ refreshProject(cwd, version2, env, controls)
30550
30736
  ]);
30551
- const commit = controls.commit ? await commitBlocks(agentDocs, version2, env) : commitState("not-requested");
30552
- const config2 = await readConfigFile(agentDocs.root ?? cwd).catch(() => void 0);
30553
- return updateV5.parse({
30737
+ await recordUpdateCheck(env, update, controls.now?.());
30738
+ const { docs, project } = refreshed;
30739
+ const commit = controls.commit ? await commitUpdate(docs, version2, env) : commitState("not-requested");
30740
+ const config2 = await readConfigFile(docs.root ?? cwd).catch(() => void 0);
30741
+ return updateV6.parse({
30554
30742
  contract: "update",
30555
- version: 5,
30743
+ version: 6,
30556
30744
  ok: true,
30557
30745
  cliVersion: version2,
30558
30746
  update,
30559
- install: update.status === "available" ? { npx: npxUpdate(update.latestVersion), npmGlobal: npmGlobalInstall(update.latestVersion) } : null,
30560
- agentDocs: { state: agentDocs.state, paths: agentDocs.paths, missing: agentDocs.missing },
30747
+ install: update.status === "available" ? { npmGlobal: npmGlobalInstall(update.latestVersion) } : null,
30748
+ agentDocs: { state: docs.state, paths: docs.paths, missing: docs.missing },
30749
+ project,
30561
30750
  commit,
30562
30751
  migrationRequired: config2 !== void 0 && needsMigration(config2)
30563
30752
  });
@@ -30572,9 +30761,11 @@ async function runUpdate(options, version2) {
30572
30761
  }
30573
30762
  const lines = [t2("update.text.current", { version: dto.cliVersion })];
30574
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"));
30575
- if (dto.install) lines.push(t2("update.text.install", { command: dto.install.npx }), t2("update.text.afterInstall"));
30576
- 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 === "no-block" ? t2("update.text.noBlock", { version: dto.cliVersion }) : t2("update.text.notInitialized"));
30577
- if (dto.agentDocs.missing.length) lines.push(t2("update.text.missing", { paths: dto.agentDocs.missing.join(", "), version: dto.cliVersion }));
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) }));
30578
30769
  if (dto.migrationRequired) lines.push(t2("update.text.migrationRequired", { version: dto.cliVersion }));
30579
30770
  const { commit } = dto;
30580
30771
  if (commit.state === "committed") lines.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
@@ -30588,7 +30779,7 @@ async function runUpdate(options, version2) {
30588
30779
  } catch (error62) {
30589
30780
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
30590
30781
  const failure2 = { code: known ? error62.code : "UPDATE_FAILED", message: known ? error62.message : t2("update.failed") };
30591
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV5.parse({ contract: "update", version: 5, ok: false, error: failure2 })) + "\n");
30782
+ process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV6.parse({ contract: "update", version: 6, ok: false, error: failure2 })) + "\n");
30592
30783
  process.exitCode = 1;
30593
30784
  }
30594
30785
  }
@@ -30596,6 +30787,7 @@ async function runUpdate(options, version2) {
30596
30787
  // src/commands/update-check.ts
30597
30788
  async function checkUpdateCommand(version2, env = process.env, controls = {}) {
30598
30789
  const update = updateCheckDisabled(env) ? disabledUpdate : await resolveUpdate(version2, controls.fetchLatest ?? fetchLatestVersion, void 0, controls.timeoutMs);
30790
+ await recordUpdateCheck(env, update, controls.now?.());
30599
30791
  return updateCheckV1.parse({
30600
30792
  contract: "update-check",
30601
30793
  version: 1,
@@ -30616,10 +30808,73 @@ async function runUpdateCheck(format2, version2) {
30616
30808
  t2("update.text.current", { version: dto.cliVersion }),
30617
30809
  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")
30618
30810
  ];
30619
- if (dto.command) lines.push(t2("update.text.install", { command: dto.command }));
30811
+ if (state2.status === "available") lines.push(t2("update.text.install", { command: npmGlobalInstall(state2.latestVersion) }), t2("update.text.afterInstall"));
30620
30812
  process.stdout.write(lines.join("\n") + "\n");
30621
30813
  }
30622
30814
 
30815
+ // src/commands/version-notice.ts
30816
+ import { spawn as spawn3 } from "node:child_process";
30817
+ import { homedir as homedir2 } from "node:os";
30818
+ var REFRESH_AFTER_MS = 60 * 60 * 1e3;
30819
+ var CHECKING_WINDOW_MS = 5 * 60 * 1e3;
30820
+ var LATER_MS = 24 * 60 * 60 * 1e3;
30821
+ var REFRESH_COMMAND = "__refresh-update";
30822
+ var noticeScope = (command) => ({ release: command !== "init" && command !== "update", projectOlder: command !== "init" && command !== "update" });
30823
+ function chooseNotice(running2, project, cache, now, scope = { release: true, projectOlder: true }) {
30824
+ if (project && isNewerRelease(project, running2)) return { kind: "project-newer", project };
30825
+ const latest = cache.latest;
30826
+ const postponed = cache.later !== void 0 && cache.later.version === latest && now < Date.parse(cache.later.until);
30827
+ if (scope.release && latest && isNewerRelease(latest, running2) && !postponed) return { kind: "release", latest };
30828
+ if (scope.projectOlder && project && isNewerRelease(running2, project)) return { kind: "project-older", project };
30829
+ return null;
30830
+ }
30831
+ function noticeText(notice, running2) {
30832
+ if (notice.kind === "project-newer") return t2("notice.projectNewer", { project: notice.project, version: running2, command: npmGlobalInstall(notice.project) });
30833
+ if (notice.kind === "release") return t2("notice.release", { latest: notice.latest, version: running2, command: npmGlobalInstall(notice.latest) });
30834
+ return t2("notice.projectOlder", { project: notice.project, version: running2 });
30835
+ }
30836
+ var needsRefresh = (cache, now) => !(cache.checkedAt && now - Date.parse(cache.checkedAt) < REFRESH_AFTER_MS) && !(cache.checkingSince && now - Date.parse(cache.checkingSince) < CHECKING_WINDOW_MS);
30837
+ var spawnRefresh = () => {
30838
+ const child = spawn3(process.execPath, [...process.execArgv, process.argv[1], REFRESH_COMMAND], { cwd: homedir2(), detached: true, stdio: "ignore", windowsHide: true });
30839
+ child.on("error", () => {
30840
+ });
30841
+ child.unref();
30842
+ };
30843
+ async function noticeVersion(version2, command, controls = {}) {
30844
+ const env = controls.env ?? process.env;
30845
+ const now = controls.now ?? Date.now();
30846
+ try {
30847
+ const project = projectSettings((await findProjectConfig(controls.cwd ?? process.cwd()))?.text).cli;
30848
+ const scope = noticeScope(command);
30849
+ let cache = {};
30850
+ if (!updateCheckDisabled(env)) {
30851
+ cache = await readUpdateCache(env);
30852
+ if (scope.release && needsRefresh(cache, now)) {
30853
+ await changeUpdateCache(env, (current2) => ({ ...current2, checkingSince: new Date(now).toISOString() }));
30854
+ (controls.refresh ?? spawnRefresh)();
30855
+ }
30856
+ }
30857
+ const notice = chooseNotice(version2, project, cache, now, scope);
30858
+ if (notice) (controls.write ?? ((line2) => process.stderr.write(line2 + "\n")))(noticeText(notice, version2));
30859
+ } catch {
30860
+ }
30861
+ }
30862
+ async function refreshUpdateCache(version2, env = process.env, fetchLatest = fetchLatestVersion, now) {
30863
+ if (updateCheckDisabled(env)) return;
30864
+ const update = await resolveUpdate(version2, fetchLatest);
30865
+ await recordUpdateCheck(env, update, now?.());
30866
+ }
30867
+ async function postponeRelease(version2, env = process.env, now = Date.now()) {
30868
+ const cache = await readUpdateCache(env);
30869
+ const later = cache.latest && isNewerRelease(cache.latest, version2) ? { version: cache.latest, until: new Date(now + LATER_MS).toISOString() } : null;
30870
+ if (later) await changeUpdateCache(env, (current2) => ({ ...current2, later }));
30871
+ return updateLaterV1.parse({ contract: "update-later", version: 1, ok: true, cliVersion: version2, later });
30872
+ }
30873
+ async function runUpdateLater(format2, version2) {
30874
+ const dto = await postponeRelease(version2);
30875
+ process.stdout.write(format2 === "json" ? JSON.stringify(dto) + "\n" : (dto.later ? t2("update.later.postponed", { latest: dto.later.version, until: new Date(dto.later.until).toLocaleString() }) : t2("update.later.nothing", { version: version2 })) + "\n");
30876
+ }
30877
+
30623
30878
  // src/main.ts
30624
30879
  var languageArgs = process.argv.slice(2).slice(0, process.argv.slice(2).indexOf("--") < 0 ? void 0 : process.argv.slice(2).indexOf("--"));
30625
30880
  var selectedLanguage;
@@ -30629,11 +30884,17 @@ for (let i = 0; i < languageArgs.length; i++) {
30629
30884
  if (value === "ko" || value === "en") selectedLanguage = value;
30630
30885
  }
30631
30886
  configureLanguage(selectedLanguage ?? environmentLanguage(), selectedLanguage);
30632
- var program2 = new Command().name("gitifact").addOption(new Option("--lang <language>", t2("help.lang")).choices(["ko", "en"])).description(t2("help.program")).version("0.8.2").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
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());
30888
+ program2.hook("preAction", async (_program, action) => {
30889
+ let top = action;
30890
+ while (top.parent && top.parent !== program2) top = top.parent;
30891
+ if (top.name() !== REFRESH_COMMAND) await noticeVersion("0.8.4", top.name());
30892
+ });
30893
+ program2.command(REFRESH_COMMAND, { hidden: true }).allowExcessArguments(false).action(() => refreshUpdateCache("0.8.4"));
30633
30894
  var format = () => new Option("--format <format>", t2("help.format")).choices(formats).default("text");
30634
- program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).action((options) => runBrowser(options, "0.8.2"));
30635
- program2.command("init").description(t2("help.init")).allowExcessArguments(false).option("--dry-run", t2("help.initDryRun")).addOption(new Option("--agent <agent>", t2("help.initAgent")).choices([...agentPresetNames])).addOption(new Option("--remove-agents", t2("help.initRemoveAgents")).conflicts("skipAgents")).option("--skip-agents", t2("help.initSkipAgents")).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runInit(options, "0.8.2"));
30636
- program2.command("update").description(t2("help.update")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).option("--commit", t2("help.updateCommit")).addOption(new Option("--check", t2("help.updateCheck")).conflicts("commit")).action((options) => options.check ? runUpdateCheck(options.format, "0.8.2") : runUpdate(options, "0.8.2"));
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"));
30637
30898
  var listed2 = (command, sorts) => {
30638
30899
  command.allowExcessArguments(false).option("--q <words>", t2("help.listQ")).option("--author <name>", t2("help.listAuthor"));
30639
30900
  if (sorts) command.addOption(new Option("--sort <key>", t2("help.listSort")).choices([...sorts]).default(sorts[0]));
@@ -30655,7 +30916,7 @@ program2.command("check").description(t2("help.check")).allowExcessArguments(fal
30655
30916
  var changes = program2.command("changes").description(t2("help.changes"));
30656
30917
  changes.command("list").description(t2("help.changesList")).allowExcessArguments(false).addOption(format()).action((o) => runChangesList(o));
30657
30918
  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));
30658
- program2.command("feedback").description(t2("help.feedback")).allowExcessArguments(false).requiredOption("--file <path>", t2("help.feedbackFile")).option("--dry-run", t2("help.feedbackDryRun")).addOption(format()).action((o) => runFeedback(o, "0.8.2"));
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"));
30659
30920
  var guide = program2.command("guide").description(t2("help.guide"));
30660
30921
  guide.command("list").description(t2("help.guideList")).allowExcessArguments(false).addOption(format()).action((o) => runGuideList(o));
30661
30922
  guide.command("show").description(t2("help.guideShow")).argument("<topic>", t2("help.guideTopic")).allowExcessArguments(false).addOption(format()).action((topic, o) => runGuideShow(topic, o));