gitifact 0.4.4 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (53) hide show
  1. package/README.md +8 -6
  2. package/dist/browser/assets/{Markdown-Drk5rJF_.js → Markdown-CqZ4w2H_.js} +3 -3
  3. package/dist/browser/assets/{MetadataListItem-o7NlLubs.js → MetadataListItem-B6xUZEC3.js} +1 -1
  4. package/dist/browser/assets/Table-DDKWqCdD.js +3 -0
  5. package/dist/browser/assets/about-Chl-XBQg.js +3 -0
  6. package/dist/browser/assets/{routes-D_2lOqSD.js → activity-F2_AhgBS.js} +1 -1
  7. package/dist/browser/assets/changelog-DrJfyXDZ.js +2 -0
  8. package/dist/browser/assets/{contributors._email-CsIeETiA.js → contributors._email-CINLTSX2.js} +1 -1
  9. package/dist/browser/assets/{contributors.index-BVn-RzjO.js → contributors.index-ChB89wcL.js} +1 -1
  10. package/dist/browser/assets/document-Jy6Buepz.js +9 -0
  11. package/dist/browser/assets/document-QMuypGH5.css +1 -0
  12. package/dist/browser/assets/features._featureId-FySsJ2tl.js +1 -0
  13. package/dist/browser/assets/{requirements-C5TAa2tx.js → features.index-DWfPG8WV.js} +1 -1
  14. package/dist/browser/assets/git-AnH-8SBT.js +1 -0
  15. package/dist/browser/assets/index-DXK4gUTr.js +48 -0
  16. package/dist/browser/assets/page-header-N7A_M6-B.js +124 -0
  17. package/dist/browser/assets/product-BKOKDrzZ.js +8 -0
  18. package/dist/browser/assets/product-DNlxFjU7.css +1 -0
  19. package/dist/browser/assets/product.index-BohDJK9z.js +1 -0
  20. package/dist/browser/assets/request-state-CO3OLoay.js +1 -0
  21. package/dist/browser/assets/{features.index-Cf-ct7tk.js → requirements-Dh2NEqNw.js} +1 -1
  22. package/dist/browser/assets/{settings-Dzak94qo.js → settings-DylPhHVJ.js} +1 -1
  23. package/dist/browser/assets/wiki-DYxjYiPl.js +1 -0
  24. package/dist/browser/assets/wiki._documentId-DJ7LAi8J.js +1 -0
  25. package/dist/browser/assets/wiki.index-DJ7LAi8J.js +1 -0
  26. package/dist/browser/index.html +11 -11
  27. package/dist/i18n/ko/block.md +6 -4
  28. package/dist/i18n/ko/changelog.md +29 -0
  29. package/dist/i18n/ko/docs/commit.md +15 -5
  30. package/dist/i18n/ko/docs/design.md +26 -18
  31. package/dist/i18n/ko/docs/spec.md +22 -16
  32. package/dist/i18n/ko/docs/wiki.default.md +27 -0
  33. package/dist/i18n/ko/docs/wiki.md +41 -0
  34. package/dist/i18n/ko/docs/workflow.md +20 -10
  35. package/dist/main.js +824 -495
  36. package/package.json +3 -3
  37. package/dist/browser/assets/Table-DmxmodQG.js +0 -3
  38. package/dist/browser/assets/about-BFpb4QWu.js +0 -3
  39. package/dist/browser/assets/changelog-rtqqVzIW.js +0 -2
  40. package/dist/browser/assets/document-BGXuvcs6.js +0 -1
  41. package/dist/browser/assets/document-D41OCO3e.css +0 -1
  42. package/dist/browser/assets/features._featureId-Bw_4HsoD.js +0 -1
  43. package/dist/browser/assets/git-E_25yRrM.js +0 -1
  44. package/dist/browser/assets/guides._documentId-CJIzrRob.js +0 -1
  45. package/dist/browser/assets/guides.index-Bh0g2XhD.js +0 -1
  46. package/dist/browser/assets/index-Bmk5GeZg.js +0 -56
  47. package/dist/browser/assets/page-header-Cmn2UulE.js +0 -126
  48. package/dist/browser/assets/product-BrvAWtBo.css +0 -1
  49. package/dist/browser/assets/product-lcmaeBnU.js +0 -8
  50. package/dist/browser/assets/product.document-BH-jLWyC.js +0 -1
  51. package/dist/browser/assets/product.index-3Hnsh67g.js +0 -1
  52. package/dist/browser/assets/request-state-hT3U3nPp.js +0 -1
  53. package/dist/i18n/ko/docs/product.md +0 -21
package/dist/main.js CHANGED
@@ -1926,13 +1926,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1926
1926
  * @param {string} source - expected values are default/config/env/cli/implied
1927
1927
  * @return {Command} `this` command for chaining
1928
1928
  */
1929
- setOptionValueWithSource(key, value, source) {
1929
+ setOptionValueWithSource(key, value, source2) {
1930
1930
  if (this._storeOptionsAsProperties) {
1931
1931
  this[key] = value;
1932
1932
  } else {
1933
1933
  this._optionValues[key] = value;
1934
1934
  }
1935
- this._optionValueSources[key] = source;
1935
+ this._optionValueSources[key] = source2;
1936
1936
  return this;
1937
1937
  }
1938
1938
  /**
@@ -1953,13 +1953,13 @@ Expecting one of '${allowedValues.join("', '")}'`);
1953
1953
  * @return {string}
1954
1954
  */
1955
1955
  getOptionValueSourceWithGlobals(key) {
1956
- let source;
1956
+ let source2;
1957
1957
  this._getCommandAndAncestors().forEach((cmd) => {
1958
1958
  if (cmd.getOptionValueSource(key) !== void 0) {
1959
- source = cmd.getOptionValueSource(key);
1959
+ source2 = cmd.getOptionValueSource(key);
1960
1960
  }
1961
1961
  });
1962
- return source;
1962
+ return source2;
1963
1963
  }
1964
1964
  /**
1965
1965
  * Get user arguments from implied or explicit arguments.
@@ -2839,8 +2839,8 @@ Expecting one of '${allowedValues.join("', '")}'`);
2839
2839
  const getErrorMessage = (option2) => {
2840
2840
  const bestOption = findBestOptionFromValue(option2);
2841
2841
  const optionKey = bestOption.attributeName();
2842
- const source = this.getOptionValueSource(optionKey);
2843
- if (source === "env") {
2842
+ const source2 = this.getOptionValueSource(optionKey);
2843
+ if (source2 === "env") {
2844
2844
  return `environment variable '${bestOption.envVar}'`;
2845
2845
  }
2846
2846
  return `option '${bestOption.flags}'`;
@@ -3405,9 +3405,9 @@ var messages_default = {
3405
3405
  "config.invalidFields": "\uD504\uB85C\uC81D\uD2B8 \uC124\uC815\uC758 \uD544\uB4DC\uB098 \uAC12\uC774 \uC62C\uBC14\uB974\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3406
3406
  "config.jsonUnreadableShort": "\uC124\uC815 JSON\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
3407
3407
  "config.schemaFields": "schemaVersion\uACFC baseline \uC124\uC815 \uD544\uB4DC\uB97C \uD655\uC778\uD558\uC138\uC694.",
3408
- "document.pathTooLong": "\uBB38\uC11C \uACBD\uB85C \uAE4A\uC774 \uB610\uB294 \uAE38\uC774 \uD55C\uB3C4\uB97C \uCD08\uACFC\uD588\uC2B5\uB2C8\uB2E4: {path}",
3409
- "document.fileName": "\uBB38\uC11C \uD30C\uC77C \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uACFC .md\uC5EC\uC57C \uD569\uB2C8\uB2E4: {path}",
3410
- "document.folderName": "\uBB38\uC11C \uD3F4\uB354 \uC774\uB984\uC740 \uC18C\uBB38\uC790\xB7\uC22B\uC790\xB7\uD558\uC774\uD508\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: {path}",
3408
+ "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}",
3409
+ "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}",
3410
+ "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}",
3411
3411
  "design.invalidCharacters": "\uC798\uBABB\uB41C \uC124\uACC4 \uBB38\uC790\uC785\uB2C8\uB2E4.",
3412
3412
  "design.idMismatch": "\uC124\uACC4\uC758 gitifact-design ID\uB294 \uAC19\uC740 \uAE30\uB2A5\uC758 S-ID\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
3413
3413
  "design.titleRequired": "\uC124\uACC4 \uC81C\uBAA9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4.",
@@ -3415,24 +3415,19 @@ var messages_default = {
3415
3415
  "design.singleTopHeading": "\uC124\uACC4 \uCD5C\uC0C1\uC704 \uC81C\uBAA9\uC740 \uD558\uB098\uB9CC \uC501\uB2C8\uB2E4.",
3416
3416
  "design.referenceFormat": "\uC124\uACC4\uC758 \uCC38\uC870 \uC8FC\uC11D \uD615\uC2DD\uC744 \uD655\uC778\uD558\uC138\uC694.",
3417
3417
  "design.unclosedFence": "\uC124\uACC4 \uCF54\uB4DC \uBE14\uB85D\uC774 \uB2EB\uD788\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.",
3418
- "document.unsupportedPath": "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBB38\uC11C \uACBD\uB85C: {path}",
3419
- "document.productOnly": "\uC81C\uD488 \uD3F4\uB354\uC5D0\uB294 PRODUCT.md \uD558\uB098\uB9CC \uB461\uB2C8\uB2E4: {path}",
3418
+ "document.unsupportedPath": "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uC704\uD0A4 \uACBD\uB85C: {path}",
3420
3419
  "document.invalidCharacters": "\uC798\uBABB\uB41C \uBB38\uC11C \uBB38\uC790: {path}",
3421
- "document.idRequired": "\uCCAB \uC904\uC5D0 \uC720\uD6A8\uD55C gitifact-{kind} \uC8FC\uC11D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: {path}",
3422
3420
  "document.titleRequired": "\uBB38\uC11C \uC81C\uBAA9\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: {path}",
3423
3421
  "document.bodyRequired": "\uBB38\uC11C \uBCF8\uBB38\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: {path}",
3424
3422
  "document.singleTopHeading": "\uBB38\uC11C \uCD5C\uC0C1\uC704 \uC81C\uBAA9\uC740 \uD558\uB098\uB9CC \uC501\uB2C8\uB2E4: {path}",
3425
3423
  "document.noMarkerInBody": "\uBB38\uC11C \uBCF8\uBB38\uC5D0\uB294 gitifact \uC8FC\uC11D\uC744 \uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {path}",
3426
3424
  "document.unclosedFence": "\uBB38\uC11C \uCF54\uB4DC \uBE14\uB85D\uC774 \uB2EB\uD788\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4: {path}",
3427
- "document.kindSet": "\uBB38\uC11C \uC885\uB958 \uC9D1\uD569\uC774 \uC798\uBABB\uB410\uC2B5\uB2C8\uB2E4.",
3428
- "document.kindMismatch": "\uBB38\uC11C \uC885\uB958\uAC00 \uD3F4\uB354\uC640 \uB2E4\uB985\uB2C8\uB2E4: {path}",
3429
- "document.duplicateId": "\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBB38\uC11C ID: {id}",
3425
+ "document.duplicateId": "\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uC704\uD0A4 \uD398\uC774\uC9C0 ID: {id}",
3430
3426
  "document.caseOnlyPath": "\uB300\uC18C\uBB38\uC790\uB9CC \uB2E4\uB978 \uBB38\uC11C \uACBD\uB85C: {path}",
3431
3427
  "history.duplicateId": "\uC800\uC7A5\uC18C \uB0B4 \uC911\uBCF5 \uBCF4\uC870 \uAE30\uB85D ID: {id}",
3432
3428
  "spec.orphanFile": "\uC694\uAD6C\uC0AC\uD56D \uBB38\uC11C \uC5C6\uB294 \uC124\uACC4 \uB610\uB294 \uC774\uC720 \uD30C\uC77C: {path}",
3433
3429
  "spec.unsupportedPath": "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uBA85\uC138 \uACBD\uB85C: {path}",
3434
3430
  "spec.invalidCharacters": "\uC798\uBABB\uB41C \uBA85\uC138 \uBB38\uC790: {path}",
3435
- "spec.idRequired": "\uCCAB \uC904\uC5D0 \uC720\uD6A8\uD55C gitifact-spec \uC8FC\uC11D\uC774 \uD544\uC694\uD569\uB2C8\uB2E4: {path}",
3436
3431
  "spec.requirementIdRequired": "\uC694\uAD6C\uC0AC\uD56D \uC81C\uBAA9 \uBC14\uB85C \uC544\uB798\uC5D0 \uC720\uD6A8\uD55C ID\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4: {path}",
3437
3432
  "spec.invalidFence": "\uC798\uBABB\uB41C \uCF54\uB4DC \uBE14\uB85D\uC785\uB2C8\uB2E4.",
3438
3433
  "spec.titleOnce": "\uBB38\uC11C \uC81C\uBAA9\uC740 \uCC98\uC74C\uC5D0 \uD55C \uBC88\uB9CC \uC501\uB2C8\uB2E4: {path}",
@@ -3460,10 +3455,8 @@ var messages_default = {
3460
3455
  "edit.emptyField": "\uBE48 \uD3B8\uC9D1 \uD544\uB4DC: {field}",
3461
3456
  "edit.invalidCharacters": "\uC798\uBABB\uB41C \uD3B8\uC9D1 \uBB38\uC790: {field}",
3462
3457
  "edit.specNotFound": "\uB300\uC0C1 \uBA85\uC138\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: {path}",
3463
- "edit.noProductToDelete": "\uC0AD\uC81C\uD560 \uC81C\uD488 \uBB38\uC11C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
3464
3458
  "edit.samePath": "\uC774\uBBF8 \uAC19\uC740 \uACBD\uB85C\uC785\uB2C8\uB2E4.",
3465
3459
  "edit.documentPathExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uBB38\uC11C \uACBD\uB85C\uC785\uB2C8\uB2E4: {path}",
3466
- "edit.guideIdNotFound": "\uC9C0\uCE68 \uBB38\uC11C ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: {id}",
3467
3460
  "edit.featureExists": "\uC774\uBBF8 \uC874\uC7AC\uD558\uB294 \uAE30\uB2A5\uC785\uB2C8\uB2E4.",
3468
3461
  "edit.noDesignToDelete": "\uC0AD\uC81C\uD560 \uC124\uACC4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
3469
3462
  "edit.specIdNotFound": "\uBA85\uC138 ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
@@ -3473,7 +3466,7 @@ var messages_default = {
3473
3466
  "edit.bodyChangesDocumentStructure": "\uBCF8\uBB38\uC774 \uBB38\uC11C \uAD6C\uC870\uB97C \uBCC0\uACBD\uD569\uB2C8\uB2E4: {path}",
3474
3467
  "reason.keepEmptySpec": "\uAE30\uC874 \uC774\uC720\uB97C \uBCF4\uC874\uD560 \uBE48 \uBA85\uC138\uB97C \uB0A8\uACA8\uC8FC\uC138\uC694: {id}",
3475
3468
  "reason.committedImmutable": "\uCEE4\uBC0B\uB41C \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {id}",
3476
- "reason.committedDocumentImmutable": "\uCEE4\uBC0B\uB41C \uBB38\uC11C \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4: {kind}",
3469
+ "reason.committedDocumentImmutable": "\uCEE4\uBC0B\uB41C \uC704\uD0A4 \uC774\uC720\uC758 \uC218\uC815\xB7\uC0AD\uC81C\xB7\uC7AC\uBC30\uCE58\uB294 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3477
3470
  "reason.count": "reasons\uB294 \uCD5C\uB300 100\uAC1C\uC758 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
3478
3471
  "reason.idGenerated": "\uC798\uBABB\uB41C \uC774\uC720 ID \uC0DD\uC131 \uACB0\uACFC\uC785\uB2C8\uB2E4.",
3479
3472
  "reason.idUnavailable": "\uACE0\uC720 \uC774\uC720 ID\uB97C \uBC1C\uAE09\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
@@ -3482,7 +3475,6 @@ var messages_default = {
3482
3475
  "reason.documentWithRequirements": "\uBB38\uC11C \uC774\uC720\uC5D0\uB294 \uC694\uAD6C\uC0AC\uD56D\uC744 \uD568\uAED8 \uC9C0\uC815\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
3483
3476
  "reason.documentTargetInvalid": "\uBB38\uC11C \uC774\uC720 \uB300\uC0C1\uC774 \uC798\uBABB\uB410\uAC70\uB098 \uC911\uBCF5\uB429\uB2C8\uB2E4.",
3484
3477
  "reason.documentUnchanged": "\uCD5C\uC885 \uBCC0\uACBD\uC774 \uC5C6\uB294 \uBB38\uC11C\uC5D0 \uC774\uC720\uB97C \uBD99\uC77C \uC218 \uC5C6\uC2B5\uB2C8\uB2E4: {id}",
3485
- "reason.singleDocumentKind": "\uD55C \uC774\uC720\uB294 \uD55C \uC885\uB958\uC758 \uBB38\uC11C\uB9CC \uB300\uC0C1\uC73C\uB85C \uD569\uB2C8\uB2E4.",
3486
3478
  "reason.invalidTarget": "\uC798\uBABB\uB41C \uC774\uC720 \uB300\uC0C1\uC785\uB2C8\uB2E4.",
3487
3479
  "reason.targetKind": "\uC774\uC720 \uB300\uC0C1 \uC885\uB958\uB97C \uD655\uC778\uD558\uC138\uC694.",
3488
3480
  "reason.targetInvalid": "\uC774\uC720 \uB300\uC0C1\uC774 \uC798\uBABB\uB410\uAC70\uB098 \uC911\uBCF5\uB429\uB2C8\uB2E4.",
@@ -3506,7 +3498,20 @@ var messages_default = {
3506
3498
  "changelog.duplicateSection": "\uAC19\uC740 \uBC84\uC804\uC5D0 {section} \uC808\uC774 \uB450 \uBC88 \uC788\uC2B5\uB2C8\uB2E4.",
3507
3499
  "changelog.itemOutsideSection": "\uC808 \uC81C\uBAA9(### Added|Changed|Removed|Fixed) \uC544\uB798\uC5D0\uB9CC \uD56D\uBAA9\uC744 \uC4F8 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
3508
3500
  "changelog.unknownLine": '\uC778\uC2DD\uD560 \uC218 \uC5C6\uB294 \uC904\uC785\uB2C8\uB2E4. \uBC84\uC804\uC740 "## X.Y.Z - YYYY-MM-DD", \uC808\uC740 "### Added|Changed|Removed|Fixed", \uD56D\uBAA9\uC740 "- "\uB85C \uC501\uB2C8\uB2E4: {text}',
3509
- "changelog.noVersions": "\uBC84\uC804\uC774 \uD558\uB098\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4."
3501
+ "changelog.noVersions": "\uBC84\uC804\uC774 \uD558\uB098\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4.",
3502
+ "frontmatter.required": "\uCCAB \uC904\uC5D0 --- \uB85C \uC2DC\uC791\uD558\uB294 frontmatter\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4.",
3503
+ "frontmatter.unclosed": "frontmatter\uB97C \uB2EB\uB294 --- \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4.",
3504
+ "frontmatter.line": "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 frontmatter \uC904\uC785\uB2C8\uB2E4: {line}",
3505
+ "frontmatter.scalar": "frontmatter \uAC12\uC740 \uB530\uC634\uD45C \uC5C6\uB294 \uB2E8\uC21C \uBB38\uC790\uC5F4\uC774\uAC70\uB098 \uD070\uB530\uC634\uD45C \uBB38\uC790\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4: {value}",
3506
+ "frontmatter.id": "frontmatter\uC758 id\uAC00 \uC720\uD6A8\uD55C {kind} ID\uAC00 \uC544\uB2D9\uB2C8\uB2E4.",
3507
+ "frontmatter.unknownKey": "\uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 frontmatter \uD0A4\uC785\uB2C8\uB2E4: {key}",
3508
+ "source.invalid": "\uCC38\uACE0 \uBB38\uC11C \uD56D\uBAA9\uC740 title\uACFC path \uB610\uB294 url \uC911 \uD558\uB098, \uC120\uD0DD\uC801 note\uB85C \uAD6C\uC131\uD569\uB2C8\uB2E4.",
3509
+ "source.url": "\uCC38\uACE0 \uBB38\uC11C url\uC740 http(s)\uC5EC\uC57C \uD569\uB2C8\uB2E4: {url}",
3510
+ "source.path": "\uCC38\uACE0 \uBB38\uC11C path\uB294 .md\uB85C \uB05D\uB098\uB294 \uC0C1\uB300 \uACBD\uB85C\uC5EC\uC57C \uD569\uB2C8\uB2E4: {path}",
3511
+ "edit.documentIdNotFound": "\uC704\uD0A4 \uD398\uC774\uC9C0 ID\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: {id}",
3512
+ "edit.invalidSources": "sources\uB294 \uCD5C\uB300 50\uAC1C\uC758 \uBC30\uC5F4\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4.",
3513
+ "config.unsupportedSchema": "\uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uC800\uC7A5 \uADDC\uC57D(schemaVersion {version})\uC740 \uC774\uC804 \uBC84\uC804\uC758 \uAC83\uC774\uB77C \uC774 CLI\uAC00 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC815\uC2DD \uBC84\uC804 \uC804 \uADDC\uC57D\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. .gitifact\uC5D0 config.json\uB9CC \uC788\uC73C\uBA74 gitifact init\uC774 \uC0C8 \uADDC\uC57D\uC73C\uB85C \uBC14\uAFC9\uB2C8\uB2E4.",
3514
+ "config.newerSchema": "\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 \uB354 \uC0C8\uB85C\uC6B4 \uC800\uC7A5 \uADDC\uC57D(schemaVersion {version})\uC744 \uC501\uB2C8\uB2E4. npm install -g gitifact@latest\uB85C CLI\uB97C \uC62C\uB9B0 \uB4A4 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694."
3510
3515
  };
3511
3516
 
3512
3517
  // ../../packages/core/dist/shared/i18n/index.js
@@ -3578,6 +3583,7 @@ function parseProjectConfig(text2) {
3578
3583
  }
3579
3584
 
3580
3585
  // ../../packages/core/dist/formats/spec-project.js
3586
+ var SCHEMA_VERSION = 2;
3581
3587
  function parseManagedConfig(text2) {
3582
3588
  let value;
3583
3589
  try {
@@ -3588,41 +3594,53 @@ function parseManagedConfig(text2) {
3588
3594
  if (!value || typeof value !== "object" || Array.isArray(value) || !("schemaVersion" in value))
3589
3595
  return parseProjectConfig(text2);
3590
3596
  const config2 = value;
3591
- if (Object.keys(config2).sort().join(",") !== "baseline,schemaVersion" || config2.schemaVersion !== 1)
3597
+ if (Object.keys(config2).sort().join(",") !== "baseline,schemaVersion")
3592
3598
  throw new InitError("INVALID_CONFIG", t("config.schemaFields"));
3599
+ if (config2.schemaVersion !== SCHEMA_VERSION) {
3600
+ const found = config2.schemaVersion;
3601
+ const newer = typeof found === "number" && found > SCHEMA_VERSION;
3602
+ throw new InitError("UNSUPPORTED_SCHEMA", newer ? t("config.newerSchema", { version: String(found) }) : t("config.unsupportedSchema", { version: String(found) }));
3603
+ }
3593
3604
  const checked = parseProjectConfig(JSON.stringify({ kind: "tryce-project", format: "workflow-1", mode: "auto", baseline: config2.baseline }));
3594
- return { schemaVersion: 1, baseline: checked.baseline };
3605
+ return { schemaVersion: SCHEMA_VERSION, baseline: checked.baseline };
3595
3606
  }
3596
3607
 
3597
3608
  // ../../packages/core/dist/formats/spec-preview.js
3598
3609
  var SpecPreviewError = class extends Error {
3599
3610
  code = "INVALID_SPEC_PREVIEW";
3600
3611
  };
3601
- var DOCUMENT_KINDS = ["product", "guide"];
3602
- var DOCUMENT_DIRS = { product: ".gitifact/product", guide: ".gitifact/guides" };
3603
- var documentHistoryPath = (kind) => DOCUMENT_DIRS[kind] + "/history.jsonl";
3604
- var PRODUCT_PATH = ".gitifact/product/PRODUCT.md";
3605
- var PRODUCT_ASSET_NAME = /^[A-Za-z0-9][A-Za-z0-9._-]{0,120}\.(?:png|jpe?g|gif|svg|webp)$/i;
3606
- function isProductAssetPath(path3) {
3607
- const match = /^\.gitifact\/product\/([^/]+)$/.exec(path3);
3608
- return !!match && PRODUCT_ASSET_NAME.test(match[1]);
3609
- }
3612
+ var WIKI_DIR = ".gitifact/wiki";
3613
+ var WIKI_HISTORY_PATH = WIKI_DIR + "/history.jsonl";
3614
+ var WIKI_ENTRY = "README.md";
3615
+ var WIKI_ENTRY_PATH = WIKI_DIR + "/" + WIKI_ENTRY;
3616
+ var ASSETS_DIR = ".gitifact/assets";
3617
+ var RECOMMENDED_ASSET_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "svg", "pdf"];
3618
+ var ASSET_SIZE_LIMIT = 1024 * 1024;
3619
+ var ASSETS_TOTAL_LIMIT = 50 * 1024 * 1024;
3610
3620
  var token = "[a-z2-7]{10}";
3611
3621
  var STORE_DIR = ".gitifact";
3612
3622
  var LEGACY_STORE_DIR = ".tryce";
3613
3623
  var STORE_DIRS = [STORE_DIR, LEGACY_STORE_DIR];
3614
3624
  var marker = "(?:gitifact|tryce)";
3615
- var recordPathPattern = /^(?:\.(?:gitifact|tryce)\/spec\/[^/]+\/(?:requirements\.md|design\.md|history\.jsonl)|\.gitifact\/(?:product|guides)\/(?:(?:[^/]+\/)*[^/]+\.md|history\.jsonl))$/;
3616
- var documentPathPattern = /^\.gitifact\/(product|guides)\/((?:[^/]+\/)*[^/]+\.md)$/;
3617
- var documentHistoryPattern = /^\.gitifact\/(product|guides)\/history\.jsonl$/;
3618
- var kindOfDir = (dir) => dir === "product" ? "product" : "guide";
3625
+ var recordPathPattern = /^(?:\.(?:gitifact|tryce)\/spec\/[^/]+\/(?:requirements\.md|design\.md|history\.jsonl)|\.gitifact\/wiki\/(?:(?:[^/]+\/)*[^/]+\.md|history\.jsonl))$/;
3626
+ var documentPathPattern = /^\.gitifact\/wiki\/((?:[^/]+\/)*[^/]+\.md)$/;
3627
+ var documentHistoryPattern = /^\.gitifact\/wiki\/history\.jsonl$/;
3619
3628
  var nameToken = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3629
+ var segmentOk = (part) => !!part && part !== "." && part !== ".." && !/[\\:\0]/.test(part);
3630
+ function isAssetPath(path3) {
3631
+ if (!path3.startsWith(ASSETS_DIR + "/"))
3632
+ return false;
3633
+ const parts = path3.slice(ASSETS_DIR.length + 1).split("/");
3634
+ return parts.every(segmentOk) && parts.length <= 8 && path3.length <= 300;
3635
+ }
3636
+ var assetExtension = (path3) => (/\.([A-Za-z0-9]+)$/.exec(path3)?.[1] ?? "").toLowerCase();
3637
+ var rootUpperName = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*\.md$/;
3620
3638
  function validateDocumentRelativePath(relative) {
3621
3639
  const parts = relative.split("/");
3622
3640
  if (relative.length > 200 || parts.length > 8)
3623
3641
  fail(t("document.pathTooLong", { path: relative }));
3624
3642
  const file2 = parts.pop();
3625
- if (!file2.endsWith(".md") || !nameToken.test(file2.slice(0, -3)))
3643
+ if (!(parts.length === 0 && rootUpperName.test(file2)) && (!file2.endsWith(".md") || !nameToken.test(file2.slice(0, -3))))
3626
3644
  fail(t("document.fileName", { path: relative }));
3627
3645
  for (const part of parts)
3628
3646
  if (!nameToken.test(part) || part.length > 80)
@@ -3634,19 +3652,115 @@ var fail = (message) => {
3634
3652
  throw new SpecPreviewError(message);
3635
3653
  };
3636
3654
  var normalized = (value) => value.replace(/\r\n/g, "\n").trim();
3655
+ var scalar = (value) => {
3656
+ const quoted = /^"((?:[^"\\]|\\["\\])*)"$/.exec(value);
3657
+ if (quoted)
3658
+ return quoted[1].replace(/\\(["\\])/g, "$1");
3659
+ if (!value || /^["'#&*!|>%@`[{-]/.test(value) || /:\s|\s#/.test(value) || value !== value.trim())
3660
+ fail(t("frontmatter.scalar", { value }));
3661
+ return value;
3662
+ };
3663
+ var quote = (value) => /^[A-Za-z0-9가-힣./_][^"#:\\]*$/.test(value) && value === value.trim() && !/\s#|:\s/.test(value) && !/^(true|false|null|yes|no|~)$/i.test(value) && !/^\d/.test(value) ? value : '"' + value.replace(/[\\"]/g, "\\$&") + '"';
3664
+ function parseFrontmatter(lines, kind, legacyIds) {
3665
+ const legacy = new RegExp(`^<!-- ${marker}-(?:${kind === "wiki" ? "product|guide" : kind}): (${legacyIds}-${token}) -->$`).exec(lines[0] ?? "");
3666
+ if (legacy) {
3667
+ lines.shift();
3668
+ return { id: legacy[1] };
3669
+ }
3670
+ if (lines[0] !== "---")
3671
+ fail(t("frontmatter.required"));
3672
+ lines.shift();
3673
+ const end = lines.indexOf("---");
3674
+ if (end === -1)
3675
+ fail(t("frontmatter.unclosed"));
3676
+ const block = lines.splice(0, end);
3677
+ lines.shift();
3678
+ const fields2 = /* @__PURE__ */ new Map();
3679
+ for (let i = 0; i < block.length; i++) {
3680
+ const line = block[i];
3681
+ if (!line.trim())
3682
+ continue;
3683
+ const entry2 = /^([a-z]+):(?: (.*))?$/.exec(line);
3684
+ if (!entry2 || fields2.has(entry2[1]))
3685
+ fail(t("frontmatter.line", { line }));
3686
+ const [, key, value] = entry2;
3687
+ if (key !== "sources") {
3688
+ if (value === void 0)
3689
+ fail(t("frontmatter.line", { line }));
3690
+ fields2.set(key, scalar(value));
3691
+ continue;
3692
+ }
3693
+ if (value !== void 0)
3694
+ fail(t("frontmatter.line", { line }));
3695
+ const sources2 = [];
3696
+ while (i + 1 < block.length && /^ /.test(block[i + 1])) {
3697
+ const item = block[++i];
3698
+ const head = /^ - ([a-z]+): (.*)$/.exec(item);
3699
+ if (!head)
3700
+ fail(t("frontmatter.line", { line: item }));
3701
+ const map2 = { [head[1]]: scalar(head[2]) };
3702
+ while (i + 1 < block.length && /^ [a-z]+: /.test(block[i + 1])) {
3703
+ const more = /^ ([a-z]+): (.*)$/.exec(block[++i]);
3704
+ if (more[1] in map2)
3705
+ fail(t("frontmatter.line", { line: block[i] }));
3706
+ map2[more[1]] = scalar(more[2]);
3707
+ }
3708
+ sources2.push(validateSource(map2));
3709
+ }
3710
+ fields2.set("sources", sources2);
3711
+ }
3712
+ const id = fields2.get("id");
3713
+ if (typeof id !== "string" || !new RegExp(`^(?:${legacyIds})-${token}$`).test(id))
3714
+ fail(t("frontmatter.id", { kind }));
3715
+ for (const key of fields2.keys())
3716
+ if (key !== "id" && !(key === "sources" && kind === "design"))
3717
+ fail(t("frontmatter.unknownKey", { key }));
3718
+ const sources = fields2.get("sources");
3719
+ return { id, ...Array.isArray(sources) ? { sources } : {} };
3720
+ }
3721
+ function validateSource(value) {
3722
+ if (!value || typeof value !== "object" || Array.isArray(value))
3723
+ fail(t("source.invalid"));
3724
+ const map2 = value;
3725
+ const keys2 = Object.keys(map2).sort();
3726
+ if (keys2.some((k) => !["title", "path", "url", "note"].includes(k)) || typeof map2.title !== "string" || !map2.title.trim() || "path" in map2 === "url" in map2)
3727
+ fail(t("source.invalid"));
3728
+ for (const key of keys2) {
3729
+ const v = map2[key];
3730
+ if (typeof v !== "string" || !v.trim() || /[\r\n\0]/.test(v) || v.length > 500)
3731
+ fail(t("source.invalid"));
3732
+ }
3733
+ if (typeof map2.url === "string" && !/^https?:\/\/\S+$/.test(map2.url))
3734
+ fail(t("source.url", { url: map2.url }));
3735
+ if (typeof map2.path === "string" && (/^[a-z]+:/i.test(map2.path) || map2.path.startsWith("/") || !map2.path.endsWith(".md")))
3736
+ fail(t("source.path", { path: map2.path }));
3737
+ return { title: map2.title.trim(), ...map2.path !== void 0 ? { path: map2.path.trim() } : {}, ...map2.url !== void 0 ? { url: map2.url.trim() } : {}, ...map2.note !== void 0 ? { note: map2.note.trim() } : {} };
3738
+ }
3739
+ function renderFrontmatter(front) {
3740
+ const lines = ["---", "id: " + front.id];
3741
+ if (front.sources?.length) {
3742
+ lines.push("sources:");
3743
+ for (const s of front.sources) {
3744
+ const entries = ["title", "path", "url", "note"].filter((k) => s[k] !== void 0).map((k) => k + ": " + quote(s[k]));
3745
+ lines.push(" - " + entries[0], ...entries.slice(1).map((e) => " " + e));
3746
+ }
3747
+ }
3748
+ lines.push("---");
3749
+ return lines.join("\n") + "\n";
3750
+ }
3637
3751
  function renderDesignPreview(id, design) {
3638
- return `<!-- gitifact-design: ${id} -->
3639
-
3752
+ return renderFrontmatter({ id, ...design.sources?.length ? { sources: design.sources } : {} }) + `
3640
3753
  # ${design.title}
3641
3754
 
3642
3755
  ${design.body}
3643
3756
  `;
3644
3757
  }
3645
- function parseDesignPreview(source, specId) {
3646
- if (source.includes("\0") || /\r(?!\n)/.test(source))
3758
+ function parseDesignPreview(source2, specId) {
3759
+ if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3647
3760
  fail(t("design.invalidCharacters"));
3648
- const lines = normalized(source).split("\n");
3649
- if (!new RegExp(`^<!-- ${marker}-design: ${specId} -->$`).test(lines.shift() ?? ""))
3761
+ const lines = normalized(source2).split("\n");
3762
+ const front = parseFrontmatter(lines, "design", "S");
3763
+ if (front.id !== specId)
3650
3764
  fail(t("design.idMismatch"));
3651
3765
  while (lines[0] === "")
3652
3766
  lines.shift();
@@ -3684,32 +3798,24 @@ function parseDesignPreview(source, specId) {
3684
3798
  }).join("\n");
3685
3799
  if (fence)
3686
3800
  fail(t("design.unclosedFence"));
3687
- return { title, body, requirements: [...references] };
3801
+ return { title, body, requirements: [...references], sources: front.sources ?? [] };
3688
3802
  }
3689
3803
  function renderDocument(doc) {
3690
- return `<!-- gitifact-${doc.kind}: ${doc.id} -->
3691
-
3804
+ return renderFrontmatter({ id: doc.id }) + `
3692
3805
  # ${doc.title}
3693
3806
 
3694
3807
  ${doc.body}
3695
3808
  `;
3696
3809
  }
3697
- function parseDocument(path3, source) {
3810
+ function parseDocument(path3, source2) {
3698
3811
  const match = documentPathPattern.exec(path3);
3699
3812
  if (!match)
3700
3813
  fail(t("document.unsupportedPath", { path: path3 }));
3701
- const kind = kindOfDir(match[1]);
3702
- if (kind === "product") {
3703
- if (path3 !== PRODUCT_PATH)
3704
- fail(t("document.productOnly", { path: path3 }));
3705
- } else
3706
- validateDocumentRelativePath(match[2]);
3707
- if (source.includes("\0") || /\r(?!\n)/.test(source))
3814
+ validateDocumentRelativePath(match[1]);
3815
+ if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3708
3816
  fail(t("document.invalidCharacters", { path: path3 }));
3709
- const lines = normalized(source).split("\n");
3710
- const id = new RegExp(`^<!-- gitifact-${kind}: ([PG]-${token}) -->$`).exec(lines.shift() ?? "")?.[1];
3711
- if (!id || id[0] !== (kind === "product" ? "P" : "G"))
3712
- return fail(t("document.idRequired", { kind, path: path3 }));
3817
+ const lines = normalized(source2).split("\n");
3818
+ const { id } = parseFrontmatter(lines, "wiki", "W");
3713
3819
  while (lines[0] === "")
3714
3820
  lines.shift();
3715
3821
  const heading = lines.shift();
@@ -3738,58 +3844,49 @@ function parseDocument(path3, source) {
3738
3844
  }
3739
3845
  if (fence)
3740
3846
  fail(t("document.unclosedFence", { path: path3 }));
3741
- return { id, kind, path: path3, title, body };
3742
- }
3743
- function parseDocumentFiles(files) {
3744
- const sets = DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: parsePreviewHistory(files.get(documentHistoryPath(kind)) ?? "", "documents") }));
3745
- for (const [path3, source] of files) {
3746
- if (!documentPathPattern.test(path3))
3747
- continue;
3748
- const doc = parseDocument(path3, source);
3749
- sets.find((s) => s.kind === doc.kind).documents.push(doc);
3750
- }
3751
- for (const set2 of sets)
3752
- set2.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
3753
- validateDocumentSets(sets);
3754
- return sets;
3755
- }
3756
- function validateDocumentSets(sets) {
3847
+ return { id, path: path3, title, body };
3848
+ }
3849
+ function parseWikiFiles(files) {
3850
+ const wiki = { documents: [], history: parsePreviewHistory(files.get(WIKI_HISTORY_PATH) ?? "", "documents") };
3851
+ for (const [path3, source2] of files)
3852
+ if (documentPathPattern.test(path3))
3853
+ wiki.documents.push(parseDocument(path3, source2));
3854
+ wiki.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
3855
+ validateWiki(wiki);
3856
+ return wiki;
3857
+ }
3858
+ function validateWiki(wiki) {
3757
3859
  const ids = /* @__PURE__ */ new Set();
3758
3860
  const paths2 = /* @__PURE__ */ new Set();
3759
3861
  const historyIds = /* @__PURE__ */ new Set();
3760
- if (sets.map((s) => s.kind).sort().join(",") !== "guide,product")
3761
- fail(t("document.kindSet"));
3762
- for (const set2 of sets) {
3763
- for (const d of set2.documents) {
3764
- if (d.kind !== set2.kind)
3765
- fail(t("document.kindMismatch", { path: d.path }));
3766
- if (ids.has(d.id))
3767
- fail(t("document.duplicateId", { id: d.id }));
3768
- ids.add(d.id);
3769
- const lower = d.path.toLowerCase();
3770
- if (paths2.has(lower))
3771
- fail(t("document.caseOnlyPath", { path: d.path }));
3772
- paths2.add(lower);
3773
- }
3774
- for (const h of set2.history) {
3775
- if (historyIds.has(h.id))
3776
- fail(t("history.duplicateId", { id: h.id }));
3777
- historyIds.add(h.id);
3778
- }
3862
+ for (const d of wiki.documents) {
3863
+ if (ids.has(d.id))
3864
+ fail(t("document.duplicateId", { id: d.id }));
3865
+ ids.add(d.id);
3866
+ const lower = d.path.toLowerCase();
3867
+ if (paths2.has(lower))
3868
+ fail(t("document.caseOnlyPath", { path: d.path }));
3869
+ paths2.add(lower);
3870
+ }
3871
+ for (const h of wiki.history) {
3872
+ if (historyIds.has(h.id))
3873
+ fail(t("history.duplicateId", { id: h.id }));
3874
+ historyIds.add(h.id);
3779
3875
  }
3780
3876
  }
3781
- var emptyDocumentSets = () => DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: [] }));
3782
- var asBundle = (value) => Array.isArray(value) ? { specs: value, documents: emptyDocumentSets() } : value;
3877
+ var emptyWiki = () => ({ documents: [], history: [] });
3878
+ var emptyBundle = () => ({ specs: [], wiki: emptyWiki() });
3879
+ var asBundle = (value) => Array.isArray(value) ? { specs: value, wiki: emptyWiki() } : value;
3783
3880
  function parsePreviewBundle(files) {
3784
- const bundle = { specs: parsePreviewFiles(files), documents: parseDocumentFiles(files) };
3881
+ const bundle = { specs: parsePreviewFiles(files), wiki: parseWikiFiles(files) };
3785
3882
  validateBundle(bundle);
3786
3883
  return bundle;
3787
3884
  }
3788
3885
  function validateBundle(bundle) {
3789
3886
  validatePreviewSnapshot(bundle.specs);
3790
- validateDocumentSets(bundle.documents);
3887
+ validateWiki(bundle.wiki);
3791
3888
  const ids = /* @__PURE__ */ new Set();
3792
- for (const h of [...bundle.specs.flatMap((s) => s.history), ...bundle.documents.flatMap((d) => d.history)]) {
3889
+ for (const h of [...bundle.specs.flatMap((s) => s.history), ...bundle.wiki.history]) {
3793
3890
  if (ids.has(h.id))
3794
3891
  fail(t("history.duplicateId", { id: h.id }));
3795
3892
  ids.add(h.id);
@@ -3799,28 +3896,92 @@ function designReferenceWarnings(specs) {
3799
3896
  const known = new Set(specs.flatMap((s) => s.requirements.map((r) => r.id)));
3800
3897
  return specs.flatMap((s) => (s.design?.requirements ?? []).filter((id) => !known.has(id)).map((id) => ({ specId: s.id, requirement: id, code: "MISSING_DESIGN_REFERENCE" })));
3801
3898
  }
3899
+ function resolveLink(from, link3) {
3900
+ const bare = link3.replace(/[?#].*$/, "");
3901
+ if (!bare || /^[a-z][a-z0-9+.-]*:/i.test(bare) || bare.startsWith("/") || bare.startsWith("//"))
3902
+ return null;
3903
+ const parts = from.split("/").slice(0, -1);
3904
+ for (const part of decodeSafe(bare).split("/")) {
3905
+ if (part === "" || part === ".")
3906
+ continue;
3907
+ if (part === "..") {
3908
+ if (!parts.length)
3909
+ return null;
3910
+ parts.pop();
3911
+ continue;
3912
+ }
3913
+ if (/[\\:\0]/.test(part))
3914
+ return null;
3915
+ parts.push(part);
3916
+ }
3917
+ return parts.join("/");
3918
+ }
3919
+ var decodeSafe = (value) => {
3920
+ try {
3921
+ return decodeURIComponent(value);
3922
+ } catch {
3923
+ return value;
3924
+ }
3925
+ };
3926
+ function extractLinks(body) {
3927
+ const links = [];
3928
+ let fence;
3929
+ for (const line of body.split("\n")) {
3930
+ if (fence) {
3931
+ if (new RegExp(`^ {0,3}${fence.char}{${fence.size},}\\s*$`).test(line))
3932
+ fence = void 0;
3933
+ continue;
3934
+ }
3935
+ const open6 = /^ {0,3}(`{3,}|~{3,})/.exec(line);
3936
+ if (open6) {
3937
+ fence = { char: open6[1][0], size: open6[1].length };
3938
+ continue;
3939
+ }
3940
+ for (const m of line.replace(/`[^`]*`/g, "").matchAll(/!?\[[^\]]*\]\((?:<([^>]+)>|([^)\s]+))(?:\s+"[^"]*")?\)/g))
3941
+ links.push(m[1] ?? m[2]);
3942
+ }
3943
+ return links;
3944
+ }
3945
+ function documentLinks(bundle) {
3946
+ const out = [];
3947
+ const collect = (from, links) => {
3948
+ for (const link3 of links) {
3949
+ const target = resolveLink(from, link3);
3950
+ if (target !== null)
3951
+ out.push({ from, link: link3, target });
3952
+ }
3953
+ };
3954
+ for (const s of bundle.specs) {
3955
+ collect(s.path, extractLinks(s.description + "\n" + s.requirements.map((r) => r.body).join("\n")));
3956
+ if (s.design) {
3957
+ const path3 = s.path.replace(/requirements\.md$/, "design.md");
3958
+ collect(path3, [...extractLinks(s.design.body), ...s.design.sources.flatMap((x) => x.path ? [x.path] : [])]);
3959
+ }
3960
+ }
3961
+ for (const d of bundle.wiki.documents)
3962
+ collect(d.path, extractLinks(d.body));
3963
+ return out;
3964
+ }
3802
3965
  function parsePreviewFiles(files) {
3803
3966
  const specs = [];
3804
- for (const [path3, source] of files) {
3967
+ for (const [path3, source2] of files) {
3805
3968
  if (documentPathPattern.test(path3) || documentHistoryPattern.test(path3))
3806
3969
  continue;
3807
3970
  if (path3.endsWith("/requirements.md")) {
3808
- specs.push(parseSpecPreview(path3, source, files.get(path3.replace(/requirements\.md$/, "history.jsonl")) ?? "", files.get(path3.replace(/requirements\.md$/, "design.md"))));
3971
+ specs.push(parseSpecPreview(path3, source2, files.get(path3.replace(/requirements\.md$/, "history.jsonl")) ?? "", files.get(path3.replace(/requirements\.md$/, "design.md"))));
3809
3972
  } else if (!files.has(path3.replace(/(?:history\.jsonl|design\.md)$/, "requirements.md")))
3810
3973
  fail(t("spec.orphanFile", { path: path3 }));
3811
3974
  }
3812
3975
  validatePreviewSnapshot(specs);
3813
3976
  return specs;
3814
3977
  }
3815
- function parseSpecPreview(path3, source, history = "", designSource) {
3978
+ function parseSpecPreview(path3, source2, history = "", designSource) {
3816
3979
  if (!/^\.(?:gitifact|tryce)\/spec\/[^/]+\/requirements\.md$/.test(path3))
3817
3980
  fail(t("spec.unsupportedPath", { path: path3 }));
3818
- if (source.includes("\0") || source.includes("\r") && /\r(?!\n)/.test(source))
3981
+ if (source2.includes("\0") || source2.includes("\r") && /\r(?!\n)/.test(source2))
3819
3982
  fail(t("spec.invalidCharacters", { path: path3 }));
3820
- const lines = normalized(source).split("\n");
3821
- const specMatch = new RegExp(`^<!-- ${marker}-spec: (S-${token}) -->$`).exec(lines[0] ?? "");
3822
- if (!specMatch)
3823
- fail(t("spec.idRequired", { path: path3 }));
3983
+ const lines = normalized(source2).split("\n");
3984
+ const specId = parseFrontmatter(lines, "spec", "S").id;
3824
3985
  let title = "";
3825
3986
  let description = "";
3826
3987
  let current;
@@ -3833,7 +3994,7 @@ function parseSpecPreview(path3, source, history = "", designSource) {
3833
3994
  else
3834
3995
  description += line + "\n";
3835
3996
  };
3836
- for (let i = 1; i < lines.length; i++) {
3997
+ for (let i = 0; i < lines.length; i++) {
3837
3998
  const line = lines[i];
3838
3999
  if (waitingId) {
3839
4000
  const match = new RegExp(`^<!-- ${marker}-req: (R-${token}) -->$`).exec(line);
@@ -3890,13 +4051,13 @@ function parseSpecPreview(path3, source, history = "", designSource) {
3890
4051
  fail(t("spec.emptyOrDuplicateRequirement", { id: r.id }));
3891
4052
  ids.add(r.id);
3892
4053
  }
3893
- return { id: specMatch[1], path: path3, title, description: normalized(description), requirements, history: parsePreviewHistory(history), ...designSource === void 0 ? {} : { design: parseDesignPreview(designSource, specMatch[1]) } };
4054
+ return { id: specId, path: path3, title, description: normalized(description), requirements, history: parsePreviewHistory(history), ...designSource === void 0 ? {} : { design: parseDesignPreview(designSource, specId) } };
3894
4055
  }
3895
- function parsePreviewHistory(source, scope = "spec") {
4056
+ function parsePreviewHistory(source2, scope = "spec") {
3896
4057
  const records = [];
3897
4058
  const ids = /* @__PURE__ */ new Set();
3898
4059
  const shapes = scope === "spec" ? ["id,reason,requirements", "designs,id,reason,requirements"] : ["documents,id,reason,requirements"];
3899
- for (const line of source.replace(/\r\n/g, "\n").split("\n")) {
4060
+ for (const line of source2.replace(/\r\n/g, "\n").split("\n")) {
3900
4061
  if (!line.trim())
3901
4062
  continue;
3902
4063
  let value;
@@ -3908,7 +4069,7 @@ function parsePreviewHistory(source, scope = "spec") {
3908
4069
  if (!value || typeof value !== "object" || Array.isArray(value))
3909
4070
  fail(t("history.invalidRecord"));
3910
4071
  const r = value;
3911
- if (!shapes.includes(Object.keys(r).sort().join(",")) || typeof r.id !== "string" || !new RegExp(`^H-${token}$`).test(r.id) || ids.has(r.id) || typeof r.reason !== "string" || !r.reason.trim() || !Array.isArray(r.requirements) || !r.requirements.length && !Array.isArray(r.designs) && !Array.isArray(r.documents) || r.documents !== void 0 && (r.requirements.length || !Array.isArray(r.documents) || !r.documents.length || r.documents.some((id) => typeof id !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(id)) || new Set(r.documents).size !== r.documents.length) || r.designs !== void 0 && (!Array.isArray(r.designs) || !r.designs.length || r.designs.some((id) => typeof id !== "string" || !/^S-[a-z2-7]{10}$/.test(id)) || new Set(r.designs).size !== r.designs.length) || r.requirements.some((id) => typeof id !== "string" || !reqId.test(id)) || new Set(r.requirements).size !== r.requirements.length)
4072
+ if (!shapes.includes(Object.keys(r).sort().join(",")) || typeof r.id !== "string" || !new RegExp(`^H-${token}$`).test(r.id) || ids.has(r.id) || typeof r.reason !== "string" || !r.reason.trim() || !Array.isArray(r.requirements) || !r.requirements.length && !Array.isArray(r.designs) && !Array.isArray(r.documents) || r.documents !== void 0 && (r.requirements.length || !Array.isArray(r.documents) || !r.documents.length || r.documents.some((id) => typeof id !== "string" || !/^W-[a-z2-7]{10}$/.test(id)) || new Set(r.documents).size !== r.documents.length) || r.designs !== void 0 && (!Array.isArray(r.designs) || !r.designs.length || r.designs.some((id) => typeof id !== "string" || !/^S-[a-z2-7]{10}$/.test(id)) || new Set(r.designs).size !== r.designs.length) || r.requirements.some((id) => typeof id !== "string" || !reqId.test(id)) || new Set(r.requirements).size !== r.requirements.length)
3912
4073
  fail(t("history.invalidFields"));
3913
4074
  ids.add(r.id);
3914
4075
  records.push({ id: r.id, requirements: r.requirements, ...r.designs === void 0 ? {} : { designs: r.designs }, ...r.documents === void 0 ? {} : { documents: r.documents }, reason: r.reason });
@@ -3935,6 +4096,7 @@ function validatePreviewSnapshot(specs) {
3935
4096
  }
3936
4097
  }
3937
4098
  }
4099
+ var sameEntry = (a, b) => a.title === b.title && a.body === b.body && JSON.stringify(a.sources ?? []) === JSON.stringify(b.sources ?? []);
3938
4100
  function compareSpecPreviews(before, after) {
3939
4101
  validatePreviewSnapshot(before);
3940
4102
  validatePreviewSnapshot(after);
@@ -3944,7 +4106,7 @@ function compareSpecPreviews(before, after) {
3944
4106
  for (const [specs, map2] of [[before, prev], [after, next]])
3945
4107
  for (const s of specs)
3946
4108
  if (s.design)
3947
- map2.set(s.id, { id: s.id, title: s.design.title, body: s.design.body, specId: s.id, path: s.path.replace(/requirements\.md$/, "design.md") });
4109
+ map2.set(s.id, { id: s.id, title: s.design.title, body: s.design.body, specId: s.id, path: s.path.replace(/requirements\.md$/, "design.md"), sources: s.design.sources });
3948
4110
  const previousHistory = new Map(before.flatMap((s) => s.history.map((h) => [h.id, h])));
3949
4111
  const reasons = after.flatMap((s) => s.history).filter((h) => {
3950
4112
  const old = previousHistory.get(h.id);
@@ -3963,7 +4125,7 @@ function compareSpecPreviews(before, after) {
3963
4125
  else {
3964
4126
  if (from.specId !== to.specId || id.startsWith("S-") && storeRelative(from.path) !== storeRelative(to.path))
3965
4127
  types.push("moved");
3966
- if (from.title !== to.title || from.body !== to.body)
4128
+ if (!sameEntry(from, to))
3967
4129
  types.push("modified");
3968
4130
  }
3969
4131
  return types.length ? [{ id, types, before: from, after: to, kind: id.startsWith("S-") ? "design" : "requirement", reasons: reasons.filter((h) => [...h.requirements, ...h.designs ?? []].includes(id)) }] : [];
@@ -3985,14 +4147,14 @@ function compareSpecPreviews(before, after) {
3985
4147
  });
3986
4148
  return { specChanges, changes };
3987
4149
  }
3988
- function compareDocumentSets(before, after) {
3989
- validateDocumentSets(before);
3990
- validateDocumentSets(after);
3991
- const index = (sets) => new Map(sets.flatMap((s) => s.documents.map((d) => [d.id, { id: d.id, title: d.title, body: d.body, specId: d.kind, path: d.path }])));
4150
+ function compareWikis(before, after) {
4151
+ validateWiki(before);
4152
+ validateWiki(after);
4153
+ const index = (wiki) => new Map(wiki.documents.map((d) => [d.id, { id: d.id, title: d.title, body: d.body, specId: "wiki", path: d.path }]));
3992
4154
  const prev = index(before);
3993
4155
  const next = index(after);
3994
- const previousHistory = new Map(before.flatMap((s) => s.history.map((h) => [h.id, h])));
3995
- const reasons = after.flatMap((s) => s.history).filter((h) => {
4156
+ const previousHistory = new Map(before.history.map((h) => [h.id, h]));
4157
+ const reasons = after.history.filter((h) => {
3996
4158
  const old = previousHistory.get(h.id);
3997
4159
  if (old && JSON.stringify(old) !== JSON.stringify(h))
3998
4160
  fail(t("history.changed", { id: h.id }));
@@ -4012,8 +4174,7 @@ function compareDocumentSets(before, after) {
4012
4174
  if (from.title !== to.title || from.body !== to.body)
4013
4175
  types.push("modified");
4014
4176
  }
4015
- const kind = (to ?? from).specId;
4016
- return types.length ? [{ id, types, before: from, after: to, kind, reasons: reasons.filter((h) => (h.documents ?? []).includes(id)) }] : [];
4177
+ return types.length ? [{ id, types, before: from, after: to, kind: "wiki", reasons: reasons.filter((h) => (h.documents ?? []).includes(id)) }] : [];
4017
4178
  });
4018
4179
  const changed = new Set(changes.map((c) => c.id));
4019
4180
  for (const h of reasons)
@@ -4027,7 +4188,7 @@ function comparePreviewBundles(before, after) {
4027
4188
  validateBundle(a);
4028
4189
  validateBundle(b);
4029
4190
  const specs = compareSpecPreviews(a.specs, b.specs);
4030
- return { specChanges: specs.specChanges, changes: [...specs.changes, ...compareDocumentSets(a.documents, b.documents)] };
4191
+ return { specChanges: specs.specChanges, changes: [...specs.changes, ...compareWikis(a.wiki, b.wiki)] };
4031
4192
  }
4032
4193
 
4033
4194
  // ../../packages/core/dist/formats/changelog.js
@@ -4106,8 +4267,7 @@ function previewPath(feature2) {
4106
4267
  return `.gitifact/spec/${feature2}/requirements.md`;
4107
4268
  }
4108
4269
  function renderSpecPreview(spec2) {
4109
- return `<!-- gitifact-spec: ${spec2.id} -->
4110
-
4270
+ return renderFrontmatter({ id: spec2.id }) + `
4111
4271
  # ${spec2.title}
4112
4272
 
4113
4273
  ` + (spec2.description ? spec2.description + "\n\n" : "") + spec2.requirements.map((r) => `## ${r.title}
@@ -4119,11 +4279,11 @@ ${r.body}
4119
4279
  function editSpecPreview(original, input2, generate) {
4120
4280
  if (!Array.isArray(input2) || !input2.length || input2.length > 100)
4121
4281
  fail2(t("edit.operationCount"));
4122
- const source = asBundle(original);
4123
- const specs = source.specs.map((s) => ({ ...s, requirements: s.requirements.map((r) => ({ ...r })), history: s.history.map((h) => ({ ...h, requirements: [...h.requirements] })) }));
4124
- const documents = source.documents.map((set2) => ({ ...set2, documents: set2.documents.map((d) => ({ ...d })), history: set2.history.map((h) => ({ ...h })) }));
4125
- validateBundle({ specs, documents });
4126
- const used = /* @__PURE__ */ new Set([...specs.flatMap((s) => [s.id, ...s.requirements.map((r) => r.id)]), ...documents.flatMap((d) => d.documents.map((x) => x.id))]);
4282
+ const source2 = asBundle(original);
4283
+ const specs = source2.specs.map((s) => ({ ...s, requirements: s.requirements.map((r) => ({ ...r })), history: s.history.map((h) => ({ ...h, requirements: [...h.requirements] })) }));
4284
+ const wiki = { documents: source2.wiki.documents.map((d) => ({ ...d })), history: source2.wiki.history.map((h) => ({ ...h })) };
4285
+ validateBundle({ specs, wiki });
4286
+ const used = /* @__PURE__ */ new Set([...specs.flatMap((s) => [s.id, ...s.requirements.map((r) => r.id)]), ...wiki.documents.map((x) => x.id)]);
4127
4287
  const allocate = (prefix) => {
4128
4288
  for (let attempt = 0; attempt < 100; attempt++) {
4129
4289
  const id = generate(prefix);
@@ -4147,14 +4307,12 @@ function editSpecPreview(original, input2, generate) {
4147
4307
  update: ["type", "id", "title", "body"],
4148
4308
  move: ["type", "id", "feature"],
4149
4309
  "rename-spec": ["type", "id", "title"],
4150
- "set-design": ["type", "feature", "title", "body"],
4310
+ "set-design": ["type", "feature", "title", "body", "sources"],
4151
4311
  "delete-design": ["type", "feature"],
4152
4312
  "create-doc": ["type", "path", "title", "body"],
4153
4313
  "update-doc": ["type", "id", "title", "body"],
4154
4314
  "move-doc": ["type", "id", "path"],
4155
- "delete-doc": ["type", "id"],
4156
- "set-product": ["type", "title", "body"],
4157
- "delete-product": ["type"]
4315
+ "delete-doc": ["type", "id"]
4158
4316
  };
4159
4317
  const type = String(op.type);
4160
4318
  const fields2 = schemas[type];
@@ -4174,41 +4332,24 @@ function editSpecPreview(original, input2, generate) {
4174
4332
  const path3 = previewPath(str("feature"));
4175
4333
  return specs.find((s) => s.path === path3) ?? fail2(t("edit.specNotFound", { path: path3 }));
4176
4334
  };
4177
- if (type === "set-product" || type === "delete-product") {
4178
- const set2 = documents.find((s) => s.kind === "product");
4179
- const existing = set2.documents[0];
4180
- if (type === "delete-product") {
4181
- if (!existing)
4182
- fail2(t("edit.noProductToDelete"));
4183
- set2.documents = [];
4184
- results.push({ type, id: existing.id });
4185
- } else {
4186
- const doc = { id: existing?.id ?? allocate("P"), kind: "product", path: PRODUCT_PATH, title: str("title"), body: str("body") };
4187
- set2.documents = [doc];
4188
- results.push({ type, id: doc.id });
4189
- }
4190
- continue;
4191
- }
4192
4335
  if (type.endsWith("-doc")) {
4193
- const all = () => documents.filter((set2) => set2.kind === "guide").flatMap((set2) => set2.documents);
4194
- const documentPath = (kind, self) => {
4336
+ const documentPath = (self) => {
4195
4337
  const relative = str("path");
4196
4338
  validateDocumentRelativePath(relative);
4197
- const path3 = `${DOCUMENT_DIRS[kind]}/${relative}`;
4339
+ const path3 = `${WIKI_DIR}/${relative}`;
4198
4340
  if (self && self.path === path3)
4199
4341
  fail2(t("edit.samePath"));
4200
- if (all().some((d) => d !== self && d.path.toLowerCase() === path3.toLowerCase()))
4342
+ if (wiki.documents.some((d) => d !== self && d.path.toLowerCase() === path3.toLowerCase()))
4201
4343
  fail2(t("edit.documentPathExists", { path: path3 }));
4202
4344
  return path3;
4203
4345
  };
4204
4346
  const find = () => {
4205
4347
  const id = str("id");
4206
- return all().find((d) => d.id === id) ?? fail2(t("edit.guideIdNotFound", { id }));
4348
+ return wiki.documents.find((d) => d.id === id) ?? fail2(t("edit.documentIdNotFound", { id }));
4207
4349
  };
4208
4350
  if (type === "create-doc") {
4209
- const kind = "guide";
4210
- const doc = { id: allocate("G"), kind, path: documentPath(kind), title: str("title"), body: str("body") };
4211
- documents.find((s) => s.kind === kind).documents.push(doc);
4351
+ const doc = { id: allocate("W"), path: documentPath(), title: str("title"), body: str("body") };
4352
+ wiki.documents.push(doc);
4212
4353
  results.push({ type, id: doc.id });
4213
4354
  } else if (type === "update-doc") {
4214
4355
  const doc = find();
@@ -4217,12 +4358,11 @@ function editSpecPreview(original, input2, generate) {
4217
4358
  results.push({ type, id: doc.id });
4218
4359
  } else if (type === "move-doc") {
4219
4360
  const doc = find();
4220
- doc.path = documentPath(doc.kind, doc);
4361
+ doc.path = documentPath(doc);
4221
4362
  results.push({ type, id: doc.id });
4222
4363
  } else {
4223
4364
  const doc = find();
4224
- const set2 = documents.find((s) => s.kind === doc.kind);
4225
- set2.documents = set2.documents.filter((d) => d.id !== doc.id);
4365
+ wiki.documents = wiki.documents.filter((d) => d.id !== doc.id);
4226
4366
  results.push({ type, id: doc.id });
4227
4367
  }
4228
4368
  continue;
@@ -4245,8 +4385,16 @@ function editSpecPreview(original, input2, generate) {
4245
4385
  if (!spec2.design)
4246
4386
  fail2(t("edit.noDesignToDelete"));
4247
4387
  delete spec2.design;
4248
- } else
4249
- spec2.design = parseDesignPreview(renderDesignPreview(spec2.id, { title: str("title"), body: str("body") }), spec2.id);
4388
+ } else {
4389
+ let sources = [];
4390
+ const list = op.sources;
4391
+ if (list !== void 0) {
4392
+ if (!Array.isArray(list) || list.length > 50)
4393
+ return fail2(t("edit.invalidSources"));
4394
+ sources = list.map(validateSource);
4395
+ }
4396
+ spec2.design = parseDesignPreview(renderDesignPreview(spec2.id, { title: str("title"), body: str("body"), sources }), spec2.id);
4397
+ }
4250
4398
  results.push({ type, id: spec2.id });
4251
4399
  } else if (type === "rename-spec") {
4252
4400
  const spec2 = specs.find((s) => s.id === str("id")) ?? fail2(t("edit.specIdNotFound"));
@@ -4254,13 +4402,13 @@ function editSpecPreview(original, input2, generate) {
4254
4402
  results.push({ type, id: spec2.id });
4255
4403
  } else {
4256
4404
  const id = str("id");
4257
- const source2 = specs.find((s) => s.requirements.some((r) => r.id === id)) ?? fail2(t("edit.requirementIdNotFound"));
4258
- const req = source2.requirements.find((r) => r.id === id);
4405
+ const source3 = specs.find((s) => s.requirements.some((r) => r.id === id)) ?? fail2(t("edit.requirementIdNotFound"));
4406
+ const req = source3.requirements.find((r) => r.id === id);
4259
4407
  if (type === "move") {
4260
4408
  const destination = target();
4261
- if (source2 === destination)
4409
+ if (source3 === destination)
4262
4410
  fail2(t("edit.sameSpec"));
4263
- source2.requirements = source2.requirements.filter((r) => r.id !== id);
4411
+ source3.requirements = source3.requirements.filter((r) => r.id !== id);
4264
4412
  destination.requirements.push(req);
4265
4413
  } else {
4266
4414
  req.title = str("title");
@@ -4274,14 +4422,12 @@ function editSpecPreview(original, input2, generate) {
4274
4422
  if (JSON.stringify({ ...parsed, history: spec2.history }) !== JSON.stringify(spec2))
4275
4423
  fail2(t("edit.bodyChangesSpecStructure"));
4276
4424
  }
4277
- for (const set2 of documents) {
4278
- set2.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
4279
- for (const doc of set2.documents)
4280
- if (JSON.stringify(parseDocument(doc.path, renderDocument(doc))) !== JSON.stringify(doc))
4281
- fail2(t("edit.bodyChangesDocumentStructure", { path: doc.path }));
4282
- }
4283
- validateBundle({ specs, documents });
4284
- return { specs, documents, results };
4425
+ wiki.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
4426
+ for (const doc of wiki.documents)
4427
+ if (JSON.stringify(parseDocument(doc.path, renderDocument(doc))) !== JSON.stringify(doc))
4428
+ fail2(t("edit.bodyChangesDocumentStructure", { path: doc.path }));
4429
+ validateBundle({ specs, wiki });
4430
+ return { specs, wiki, results };
4285
4431
  }
4286
4432
 
4287
4433
  // ../../packages/core/dist/use-cases/prepare-spec-preview.js
@@ -4295,7 +4441,7 @@ function pendingPreviewReasons(before, current) {
4295
4441
  const b = asBundle(current);
4296
4442
  return [
4297
4443
  ...b.specs.flatMap((s) => s.history.slice(a.specs.find((p) => p.id === s.id)?.history.length ?? 0).map((h) => ({ specId: s.id, path: historyPath(s), ...h }))),
4298
- ...b.documents.flatMap((d) => d.history.slice(a.documents.find((p) => p.kind === d.kind)?.history.length ?? 0).map((h) => ({ kind: d.kind, path: documentHistoryPath(d.kind), ...h })))
4444
+ ...b.wiki.history.slice(a.wiki.history.length).map((h) => ({ kind: "wiki", path: WIKI_HISTORY_PATH, ...h }))
4299
4445
  ];
4300
4446
  }
4301
4447
  function finalSpecPreviewChanges(beforeValue, currentValue) {
@@ -4310,14 +4456,11 @@ function finalSpecPreviewChanges(beforeValue, currentValue) {
4310
4456
  if (next && !equal(next.history.slice(0, previous.history.length), previous.history))
4311
4457
  fail3(t("reason.committedImmutable", { id: previous.id }));
4312
4458
  }
4313
- for (const previous of before.documents) {
4314
- const next = current.documents.find((s) => s.kind === previous.kind);
4315
- if (!equal(next.history.slice(0, previous.history.length), previous.history))
4316
- fail3(t("reason.committedDocumentImmutable", { kind: previous.kind }));
4317
- }
4459
+ if (!equal(current.wiki.history.slice(0, before.wiki.history.length), before.wiki.history))
4460
+ fail3(t("reason.committedDocumentImmutable"));
4318
4461
  const clean = {
4319
4462
  specs: current.specs.map((s) => ({ ...s, history: before.specs.find((p) => p.id === s.id)?.history ?? [] })),
4320
- documents: current.documents.map((d) => ({ ...d, history: before.documents.find((p) => p.kind === d.kind)?.history ?? [] }))
4463
+ wiki: { documents: current.wiki.documents, history: before.wiki.history }
4321
4464
  };
4322
4465
  return comparePreviewBundles(before, clean);
4323
4466
  }
@@ -4332,8 +4475,8 @@ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles,
4332
4475
  const writes = /* @__PURE__ */ new Map();
4333
4476
  const pending = /* @__PURE__ */ new Map();
4334
4477
  const covered = /* @__PURE__ */ new Set();
4335
- const used = new Set([...before.flatMap((s) => s.history), ...current.flatMap((s) => s.history), ...beforeBundle.documents.flatMap((d) => d.history), ...currentBundle.documents.flatMap((d) => d.history)].map((h) => h.id));
4336
- const documentPending = /* @__PURE__ */ new Map();
4478
+ const used = new Set([...before.flatMap((s) => s.history), ...current.flatMap((s) => s.history), ...beforeBundle.wiki.history, ...currentBundle.wiki.history].map((h) => h.id));
4479
+ const wikiPending = [];
4337
4480
  const allocate = () => {
4338
4481
  for (let attempt = 0; attempt < 100; attempt++) {
4339
4482
  const id = generate();
@@ -4359,21 +4502,16 @@ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles,
4359
4502
  if (raw.requirements.length)
4360
4503
  fail3(t("reason.documentWithRequirements"));
4361
4504
  const ids = raw.documents;
4362
- let kind;
4363
4505
  for (const value2 of ids) {
4364
- if (typeof value2 !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(value2) || covered.has(value2))
4506
+ if (typeof value2 !== "string" || !/^W-[a-z2-7]{10}$/.test(value2) || covered.has(value2))
4365
4507
  return fail3(t("reason.documentTargetInvalid"));
4366
- const change2 = delta.changes.find((c) => c.id === value2) ?? fail3(t("reason.documentUnchanged", { id: value2 }));
4367
- if (kind && change2.kind !== kind)
4368
- fail3(t("reason.singleDocumentKind"));
4369
- kind = change2.kind;
4508
+ if (!delta.changes.some((c) => c.id === value2))
4509
+ fail3(t("reason.documentUnchanged", { id: value2 }));
4370
4510
  covered.add(value2);
4371
4511
  }
4372
4512
  const documents = [...ids].sort();
4373
- const set2 = currentBundle.documents.find((d) => d.kind === kind);
4374
- const oldCount = beforeBundle.documents.find((d) => d.kind === kind)?.history.length ?? 0;
4375
- const reusable = set2.history.slice(oldCount).find((h) => h.reason === reason && equal([...h.documents ?? []].sort(), documents));
4376
- documentPending.set(kind, [...documentPending.get(kind) ?? [], { id: reusable?.id ?? allocate(), requirements: [], documents, reason }]);
4513
+ const reusable = currentBundle.wiki.history.slice(beforeBundle.wiki.history.length).find((h) => h.reason === reason && equal([...h.documents ?? []].sort(), documents));
4514
+ wikiPending.push({ id: reusable?.id ?? allocate(), requirements: [], documents, reason });
4377
4515
  continue;
4378
4516
  }
4379
4517
  for (const value2 of [...raw.requirements, ...raw.designs ?? []]) {
@@ -4401,34 +4539,32 @@ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles,
4401
4539
  pending.set(specId, [...pending.get(specId) ?? [], record3]);
4402
4540
  }
4403
4541
  }
4542
+ const historyText = (base, records) => {
4543
+ let text2 = base ?? "";
4544
+ if (records.length)
4545
+ text2 += (text2 && !text2.endsWith("\n") ? "\n" : "") + records.map((h) => JSON.stringify(h) + "\n").join("");
4546
+ return base !== void 0 || records.length ? text2 : null;
4547
+ };
4404
4548
  for (const spec2 of current) {
4405
4549
  const old = before.find((s) => s.id === spec2.id);
4406
4550
  const base = old ? baseFiles.get(historyPath(old)) : void 0;
4407
4551
  const records = (pending.get(spec2.id) ?? []).sort((a, b) => (a.requirements[0] ?? a.designs[0]) < (b.requirements[0] ?? b.designs[0]) ? -1 : 1);
4408
- let text2 = base ?? "";
4409
- if (records.length)
4410
- text2 += (text2 && !text2.endsWith("\n") ? "\n" : "") + records.map((h) => JSON.stringify(h) + "\n").join("");
4411
4552
  const path3 = historyPath(spec2);
4412
- const next = base !== void 0 || records.length ? text2 : null;
4553
+ const next = historyText(base, records);
4413
4554
  if ((currentFiles.get(path3) ?? null) !== next)
4414
4555
  writes.set(path3, next);
4415
4556
  }
4416
- for (const set2 of currentBundle.documents) {
4417
- const path3 = documentHistoryPath(set2.kind);
4418
- const base = baseFiles.get(path3);
4419
- const records = (documentPending.get(set2.kind) ?? []).sort((a, b) => a.documents[0] < b.documents[0] ? -1 : 1);
4420
- let text2 = base ?? "";
4421
- if (records.length)
4422
- text2 += (text2 && !text2.endsWith("\n") ? "\n" : "") + records.map((h) => JSON.stringify(h) + "\n").join("");
4423
- const next = base !== void 0 || records.length ? text2 : null;
4424
- if ((currentFiles.get(path3) ?? null) !== next)
4425
- writes.set(path3, next);
4557
+ {
4558
+ const records = wikiPending.sort((a, b) => a.documents[0] < b.documents[0] ? -1 : 1);
4559
+ const next = historyText(baseFiles.get(WIKI_HISTORY_PATH), records);
4560
+ if ((currentFiles.get(WIKI_HISTORY_PATH) ?? null) !== next)
4561
+ writes.set(WIKI_HISTORY_PATH, next);
4426
4562
  }
4427
4563
  return {
4428
4564
  writes,
4429
4565
  changes: delta.changes,
4430
4566
  specChanges: delta.specChanges,
4431
- reasons: [...[...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))), ...[...documentPending].flatMap(([kind, records]) => records.map((record3) => ({ kind, ...record3 })))],
4567
+ reasons: [...[...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))), ...wikiPending.map((record3) => ({ kind: "wiki", ...record3 }))],
4432
4568
  withoutReason: delta.changes.filter((c) => !covered.has(c.id)).map((c) => c.id)
4433
4569
  };
4434
4570
  }
@@ -4629,7 +4765,7 @@ function createRepositoryReader(cwd, options = {}) {
4629
4765
  return output2;
4630
4766
  };
4631
4767
  const discover = async () => {
4632
- const scalar = async (flag) => decodeGitLine(await invoke(["rev-parse", "--path-format=absolute", flag]));
4768
+ const scalar2 = async (flag) => decodeGitLine(await invoke(["rev-parse", "--path-format=absolute", flag]));
4633
4769
  const flags = ["--is-bare-repository", "--is-inside-git-dir", "--is-inside-work-tree"];
4634
4770
  const paths2 = ["--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-object-format"];
4635
4771
  let combined;
@@ -4646,7 +4782,7 @@ function createRepositoryReader(cwd, options = {}) {
4646
4782
  }
4647
4783
  if (bare === "true" || internal === "true" || inside !== "true") throw new RepositoryReadError("UNSUPPORTED_REPOSITORY");
4648
4784
  let values = combined.slice(3);
4649
- if (values.length !== paths2.length) values = await Promise.all(paths2.map(scalar));
4785
+ if (values.length !== paths2.length) values = await Promise.all(paths2.map(scalar2));
4650
4786
  const [rootPath, gitDir, commonDir, objectFormat] = values;
4651
4787
  if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new RepositoryReadError("GIT_UNSUPPORTED");
4652
4788
  return { rootPath, gitDir, commonDir, objectFormat };
@@ -5381,10 +5517,10 @@ function cached(getter) {
5381
5517
  function nullish(input2) {
5382
5518
  return input2 === null || input2 === void 0;
5383
5519
  }
5384
- function cleanRegex(source) {
5385
- const start = source.startsWith("^") ? 1 : 0;
5386
- const end = source.endsWith("$") ? source.length - 1 : source.length;
5387
- return source.slice(start, end);
5520
+ function cleanRegex(source2) {
5521
+ const start = source2.startsWith("^") ? 1 : 0;
5522
+ const end = source2.endsWith("$") ? source2.length - 1 : source2.length;
5523
+ return source2.slice(start, end);
5388
5524
  }
5389
5525
  function floatSafeRemainder(val, step) {
5390
5526
  const ratio = val / step;
@@ -5452,28 +5588,28 @@ function putProp(target, key, value) {
5452
5588
  else
5453
5589
  target[key] = value;
5454
5590
  }
5455
- function mirrorShape(target, source, keys2, wrap) {
5456
- const raw = sourceShape(source);
5591
+ function mirrorShape(target, source2, keys2, wrap) {
5592
+ const raw = sourceShape(source2);
5457
5593
  for (const key of keys2) {
5458
5594
  const desc = Object.getOwnPropertyDescriptor(raw, key);
5459
5595
  if (!desc.enumerable)
5460
5596
  continue;
5461
5597
  if (desc.get) {
5462
5598
  deferProp(target, key, () => {
5463
- const value = source._zod.def.shape[key];
5599
+ const value = source2._zod.def.shape[key];
5464
5600
  return wrap ? wrap(value, key) : value;
5465
5601
  });
5466
5602
  } else
5467
5603
  putProp(target, key, wrap ? wrap(desc.value, key) : desc.value);
5468
5604
  }
5469
5605
  }
5470
- function mirrorProps(target, source) {
5471
- for (const key of Reflect.ownKeys(source)) {
5472
- const desc = Object.getOwnPropertyDescriptor(source, key);
5606
+ function mirrorProps(target, source2) {
5607
+ for (const key of Reflect.ownKeys(source2)) {
5608
+ const desc = Object.getOwnPropertyDescriptor(source2, key);
5473
5609
  if (!desc.enumerable)
5474
5610
  continue;
5475
5611
  if (desc.get)
5476
- deferProp(target, key, () => source[key]);
5612
+ deferProp(target, key, () => source2[key]);
5477
5613
  else
5478
5614
  putProp(target, key, desc.value);
5479
5615
  }
@@ -6686,8 +6822,8 @@ var creditCard = /^\d(?:[ -]?\d){11,18}$/;
6686
6822
  var currencyCode = /^(?:AED|AFN|ALL|AMD|AOA|ARS|AUD|AWG|AZN|BAM|BBD|BDT|BHD|BIF|BMD|BND|BOB|BOV|BRL|BSD|BTN|BWP|BYN|BZD|CAD|CDF|CHE|CHF|CHW|CLF|CLP|CNY|COP|COU|CRC|CUP|CVE|CZK|DJF|DKK|DOP|DZD|EGP|ERN|ETB|EUR|FJD|FKP|GBP|GEL|GHS|GIP|GMD|GNF|GTQ|GYD|HKD|HNL|HTG|HUF|IDR|ILS|INR|IQD|IRR|ISK|JMD|JOD|JPY|KES|KGS|KHR|KMF|KPW|KRW|KWD|KYD|KZT|LAK|LBP|LKR|LRD|LSL|LYD|MAD|MDL|MGA|MKD|MMK|MNT|MOP|MRU|MUR|MVR|MWK|MXN|MXV|MYR|MZN|NAD|NGN|NIO|NOK|NPR|NZD|OMR|PAB|PEN|PGK|PHP|PKR|PLN|PYG|QAR|RON|RSD|RUB|RWF|SAR|SBD|SCR|SDG|SEK|SGD|SHP|SLE|SOS|SRD|SSP|STN|SVC|SYP|SZL|THB|TJS|TMT|TND|TOP|TRY|TTD|TWD|TZS|UAH|UGX|USD|USN|UYI|UYU|UYW|UZS|VED|VES|VND|VUV|WST|XAD|XAF|XAG|XAU|XBA|XBB|XBC|XBD|XCD|XCG|XDR|XOF|XPD|XPF|XPT|XSU|XTS|XUA|XXX|YER|ZAR|ZMW|ZWG)$/;
6687
6823
  var iban = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
6688
6824
  var dateSource = `(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))`;
6689
- function anchor(source) {
6690
- return new RegExp(`^${source}$`);
6825
+ function anchor(source2) {
6826
+ return new RegExp(`^${source2}$`);
6691
6827
  }
6692
6828
  var date = /* @__PURE__ */ anchor(dateSource);
6693
6829
  function timeSource(args) {
@@ -9510,11 +9646,11 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
9510
9646
  const regexParts = [];
9511
9647
  for (const part of def.parts) {
9512
9648
  if (typeof part === "object" && part !== null) {
9513
- const source = partPattern(part);
9514
- if (!source) {
9649
+ const source2 = partPattern(part);
9650
+ if (!source2) {
9515
9651
  throw new Error(`Invalid template literal part, no pattern found: ${[...part._zod.traits].shift()}`);
9516
9652
  }
9517
- regexParts.push(cleanRegex(source));
9653
+ regexParts.push(cleanRegex(source2));
9518
9654
  } else if (part === null || primitiveTypes.has(typeof part)) {
9519
9655
  regexParts.push(escapeRegex(`${part}`));
9520
9656
  } else {
@@ -17640,9 +17776,9 @@ function withParser(schema, parser) {
17640
17776
  installCompiledUserMethods(clone2, schema, parser);
17641
17777
  return clone2;
17642
17778
  }
17643
- function installCompiledUserMethods(target, source, parser) {
17779
+ function installCompiledUserMethods(target, source2, parser) {
17644
17780
  const targetAny = target;
17645
- const sourceAny = source;
17781
+ const sourceAny = source2;
17646
17782
  if (typeof sourceAny.safeParse === "function") {
17647
17783
  const originalSafeParse = sourceAny.safeParse;
17648
17784
  targetAny.safeParse = (data, params) => {
@@ -20237,10 +20373,10 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
20237
20373
 
20238
20374
  // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/to-json-schema.js
20239
20375
  function assignProps(target, ...sources) {
20240
- for (const source of sources) {
20241
- for (const key of Reflect.ownKeys(source)) {
20242
- if (Object.prototype.propertyIsEnumerable.call(source, key)) {
20243
- assignProp(target, key, source[key]);
20376
+ for (const source2 of sources) {
20377
+ for (const key of Reflect.ownKeys(source2)) {
20378
+ if (Object.prototype.propertyIsEnumerable.call(source2, key)) {
20379
+ assignProp(target, key, source2[key]);
20244
20380
  }
20245
20381
  }
20246
20382
  }
@@ -24454,44 +24590,67 @@ var browserHttpErrorV1 = external_exports.strictObject({
24454
24590
  })
24455
24591
  });
24456
24592
 
24457
- // ../../packages/contracts/dist/versions/project-init-v4.js
24458
- var projectInitV4 = external_exports.discriminatedUnion("ok", [
24593
+ // ../../packages/contracts/dist/versions/browser-session-v2.js
24594
+ var release = external_exports.string().regex(/^\d+\.\d+\.\d+$/);
24595
+ var updateStateV1 = external_exports.strictObject({
24596
+ status: external_exports.enum(["checking", "available", "up-to-date", "unavailable", "disabled"]),
24597
+ latestVersion: release.nullable()
24598
+ }).refine((value) => (value.status === "available" || value.status === "up-to-date") === (value.latestVersion !== null), "latestVersion is set exactly when the check produced a result.");
24599
+ var browserSessionV2 = external_exports.strictObject({
24600
+ contract: external_exports.literal("browser-session"),
24601
+ version: external_exports.literal(2),
24602
+ sessionId: external_exports.uuid(),
24603
+ repository: external_exports.strictObject({
24604
+ key: external_exports.string().regex(/^repo:[0-9a-f]{64}$/),
24605
+ worktreeKey: external_exports.string().regex(/^worktree:[0-9a-f]{64}$/)
24606
+ }),
24607
+ // The running CLI, the same value as `gitifact --version`. A development build may carry a pre-release suffix.
24608
+ cliVersion: external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/),
24609
+ update: updateStateV1
24610
+ });
24611
+
24612
+ // ../../packages/contracts/dist/versions/project-init-v5.js
24613
+ var projectInitV5 = external_exports.discriminatedUnion("ok", [
24459
24614
  external_exports.strictObject({
24460
24615
  contract: external_exports.literal("project-init"),
24461
- version: external_exports.literal(4),
24616
+ version: external_exports.literal(5),
24462
24617
  ok: external_exports.literal(true),
24463
- outcome: external_exports.enum(["planned", "created", "already-initialized"]),
24618
+ outcome: external_exports.enum(["planned", "created", "replaced", "already-initialized"]),
24464
24619
  rootPath: external_exports.string(),
24465
24620
  configPath: external_exports.literal(".gitifact/config.json"),
24466
- schemaVersion: external_exports.literal(1),
24621
+ schemaVersion: external_exports.literal(2),
24467
24622
  baseline: external_exports.discriminatedUnion("kind", [
24468
24623
  external_exports.strictObject({ kind: external_exports.literal("empty") }),
24469
24624
  external_exports.strictObject({ kind: external_exports.literal("commit"), objectFormat: external_exports.enum(["sha1", "sha256"]), commit: external_exports.string() })
24470
24625
  ]),
24471
- agentDocs: external_exports.strictObject({ mode: external_exports.enum(["install", "remove", "skip"]), paths: external_exports.array(external_exports.string()) })
24626
+ agentDocs: external_exports.strictObject({ mode: external_exports.enum(["install", "remove", "skip"]), paths: external_exports.array(external_exports.string()) }),
24627
+ update: updateStateV1,
24628
+ // Present only when a newer release exists. The command is for npm global installs; other installs differ.
24629
+ install: external_exports.strictObject({ npmGlobal: external_exports.string().min(1) }).nullable()
24472
24630
  }),
24473
- external_exports.strictObject({ contract: external_exports.literal("project-init"), version: external_exports.literal(4), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24631
+ external_exports.strictObject({ contract: external_exports.literal("project-init"), version: external_exports.literal(5), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24474
24632
  ]);
24475
24633
 
24476
- // ../../packages/contracts/dist/versions/browser-specs-v1.js
24634
+ // ../../packages/contracts/dist/versions/browser-specs-v2.js
24477
24635
  var requirement = external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), body: external_exports.string() });
24478
24636
  var contributor = external_exports.strictObject({ email: external_exports.string(), name: external_exports.string(), commits: external_exports.number().int().nonnegative(), latest: external_exports.string() });
24637
+ var source = external_exports.strictObject({ title: external_exports.string(), path: external_exports.string().optional(), url: external_exports.string().optional(), note: external_exports.string().optional() });
24479
24638
  var feature = external_exports.strictObject({
24480
24639
  id: external_exports.string(),
24481
24640
  path: external_exports.string(),
24482
24641
  title: external_exports.string(),
24483
24642
  description: external_exports.string(),
24484
24643
  requirements: external_exports.array(requirement),
24485
- design: external_exports.strictObject({ title: external_exports.string(), body: external_exports.string(), requirements: external_exports.array(external_exports.string()) }).optional(),
24644
+ design: external_exports.strictObject({ title: external_exports.string(), body: external_exports.string(), requirements: external_exports.array(external_exports.string()), sources: external_exports.array(source) }).optional(),
24486
24645
  // Authors of commits touching the feature folder (current or legacy store) and the latest such commit; empty until first committed.
24487
24646
  contributors: external_exports.array(contributor),
24488
24647
  updatedAt: external_exports.string().nullable()
24489
24648
  });
24490
- var snapshot = requirement.extend({ specId: external_exports.string(), path: external_exports.string() }).nullable();
24491
- var document = external_exports.strictObject({ id: external_exports.string(), kind: external_exports.enum(["product", "guide"]), path: external_exports.string(), title: external_exports.string(), body: external_exports.string(), updatedAt: external_exports.string().nullable() });
24492
- var browserSpecsV1 = external_exports.strictObject({
24649
+ var snapshot = requirement.extend({ specId: external_exports.string(), path: external_exports.string(), sources: external_exports.array(source).optional() }).nullable();
24650
+ var document = external_exports.strictObject({ id: external_exports.string(), path: external_exports.string(), title: external_exports.string(), body: external_exports.string(), updatedAt: external_exports.string().nullable() });
24651
+ var browserSpecsV2 = external_exports.strictObject({
24493
24652
  contract: external_exports.literal("browser-specs"),
24494
- version: external_exports.literal(1),
24653
+ version: external_exports.literal(2),
24495
24654
  sessionId: external_exports.string(),
24496
24655
  head: external_exports.string().nullable(),
24497
24656
  observedAt: external_exports.string(),
@@ -24507,7 +24666,7 @@ var browserSpecsV1 = external_exports.strictObject({
24507
24666
  committer: external_exports.string(),
24508
24667
  message: external_exports.string(),
24509
24668
  id: external_exports.string(),
24510
- kind: external_exports.enum(["requirement", "design", "product", "guide"]).optional(),
24669
+ kind: external_exports.enum(["requirement", "design", "wiki"]).optional(),
24511
24670
  types: external_exports.array(external_exports.enum(["created", "modified", "moved", "deleted"])),
24512
24671
  before: snapshot,
24513
24672
  after: snapshot,
@@ -24544,26 +24703,7 @@ var changelogV1 = external_exports.strictObject({
24544
24703
  }
24545
24704
  });
24546
24705
 
24547
- // ../../packages/contracts/dist/versions/browser-session-v2.js
24548
- var release = external_exports.string().regex(/^\d+\.\d+\.\d+$/);
24549
- var updateStateV1 = external_exports.strictObject({
24550
- status: external_exports.enum(["checking", "available", "up-to-date", "unavailable", "disabled"]),
24551
- latestVersion: release.nullable()
24552
- }).refine((value) => (value.status === "available" || value.status === "up-to-date") === (value.latestVersion !== null), "latestVersion is set exactly when the check produced a result.");
24553
- var browserSessionV2 = external_exports.strictObject({
24554
- contract: external_exports.literal("browser-session"),
24555
- version: external_exports.literal(2),
24556
- sessionId: external_exports.uuid(),
24557
- repository: external_exports.strictObject({
24558
- key: external_exports.string().regex(/^repo:[0-9a-f]{64}$/),
24559
- worktreeKey: external_exports.string().regex(/^worktree:[0-9a-f]{64}$/)
24560
- }),
24561
- // The running CLI, the same value as `gitifact --version`. A development build may carry a pre-release suffix.
24562
- cliVersion: external_exports.string().regex(/^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/),
24563
- update: updateStateV1
24564
- });
24565
-
24566
- // ../../packages/contracts/dist/versions/update-v2.js
24706
+ // ../../packages/contracts/dist/versions/update-v3.js
24567
24707
  var commitReason = external_exports.enum(["untracked", "other-changes", "commit-failed"]);
24568
24708
  var agentDocsCommit = external_exports.strictObject({
24569
24709
  state: external_exports.enum(["not-requested", "nothing", "committed", "skipped"]),
@@ -24573,10 +24713,10 @@ var agentDocsCommit = external_exports.strictObject({
24573
24713
  reason: commitReason.nullable(),
24574
24714
  detail: external_exports.string().nullable()
24575
24715
  }).refine((value) => value.state === "committed" === (value.commit !== null) && value.state === "skipped" === (value.reason !== null), "commit is set exactly when committed, reason exactly when skipped.");
24576
- var updateV2 = external_exports.discriminatedUnion("ok", [
24716
+ var updateV3 = external_exports.discriminatedUnion("ok", [
24577
24717
  external_exports.strictObject({
24578
24718
  contract: external_exports.literal("update"),
24579
- version: external_exports.literal(2),
24719
+ version: external_exports.literal(3),
24580
24720
  ok: external_exports.literal(true),
24581
24721
  cliVersion: external_exports.string().min(1),
24582
24722
  update: updateStateV1,
@@ -24584,10 +24724,11 @@ var updateV2 = external_exports.discriminatedUnion("ok", [
24584
24724
  install: external_exports.strictObject({ npmGlobal: external_exports.string().min(1) }).nullable(),
24585
24725
  // refreshed: blocks whose version line differed were rewritten. current: every block already matched.
24586
24726
  // not-initialized: no .gitifact config here, so only the version check ran. no-block: no file carries a block.
24587
- agentDocs: external_exports.strictObject({ state: external_exports.enum(["refreshed", "current", "not-initialized", "no-block"]), paths: external_exports.array(external_exports.string()) }),
24727
+ // missing: files `gitifact init` would add and update does not create, e.g. CLAUDE.md when only AGENTS.md has the block.
24728
+ 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()) }),
24588
24729
  commit: agentDocsCommit
24589
24730
  }),
24590
- external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(2), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24731
+ external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(3), ok: external_exports.literal(false), error: external_exports.strictObject({ code: external_exports.string(), message: external_exports.string() }) })
24591
24732
  ]);
24592
24733
 
24593
24734
  // src/shared/i18n/ko/messages.json
@@ -24605,7 +24746,7 @@ var messages_default2 = {
24605
24746
  "help.initRemoveAgents": "GITIFACT \uBE14\uB85D\uC744 \uC81C\uAC70",
24606
24747
  "help.initSkipAgents": "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC74C",
24607
24748
  "help.docs": "\uC5D0\uC774\uC804\uD2B8\uC6A9 \uC791\uC5C5 \uC9C0\uCE68 \uCD9C\uB825 (\uC8FC\uC81C \uC5C6\uC774 \uC2E4\uD589\uD558\uBA74 \uBAA9\uB85D)",
24608
- "help.docsTopic": "workflow, spec, design, product, commit \uC911 \uD558\uB098",
24749
+ "help.docsTopic": "workflow, spec, design, wiki, commit \uC911 \uD558\uB098",
24609
24750
  "help.migrate": ".tryce \uC800\uC7A5\uC18C\uB97C .gitifact\uB85C \uC804\uD658 (\uACBD\uB85C\uC640 \uB9C8\uCEE4\uB9CC \uBC14\uAFB8\uACE0 ID\xB7\uC774\uC720\xB7\uC774\uB825\uC740 \uBCF4\uC874)",
24610
24751
  "help.migrateDryRun": "\uD30C\uC77C\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uC804\uD658 \uACC4\uD68D \uD655\uC778",
24611
24752
  "help.spec": "Markdown \uBA85\uC138 \uC791\uC131\xB7\uC870\uD68C\xB7Git \uAE30\uB85D",
@@ -24622,11 +24763,11 @@ var messages_default2 = {
24622
24763
  "help.specDiffFrom": "\uC774\uC804 \uCEE4\uBC0B",
24623
24764
  "help.specDiffTo": "\uC774\uD6C4 \uCEE4\uBC0B",
24624
24765
  "help.specChanges": "HEAD \uB300\uBE44 \uCD5C\uC885 \uBA85\uC138 \uCC28\uC774\uC640 \uBBF8\uCEE4\uBC0B \uC774\uC720 \uC870\uD68C",
24625
- "help.specWorking": "\uD604\uC7AC \uBA85\uC138\xB7\uC81C\uD488\xB7\uC9C0\uCE68 \uC804\uCCB4\uC640 stamp, \uC785\uB825 \uD30C\uC77C \uACBD\uB85C \uC870\uD68C",
24766
+ "help.specWorking": "\uD604\uC7AC \uBA85\uC138\xB7\uC704\uD0A4 \uC804\uCCB4\uC640 stamp, \uC785\uB825 \uD30C\uC77C \uACBD\uB85C \uC870\uD68C",
24626
24767
  "help.specWorkingStamp": "stamp\uC640 \uC785\uB825 \uD30C\uC77C \uACBD\uB85C\uB9CC \uCD9C\uB825",
24627
- "help.specWorkingFeature": "\uD55C \uAE30\uB2A5 \uD3F4\uB354\uC758 \uBA85\uC138\uB9CC \uCD9C\uB825 (\uC81C\uD488\xB7\uC9C0\uCE68 \uC81C\uC678)",
24768
+ "help.specWorkingFeature": "\uD55C \uAE30\uB2A5 \uD3F4\uB354\uC758 \uBA85\uC138\uB9CC \uCD9C\uB825 (\uC704\uD0A4 \uC81C\uC678)",
24628
24769
  "help.specWorkingIds": "\uBCF8\uBB38 \uC5C6\uC774 ID\xB7\uC81C\uBAA9\xB7\uACBD\uB85C\uB9CC \uCD9C\uB825",
24629
- "help.specSave": "\uC694\uAD6C\uC0AC\uD56D\xB7\uC124\uACC4\xB7\uBB38\uC11C \uC800\uC7A5",
24770
+ "help.specSave": "\uC694\uAD6C\uC0AC\uD56D\xB7\uC124\uACC4\xB7\uC704\uD0A4 \uC800\uC7A5",
24630
24771
  "help.specRead": "\uCEE4\uBC0B \uC2DC\uC810\uC758 \uBA85\uC138 \uC870\uD68C",
24631
24772
  "help.specDiff": "\uB450 \uCEE4\uBC0B \uC0AC\uC774\uC758 \uBA85\uC138 \uBCC0\uACBD \uC870\uD68C",
24632
24773
  "git.operationInProgress": "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: {marker}",
@@ -24722,7 +24863,6 @@ var messages_default2 = {
24722
24863
  "server.crossSite": "\uC678\uBD80 \uD398\uC774\uC9C0\uC758 \uC694\uCCAD\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24723
24864
  "server.noQuery": "\uC870\uD68C \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24724
24865
  "server.methodNotAllowed": "\uD5C8\uC6A9\uD558\uC9C0 \uC54A\uB294 \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.",
24725
- "server.productImageNotFound": "\uC81C\uD488 \uC774\uBBF8\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24726
24866
  "server.apiNotFound": "API\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24727
24867
  "server.sessionChanged": "\uC11C\uBC84 \uC138\uC158\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.",
24728
24868
  "server.badRange": "\uC870\uD68C \uBC94\uC704\uB97C \uD655\uC778\uD558\uC138\uC694.",
@@ -24777,6 +24917,7 @@ var messages_default2 = {
24777
24917
  "init.configDeleted": "\uAE30\uC874 \uC124\uC815\uC758 \uC0AD\uC81C\uB97C \uC2E0\uADDC \uB3C4\uC785\uC73C\uB85C \uCC98\uB9AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24778
24918
  "init.existingSpecs": "\uAE30\uC874 specs \uACBD\uB85C\uB97C \uD655\uC778\uD558\uC138\uC694.",
24779
24919
  "init.orphanStore": "\uC124\uC815 \uC5C6\uB294 .gitifact \uC790\uB8CC\uB97C \uC790\uB3D9 \uCC44\uD0DD\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24920
+ "init.legacyRecords": "\uC774 \uD504\uB85C\uC81D\uD2B8\uC5D0\uB294 \uC774\uC804 \uC800\uC7A5 \uADDC\uC57D(schemaVersion {version})\uC73C\uB85C \uC4F4 \uBA85\uC138\uB098 \uAE30\uB85D\uC774 \uC788\uC5B4 \uC124\uC815\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC774 CLI\uB294 \uC774\uC804 \uADDC\uC57D\uC744 \uC804\uD658\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uAE30\uB85D\uC744 \uB2E4\uB978 \uACF3\uC5D0 \uBCF4\uAD00\uD558\uACE0 .gitifact\uC5D0 config.json\uB9CC \uB0A8\uAE34 \uB4A4 \uB2E4\uC2DC init \uD558\uC138\uC694.",
24780
24921
  "init.headOrIndexChanged": "HEAD \uB610\uB294 index\uAC00 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4.",
24781
24922
  "init.configAppeared": "\uB2E4\uB978 \uC2E4\uD589\uC774 \uC124\uC815\uC744 \uB9CC\uB4E4\uC5C8\uC2B5\uB2C8\uB2E4.",
24782
24923
  "init.changedAfterWrite": "\uC124\uC815 \uC0DD\uC131 \uD6C4 \uC785\uB825\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uD604\uC7AC \uD30C\uC77C\uC744 \uD655\uC778\uD558\uC138\uC694.",
@@ -24786,6 +24927,7 @@ var messages_default2 = {
24786
24927
  "init.text.blockUpdated": "\uBE14\uB85D \uAC31\uC2E0",
24787
24928
  "init.text.storage": "\uC800\uC7A5 \uADDC\uC57D",
24788
24929
  "init.text.agentDocs": "\uC5D0\uC774\uC804\uD2B8 \uC9C0\uCE68",
24930
+ "init.text.updateAvailable": "\uC0C8 \uBC84\uC804 {version}\uC774 \uC788\uC2B5\uB2C8\uB2E4. {command}\uB85C \uC124\uCE58\uD55C \uB4A4 gitifact init\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC9C0\uCE68 \uBE14\uB85D\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
24789
24931
  "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.",
24790
24932
  "config.storeNotDirectory": ".gitifact\uB294 \uB9C1\uD06C\uAC00 \uC544\uB2CC \uC77C\uBC18 \uB514\uB809\uD130\uB9AC\uC5EC\uC57C \uD569\uB2C8\uB2E4.",
24791
24933
  "config.migrateFirst": ".tryce \uC800\uC7A5\uC18C\uC785\uB2C8\uB2E4. gitifact migrate\uB85C .gitifact\uC5D0 \uC804\uD658\uD558\uC138\uC694.",
@@ -24820,8 +24962,7 @@ var messages_default2 = {
24820
24962
  "browser.assetsUnreadable": "\uBE0C\uB77C\uC6B0\uC800 \uBE4C\uB4DC \uC790\uC0B0\uC744 \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. pnpm build\uB85C \uB2E4\uC2DC \uBE4C\uB4DC\uD558\uC138\uC694.",
24821
24963
  "docs.summary.workflow": "\uC2DC\uC791 \uC2DC \uD655\uC778\uD560 \uAC83, \uC694\uAD6C\uC0AC\uD56D\uC73C\uB85C \uB0A8\uAE38 \uC694\uCCAD\uC758 \uAD6C\uBD84, \uB9C8\uBB34\uB9AC \uBCF4\uACE0",
24822
24964
  "docs.summary.spec": "\uC0AC\uC6A9\uC790 \uC2A4\uD1A0\uB9AC\xB7\uC218\uC6A9 \uC870\uAC74 \uD615\uC2DD, ID \uADDC\uCE59, spec save \uC785\uB825",
24823
- "docs.summary.design": "design.md \uBAA9\uCC28, \uCC38\uC870 \uC8FC\uC11D, \uAC1C\uC815 \uBC29\uC2DD",
24824
- "docs.summary.product": "PRODUCT.md\uC640 guides \uBB38\uC11C\uC758 \uC5ED\uD560\uACFC \uC800\uC7A5 \uBA85\uB839",
24965
+ "docs.summary.design": "design.md \uBAA9\uCC28, \uCC38\uC870 \uC8FC\uC11D\uACFC \uCC38\uACE0 \uBB38\uC11C, \uAC1C\uC815 \uBC29\uC2DD",
24825
24966
  "docs.summary.commit": "spec commit \uC785\uB825, \uC774\uC720 \uAE30\uB85D, \uC2E4\uD328 \uD6C4 \uBCF5\uAD6C",
24826
24967
  "help.browserNoUpdateCheck": "\uC0C8 \uBC84\uC804 \uD655\uC778\uC744 \uC704\uD55C npm \uB808\uC9C0\uC2A4\uD2B8\uB9AC \uC870\uD68C\uB97C \uB054 (GITIFACT_NO_UPDATE_CHECK\uC640 \uAC19\uC74C)",
24827
24968
  "help.updateCommit": "\uAC31\uC2E0\uD55C GITIFACT \uBE14\uB85D\uC774 HEAD\uC640 \uBE14\uB85D \uC548\uC5D0\uC11C\uB9CC \uB2E4\uB974\uBA74 \uADF8 \uD30C\uC77C\uB9CC \uACE0\uC815 \uBA54\uC2DC\uC9C0\uB85C \uCEE4\uBC0B",
@@ -24840,13 +24981,19 @@ var messages_default2 = {
24840
24981
  "update.text.blockCurrent": "\uC9C0\uCE68 \uBE14\uB85D\uC740 \uC774\uBBF8 \uD604\uC7AC \uBC84\uC804\uC785\uB2C8\uB2E4: {paths}",
24841
24982
  "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.",
24842
24983
  "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.",
24984
+ "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.",
24843
24985
  "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
24844
24986
  "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
24845
24987
  "update.text.commitSkipped.untracked": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. Git\uC774 \uCD94\uC801\uD558\uC9C0 \uC54A\uB294 \uC9C0\uCE68 \uD30C\uC77C\uC785\uB2C8\uB2E4: {paths}",
24846
24988
  "update.text.commitSkipped.other-changes": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uBE14\uB85D \uBC16\uC5D0\uB3C4 \uCEE4\uBC0B\uB418\uC9C0 \uC54A\uC740 \uC218\uC815\uC774 \uC788\uC2B5\uB2C8\uB2E4: {paths}",
24847
24989
  "update.text.commitSkipped.commit-failed": "\uCEE4\uBC0B\uD558\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. git commit\uC774 \uAC70\uBD80\uB410\uACE0 \uAC31\uC2E0\uD55C \uD30C\uC77C\uC740 \uADF8\uB300\uB85C \uB0A8\uC544 \uC788\uC2B5\uB2C8\uB2E4: {paths}",
24848
24990
  "update.commitResultUnknown": "\uCEE4\uBC0B \uACB0\uACFC\uB97C \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. HEAD {commit}\uC758 \uBD80\uBAA8\uC640 \uBCC0\uACBD \uD30C\uC77C\uC744 \uC9C1\uC811 \uD655\uC778\uD558\uC138\uC694.",
24849
- "update.failed": "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4."
24991
+ "update.failed": "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.",
24992
+ "docs.summary.wiki": "\uD504\uB85C\uC81D\uD2B8 \uC704\uD0A4\uC758 \uD615\uC2DD\uACFC \uC800\uC7A5 \uBA85\uB839, \uC5D0\uC14B \uADDC\uCE59, \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uC704\uD0A4 \uC6B4\uC601 \uBC29\uCE68(wiki/README.md)",
24993
+ "server.assetNotFound": "\uC5D0\uC14B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24994
+ "docs.wikiPolicyProject": "\uC6B4\uC601 \uBC29\uCE68 (.gitifact/wiki/README.md)",
24995
+ "docs.wikiPolicyDefault": "\uC6B4\uC601 \uBC29\uCE68 (\uAE30\uBCF8\uAC12. .gitifact/wiki/README.md\uAC00 \uC5C6\uC5B4 \uB0B4\uC7A5 \uBC29\uCE68\uC744 \uC2E3\uB294\uB2E4)",
24996
+ "init.wikiReadmeTitle": "\uC704\uD0A4 \uC6B4\uC601 \uBC29\uCE68"
24850
24997
  };
24851
24998
 
24852
24999
  // src/shared/i18n/index.ts
@@ -24922,8 +25069,8 @@ async function runStatus(format) {
24922
25069
  import { createServer } from "node:http";
24923
25070
  import { randomUUID as randomUUID4 } from "node:crypto";
24924
25071
  import { fileURLToPath } from "node:url";
24925
- import { lstat as lstat3, readFile as readFile4 } from "node:fs/promises";
24926
- import { join as join4 } from "node:path";
25072
+ import { lstat as lstat4, readFile as readFile4 } from "node:fs/promises";
25073
+ import { join as join5 } from "node:path";
24927
25074
 
24928
25075
  // src/server/assets.ts
24929
25076
  import { readdir, readFile } from "node:fs/promises";
@@ -24956,6 +25103,7 @@ function contentType(path3) {
24956
25103
  if (/\.jpe?g$/.test(path3)) return "image/jpeg";
24957
25104
  if (path3.endsWith(".gif")) return "image/gif";
24958
25105
  if (path3.endsWith(".webp")) return "image/webp";
25106
+ if (path3.endsWith(".pdf")) return "application/pdf";
24959
25107
  return "application/octet-stream";
24960
25108
  }
24961
25109
 
@@ -25062,7 +25210,7 @@ function commandScoped(key, load) {
25062
25210
  }
25063
25211
 
25064
25212
  // src/adapters/git/spec-preview-reader.ts
25065
- var RECORD_PATHSPECS = [...STORE_DIRS.map((d) => d + "/spec/"), ...Object.values(DOCUMENT_DIRS).map((d) => d + "/")];
25213
+ var RECORD_PATHSPECS = [...STORE_DIRS.map((d) => d + "/spec/"), WIKI_DIR + "/"];
25066
25214
  var LegacyBaselineError = class extends SpecPreviewError {
25067
25215
  };
25068
25216
  function specPreviewReader(cwd) {
@@ -25096,7 +25244,7 @@ function specPreviewReader(cwd) {
25096
25244
  const [, mode, oid2, stage, path3] = match;
25097
25245
  if (!["100644", "100755"].includes(mode) || stage !== "0") throw new SpecPreviewError(t2("reader.stagingConflictOrLink"));
25098
25246
  if (!recordPathPattern.test(path3)) {
25099
- if (/^\.gitifact\/(?:product|guides)\//.test(path3)) continue;
25247
+ if (path3.startsWith(WIKI_DIR + "/")) continue;
25100
25248
  throw new SpecPreviewError(t2("reader.stagingUnsupported"));
25101
25249
  }
25102
25250
  files.set(path3, oid2);
@@ -25192,38 +25340,85 @@ function specPreviewReader(cwd) {
25192
25340
  }
25193
25341
 
25194
25342
  // src/adapters/filesystem/spec-preview-store.ts
25195
- import { lstat as lstat2, readdir as readdir2, readFile as readFile3, mkdir as mkdir2, writeFile, rename, unlink as unlink2, rmdir } from "node:fs/promises";
25196
- import { join as join3, dirname } from "node:path";
25343
+ import { lstat as lstat3, readdir as readdir3, readFile as readFile3, mkdir as mkdir2, writeFile, rename as rename2, unlink as unlink2, rmdir } from "node:fs/promises";
25344
+ import { join as join4, dirname } from "node:path";
25197
25345
  import { createHash as createHash2, randomBytes } from "node:crypto";
25198
25346
 
25199
- // src/adapters/filesystem/config-file.ts
25200
- import { lstat, readFile as readFile2, realpath, mkdir, open as open2, link, unlink } from "node:fs/promises";
25347
+ // src/adapters/filesystem/working-warnings.ts
25348
+ import { lstat, readdir as readdir2 } from "node:fs/promises";
25201
25349
  import { join as join2 } from "node:path";
25350
+ var info = async (path3) => lstat(path3).catch((e) => {
25351
+ if (e.code === "ENOENT") return void 0;
25352
+ throw e;
25353
+ });
25354
+ var posix = (path3) => path3.split("\\").join("/");
25355
+ async function listAssets(root) {
25356
+ const out = [];
25357
+ async function visit2(relative, depth) {
25358
+ const stat3 = await info(join2(root, relative));
25359
+ if (!stat3 || stat3.isSymbolicLink()) return;
25360
+ if (stat3.isDirectory()) {
25361
+ if (depth > 8 || out.length > 4e3) return;
25362
+ for (const name of (await readdir2(join2(root, relative))).sort()) await visit2(relative + "/" + name, depth + 1);
25363
+ } else if (stat3.isFile()) out.push({ path: posix(relative), bytes: stat3.size });
25364
+ }
25365
+ await visit2(ASSETS_DIR, 0);
25366
+ return out;
25367
+ }
25368
+ async function workingWarnings(root, bundle) {
25369
+ const warnings = designReferenceWarnings(bundle.specs);
25370
+ const records = /* @__PURE__ */ new Set([...bundle.specs.flatMap((s) => [s.path, ...s.design ? [s.path.replace(/requirements\.md$/, "design.md")] : []]), ...bundle.wiki.documents.map((d) => d.path)]);
25371
+ const assets = await listAssets(root);
25372
+ const assetPaths = new Set(assets.map((a) => a.path));
25373
+ const referenced = /* @__PURE__ */ new Set();
25374
+ for (const { from, link: link3, target } of documentLinks(bundle)) {
25375
+ if (records.has(target)) continue;
25376
+ if (assetPaths.has(target)) {
25377
+ referenced.add(target);
25378
+ continue;
25379
+ }
25380
+ const stat3 = target.startsWith(".gitifact/") ? void 0 : await info(join2(root, ...target.split("/")));
25381
+ if (!stat3?.isFile()) warnings.push({ code: "MISSING_LINK_TARGET", path: from, target, link: link3 });
25382
+ }
25383
+ let total = 0;
25384
+ for (const asset of assets) {
25385
+ total += asset.bytes;
25386
+ if (asset.bytes > ASSET_SIZE_LIMIT) warnings.push({ code: "ASSET_SIZE", path: asset.path, bytes: asset.bytes });
25387
+ if (!RECOMMENDED_ASSET_EXTENSIONS.includes(assetExtension(asset.path))) warnings.push({ code: "ASSET_EXTENSION", path: asset.path });
25388
+ if (!referenced.has(asset.path)) warnings.push({ code: "UNREFERENCED_ASSET", path: asset.path });
25389
+ }
25390
+ if (total > ASSETS_TOTAL_LIMIT) warnings.push({ code: "ASSETS_TOTAL_SIZE", bytes: total });
25391
+ return warnings;
25392
+ }
25393
+
25394
+ // src/adapters/filesystem/config-file.ts
25395
+ import { lstat as lstat2, readFile as readFile2, realpath, mkdir, open as open2, link, rename, unlink } from "node:fs/promises";
25396
+ import { join as join3 } from "node:path";
25202
25397
  import { randomUUID as randomUUID3 } from "node:crypto";
25203
25398
  var missing = (error62) => error62.code === "ENOENT";
25204
- var fileInfo = (path3) => lstat(path3).catch((error62) => {
25399
+ var fileInfo = (path3) => lstat2(path3).catch((error62) => {
25205
25400
  if (missing(error62)) return void 0;
25206
25401
  throw error62;
25207
25402
  });
25208
25403
  async function configDirectory(root) {
25209
- const path3 = join2(root, ".gitifact");
25210
- const info5 = await fileInfo(path3);
25211
- if (info5 && (!info5.isDirectory() || info5.isSymbolicLink())) throw new InitError("PATH_CONFLICT", t2("config.storeNotDirectory"));
25212
- return info5;
25404
+ const path3 = join3(root, ".gitifact");
25405
+ const info7 = await fileInfo(path3);
25406
+ if (info7 && (!info7.isDirectory() || info7.isSymbolicLink())) throw new InitError("PATH_CONFLICT", t2("config.storeNotDirectory"));
25407
+ return info7;
25213
25408
  }
25214
25409
  async function readConfigFile(root) {
25215
25410
  await configDirectory(root);
25216
- const path3 = join2(root, ".gitifact", "config.json");
25217
- const info5 = await fileInfo(path3);
25218
- if (!info5) {
25219
- if (await fileInfo(join2(root, ".tryce", "config.json"))) throw new InitError("MIGRATION_REQUIRED", t2("config.migrateFirst"));
25411
+ const path3 = join3(root, ".gitifact", "config.json");
25412
+ const info7 = await fileInfo(path3);
25413
+ if (!info7) {
25414
+ if (await fileInfo(join3(root, ".tryce", "config.json"))) throw new InitError("MIGRATION_REQUIRED", t2("config.migrateFirst"));
25220
25415
  return void 0;
25221
25416
  }
25222
- if (!info5.isFile() || info5.isSymbolicLink() || info5.size > 65536) throw new InitError("PATH_CONFLICT", t2("config.fileShape"));
25417
+ if (!info7.isFile() || info7.isSymbolicLink() || info7.size > 65536) throw new InitError("PATH_CONFLICT", t2("config.fileShape"));
25223
25418
  const bytes = await readFile2(path3);
25224
25419
  const after = await fileInfo(path3);
25225
25420
  await configDirectory(root);
25226
- if (!after || after.ino !== info5.ino || after.dev !== info5.dev || after.mtimeMs !== info5.mtimeMs || bytes.length > 65536) {
25421
+ if (!after || after.ino !== info7.ino || after.dev !== info7.dev || after.mtimeMs !== info7.mtimeMs || bytes.length > 65536) {
25227
25422
  throw new InitError("INPUT_CHANGED", t2("config.changedWhileReading"));
25228
25423
  }
25229
25424
  try {
@@ -25232,8 +25427,8 @@ async function readConfigFile(root) {
25232
25427
  throw new InitError("INVALID_CONFIG", t2("config.notUtf8Json"));
25233
25428
  }
25234
25429
  }
25235
- async function publishConfig(root, text2, recheck, beforePublish) {
25236
- const directory = join2(root, ".gitifact");
25430
+ async function publishConfig(root, text2, recheck, beforePublish, replace = false) {
25431
+ const directory = join3(root, ".gitifact");
25237
25432
  await configDirectory(root);
25238
25433
  await mkdir(directory).catch((error62) => {
25239
25434
  if (error62.code !== "EEXIST") throw error62;
@@ -25246,7 +25441,7 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25246
25441
  throw new InitError("INPUT_CHANGED", t2("config.directoryChanged"));
25247
25442
  }
25248
25443
  };
25249
- const temporary = join2(directory, ".init-" + randomUUID3() + ".tmp");
25444
+ const temporary = join3(directory, ".init-" + randomUUID3() + ".tmp");
25250
25445
  const handle = await open2(temporary, "wx", 384);
25251
25446
  const tempOwner = await handle.stat();
25252
25447
  try {
@@ -25263,7 +25458,8 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25263
25458
  const bytes = await readFile2(temporary, "utf8");
25264
25459
  if (bytes !== text2) throw new InitError("INPUT_CHANGED", t2("config.temporaryChanged"));
25265
25460
  parseManagedConfig(bytes);
25266
- await link(temporary, join2(directory, "config.json"));
25461
+ if (replace) await rename(temporary, join3(directory, "config.json"));
25462
+ else await link(temporary, join3(directory, "config.json"));
25267
25463
  } finally {
25268
25464
  await unchanged();
25269
25465
  const current = await fileInfo(temporary);
@@ -25275,14 +25471,14 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25275
25471
  var fail4 = (message) => {
25276
25472
  throw new SpecPreviewError(message);
25277
25473
  };
25278
- var info = async (path3) => lstat2(path3).catch((e) => {
25474
+ var info2 = async (path3) => lstat3(path3).catch((e) => {
25279
25475
  if (e.code === "ENOENT") return void 0;
25280
25476
  throw e;
25281
25477
  });
25282
25478
  var digest = (value) => createHash2("sha256").update(value).digest("hex");
25283
25479
  var LEGACY_LOCKS = ["tryce-spec-preview.lock", "tryce-spec-commit.lock"];
25284
25480
  async function failOnLegacyLock(gitDir) {
25285
- for (const name of LEGACY_LOCKS) if (await info(join3(gitDir, name))) fail4(t2("store.legacyLock", { path: join3(gitDir, name) }));
25481
+ for (const name of LEGACY_LOCKS) if (await info2(join4(gitDir, name))) fail4(t2("store.legacyLock", { path: join4(gitDir, name) }));
25286
25482
  }
25287
25483
  var decode3 = (bytes) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
25288
25484
  var generatePreviewId = (prefix) => prefix + "-" + [...randomBytes(10)].map((n) => "abcdefghijklmnopqrstuvwxyz234567"[n & 31]).join("");
@@ -25292,50 +25488,50 @@ async function snapshot2(root) {
25292
25488
  const files = /* @__PURE__ */ new Map();
25293
25489
  let count = 0;
25294
25490
  let bytes = 0;
25295
- const parent = await info(join3(root, ".gitifact"));
25491
+ const parent = await info2(join4(root, ".gitifact"));
25296
25492
  if (parent && (!parent.isDirectory() || parent.isSymbolicLink())) fail4(t2("store.storeNotDirectory"));
25297
- if (!parent && await info(join3(root, ".tryce"))) fail4(t2("store.migrateFirst"));
25493
+ if (!parent && await info2(join4(root, ".tryce"))) fail4(t2("store.migrateFirst"));
25298
25494
  const config2 = await readConfigFile(root);
25299
- if (config2 !== void 0 && !("schemaVersion" in parseManagedConfig(config2)) || await info(join3(root, "specs"))) fail4(t2("store.legacyProject"));
25495
+ if (config2 !== void 0 && !("schemaVersion" in parseManagedConfig(config2)) || await info2(join4(root, "specs"))) fail4(t2("store.legacyProject"));
25300
25496
  async function visit2(path3, depth, documents) {
25301
- const stat3 = await info(join3(root, path3));
25497
+ const stat3 = await info2(join4(root, path3));
25302
25498
  if (!stat3) return;
25303
25499
  if (stat3.isSymbolicLink() || !stat3.isDirectory() && !stat3.isFile()) fail4(t2("store.linkOrSpecial", { path: path3 }));
25304
25500
  if (++count > 4e3) fail4(t2("store.tooManyFiles"));
25305
25501
  if (stat3.isDirectory()) {
25306
25502
  if (depth > (documents ? 8 : 1)) fail4(t2("store.tooDeep", { path: path3 }));
25307
- for (const name of (await readdir2(join3(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1, documents);
25503
+ for (const name of (await readdir3(join4(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1, documents);
25308
25504
  } else {
25309
25505
  if (path3.endsWith("/tryce.json")) fail4(t2("store.legacyJson"));
25310
25506
  if (documents ? !(path3.endsWith(".md") || depth === 1 && path3.endsWith("/history.jsonl")) : !/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
25311
25507
  if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/")) fail4(t2("store.unsupportedPath", { path: path3 }));
25312
25508
  if (stat3.nlink !== 1 || stat3.size > 1024 * 1024) fail4(t2("store.hardLinkOrSize"));
25313
- const raw = await readFile3(join3(root, path3));
25509
+ const raw = await readFile3(join4(root, path3));
25314
25510
  bytes += raw.length;
25315
25511
  if (raw.length > 1024 * 1024 || bytes > 16 * 1024 * 1024) fail4(t2("store.sizeLimit"));
25316
25512
  files.set(path3, decode3(raw));
25317
25513
  }
25318
25514
  }
25319
25515
  await visit2(".gitifact/spec", 0, false);
25320
- for (const dir of Object.values(DOCUMENT_DIRS)) await visit2(dir, 0, true);
25516
+ await visit2(WIKI_DIR, 0, true);
25321
25517
  const bundle = parsePreviewBundle(files);
25322
- return { files, specs: bundle.specs, documents: bundle.documents, bundle, config: config2, stamp: digest(JSON.stringify([...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (config2 ?? "")) };
25518
+ return { root, files, specs: bundle.specs, wiki: bundle.wiki, bundle, config: config2, stamp: digest(JSON.stringify([...files].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (config2 ?? "")) };
25323
25519
  }
25324
25520
  async function readWorkingPreviewState(cwd) {
25325
25521
  const { root, gitDir } = await specPreviewReader(cwd).location();
25326
25522
  await failOnLegacyLock(gitDir);
25327
- if (await info(join3(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.locked"));
25523
+ if (await info2(join4(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.locked"));
25328
25524
  const first = await snapshot2(root);
25329
25525
  const second = await snapshot2(root);
25330
25526
  if (first.stamp !== second.stamp) fail4(t2("store.changedWhileReading"));
25331
- if (await info(join3(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.lockedWhileReading"));
25527
+ if (await info2(join4(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.lockedWhileReading"));
25332
25528
  return first;
25333
25529
  }
25334
25530
  async function readWorkingPreview(cwd) {
25335
- const { stamp, specs, documents } = await readWorkingPreviewState(cwd);
25336
- return { stamp, specs, documents, warnings: designReferenceWarnings(specs) };
25531
+ const { root, stamp, specs, wiki, bundle } = await readWorkingPreviewState(cwd);
25532
+ return { stamp, specs, wiki, warnings: await workingWarnings(root, bundle) };
25337
25533
  }
25338
- async function saveWorkingPreview(cwd, input2, publish = rename) {
25534
+ async function saveWorkingPreview(cwd, input2, publish = rename2) {
25339
25535
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) fail4(t2("store.invalidInput"));
25340
25536
  const request = input2;
25341
25537
  if (Object.keys(request).sort().join(",") !== "expected,operations" || typeof request.expected !== "string") fail4(t2("store.expectedOperations"));
@@ -25347,8 +25543,8 @@ async function saveWorkingPreview(cwd, input2, publish = rename) {
25347
25543
  if (!old || renderSpecPreview(old) !== renderSpecPreview(spec2)) writes.set(spec2.path, renderSpecPreview(spec2));
25348
25544
  if (JSON.stringify(old?.design) !== JSON.stringify(spec2.design)) writes.set(spec2.path.replace(/requirements\.md$/, "design.md"), spec2.design ? renderDesignPreview(spec2.id, spec2.design) : null);
25349
25545
  }
25350
- const previousDocs = before.documents.flatMap((s) => s.documents);
25351
- const nextDocs = result.documents.flatMap((s) => s.documents);
25546
+ const previousDocs = before.wiki.documents;
25547
+ const nextDocs = result.wiki.documents;
25352
25548
  for (const doc of nextDocs) {
25353
25549
  const old = previousDocs.find((d) => d.id === doc.id);
25354
25550
  if (old && old.path !== doc.path) writes.set(old.path, null);
@@ -25359,10 +25555,10 @@ async function saveWorkingPreview(cwd, input2, publish = rename) {
25359
25555
  }, publish);
25360
25556
  }
25361
25557
  var readLockedPreviewState = (root) => snapshot2(root);
25362
- async function previewTransaction(cwd, expected2, build, publish = rename, afterPublish) {
25558
+ async function previewTransaction(cwd, expected2, build, publish = rename2, afterPublish) {
25363
25559
  const { root, gitDir } = await specPreviewReader(cwd).location();
25364
25560
  await failOnLegacyLock(gitDir);
25365
- const lock = join3(gitDir, "gitifact-spec-preview.lock");
25561
+ const lock = join4(gitDir, "gitifact-spec-preview.lock");
25366
25562
  try {
25367
25563
  await mkdir2(lock);
25368
25564
  } catch (e) {
@@ -25375,7 +25571,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25375
25571
  const published = [];
25376
25572
  let keepRecovery = false;
25377
25573
  async function directory(path3) {
25378
- const stat3 = await info(path3);
25574
+ const stat3 = await info2(path3);
25379
25575
  if (stat3) {
25380
25576
  if (!stat3.isDirectory() || stat3.isSymbolicLink()) fail4(t2("store.notDirectory", { path: path3 }));
25381
25577
  return;
@@ -25399,14 +25595,14 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25399
25595
  else finalFiles.set(c.path, c.after);
25400
25596
  }
25401
25597
  if (finalFiles.size > 2e3 || [...finalFiles.values()].reduce((n, s) => n + Buffer.byteLength(s), 0) > 16 * 1024 * 1024) fail4(t2("store.totalLimit"));
25402
- await writeFile(join3(lock, "recovery.json"), JSON.stringify({ root, changed }), { flag: "wx" });
25598
+ await writeFile(join4(lock, "recovery.json"), JSON.stringify({ root, changed }), { flag: "wx" });
25403
25599
  for (const c of changed) {
25404
25600
  if (c.after === null) {
25405
25601
  temporary.push("");
25406
25602
  continue;
25407
25603
  }
25408
- await directory(dirname(join3(root, c.path)));
25409
- const temp = join3(dirname(join3(root, c.path)), ".gitifact-save-" + randomBytes(12).toString("hex"));
25604
+ await directory(dirname(join4(root, c.path)));
25605
+ const temp = join4(dirname(join4(root, c.path)), ".gitifact-save-" + randomBytes(12).toString("hex"));
25410
25606
  await writeFile(temp, c.after, { flag: "wx" });
25411
25607
  temporary.push(temp);
25412
25608
  }
@@ -25414,8 +25610,8 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25414
25610
  await result.recheck?.();
25415
25611
  for (let i = 0; i < changed.length; i++) {
25416
25612
  const c = changed[i];
25417
- const path3 = join3(root, c.path);
25418
- const stat3 = await info(path3);
25613
+ const path3 = join4(root, c.path);
25614
+ const stat3 = await info2(path3);
25419
25615
  if (stat3?.isSymbolicLink() || stat3 && !stat3.isFile() || (stat3 ? decode3(await readFile3(path3)) : null) !== c.before) fail4(t2("store.changedBeforeWrite"));
25420
25616
  if (c.after === null) await unlink2(path3);
25421
25617
  else await publish(temporary[i], path3);
@@ -25425,7 +25621,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25425
25621
  if (after.stamp !== digest(JSON.stringify([...finalFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (before.config ?? ""))) fail4(t2("store.changedWhileWriting"));
25426
25622
  await result.recheck?.();
25427
25623
  await afterPublish?.(after);
25428
- return { ...result.data, stamp: after.stamp, paths: changed.map((c) => c.path), specs: after.specs, documents: after.documents, warnings: designReferenceWarnings(after.specs) };
25624
+ return { ...result.data, stamp: after.stamp, paths: changed.map((c) => c.path), specs: after.specs, wiki: after.wiki, warnings: await workingWarnings(root, after.bundle) };
25429
25625
  } catch (error62) {
25430
25626
  if (error62 instanceof PreservedPreviewError) {
25431
25627
  keepRecovery = true;
@@ -25433,18 +25629,18 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25433
25629
  }
25434
25630
  for (const c of [...published].reverse()) {
25435
25631
  try {
25436
- const path3 = join3(root, c.path);
25437
- const stat3 = await info(path3);
25632
+ const path3 = join4(root, c.path);
25633
+ const stat3 = await info2(path3);
25438
25634
  if (c.after === null ? !!stat3 : !stat3?.isFile() || stat3.isSymbolicLink() || decode3(await readFile3(path3)) !== c.after) {
25439
25635
  keepRecovery = true;
25440
25636
  continue;
25441
25637
  }
25442
25638
  if (c.before === null) await unlink2(path3);
25443
25639
  else {
25444
- const temp = join3(dirname(path3), ".gitifact-restore-" + randomBytes(12).toString("hex"));
25640
+ const temp = join4(dirname(path3), ".gitifact-restore-" + randomBytes(12).toString("hex"));
25445
25641
  temporary.push(temp);
25446
25642
  await writeFile(temp, c.before, { flag: "wx" });
25447
- await rename(temp, path3);
25643
+ await rename2(temp, path3);
25448
25644
  }
25449
25645
  } catch {
25450
25646
  keepRecovery = true;
@@ -25459,7 +25655,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25459
25655
  for (const path3 of [...createdDirs].reverse()) await rmdir(path3).catch(() => {
25460
25656
  });
25461
25657
  if (!keepRecovery) {
25462
- await unlink2(join3(lock, "recovery.json")).catch((e) => {
25658
+ await unlink2(join4(lock, "recovery.json")).catch((e) => {
25463
25659
  if (e.code !== "ENOENT") throw e;
25464
25660
  });
25465
25661
  await rmdir(lock);
@@ -25501,8 +25697,8 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25501
25697
  if (cursor > 0 && expectedHead !== base.head) throw new SpecPreviewError(t2("specReader.historyChanged"));
25502
25698
  const current = await readWorkingPreviewState(root);
25503
25699
  const bare = current.specs.map(({ history: _history, ...s }) => ({ ...s, contributors: [], updatedAt: null }));
25504
- const bareDocuments = current.documents.flatMap((set2) => set2.documents).map((d) => ({ ...d, updatedAt: null }));
25505
- if (!base.head) return browserSpecsV1.parse({ contract: "browser-specs", version: 1, sessionId, head: null, observedAt: (/* @__PURE__ */ new Date()).toISOString(), working: bare.length > 0 || bareDocuments.length > 0, features: bare, documents: bareDocuments, events: [], contributors: [], contributorsLimited: false, nextCursor: null, boundary: false });
25700
+ const bareDocuments = current.wiki.documents.map((d) => ({ ...d, updatedAt: null }));
25701
+ if (!base.head) return browserSpecsV2.parse({ contract: "browser-specs", version: 2, sessionId, head: null, observedAt: (/* @__PURE__ */ new Date()).toISOString(), working: bare.length > 0 || bareDocuments.length > 0, features: bare, documents: bareDocuments, events: [], contributors: [], contributorsLimited: false, nextCursor: null, boundary: false });
25506
25702
  const head = base.head;
25507
25703
  const features = await Promise.all(bare.map(async (feature2) => {
25508
25704
  const folder = feature2.path.replace(/^\.(?:gitifact|tryce)\/(spec\/[^/]+)\/requirements\.md$/, "$1");
@@ -25518,7 +25714,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25518
25714
  return { ...feature2, contributors: [...people.values()].sort((a, b) => b.commits - a.commits), updatedAt: lines[0]?.split("\0")[2] ?? null };
25519
25715
  }));
25520
25716
  const documents = await Promise.all(bareDocuments.map(async (doc) => ({ ...doc, updatedAt: (await git(["log", "--format=%aI", "--max-count=1", head, "--", doc.path])).trim() || null })));
25521
- const documentDirs = Object.values(DOCUMENT_DIRS);
25717
+ const documentDirs = [WIKI_DIR];
25522
25718
  const [rows, dirty] = await Promise.all([
25523
25719
  git([
25524
25720
  "log",
@@ -25552,7 +25748,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25552
25748
  const [commit, parents, author, email3, date5, committer, message] = row.split("\0");
25553
25749
  if (!commit || author === void 0 || email3 === void 0 || !date5 || committer === void 0 || message === void 0) throw new SpecPreviewError(t2("specReader.historyUnreadable"));
25554
25750
  const after = await snapshot3(commit);
25555
- let before = { specs: [], documents: [{ kind: "product", documents: [], history: [] }, { kind: "guide", documents: [], history: [] }] };
25751
+ let before = emptyBundle();
25556
25752
  const parent = parents?.split(" ")[0];
25557
25753
  if (parent) {
25558
25754
  try {
@@ -25579,9 +25775,9 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25579
25775
  }));
25580
25776
  }));
25581
25777
  if (await readHead() !== head || (await readWorkingPreviewState(root)).stamp !== current.stamp) throw new SpecPreviewError(t2("specReader.projectChanged"));
25582
- return browserSpecsV1.parse({
25778
+ return browserSpecsV2.parse({
25583
25779
  contract: "browser-specs",
25584
- version: 1,
25780
+ version: 2,
25585
25781
  sessionId,
25586
25782
  head,
25587
25783
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -25675,18 +25871,27 @@ async function startBrowserServer(options) {
25675
25871
  if (api) {
25676
25872
  if (request.headers["sec-fetch-site"] === "cross-site" && !allowedOrigin) return fail8(response, 403, "FORBIDDEN", t2("server.crossSite"));
25677
25873
  if (url2.search && path3 !== "/api/v1/specs" && path3 !== "/api/v1/changelog") return fail8(response, 400, "BAD_REQUEST", t2("server.noQuery"));
25678
- if (path3.startsWith("/api/v1/product/assets/")) {
25679
- const name = path3.slice("/api/v1/product/assets/".length);
25874
+ if (path3.startsWith("/api/v1/assets/")) {
25875
+ const relative = path3.slice("/api/v1/assets/".length);
25876
+ const assetPath2 = ASSETS_DIR + "/" + relative;
25680
25877
  if (request.method !== "GET") {
25681
25878
  response.setHeader("Allow", "GET");
25682
25879
  return fail8(response, 405, "METHOD_NOT_ALLOWED", t2("server.methodNotAllowed"));
25683
25880
  }
25684
- if (!isProductAssetPath(".gitifact/product/" + name)) return fail8(response, 404, "NOT_FOUND", t2("server.productImageNotFound"));
25685
- const file2 = join4(initial.repository.rootPath, ".gitifact", "product", name);
25686
- const stat3 = await lstat3(file2).catch(() => void 0);
25687
- if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > 5 * 1024 * 1024) return fail8(response, 404, "NOT_FOUND", t2("server.productImageNotFound"));
25881
+ if (!isAssetPath(assetPath2)) return fail8(response, 404, "NOT_FOUND", t2("server.assetNotFound"));
25882
+ const file2 = join5(initial.repository.rootPath, ...assetPath2.split("/"));
25883
+ const stat3 = await lstat4(file2).catch(() => void 0);
25884
+ if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > 20 * 1024 * 1024) return fail8(response, 404, "NOT_FOUND", t2("server.assetNotFound"));
25688
25885
  const bytes = await readFile4(file2);
25689
- response.writeHead(200, { "Content-Type": contentType(name.toLowerCase()), "Cache-Control": "no-cache", "Content-Length": bytes.length });
25886
+ const extension = assetExtension(relative);
25887
+ const inline = ["png", "jpg", "jpeg", "gif", "webp", "svg"].includes(extension);
25888
+ response.writeHead(200, {
25889
+ "Content-Type": contentType("." + extension),
25890
+ "Cache-Control": "no-cache",
25891
+ "Content-Length": bytes.length,
25892
+ "Content-Security-Policy": "default-src 'none'; sandbox",
25893
+ "Content-Disposition": inline ? "inline" : 'attachment; filename="' + encodeURIComponent(relative.split("/").pop()) + '"'
25894
+ });
25690
25895
  response.end(bytes);
25691
25896
  return;
25692
25897
  }
@@ -25850,13 +26055,13 @@ async function runBrowser(options, version2) {
25850
26055
  }
25851
26056
 
25852
26057
  // src/commands/spec-init.ts
25853
- import { readdir as readdir3 } from "node:fs/promises";
25854
- import { join as join7 } from "node:path";
26058
+ import { mkdir as mkdir4, readdir as readdir4, writeFile as writeFile2 } from "node:fs/promises";
26059
+ import { join as join9 } from "node:path";
25855
26060
 
25856
26061
  // src/adapters/git/init-repository.ts
25857
26062
  import { createHash as createHash3 } from "node:crypto";
25858
26063
  import { readFile as readFile5 } from "node:fs/promises";
25859
- import { join as join5 } from "node:path";
26064
+ import { join as join6 } from "node:path";
25860
26065
  function initRepository(cwd, inherited = process.env) {
25861
26066
  const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
25862
26067
  const runner = createGitRunner();
@@ -25876,7 +26081,7 @@ function initRepository(cwd, inherited = process.env) {
25876
26081
  const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
25877
26082
  const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
25878
26083
  for (const marker2 of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"]) {
25879
- if (await fileInfo(join5(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
26084
+ if (await fileInfo(join6(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
25880
26085
  }
25881
26086
  if (state.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
25882
26087
  const index = await readFile5(indexPath).catch((error62) => {
@@ -25913,23 +26118,23 @@ function initRepository(cwd, inherited = process.env) {
25913
26118
  }
25914
26119
 
25915
26120
  // src/adapters/filesystem/managed-file.ts
25916
- import { open as open3, readFile as readFile6, mkdir as mkdir3, realpath as realpath2, rename as rename2, link as link2, unlink as unlink3 } from "node:fs/promises";
25917
- import { join as join6, dirname as dirname2 } from "node:path";
26121
+ import { open as open3, readFile as readFile6, mkdir as mkdir3, realpath as realpath2, rename as rename3, link as link2, unlink as unlink3 } from "node:fs/promises";
26122
+ import { join as join7, dirname as dirname2 } from "node:path";
25918
26123
  import { randomUUID as randomUUID5 } from "node:crypto";
25919
26124
  var DEFAULT_LIMIT = 1024 * 1024;
25920
26125
  async function managedPath(root, path3, create = false) {
25921
26126
  let full = root;
25922
26127
  const parts = path3.split("/");
25923
26128
  for (let i = 0; i < parts.length; i++) {
25924
- full = join6(full, parts[i]);
25925
- let info5 = await fileInfo(full);
25926
- if (!info5 && create && i < parts.length - 1) {
26129
+ full = join7(full, parts[i]);
26130
+ let info7 = await fileInfo(full);
26131
+ if (!info7 && create && i < parts.length - 1) {
25927
26132
  await mkdir3(full).catch((error62) => {
25928
26133
  if (error62.code !== "EEXIST") throw error62;
25929
26134
  });
25930
- info5 = await fileInfo(full);
26135
+ info7 = await fileInfo(full);
25931
26136
  }
25932
- if (info5 && (info5.isSymbolicLink() || (i < parts.length - 1 ? !info5.isDirectory() : !info5.isFile()))) {
26137
+ if (info7 && (info7.isSymbolicLink() || (i < parts.length - 1 ? !info7.isDirectory() : !info7.isFile()))) {
25933
26138
  throw new InitError("PATH_CONFLICT", t2("managedFile.notRegularPath", { path: path3 }));
25934
26139
  }
25935
26140
  }
@@ -25969,7 +26174,7 @@ async function managedWrite(root, path3, previous, next, recheck) {
25969
26174
  if (previous !== null) await unlink3(full);
25970
26175
  return;
25971
26176
  }
25972
- const temp = join6(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
26177
+ const temp = join7(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
25973
26178
  const handle = await open3(temp, "wx", 384);
25974
26179
  const tempOwner = await handle.stat();
25975
26180
  try {
@@ -25983,7 +26188,7 @@ async function managedWrite(root, path3, previous, next, recheck) {
25983
26188
  await unchanged();
25984
26189
  if (await managedRead(root, path3) !== previous || await readFile6(temp, "utf8") !== next) throw new InitError("INPUT_CHANGED", t2("managedFile.publishTargetChanged", { path: path3 }));
25985
26190
  if (previous === null) await link2(temp, full);
25986
- else await rename2(temp, full);
26191
+ else await rename3(temp, full);
25987
26192
  } finally {
25988
26193
  await unchanged();
25989
26194
  const now = await fileInfo(temp);
@@ -25995,14 +26200,53 @@ async function managedWrite(root, path3, previous, next, recheck) {
25995
26200
  import { readFile as readFile8 } from "node:fs/promises";
25996
26201
 
25997
26202
  // src/commands/docs.ts
25998
- import { readFile as readFile7 } from "node:fs/promises";
25999
- var docTopics = ["workflow", "spec", "design", "product", "commit"];
26203
+ import { lstat as lstat5, readFile as readFile7 } from "node:fs/promises";
26204
+ import { dirname as dirname3, join as join8 } from "node:path";
26205
+ var docTopics = ["workflow", "spec", "design", "wiki", "commit"];
26000
26206
  var isDocTopic = (value) => docTopics.includes(value);
26001
- var readBundled = (topic, lang) => readFile7(new URL("./i18n/" + lang + "/docs/" + topic + ".md", import.meta.url), "utf8");
26207
+ var readBundledDoc = (name, lang) => readFile7(new URL("./i18n/" + lang + "/docs/" + name + ".md", import.meta.url), "utf8");
26208
+ var info3 = (path3) => lstat5(path3).catch((e) => {
26209
+ if (e.code === "ENOENT") return void 0;
26210
+ throw e;
26211
+ });
26002
26212
  function listDocTopics(lang = defaultLanguage2) {
26003
26213
  const width = Math.max(...docTopics.map((topic) => topic.length));
26004
26214
  return docTopics.map((topic) => topic.padEnd(width) + " " + t2(`docs.summary.${topic}`, {}, lang)).join("\n") + "\n";
26005
26215
  }
26216
+ async function findProjectRoot(cwd) {
26217
+ for (let dir = cwd; ; dir = dirname3(dir)) {
26218
+ if ((await info3(join8(dir, ".gitifact", "config.json")))?.isFile()) return dir;
26219
+ if (dirname3(dir) === dir) return void 0;
26220
+ }
26221
+ }
26222
+ async function readWikiPolicy(root) {
26223
+ if (!root) return void 0;
26224
+ const path3 = join8(root, ...WIKI_ENTRY_PATH.split("/"));
26225
+ const stat3 = await info3(path3);
26226
+ if (!stat3?.isFile() || stat3.size > 1024 * 1024) return void 0;
26227
+ const lines = (await readFile7(path3, "utf8")).replace(/\r\n/g, "\n").split("\n");
26228
+ if (lines[0] === "---") {
26229
+ const end = lines.indexOf("---", 1);
26230
+ if (end > 0) lines.splice(0, end + 1);
26231
+ }
26232
+ while (lines.length && !lines[0].trim()) lines.shift();
26233
+ if (lines[0]?.startsWith("# ")) lines.shift();
26234
+ let fence = false;
26235
+ const body = lines.map((line) => {
26236
+ if (/^ {0,3}(`{3,}|~{3,})/.test(line)) fence = !fence;
26237
+ return !fence && /^#{1,5} /.test(line) ? "#" + line : line;
26238
+ }).join("\n").trim();
26239
+ return body || void 0;
26240
+ }
26241
+ async function renderDoc(topic, controls = {}, lang = defaultLanguage2) {
26242
+ const read = controls.readDoc ?? readBundledDoc;
26243
+ const format = (await read(topic, lang)).trimEnd();
26244
+ if (topic !== "wiki") return format + "\n";
26245
+ const own2 = await readWikiPolicy(await findProjectRoot(controls.cwd ?? process.cwd()));
26246
+ const policy = own2 ?? (await read("wiki.default", lang)).trim().replace(/^(#{1,5}) /gm, "#$1 ");
26247
+ const heading = own2 ? t2("docs.wikiPolicyProject", {}, lang) : t2("docs.wikiPolicyDefault", {}, lang);
26248
+ return format + "\n\n## " + heading + "\n\n" + policy + "\n";
26249
+ }
26006
26250
  async function runDocs(topic, controls = {}) {
26007
26251
  if (topic === void 0) {
26008
26252
  process.stdout.write(listDocTopics());
@@ -26014,7 +26258,7 @@ async function runDocs(topic, controls = {}) {
26014
26258
  process.exitCode = 1;
26015
26259
  return;
26016
26260
  }
26017
- process.stdout.write(await (controls.readDoc ?? readBundled)(topic, defaultLanguage2));
26261
+ process.stdout.write(await renderDoc(topic, controls));
26018
26262
  }
26019
26263
 
26020
26264
  // src/commands/agent-block.ts
@@ -26055,6 +26299,16 @@ function isWrapperFile(text2) {
26055
26299
  const lines = own2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
26056
26300
  return lines.length > 0 && lines.every((line) => /^@(?:\.\/)?[^\s]+$/.test(line) && candidatePaths.includes(line.replace(/^@(?:\.\/)?/, "")));
26057
26301
  }
26302
+ var CLAUDE_WRAPPER_PATH = "CLAUDE.md";
26303
+ function claudeWrapperFor(agents) {
26304
+ return "@AGENTS.md" + eolOf(agents);
26305
+ }
26306
+ function isGeneratedClaudeWrapper(text2) {
26307
+ return text2.trim() === "@AGENTS.md";
26308
+ }
26309
+ function lacksClaudeFile(existing) {
26310
+ return !existing.has("CLAUDE.md") && !existing.has(".claude/CLAUDE.md");
26311
+ }
26058
26312
  function resolveAgentPaths(preset, existing) {
26059
26313
  if (preset && preset !== "all") {
26060
26314
  const order = agentPresets[preset];
@@ -26088,7 +26342,7 @@ function removeBlock(text2, boilerplate) {
26088
26342
  }
26089
26343
 
26090
26344
  // src/commands/agent-docs.ts
26091
- var skippedAgentDocs = { mode: "skip", paths: [], writes: [] };
26345
+ var skippedAgentDocs = { mode: "skip", paths: [], writes: [], missing: [] };
26092
26346
  async function planAgentDocs(root, options) {
26093
26347
  if (options.skip) return skippedAgentDocs;
26094
26348
  const existing = /* @__PURE__ */ new Map();
@@ -26106,7 +26360,12 @@ async function planAgentDocs(root, options) {
26106
26360
  paths2.push(path3);
26107
26361
  writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
26108
26362
  }
26109
- return { mode: "remove", paths: paths2, writes };
26363
+ const wrapper = existing.get(CLAUDE_WRAPPER_PATH);
26364
+ if (writes.some((write) => write.path === "AGENTS.md" && write.next === null) && wrapper !== void 0 && isGeneratedClaudeWrapper(wrapper)) {
26365
+ paths2.push(CLAUDE_WRAPPER_PATH);
26366
+ writes.push({ path: CLAUDE_WRAPPER_PATH, previous: wrapper, next: null });
26367
+ }
26368
+ return { mode: "remove", paths: paths2, writes, missing: [] };
26110
26369
  }
26111
26370
  const block = await renderAgentBlock(options.version, options);
26112
26371
  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);
@@ -26120,11 +26379,19 @@ async function planAgentDocs(root, options) {
26120
26379
  paths2.push(create);
26121
26380
  writes.push({ path: create, previous: null, next: injectBlock(null, block, boilerplateFor(create)) });
26122
26381
  }
26382
+ const missing2 = [];
26383
+ if (paths2.includes("AGENTS.md") && lacksClaudeFile(existing)) {
26384
+ if (options.onlyExisting) missing2.push(CLAUDE_WRAPPER_PATH);
26385
+ else {
26386
+ paths2.push(CLAUDE_WRAPPER_PATH);
26387
+ writes.push({ path: CLAUDE_WRAPPER_PATH, previous: null, next: claudeWrapperFor(existing.get("AGENTS.md") ?? null) });
26388
+ }
26389
+ }
26123
26390
  for (const [path3, text2] of existing) {
26124
26391
  if (paths2.includes(path3) || !isWrapperFile(text2) || !findBlock(text2)) continue;
26125
26392
  writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
26126
26393
  }
26127
- return { mode: "install", paths: paths2, writes };
26394
+ return { mode: "install", paths: paths2, writes, missing: missing2 };
26128
26395
  }
26129
26396
  async function applyAgentDocs(root, plan, recheck) {
26130
26397
  for (const write of plan.writes) await managedWrite(root, write.path, write.previous, write.next, recheck);
@@ -26132,22 +26399,27 @@ async function applyAgentDocs(root, plan, recheck) {
26132
26399
  }
26133
26400
 
26134
26401
  // src/commands/spec-init.ts
26135
- async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs) {
26402
+ async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs, readDoc = (name) => readBundledDoc(name, defaultLanguage2), update = disabledUpdate) {
26136
26403
  const repo = initRepository(cwd, env);
26137
26404
  const first = await repo.inspect();
26138
26405
  const root = first.state.repository.rootPath;
26139
26406
  const docsPlan = agentDocs ? await planAgentDocs(root, agentDocs) : skippedAgentDocs;
26140
- const result = (config3, outcome) => projectInitV4.parse({
26141
- contract: "project-init",
26142
- version: 4,
26143
- ok: true,
26144
- outcome,
26145
- rootPath: root,
26146
- configPath: ".gitifact/config.json",
26147
- schemaVersion: config3.schemaVersion,
26148
- baseline: config3.baseline,
26149
- agentDocs: { mode: docsPlan.mode, paths: docsPlan.paths }
26150
- });
26407
+ const result = async (config3, outcome) => {
26408
+ const checked = await update;
26409
+ return projectInitV5.parse({
26410
+ contract: "project-init",
26411
+ version: 5,
26412
+ ok: true,
26413
+ outcome,
26414
+ rootPath: root,
26415
+ configPath: ".gitifact/config.json",
26416
+ schemaVersion: config3.schemaVersion,
26417
+ baseline: config3.baseline,
26418
+ agentDocs: { mode: docsPlan.mode, paths: docsPlan.paths },
26419
+ update: checked,
26420
+ install: checked.status === "available" ? { npmGlobal: npmGlobalInstall(checked.latestVersion) } : null
26421
+ });
26422
+ };
26151
26423
  const existing = async (text3) => {
26152
26424
  const config3 = parseManagedConfig(text3);
26153
26425
  if (!("schemaVersion" in config3)) throw new InitError("MIGRATION_REQUIRED", t2("init.migrationRequired"));
@@ -26159,19 +26431,49 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
26159
26431
  if (!dryRun) await applyAgentDocs(root, docsPlan, unchanged);
26160
26432
  return result(config3, "already-initialized");
26161
26433
  };
26434
+ const config2 = { schemaVersion: SCHEMA_VERSION, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
26435
+ const text2 = JSON.stringify(config2, null, 2) + "\n";
26436
+ const replaceLegacy = async (before, version2) => {
26437
+ const onlyConfig = async () => {
26438
+ const entries = await readdir4(join9(root, ".gitifact"));
26439
+ if (await fileInfo(join9(root, "specs")) || entries.some((name) => name !== "config.json" && !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) {
26440
+ throw new InitError("UNSUPPORTED_SCHEMA", t2("init.legacyRecords", { version: String(version2) }));
26441
+ }
26442
+ };
26443
+ const recheck2 = async () => {
26444
+ await onlyConfig();
26445
+ await repo.checkIgnore(root);
26446
+ if ((await repo.inspect()).stamp !== first.stamp) throw new InitError("INPUT_CHANGED", t2("init.headOrIndexChanged"));
26447
+ if (await readConfigFile(root) !== before) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
26448
+ };
26449
+ await recheck2();
26450
+ if (dryRun) return result(config2, "planned");
26451
+ await publishConfig(root, text2, recheck2, beforePublish, true);
26452
+ return finish("replaced");
26453
+ };
26454
+ const finish = async (outcome) => {
26455
+ const published = async () => {
26456
+ if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text2) throw new InitError("INPUT_CHANGED_AFTER_WRITE", t2("init.changedAfterWrite"));
26457
+ };
26458
+ await published();
26459
+ await applyAgentDocs(root, docsPlan, published);
26460
+ await writeWikiPolicy(root, readDoc);
26461
+ return result(config2, outcome);
26462
+ };
26162
26463
  const old = await readConfigFile(root);
26163
- if (old !== void 0) return existing(old);
26464
+ if (old !== void 0) {
26465
+ const legacy = legacySchemaVersion(old);
26466
+ return legacy === void 0 ? existing(old) : replaceLegacy(old, legacy);
26467
+ }
26164
26468
  if (first.trackedConfig) throw new InitError("CONFIG_DELETED", t2("init.configDeleted"));
26165
26469
  const checkRecords = async () => {
26166
- if (await fileInfo(join7(root, "specs"))) throw new InitError("EXISTING_RECORDS", t2("init.existingSpecs"));
26167
- const entries = await readdir3(join7(root, ".gitifact")).catch((e) => {
26470
+ if (await fileInfo(join9(root, "specs"))) throw new InitError("EXISTING_RECORDS", t2("init.existingSpecs"));
26471
+ const entries = await readdir4(join9(root, ".gitifact")).catch((e) => {
26168
26472
  if (e.code === "ENOENT") return [];
26169
26473
  throw e;
26170
26474
  });
26171
26475
  if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", t2("init.orphanStore"));
26172
26476
  };
26173
- const config2 = { schemaVersion: 1, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
26174
- const text2 = JSON.stringify(config2, null, 2) + "\n";
26175
26477
  const recheck = async () => {
26176
26478
  await checkRecords();
26177
26479
  await repo.checkIgnore(root);
@@ -26189,34 +26491,59 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
26189
26491
  }
26190
26492
  throw error62;
26191
26493
  }
26192
- const published = async () => {
26193
- if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== text2) throw new InitError("INPUT_CHANGED_AFTER_WRITE", t2("init.changedAfterWrite"));
26194
- };
26195
- await published();
26196
- await applyAgentDocs(root, docsPlan, published);
26197
- return result(config2, "created");
26494
+ return finish("created");
26495
+ }
26496
+ function legacySchemaVersion(text2) {
26497
+ let value;
26498
+ try {
26499
+ value = JSON.parse(text2);
26500
+ } catch {
26501
+ return void 0;
26502
+ }
26503
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
26504
+ const version2 = value.schemaVersion;
26505
+ return Number.isInteger(version2) && version2 < SCHEMA_VERSION ? version2 : void 0;
26506
+ }
26507
+ async function writeWikiPolicy(root, readDoc) {
26508
+ if (await fileInfo(join9(root, ...WIKI_DIR.split("/")))) return;
26509
+ const template = await readDoc("wiki.default").catch((e) => {
26510
+ if (e.code === "ENOENT") return void 0;
26511
+ throw e;
26512
+ });
26513
+ if (template === void 0) return;
26514
+ const doc = { id: generatePreviewId("W"), path: WIKI_ENTRY_PATH, title: t2("init.wikiReadmeTitle"), body: template.trim() };
26515
+ const text2 = renderDocument(doc);
26516
+ parseDocument(doc.path, text2);
26517
+ await mkdir4(join9(root, ...WIKI_DIR.split("/")), { recursive: true });
26518
+ await writeFile2(join9(root, ...WIKI_ENTRY_PATH.split("/")), text2, { flag: "wx" });
26198
26519
  }
26199
26520
 
26200
26521
  // src/commands/init.ts
26201
26522
  async function runInit(options, version2) {
26523
+ const cancel = new AbortController();
26524
+ const update = updateCheckDisabled(process.env) ? disabledUpdate : resolveUpdate(version2, fetchLatestVersion, cancel.signal);
26202
26525
  try {
26203
26526
  const dto = await initializeSpecProject(
26204
26527
  process.cwd(),
26205
26528
  options.dryRun,
26206
26529
  process.env,
26207
26530
  void 0,
26208
- { version: version2, agent: options.agent, remove: options.removeAgents, skip: options.skipAgents }
26531
+ { version: version2, agent: options.agent, remove: options.removeAgents, skip: options.skipAgents },
26532
+ void 0,
26533
+ update
26209
26534
  );
26210
26535
  if (!dto.ok) throw new Error("unreachable");
26211
26536
  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") + ")");
26537
+ const newer = dto.install ? t2("init.text.updateAvailable", { version: dto.update.latestVersion, command: dto.install.npmGlobal }) + "\n" : "";
26212
26538
  process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
26213
- ${t2("init.text.storage")}: schemaVersion 1
26539
+ ${t2("init.text.storage")}: schemaVersion ${dto.schemaVersion}
26214
26540
  ${t2("init.text.agentDocs")}: ${docs}
26215
- ` : JSON.stringify(dto) + "\n");
26541
+ ${newer}` : JSON.stringify(dto) + "\n");
26216
26542
  } catch (error62) {
26543
+ cancel.abort();
26217
26544
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
26218
26545
  const failure2 = { code: known ? error62.code : "INIT_FAILED", message: known ? error62.message : t2("init.failed") };
26219
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(projectInitV4.parse({ contract: "project-init", version: 4, ok: false, error: failure2 })) + "\n");
26546
+ process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(projectInitV5.parse({ contract: "project-init", version: 5, ok: false, error: failure2 })) + "\n");
26220
26547
  process.exitCode = 1;
26221
26548
  }
26222
26549
  }
@@ -26262,19 +26589,20 @@ async function refreshBlocks(cwd, version2, env, controls) {
26262
26589
  first = await repo.inspect();
26263
26590
  config2 = await readConfigFile(first.state.repository.rootPath);
26264
26591
  } catch (error62) {
26265
- if (error62 instanceof RepositoryReadError && error62.code === "NOT_A_REPOSITORY" || error62 instanceof InitError && error62.code === "MIGRATION_REQUIRED") return { state: "not-initialized", paths: [] };
26592
+ if (error62 instanceof RepositoryReadError && error62.code === "NOT_A_REPOSITORY" || error62 instanceof InitError && error62.code === "MIGRATION_REQUIRED") return { state: "not-initialized", paths: [], missing: [] };
26266
26593
  throw error62;
26267
26594
  }
26268
- if (config2 === void 0) return { state: "not-initialized", paths: [] };
26595
+ if (config2 === void 0) return { state: "not-initialized", paths: [], missing: [] };
26269
26596
  const root = first.state.repository.rootPath;
26270
26597
  const plan = await planAgentDocs(root, { version: version2, onlyExisting: true, ...controls });
26271
- if (plan.paths.length === 0) return { state: "no-block", paths: [], root };
26272
- if (plan.writes.length === 0) return { state: "current", paths: plan.paths, root, candidates: plan.paths };
26598
+ const { missing: missing2 } = plan;
26599
+ if (plan.paths.length === 0) return { state: "no-block", paths: [], missing: missing2, root };
26600
+ if (plan.writes.length === 0) return { state: "current", paths: plan.paths, missing: missing2, root, candidates: plan.paths };
26273
26601
  const unchanged = async () => {
26274
26602
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== config2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
26275
26603
  };
26276
26604
  const applied = await applyAgentDocs(root, plan, unchanged);
26277
- return { state: "refreshed", paths: applied, root, candidates: plan.paths };
26605
+ return { state: "refreshed", paths: applied, missing: missing2, root, candidates: plan.paths };
26278
26606
  }
26279
26607
  function outsideBlock(text2) {
26280
26608
  const normalized2 = text2.replace(/\r\n/g, "\n");
@@ -26320,14 +26648,14 @@ async function updateCommand(cwd, version2, env = process.env, controls = {}) {
26320
26648
  refreshBlocks(cwd, version2, env, controls)
26321
26649
  ]);
26322
26650
  const commit = controls.commit ? await commitBlocks(agentDocs, version2, env) : commitState("not-requested");
26323
- return updateV2.parse({
26651
+ return updateV3.parse({
26324
26652
  contract: "update",
26325
- version: 2,
26653
+ version: 3,
26326
26654
  ok: true,
26327
26655
  cliVersion: version2,
26328
26656
  update,
26329
26657
  install: update.status === "available" ? { npmGlobal: npmGlobalInstall(update.latestVersion) } : null,
26330
- agentDocs: { state: agentDocs.state, paths: agentDocs.paths },
26658
+ agentDocs: { state: agentDocs.state, paths: agentDocs.paths, missing: agentDocs.missing },
26331
26659
  commit
26332
26660
  });
26333
26661
  }
@@ -26343,6 +26671,7 @@ async function runUpdate(options, version2) {
26343
26671
  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"));
26344
26672
  if (dto.install) lines.push(t2("update.text.install", { command: dto.install.npmGlobal }), t2("update.text.afterInstall"));
26345
26673
  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") : t2("update.text.notInitialized"));
26674
+ if (dto.agentDocs.missing.length) lines.push(t2("update.text.missing", { paths: dto.agentDocs.missing.join(", ") }));
26346
26675
  const { commit } = dto;
26347
26676
  if (commit.state === "committed") lines.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
26348
26677
  else if (commit.state === "nothing") lines.push(t2("update.text.commitNothing"));
@@ -26355,18 +26684,18 @@ async function runUpdate(options, version2) {
26355
26684
  } catch (error62) {
26356
26685
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
26357
26686
  const failure2 = { code: known ? error62.code : "UPDATE_FAILED", message: known ? error62.message : t2("update.failed") };
26358
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV2.parse({ contract: "update", version: 2, ok: false, error: failure2 })) + "\n");
26687
+ process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV3.parse({ contract: "update", version: 3, ok: false, error: failure2 })) + "\n");
26359
26688
  process.exitCode = 1;
26360
26689
  }
26361
26690
  }
26362
26691
 
26363
26692
  // src/commands/spec-preview.ts
26364
- import { join as join13 } from "node:path";
26693
+ import { join as join15 } from "node:path";
26365
26694
  import { readFile as readFile12, stat as stat2 } from "node:fs/promises";
26366
26695
 
26367
26696
  // src/adapters/filesystem/spec-preview-prepare.ts
26368
- import { lstat as lstat4 } from "node:fs/promises";
26369
- import { join as join8 } from "node:path";
26697
+ import { lstat as lstat6 } from "node:fs/promises";
26698
+ import { join as join10 } from "node:path";
26370
26699
  import { createHash as createHash4 } from "node:crypto";
26371
26700
  var fail5 = (message) => {
26372
26701
  throw new SpecPreviewError(message);
@@ -26379,7 +26708,7 @@ async function readPreviewContext(cwd) {
26379
26708
  const { gitDir } = await reader.location();
26380
26709
  const checkOperation = async () => {
26381
26710
  for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) {
26382
- const exists = await lstat4(join8(gitDir, name)).catch((e) => {
26711
+ const exists = await lstat6(join10(gitDir, name)).catch((e) => {
26383
26712
  if (e.code === "ENOENT") return void 0;
26384
26713
  throw e;
26385
26714
  });
@@ -26433,9 +26762,9 @@ async function verifyPreparedPreview(cwd, input2, staged) {
26433
26762
  if (staged) {
26434
26763
  const index = await c.reader.index();
26435
26764
  if (index.size !== current.files.size) fail5(t2("prepare.stagingScope"));
26436
- for (const [path3, source] of current.files) {
26765
+ for (const [path3, source2] of current.files) {
26437
26766
  const oid2 = index.get(path3);
26438
- const bytes = Buffer.from(source);
26767
+ const bytes = Buffer.from(source2);
26439
26768
  const hash3 = createHash4(oid2?.length === 64 ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
26440
26769
  if (oid2 !== hash3) fail5(t2("prepare.stagingContent", { path: path3 }));
26441
26770
  }
@@ -26448,26 +26777,26 @@ async function verifyPreparedPreview(cwd, input2, staged) {
26448
26777
 
26449
26778
  // src/adapters/filesystem/agent-inputs.ts
26450
26779
  import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
26451
- import { lstat as lstat5, mkdir as mkdir4, readdir as readdir4, realpath as realpath3, unlink as unlink4, writeFile as writeFile2 } from "node:fs/promises";
26780
+ import { lstat as lstat7, mkdir as mkdir5, readdir as readdir5, realpath as realpath3, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
26452
26781
  import { tmpdir } from "node:os";
26453
- import { basename, dirname as dirname3, join as join9 } from "node:path";
26782
+ import { basename, dirname as dirname4, join as join11 } from "node:path";
26454
26783
  var maxAge = 7 * 24 * 60 * 60 * 1e3;
26455
- var info2 = (path3) => lstat5(path3).catch((e) => {
26784
+ var info4 = (path3) => lstat7(path3).catch((e) => {
26456
26785
  if (e.code === "ENOENT") return void 0;
26457
26786
  throw e;
26458
26787
  });
26459
26788
  var comparable = (path3) => process.platform === "win32" ? path3.toLowerCase() : path3;
26460
26789
  async function candidates(root, controls) {
26461
26790
  const key = createHash5("sha256").update(comparable(await realpath3(root))).digest("hex").slice(0, 16);
26462
- return { system: join9((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join9(root, ".gitifact", "tmp") };
26791
+ return { system: join11((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join11(root, ".gitifact", "tmp") };
26463
26792
  }
26464
26793
  async function writable(directory) {
26465
26794
  try {
26466
- await mkdir4(directory, { recursive: true });
26467
- const stat3 = await info2(directory);
26795
+ await mkdir5(directory, { recursive: true });
26796
+ const stat3 = await info4(directory);
26468
26797
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) return false;
26469
- const probe = join9(directory, ".probe-" + randomBytes2(8).toString("hex"));
26470
- await writeFile2(probe, "", { flag: "wx" });
26798
+ const probe = join11(directory, ".probe-" + randomBytes2(8).toString("hex"));
26799
+ await writeFile3(probe, "", { flag: "wx" });
26471
26800
  await unlink4(probe);
26472
26801
  return true;
26473
26802
  } catch {
@@ -26480,28 +26809,28 @@ async function prepareAgentInputs(root, controls = {}) {
26480
26809
  if (await writable(system)) directory = system;
26481
26810
  else if (await writable(project)) {
26482
26811
  directory = project;
26483
- await writeFile2(join9(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
26812
+ await writeFile3(join11(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
26484
26813
  if (e.code !== "EEXIST") throw e;
26485
26814
  });
26486
26815
  }
26487
26816
  if (!directory) return void 0;
26488
26817
  const now = (controls.now ?? Date.now)();
26489
- for (const name of await readdir4(directory).catch(() => [])) {
26818
+ for (const name of await readdir5(directory).catch(() => [])) {
26490
26819
  if (name === ".gitignore") continue;
26491
- const stat3 = await info2(join9(directory, name)).catch(() => void 0);
26492
- if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink4(join9(directory, name)).catch(() => void 0);
26820
+ const stat3 = await info4(join11(directory, name)).catch(() => void 0);
26821
+ if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink4(join11(directory, name)).catch(() => void 0);
26493
26822
  }
26494
- return { save: join9(directory, "save.json"), commit: join9(directory, "commit.json") };
26823
+ return { save: join11(directory, "save.json"), commit: join11(directory, "commit.json") };
26495
26824
  }
26496
26825
  async function discardAgentInput(root, file2, controls = {}) {
26497
26826
  try {
26498
- const stat3 = await info2(file2);
26827
+ const stat3 = await info4(file2);
26499
26828
  if (!stat3?.isFile() || stat3.isSymbolicLink()) return false;
26500
- const parent = comparable(await realpath3(dirname3(file2)));
26829
+ const parent = comparable(await realpath3(dirname4(file2)));
26501
26830
  const { system, project } = await candidates(root, controls);
26502
26831
  let managed = false;
26503
26832
  for (const directory of [system, project]) {
26504
- if (await info2(directory) && comparable(await realpath3(directory)) === parent) managed = true;
26833
+ if (await info4(directory) && comparable(await realpath3(directory)) === parent) managed = true;
26505
26834
  }
26506
26835
  if (!managed || basename(file2) === ".gitignore") return false;
26507
26836
  await unlink4(file2);
@@ -26513,13 +26842,13 @@ async function discardAgentInput(root, file2, controls = {}) {
26513
26842
 
26514
26843
  // src/commands/spec-preview-commit.ts
26515
26844
  import { randomUUID as randomUUID6 } from "node:crypto";
26516
- import { readFile as readFile10, mkdir as mkdir5, open as open4, rename as rename3, unlink as unlink5, rmdir as rmdir2, writeFile as writeFile3 } from "node:fs/promises";
26517
- import { join as join11 } from "node:path";
26845
+ import { readFile as readFile10, mkdir as mkdir6, open as open4, rename as rename4, unlink as unlink5, rmdir as rmdir2, writeFile as writeFile4 } from "node:fs/promises";
26846
+ import { join as join13 } from "node:path";
26518
26847
 
26519
26848
  // src/commands/spec-commit-files.ts
26520
26849
  import { createHash as createHash6 } from "node:crypto";
26521
- import { lstat as lstat6, readFile as readFile9 } from "node:fs/promises";
26522
- import { join as join10 } from "node:path";
26850
+ import { lstat as lstat8, readFile as readFile9 } from "node:fs/promises";
26851
+ import { join as join12 } from "node:path";
26523
26852
  var fail6 = (message) => {
26524
26853
  throw new SpecPreviewError(message);
26525
26854
  };
@@ -26528,7 +26857,7 @@ var optional2 = (path3) => readFile9(path3).catch((e) => {
26528
26857
  if (e.code === "ENOENT") return null;
26529
26858
  throw e;
26530
26859
  });
26531
- var info3 = (path3) => lstat6(path3).catch((e) => {
26860
+ var info5 = (path3) => lstat8(path3).catch((e) => {
26532
26861
  if (e.code === "ENOENT") return void 0;
26533
26862
  throw e;
26534
26863
  });
@@ -26540,11 +26869,11 @@ async function fingerprint(root, path3) {
26540
26869
  validPath2(path3);
26541
26870
  const parts = path3.split("/");
26542
26871
  for (let i = 1; i <= parts.length; i++) {
26543
- const target = join10(root, ...parts.slice(0, i));
26544
- const stat3 = await info3(target);
26872
+ const target = join12(root, ...parts.slice(0, i));
26873
+ const stat3 = await info5(target);
26545
26874
  if (!stat3) return null;
26546
26875
  if (stat3.isSymbolicLink() || (i < parts.length ? !stat3.isDirectory() : !stat3.isFile())) fail6(t2("commitFiles.linkOrDirectory"));
26547
- if (i < parts.length && await info3(join10(target, ".git"))) fail6(t2("commitFiles.nestedRepository"));
26876
+ if (i < parts.length && await info5(join12(target, ".git"))) fail6(t2("commitFiles.nestedRepository"));
26548
26877
  if (i === parts.length) {
26549
26878
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail6(t2("commitFiles.hardLinkOrSize"));
26550
26879
  return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile9(target)]));
@@ -26575,9 +26904,9 @@ function policyPaths(files) {
26575
26904
  return [...all].sort();
26576
26905
  }
26577
26906
  async function checkLegacySelection(root, selected) {
26578
- const legacyJson = /^\.(?:gitifact|tryce)\/(?:spec\/[^/]+\/tryce\.json|notes\/N-[a-f0-9-]+\.json|mode-[a-f0-9-]+\.json|config\.(?:init-1|prototype-1)\.[a-f0-9]+\.json)$/;
26907
+ const legacyJson = /^\.(?:gitifact|tryce)\/(?:spec\/[^/]+\/tryce\.json|notes\/N-[a-f0-9-]+\.json|mode-[a-f0-9-]+\.json|config\.(?:init-1|prototype-1)\.[a-f0-9]+\.json)$|^\.gitifact\/(?:product|guides|overrides)\/.+$/;
26579
26908
  for (const p of selected.filter((p2) => p2.startsWith(".gitifact/") && !record2(p2) && p2 !== ".gitifact/config.json")) {
26580
- if (isProductAssetPath(p)) continue;
26909
+ if (isAssetPath(p)) continue;
26581
26910
  if (!legacyJson.test(p) || await fingerprint(root, p) !== null) fail6(t2("commitFiles.legacyDeleteOnly", { path: p }));
26582
26911
  }
26583
26912
  for (const p of selected.filter((p2) => p2.startsWith(".tryce/"))) {
@@ -26610,8 +26939,8 @@ async function previewCommit(cwd, action, input2) {
26610
26939
  });
26611
26940
  const indexPath = (await git(["rev-parse", "--path-format=absolute", "--git-path", "index"])).toString("utf8").trim();
26612
26941
  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();
26613
- const busy = join11(gitDir, "gitifact-spec-commit.lock");
26614
- if (await info3(busy)) fail6(t2("commit.busy", { path: busy }));
26942
+ const busy = join13(gitDir, "gitifact-spec-commit.lock");
26943
+ if (await info5(busy)) fail6(t2("commit.busy", { path: busy }));
26615
26944
  if ((await staged()).length) fail6(t2("commit.existingStagingPlan"));
26616
26945
  const request = object3(input2);
26617
26946
  let plan;
@@ -26668,33 +26997,33 @@ async function previewCommit(cwd, action, input2) {
26668
26997
  if (!("plan" in refreshed) || refreshed.plan.digest !== plan.digest) fail6(t2("commit.planStale"));
26669
26998
  const before = await reader.baseline();
26670
26999
  const original = await optional2(indexPath);
26671
- await mkdir5(busy);
26672
- const draftLock = join11(gitDir, "gitifact-spec-preview.lock");
27000
+ await mkdir6(busy);
27001
+ const draftLock = join13(gitDir, "gitifact-spec-preview.lock");
26673
27002
  let draftOwned = false;
26674
27003
  let indexLock;
26675
27004
  let uncertain = false;
26676
27005
  let commitStarted = false;
26677
- const temporary = join11(gitDir, "gitifact-commit-index-" + randomUUID6());
27006
+ const temporary = join13(gitDir, "gitifact-commit-index-" + randomUUID6());
26678
27007
  const checkFiles = async () => {
26679
27008
  for (const f of [...plan.files, ...plan.context]) if (f.hash !== await fingerprint(root, f.path)) fail6(t2("commit.fileChangedPreparing", { path: f.path }));
26680
27009
  if ((await readLockedPreviewState(root)).stamp !== object3(plan.verification).stamp) fail6(t2("commit.specsChangedPreparing"));
26681
- for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) if (await info3(join11(gitDir, name))) fail6(t2("commit.integrationStarted"));
27010
+ for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) if (await info5(join13(gitDir, name))) fail6(t2("commit.integrationStarted"));
26682
27011
  };
26683
27012
  try {
26684
27013
  await verifyPreparedPreview(cwd, plan.verification, false);
26685
- await mkdir5(draftLock);
27014
+ await mkdir6(draftLock);
26686
27015
  draftOwned = true;
26687
27016
  indexLock = await open4(indexPath + ".lock", "wx", 384);
26688
27017
  if (plan.index !== hash2(await optional2(indexPath) ?? Buffer.alloc(0)) || (await staged()).length) fail6(t2("commit.indexChangedLocked"));
26689
- await writeFile3(join11(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
26690
- if (original) await writeFile3(temporary, original, { flag: "wx" });
27018
+ await writeFile4(join13(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
27019
+ if (original) await writeFile4(temporary, original, { flag: "wx" });
26691
27020
  else await git(["read-tree", "--empty"], temporary);
26692
27021
  await git(["add", "--", ...selected], temporary);
26693
27022
  const actual = await staged(temporary);
26694
27023
  if (!actual.length || actual.some((p) => !selected.includes(p))) fail6(t2("commit.unexpectedFiles"));
26695
27024
  for (const file2 of plan.files.filter((f) => record2(f.path) && f.hash !== null)) {
26696
27025
  const stagedBytes = await git(["show", ":" + file2.path], temporary);
26697
- if (!stagedBytes.equals(await readFile10(join11(root, file2.path)))) fail6(t2("commit.filterChanged"));
27026
+ if (!stagedBytes.equals(await readFile10(join13(root, file2.path)))) fail6(t2("commit.filterChanged"));
26698
27027
  }
26699
27028
  await checkFiles();
26700
27029
  if (JSON.stringify(before) !== JSON.stringify(await reader.baseline())) fail6(t2("commit.headChanged"));
@@ -26712,7 +27041,7 @@ async function previewCommit(cwd, action, input2) {
26712
27041
  await indexLock.writeFile(await readFile10(temporary));
26713
27042
  await indexLock.sync();
26714
27043
  await indexLock.close();
26715
- await rename3(indexPath + ".lock", indexPath);
27044
+ await rename4(indexPath + ".lock", indexPath);
26716
27045
  indexLock = void 0;
26717
27046
  return { outcome: "committed", commit: after.head, paths: actual, requirements: plan.requirements, pushed: false };
26718
27047
  } catch (error62) {
@@ -26733,7 +27062,7 @@ async function previewCommit(cwd, action, input2) {
26733
27062
  });
26734
27063
  await unlink5(temporary).catch(() => {
26735
27064
  });
26736
- await unlink5(join11(busy, "recovery.json")).catch(() => {
27065
+ await unlink5(join13(busy, "recovery.json")).catch(() => {
26737
27066
  });
26738
27067
  await rmdir2(busy);
26739
27068
  if (draftOwned) await rmdir2(draftLock);
@@ -26743,8 +27072,8 @@ async function previewCommit(cwd, action, input2) {
26743
27072
 
26744
27073
  // src/commands/spec-commit.ts
26745
27074
  import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
26746
- import { mkdir as mkdir6, open as open5, readFile as readFile11, rename as rename4, rmdir as rmdir3, unlink as unlink6, writeFile as writeFile4 } from "node:fs/promises";
26747
- import { join as join12 } from "node:path";
27075
+ import { mkdir as mkdir7, open as open5, readFile as readFile11, rename as rename5, rmdir as rmdir3, unlink as unlink6, writeFile as writeFile5 } from "node:fs/promises";
27076
+ import { join as join14 } from "node:path";
26748
27077
  var fields = ["reasons", "paths", "message", "authorization", "requirements", "policyFiles", "expected"];
26749
27078
  var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
26750
27079
  var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -26783,9 +27112,9 @@ async function specCommit(cwd, input2, dryRun) {
26783
27112
  ...stdin ? { input: stdin } : {}
26784
27113
  });
26785
27114
  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();
26786
- const busy = join12(gitDir, "gitifact-spec-commit.lock");
27115
+ const busy = join14(gitDir, "gitifact-spec-commit.lock");
26787
27116
  await failOnLegacyLock(gitDir);
26788
- if (await info3(busy)) fail6(t2("commit.busy", { path: busy }));
27117
+ if (await info5(busy)) fail6(t2("commit.busy", { path: busy }));
26789
27118
  if ((await staged()).length) fail6(t2("commit.existingStaging"));
26790
27119
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
26791
27120
  const current = c.working;
@@ -26835,22 +27164,22 @@ async function specCommit(cwd, input2, dryRun) {
26835
27164
  const trailers = [
26836
27165
  ...requirements.map((id) => "Gitifact-Req: " + id),
26837
27166
  ...prepared.changes.filter((c2) => c2.kind === "design").map((c2) => "Gitifact-Design: " + c2.id),
26838
- ...prepared.changes.filter((c2) => c2.kind === "product" || c2.kind === "guide").map((c2) => "Gitifact-Doc: " + c2.id)
27167
+ ...prepared.changes.filter((c2) => c2.kind === "wiki").map((c2) => "Gitifact-Doc: " + c2.id)
26839
27168
  ];
26840
- const temporary = join12(gitDir, "gitifact-commit-index-" + randomUUID7());
27169
+ const temporary = join14(gitDir, "gitifact-commit-index-" + randomUUID7());
26841
27170
  let owned = false;
26842
27171
  let uncertain = false;
26843
27172
  let indexLock;
26844
27173
  let committed;
26845
27174
  try {
26846
27175
  try {
26847
- await mkdir6(busy);
27176
+ await mkdir7(busy);
26848
27177
  owned = true;
26849
27178
  } catch (e) {
26850
27179
  if (e.code === "EEXIST") fail6(t2("commit.busy", { path: busy }));
26851
27180
  throw e;
26852
27181
  }
26853
- await previewTransaction(cwd, current.stamp, async () => ({ writes, data: {}, recheck: c.recheck }), rename4, async (published) => {
27182
+ await previewTransaction(cwd, current.stamp, async () => ({ writes, data: {}, recheck: c.recheck }), rename5, async (published) => {
26854
27183
  let commitStarted = false;
26855
27184
  try {
26856
27185
  const lock = indexLock = await open5(indexPath + ".lock", "wx", 384);
@@ -26858,8 +27187,8 @@ async function specCommit(cwd, input2, dryRun) {
26858
27187
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail6(t2("commit.indexChanged"));
26859
27188
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected, ...contextPaths])]);
26860
27189
  for (const [path3, value] of started) if (locked.get(path3) !== value) fail6(t2("commit.fileChangedRunning", { path: path3 }));
26861
- await writeFile4(join12(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
26862
- if (original) await writeFile4(temporary, original, { flag: "wx" });
27190
+ await writeFile5(join14(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
27191
+ if (original) await writeFile5(temporary, original, { flag: "wx" });
26863
27192
  else await git(["read-tree", "--empty"], temporary);
26864
27193
  await git(["add", "--", ...stageable], temporary);
26865
27194
  const actual = await staged(temporary);
@@ -26874,7 +27203,7 @@ async function specCommit(cwd, input2, dryRun) {
26874
27203
  const size = Number(header[2]);
26875
27204
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail6(t2("commit.stagedUnreadable", { path: path3 }));
26876
27205
  const staged2 = output2.subarray(end + 1, end + 1 + size);
26877
- const file2 = await readFile11(join12(root, path3));
27206
+ const file2 = await readFile11(join14(root, path3));
26878
27207
  if (!staged2.equals(file2) && !staged2.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail6(t2("commit.filterChanged"));
26879
27208
  offset = end + size + 2;
26880
27209
  }
@@ -26882,7 +27211,7 @@ async function specCommit(cwd, input2, dryRun) {
26882
27211
  const again = await hashes([...locked.keys()]);
26883
27212
  for (const [path3, value] of locked) if (again.get(path3) !== value) fail6(t2("commit.fileChangedPreparing", { path: path3 }));
26884
27213
  if ((await readLockedPreviewState(root)).stamp !== published.stamp) fail6(t2("commit.specsChangedPreparing"));
26885
- for (const name of integration) if (await info3(join12(gitDir, name))) fail6(t2("commit.integrationStarted"));
27214
+ for (const name of integration) if (await info5(join14(gitDir, name))) fail6(t2("commit.integrationStarted"));
26886
27215
  if (!same2(c.base, await c.reader.baseline())) fail6(t2("commit.headChanged"));
26887
27216
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
26888
27217
  commitStarted = true;
@@ -26900,7 +27229,7 @@ async function specCommit(cwd, input2, dryRun) {
26900
27229
  await lock.writeFile(await readFile11(temporary));
26901
27230
  await lock.sync();
26902
27231
  await lock.close();
26903
- await rename4(indexPath + ".lock", indexPath);
27232
+ await rename5(indexPath + ".lock", indexPath);
26904
27233
  indexLock = void 0;
26905
27234
  committed = { commit: after.head, paths: actual };
26906
27235
  } catch (error62) {
@@ -26924,7 +27253,7 @@ async function specCommit(cwd, input2, dryRun) {
26924
27253
  await unlink6(temporary).catch(() => {
26925
27254
  });
26926
27255
  if (owned) {
26927
- await unlink6(join12(busy, "recovery.json")).catch(() => {
27256
+ await unlink6(join14(busy, "recovery.json")).catch(() => {
26928
27257
  });
26929
27258
  await rmdir3(busy);
26930
27259
  }
@@ -26941,7 +27270,7 @@ async function execute(action, options, controls) {
26941
27270
  try {
26942
27271
  const guard = specPreviewReader(process.cwd());
26943
27272
  const { root, gitDir, objectFormat } = await guard.location();
26944
- for (const marker2 of operations) if (await fileInfo(join13(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
27273
+ for (const marker2 of operations) if (await fileInfo(join15(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
26945
27274
  if (await guard.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
26946
27275
  const raw = await readConfigFile(root);
26947
27276
  if (raw === void 0) throw new SpecPreviewError(t2("preview.notInitialized"));
@@ -26973,7 +27302,7 @@ async function execute(action, options, controls) {
26973
27302
  if (action === "read") {
26974
27303
  const commit = await reader.resolve(options.ref ?? "HEAD");
26975
27304
  const bundle = await reader.readBundle(commit);
26976
- process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs: bundle.specs, documents: bundle.documents }) + "\n");
27305
+ process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs: bundle.specs, wiki: bundle.wiki }) + "\n");
26977
27306
  } else {
26978
27307
  const [from, to] = await Promise.all([reader.resolve(options.from), reader.resolve(options.to)]);
26979
27308
  const [before, after] = await Promise.all([reader.readBundle(from), reader.readBundle(to)]);
@@ -26986,8 +27315,8 @@ async function execute(action, options, controls) {
26986
27315
  }
26987
27316
  }
26988
27317
  async function readInputFile(path3) {
26989
- const source = await stat2(path3);
26990
- if (!source.isFile() || source.size > 1024 * 1024) throw new SpecPreviewError(t2("preview.inputFile"));
27318
+ const source2 = await stat2(path3);
27319
+ if (!source2.isFile() || source2.size > 1024 * 1024) throw new SpecPreviewError(t2("preview.inputFile"));
26991
27320
  return readFile12(path3);
26992
27321
  }
26993
27322
  async function readStdin(stream) {
@@ -27002,29 +27331,29 @@ async function readStdin(stream) {
27002
27331
  }
27003
27332
  function narrowWorking(working, options, inputs) {
27004
27333
  if (options.stamp) return { stamp: working.stamp, inputs };
27005
- let { specs, documents, warnings } = working;
27334
+ let { specs, wiki, warnings } = working;
27006
27335
  if (options.feature !== void 0) {
27007
27336
  specs = specs.filter((s) => s.path === ".gitifact/spec/" + options.feature + "/requirements.md");
27008
27337
  if (!specs.length) throw new SpecPreviewError(t2("preview.unknownFeature", { feature: options.feature }));
27009
- documents = [];
27010
- warnings = warnings.filter((w) => w.specId === specs[0].id);
27338
+ wiki = { documents: [], history: [] };
27339
+ warnings = warnings.filter((w) => w.code === "MISSING_DESIGN_REFERENCE" && w.specId === specs[0].id);
27011
27340
  }
27012
- if (!options.ids) return { stamp: working.stamp, inputs, specs, documents, warnings };
27341
+ if (!options.ids) return { stamp: working.stamp, inputs, specs, wiki, warnings };
27013
27342
  return {
27014
27343
  stamp: working.stamp,
27015
27344
  inputs,
27016
27345
  warnings,
27017
27346
  specs: specs.map((s) => ({ id: s.id, path: s.path, title: s.title, design: s.design?.title ?? null, requirements: s.requirements.map((r) => ({ id: r.id, title: r.title })) })),
27018
- documents: documents.map((set2) => ({ kind: set2.kind, documents: set2.documents.map((d) => ({ id: d.id, path: d.path, title: d.title })) }))
27347
+ wiki: { documents: wiki.documents.map((d) => ({ id: d.id, path: d.path, title: d.title })) }
27019
27348
  };
27020
27349
  }
27021
27350
 
27022
27351
  // src/commands/migrate.ts
27023
- import { lstat as lstat8, readdir as readdir5, readFile as readFile13, rename as rename5, writeFile as writeFile5, unlink as unlink7 } from "node:fs/promises";
27024
- import { join as join14 } from "node:path";
27352
+ import { lstat as lstat10, readdir as readdir6, readFile as readFile13, rename as rename6, writeFile as writeFile6, unlink as unlink7 } from "node:fs/promises";
27353
+ import { join as join16 } from "node:path";
27025
27354
  import { randomBytes as randomBytes3 } from "node:crypto";
27026
27355
  var operations2 = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
27027
- var info4 = (path3) => lstat8(path3).catch((e) => {
27356
+ var info6 = (path3) => lstat10(path3).catch((e) => {
27028
27357
  if (e.code === "ENOENT") return void 0;
27029
27358
  throw e;
27030
27359
  });
@@ -27035,40 +27364,40 @@ var fail7 = (code, message) => {
27035
27364
  async function migrateStore(cwd, dryRun = false) {
27036
27365
  const reader = specPreviewReader(cwd);
27037
27366
  const { root, gitDir } = await reader.location();
27038
- for (const marker3 of operations2) if (await info4(join14(gitDir, marker3))) fail7("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker3 }));
27367
+ for (const marker3 of operations2) if (await info6(join16(gitDir, marker3))) fail7("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker3 }));
27039
27368
  if (await reader.hasUnmerged()) fail7("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
27040
- for (const name of [...LEGACY_LOCKS, "gitifact-spec-preview.lock", "gitifact-spec-commit.lock"]) if (await info4(join14(gitDir, name))) fail7("LOCKED", t2("migrate.locked", { path: join14(gitDir, name) }));
27041
- const from = join14(root, ".tryce");
27042
- const to = join14(root, ".gitifact");
27043
- const fromInfo = await info4(from);
27044
- if (await info4(to)) fail7("ALREADY_MIGRATED", t2("migrate.alreadyMigrated"));
27369
+ for (const name of [...LEGACY_LOCKS, "gitifact-spec-preview.lock", "gitifact-spec-commit.lock"]) if (await info6(join16(gitDir, name))) fail7("LOCKED", t2("migrate.locked", { path: join16(gitDir, name) }));
27370
+ const from = join16(root, ".tryce");
27371
+ const to = join16(root, ".gitifact");
27372
+ const fromInfo = await info6(from);
27373
+ if (await info6(to)) fail7("ALREADY_MIGRATED", t2("migrate.alreadyMigrated"));
27045
27374
  if (!fromInfo) return fail7("NOTHING_TO_MIGRATE", t2("migrate.nothingToMigrate"));
27046
27375
  if (!fromInfo.isDirectory() || fromInfo.isSymbolicLink()) fail7("PATH_CONFLICT", t2("migrate.notDirectory"));
27047
- const configInfo = await info4(join14(from, "config.json"));
27376
+ const configInfo = await info6(join16(from, "config.json"));
27048
27377
  if (!configInfo?.isFile() || configInfo.isSymbolicLink()) fail7("NOT_INITIALIZED", t2("migrate.noConfig"));
27049
- const config2 = parseManagedConfig(decode4(await readFile13(join14(from, "config.json"))));
27378
+ const config2 = parseManagedConfig(decode4(await readFile13(join16(from, "config.json"))));
27050
27379
  if (!("schemaVersion" in config2)) fail7("UNSUPPORTED_FORMAT", t2("migrate.legacyJson"));
27051
- const top = (await readdir5(from)).sort();
27380
+ const top = (await readdir6(from)).sort();
27052
27381
  if (top.some((name) => name !== "config.json" && name !== "spec")) fail7("UNSUPPORTED_FORMAT", t2("migrate.extraEntries"));
27053
27382
  const files = /* @__PURE__ */ new Map();
27054
27383
  const rewrites = [];
27055
27384
  const legacyRecords = [];
27056
27385
  const marker2 = /^<!-- tryce-(spec|req|design|ref): /;
27057
27386
  if (top.includes("spec")) {
27058
- for (const feature2 of (await readdir5(join14(from, "spec"))).sort()) {
27059
- const dir = join14(from, "spec", feature2);
27060
- const stat3 = await info4(dir);
27387
+ for (const feature2 of (await readdir6(join16(from, "spec"))).sort()) {
27388
+ const dir = join16(from, "spec", feature2);
27389
+ const stat3 = await info6(dir);
27061
27390
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) fail7("UNSUPPORTED_FORMAT", t2("migrate.notSpecFolder", { feature: feature2 }));
27062
- for (const name of (await readdir5(dir)).sort()) {
27391
+ for (const name of (await readdir6(dir)).sort()) {
27063
27392
  if (!["requirements.md", "design.md", "history.jsonl", "tryce.json"].includes(name)) fail7("UNSUPPORTED_FORMAT", t2("migrate.unsupportedFile", { feature: feature2, name }));
27064
- const fileStat = await info4(join14(dir, name));
27393
+ const fileStat = await info6(join16(dir, name));
27065
27394
  if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.nlink !== 1 || fileStat.size > 1024 * 1024) fail7("UNSUPPORTED_FORMAT", t2("migrate.fileTooLarge", { name }));
27066
27395
  const newPath = `.gitifact/spec/${feature2}/${name}`;
27067
27396
  if (name === "tryce.json") {
27068
27397
  legacyRecords.push(newPath);
27069
27398
  continue;
27070
27399
  }
27071
- const text2 = decode4(await readFile13(join14(dir, name)));
27400
+ const text2 = decode4(await readFile13(join16(dir, name)));
27072
27401
  if (name === "history.jsonl") {
27073
27402
  files.set(newPath, text2);
27074
27403
  continue;
@@ -27111,13 +27440,13 @@ async function migrateStore(cwd, dryRun = false) {
27111
27440
  ]
27112
27441
  });
27113
27442
  if (dryRun) return result("planned");
27114
- await rename5(from, to);
27443
+ await rename6(from, to);
27115
27444
  for (const r of rewrites) {
27116
- const full = join14(root, r.path);
27117
- const temp = join14(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes3(8).toString("hex"));
27118
- await writeFile5(temp, r.content, { flag: "wx" });
27445
+ const full = join16(root, r.path);
27446
+ const temp = join16(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes3(8).toString("hex"));
27447
+ await writeFile6(temp, r.content, { flag: "wx" });
27119
27448
  try {
27120
- await rename5(temp, full);
27449
+ await rename6(temp, full);
27121
27450
  } finally {
27122
27451
  await unlink7(temp).catch(() => {
27123
27452
  });
@@ -27137,16 +27466,16 @@ async function runMigrate(options) {
27137
27466
  }
27138
27467
 
27139
27468
  // src/main.ts
27140
- var program2 = new Command().name("gitifact").description(t2("help.program")).version("0.4.4").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
27469
+ var program2 = new Command().name("gitifact").description(t2("help.program")).version("0.5.1").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
27141
27470
  program2.command("status").description(t2("help.status")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action(async (options) => {
27142
27471
  await runStatus(options.format);
27143
27472
  });
27144
- program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).option("--no-update-check", t2("help.browserNoUpdateCheck")).action((options) => runBrowser(options, "0.4.4"));
27145
- 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.4.4"));
27473
+ program2.command("browser").description(t2("help.browser")).allowExcessArguments(false).option("--port <port>", t2("help.browserPort"), parsePort, 0).option("--dev", t2("help.browserDev")).option("--no-update-check", t2("help.browserNoUpdateCheck")).action((options) => runBrowser(options, "0.5.1"));
27474
+ 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.5.1"));
27146
27475
  program2.command("docs").description(t2("help.docs")).argument("[topic]", t2("help.docsTopic")).allowExcessArguments(false).action(async (topic) => {
27147
27476
  await runDocs(topic);
27148
27477
  });
27149
- 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")).action((options) => runUpdate(options, "0.4.4"));
27478
+ 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")).action((options) => runUpdate(options, "0.5.1"));
27150
27479
  program2.command("migrate").description(t2("help.migrate")).allowExcessArguments(false).option("--dry-run", t2("help.migrateDryRun")).action(runMigrate);
27151
27480
  var spec = program2.command("spec").description(t2("help.spec"));
27152
27481
  var replaced = t2("help.deprecated");