gitifact 0.4.4 → 0.5.0

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 +7 -5
  2. package/dist/browser/assets/{Markdown-Drk5rJF_.js → Markdown-C8C4nHPG.js} +3 -3
  3. package/dist/browser/assets/{MetadataListItem-o7NlLubs.js → MetadataListItem-CcPRe-aG.js} +1 -1
  4. package/dist/browser/assets/Table-B_rvjexw.js +3 -0
  5. package/dist/browser/assets/about-D6bBZN8p.js +3 -0
  6. package/dist/browser/assets/{routes-D_2lOqSD.js → activity-B7x648pR.js} +1 -1
  7. package/dist/browser/assets/changelog-DaxXahXm.js +2 -0
  8. package/dist/browser/assets/{contributors._email-CsIeETiA.js → contributors._email-BZevZqRL.js} +1 -1
  9. package/dist/browser/assets/{contributors.index-BVn-RzjO.js → contributors.index-BgA9LkC0.js} +1 -1
  10. package/dist/browser/assets/document-CFtIdIPN.js +9 -0
  11. package/dist/browser/assets/document-QMuypGH5.css +1 -0
  12. package/dist/browser/assets/features._featureId-BBYORTqC.js +1 -0
  13. package/dist/browser/assets/{requirements-C5TAa2tx.js → features.index-DuwIen6d.js} +1 -1
  14. package/dist/browser/assets/git-DVoIz_Mx.js +1 -0
  15. package/dist/browser/assets/index-BP3PrawI.js +48 -0
  16. package/dist/browser/assets/page-header-Zwbd52Q5.js +124 -0
  17. package/dist/browser/assets/product-DNlxFjU7.css +1 -0
  18. package/dist/browser/assets/product-euizlTTB.js +8 -0
  19. package/dist/browser/assets/product.index-B4D5SxOO.js +1 -0
  20. package/dist/browser/assets/request-state-Dz-oeAkg.js +1 -0
  21. package/dist/browser/assets/{features.index-Cf-ct7tk.js → requirements-wk9Joy7M.js} +1 -1
  22. package/dist/browser/assets/{settings-Dzak94qo.js → settings-DwA6H4Ax.js} +1 -1
  23. package/dist/browser/assets/wiki-DkAnuzON.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 +21 -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 +710 -437
  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,19 @@ 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 CLI\uAC00 \uC77D\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC815\uC2DD \uBC84\uC804 \uC804 \uADDC\uC57D\uC740 \uC804\uD658\uD558\uC9C0 \uC54A\uC73C\uBA70 \uC0C8\uB85C init \uD569\uB2C8\uB2E4."
3510
3514
  };
3511
3515
 
3512
3516
  // ../../packages/core/dist/shared/i18n/index.js
@@ -3578,6 +3582,7 @@ function parseProjectConfig(text2) {
3578
3582
  }
3579
3583
 
3580
3584
  // ../../packages/core/dist/formats/spec-project.js
3585
+ var SCHEMA_VERSION = 2;
3581
3586
  function parseManagedConfig(text2) {
3582
3587
  let value;
3583
3588
  try {
@@ -3588,41 +3593,50 @@ function parseManagedConfig(text2) {
3588
3593
  if (!value || typeof value !== "object" || Array.isArray(value) || !("schemaVersion" in value))
3589
3594
  return parseProjectConfig(text2);
3590
3595
  const config2 = value;
3591
- if (Object.keys(config2).sort().join(",") !== "baseline,schemaVersion" || config2.schemaVersion !== 1)
3596
+ if (Object.keys(config2).sort().join(",") !== "baseline,schemaVersion")
3592
3597
  throw new InitError("INVALID_CONFIG", t("config.schemaFields"));
3598
+ if (config2.schemaVersion !== SCHEMA_VERSION)
3599
+ throw new InitError("UNSUPPORTED_SCHEMA", t("config.unsupportedSchema", { version: String(config2.schemaVersion) }));
3593
3600
  const checked = parseProjectConfig(JSON.stringify({ kind: "tryce-project", format: "workflow-1", mode: "auto", baseline: config2.baseline }));
3594
- return { schemaVersion: 1, baseline: checked.baseline };
3601
+ return { schemaVersion: SCHEMA_VERSION, baseline: checked.baseline };
3595
3602
  }
3596
3603
 
3597
3604
  // ../../packages/core/dist/formats/spec-preview.js
3598
3605
  var SpecPreviewError = class extends Error {
3599
3606
  code = "INVALID_SPEC_PREVIEW";
3600
3607
  };
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
- }
3608
+ var WIKI_DIR = ".gitifact/wiki";
3609
+ var WIKI_HISTORY_PATH = WIKI_DIR + "/history.jsonl";
3610
+ var WIKI_ENTRY = "README.md";
3611
+ var WIKI_ENTRY_PATH = WIKI_DIR + "/" + WIKI_ENTRY;
3612
+ var ASSETS_DIR = ".gitifact/assets";
3613
+ var RECOMMENDED_ASSET_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "webp", "svg", "pdf"];
3614
+ var ASSET_SIZE_LIMIT = 1024 * 1024;
3615
+ var ASSETS_TOTAL_LIMIT = 50 * 1024 * 1024;
3610
3616
  var token = "[a-z2-7]{10}";
3611
3617
  var STORE_DIR = ".gitifact";
3612
3618
  var LEGACY_STORE_DIR = ".tryce";
3613
3619
  var STORE_DIRS = [STORE_DIR, LEGACY_STORE_DIR];
3614
3620
  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";
3621
+ var recordPathPattern = /^(?:\.(?:gitifact|tryce)\/spec\/[^/]+\/(?:requirements\.md|design\.md|history\.jsonl)|\.gitifact\/wiki\/(?:(?:[^/]+\/)*[^/]+\.md|history\.jsonl))$/;
3622
+ var documentPathPattern = /^\.gitifact\/wiki\/((?:[^/]+\/)*[^/]+\.md)$/;
3623
+ var documentHistoryPattern = /^\.gitifact\/wiki\/history\.jsonl$/;
3619
3624
  var nameToken = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
3625
+ var segmentOk = (part) => !!part && part !== "." && part !== ".." && !/[\\:\0]/.test(part);
3626
+ function isAssetPath(path3) {
3627
+ if (!path3.startsWith(ASSETS_DIR + "/"))
3628
+ return false;
3629
+ const parts = path3.slice(ASSETS_DIR.length + 1).split("/");
3630
+ return parts.every(segmentOk) && parts.length <= 8 && path3.length <= 300;
3631
+ }
3632
+ var assetExtension = (path3) => (/\.([A-Za-z0-9]+)$/.exec(path3)?.[1] ?? "").toLowerCase();
3633
+ var rootUpperName = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)*\.md$/;
3620
3634
  function validateDocumentRelativePath(relative) {
3621
3635
  const parts = relative.split("/");
3622
3636
  if (relative.length > 200 || parts.length > 8)
3623
3637
  fail(t("document.pathTooLong", { path: relative }));
3624
3638
  const file2 = parts.pop();
3625
- if (!file2.endsWith(".md") || !nameToken.test(file2.slice(0, -3)))
3639
+ if (!(parts.length === 0 && rootUpperName.test(file2)) && (!file2.endsWith(".md") || !nameToken.test(file2.slice(0, -3))))
3626
3640
  fail(t("document.fileName", { path: relative }));
3627
3641
  for (const part of parts)
3628
3642
  if (!nameToken.test(part) || part.length > 80)
@@ -3634,19 +3648,115 @@ var fail = (message) => {
3634
3648
  throw new SpecPreviewError(message);
3635
3649
  };
3636
3650
  var normalized = (value) => value.replace(/\r\n/g, "\n").trim();
3651
+ var scalar = (value) => {
3652
+ const quoted = /^"((?:[^"\\]|\\["\\])*)"$/.exec(value);
3653
+ if (quoted)
3654
+ return quoted[1].replace(/\\(["\\])/g, "$1");
3655
+ if (!value || /^["'#&*!|>%@`[{-]/.test(value) || /:\s|\s#/.test(value) || value !== value.trim())
3656
+ fail(t("frontmatter.scalar", { value }));
3657
+ return value;
3658
+ };
3659
+ 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, "\\$&") + '"';
3660
+ function parseFrontmatter(lines, kind, legacyIds) {
3661
+ const legacy = new RegExp(`^<!-- ${marker}-(?:${kind === "wiki" ? "product|guide" : kind}): (${legacyIds}-${token}) -->$`).exec(lines[0] ?? "");
3662
+ if (legacy) {
3663
+ lines.shift();
3664
+ return { id: legacy[1] };
3665
+ }
3666
+ if (lines[0] !== "---")
3667
+ fail(t("frontmatter.required"));
3668
+ lines.shift();
3669
+ const end = lines.indexOf("---");
3670
+ if (end === -1)
3671
+ fail(t("frontmatter.unclosed"));
3672
+ const block = lines.splice(0, end);
3673
+ lines.shift();
3674
+ const fields2 = /* @__PURE__ */ new Map();
3675
+ for (let i = 0; i < block.length; i++) {
3676
+ const line = block[i];
3677
+ if (!line.trim())
3678
+ continue;
3679
+ const entry2 = /^([a-z]+):(?: (.*))?$/.exec(line);
3680
+ if (!entry2 || fields2.has(entry2[1]))
3681
+ fail(t("frontmatter.line", { line }));
3682
+ const [, key, value] = entry2;
3683
+ if (key !== "sources") {
3684
+ if (value === void 0)
3685
+ fail(t("frontmatter.line", { line }));
3686
+ fields2.set(key, scalar(value));
3687
+ continue;
3688
+ }
3689
+ if (value !== void 0)
3690
+ fail(t("frontmatter.line", { line }));
3691
+ const sources2 = [];
3692
+ while (i + 1 < block.length && /^ /.test(block[i + 1])) {
3693
+ const item = block[++i];
3694
+ const head = /^ - ([a-z]+): (.*)$/.exec(item);
3695
+ if (!head)
3696
+ fail(t("frontmatter.line", { line: item }));
3697
+ const map2 = { [head[1]]: scalar(head[2]) };
3698
+ while (i + 1 < block.length && /^ [a-z]+: /.test(block[i + 1])) {
3699
+ const more = /^ ([a-z]+): (.*)$/.exec(block[++i]);
3700
+ if (more[1] in map2)
3701
+ fail(t("frontmatter.line", { line: block[i] }));
3702
+ map2[more[1]] = scalar(more[2]);
3703
+ }
3704
+ sources2.push(validateSource(map2));
3705
+ }
3706
+ fields2.set("sources", sources2);
3707
+ }
3708
+ const id = fields2.get("id");
3709
+ if (typeof id !== "string" || !new RegExp(`^(?:${legacyIds})-${token}$`).test(id))
3710
+ fail(t("frontmatter.id", { kind }));
3711
+ for (const key of fields2.keys())
3712
+ if (key !== "id" && !(key === "sources" && kind === "design"))
3713
+ fail(t("frontmatter.unknownKey", { key }));
3714
+ const sources = fields2.get("sources");
3715
+ return { id, ...Array.isArray(sources) ? { sources } : {} };
3716
+ }
3717
+ function validateSource(value) {
3718
+ if (!value || typeof value !== "object" || Array.isArray(value))
3719
+ fail(t("source.invalid"));
3720
+ const map2 = value;
3721
+ const keys2 = Object.keys(map2).sort();
3722
+ if (keys2.some((k) => !["title", "path", "url", "note"].includes(k)) || typeof map2.title !== "string" || !map2.title.trim() || "path" in map2 === "url" in map2)
3723
+ fail(t("source.invalid"));
3724
+ for (const key of keys2) {
3725
+ const v = map2[key];
3726
+ if (typeof v !== "string" || !v.trim() || /[\r\n\0]/.test(v) || v.length > 500)
3727
+ fail(t("source.invalid"));
3728
+ }
3729
+ if (typeof map2.url === "string" && !/^https?:\/\/\S+$/.test(map2.url))
3730
+ fail(t("source.url", { url: map2.url }));
3731
+ if (typeof map2.path === "string" && (/^[a-z]+:/i.test(map2.path) || map2.path.startsWith("/") || !map2.path.endsWith(".md")))
3732
+ fail(t("source.path", { path: map2.path }));
3733
+ 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() } : {} };
3734
+ }
3735
+ function renderFrontmatter(front) {
3736
+ const lines = ["---", "id: " + front.id];
3737
+ if (front.sources?.length) {
3738
+ lines.push("sources:");
3739
+ for (const s of front.sources) {
3740
+ const entries = ["title", "path", "url", "note"].filter((k) => s[k] !== void 0).map((k) => k + ": " + quote(s[k]));
3741
+ lines.push(" - " + entries[0], ...entries.slice(1).map((e) => " " + e));
3742
+ }
3743
+ }
3744
+ lines.push("---");
3745
+ return lines.join("\n") + "\n";
3746
+ }
3637
3747
  function renderDesignPreview(id, design) {
3638
- return `<!-- gitifact-design: ${id} -->
3639
-
3748
+ return renderFrontmatter({ id, ...design.sources?.length ? { sources: design.sources } : {} }) + `
3640
3749
  # ${design.title}
3641
3750
 
3642
3751
  ${design.body}
3643
3752
  `;
3644
3753
  }
3645
- function parseDesignPreview(source, specId) {
3646
- if (source.includes("\0") || /\r(?!\n)/.test(source))
3754
+ function parseDesignPreview(source2, specId) {
3755
+ if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3647
3756
  fail(t("design.invalidCharacters"));
3648
- const lines = normalized(source).split("\n");
3649
- if (!new RegExp(`^<!-- ${marker}-design: ${specId} -->$`).test(lines.shift() ?? ""))
3757
+ const lines = normalized(source2).split("\n");
3758
+ const front = parseFrontmatter(lines, "design", "S");
3759
+ if (front.id !== specId)
3650
3760
  fail(t("design.idMismatch"));
3651
3761
  while (lines[0] === "")
3652
3762
  lines.shift();
@@ -3684,32 +3794,24 @@ function parseDesignPreview(source, specId) {
3684
3794
  }).join("\n");
3685
3795
  if (fence)
3686
3796
  fail(t("design.unclosedFence"));
3687
- return { title, body, requirements: [...references] };
3797
+ return { title, body, requirements: [...references], sources: front.sources ?? [] };
3688
3798
  }
3689
3799
  function renderDocument(doc) {
3690
- return `<!-- gitifact-${doc.kind}: ${doc.id} -->
3691
-
3800
+ return renderFrontmatter({ id: doc.id }) + `
3692
3801
  # ${doc.title}
3693
3802
 
3694
3803
  ${doc.body}
3695
3804
  `;
3696
3805
  }
3697
- function parseDocument(path3, source) {
3806
+ function parseDocument(path3, source2) {
3698
3807
  const match = documentPathPattern.exec(path3);
3699
3808
  if (!match)
3700
3809
  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))
3810
+ validateDocumentRelativePath(match[1]);
3811
+ if (source2.includes("\0") || /\r(?!\n)/.test(source2))
3708
3812
  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 }));
3813
+ const lines = normalized(source2).split("\n");
3814
+ const { id } = parseFrontmatter(lines, "wiki", "W");
3713
3815
  while (lines[0] === "")
3714
3816
  lines.shift();
3715
3817
  const heading = lines.shift();
@@ -3738,58 +3840,49 @@ function parseDocument(path3, source) {
3738
3840
  }
3739
3841
  if (fence)
3740
3842
  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) {
3843
+ return { id, path: path3, title, body };
3844
+ }
3845
+ function parseWikiFiles(files) {
3846
+ const wiki = { documents: [], history: parsePreviewHistory(files.get(WIKI_HISTORY_PATH) ?? "", "documents") };
3847
+ for (const [path3, source2] of files)
3848
+ if (documentPathPattern.test(path3))
3849
+ wiki.documents.push(parseDocument(path3, source2));
3850
+ wiki.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
3851
+ validateWiki(wiki);
3852
+ return wiki;
3853
+ }
3854
+ function validateWiki(wiki) {
3757
3855
  const ids = /* @__PURE__ */ new Set();
3758
3856
  const paths2 = /* @__PURE__ */ new Set();
3759
3857
  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
- }
3858
+ for (const d of wiki.documents) {
3859
+ if (ids.has(d.id))
3860
+ fail(t("document.duplicateId", { id: d.id }));
3861
+ ids.add(d.id);
3862
+ const lower = d.path.toLowerCase();
3863
+ if (paths2.has(lower))
3864
+ fail(t("document.caseOnlyPath", { path: d.path }));
3865
+ paths2.add(lower);
3866
+ }
3867
+ for (const h of wiki.history) {
3868
+ if (historyIds.has(h.id))
3869
+ fail(t("history.duplicateId", { id: h.id }));
3870
+ historyIds.add(h.id);
3779
3871
  }
3780
3872
  }
3781
- var emptyDocumentSets = () => DOCUMENT_KINDS.map((kind) => ({ kind, documents: [], history: [] }));
3782
- var asBundle = (value) => Array.isArray(value) ? { specs: value, documents: emptyDocumentSets() } : value;
3873
+ var emptyWiki = () => ({ documents: [], history: [] });
3874
+ var emptyBundle = () => ({ specs: [], wiki: emptyWiki() });
3875
+ var asBundle = (value) => Array.isArray(value) ? { specs: value, wiki: emptyWiki() } : value;
3783
3876
  function parsePreviewBundle(files) {
3784
- const bundle = { specs: parsePreviewFiles(files), documents: parseDocumentFiles(files) };
3877
+ const bundle = { specs: parsePreviewFiles(files), wiki: parseWikiFiles(files) };
3785
3878
  validateBundle(bundle);
3786
3879
  return bundle;
3787
3880
  }
3788
3881
  function validateBundle(bundle) {
3789
3882
  validatePreviewSnapshot(bundle.specs);
3790
- validateDocumentSets(bundle.documents);
3883
+ validateWiki(bundle.wiki);
3791
3884
  const ids = /* @__PURE__ */ new Set();
3792
- for (const h of [...bundle.specs.flatMap((s) => s.history), ...bundle.documents.flatMap((d) => d.history)]) {
3885
+ for (const h of [...bundle.specs.flatMap((s) => s.history), ...bundle.wiki.history]) {
3793
3886
  if (ids.has(h.id))
3794
3887
  fail(t("history.duplicateId", { id: h.id }));
3795
3888
  ids.add(h.id);
@@ -3799,28 +3892,92 @@ function designReferenceWarnings(specs) {
3799
3892
  const known = new Set(specs.flatMap((s) => s.requirements.map((r) => r.id)));
3800
3893
  return specs.flatMap((s) => (s.design?.requirements ?? []).filter((id) => !known.has(id)).map((id) => ({ specId: s.id, requirement: id, code: "MISSING_DESIGN_REFERENCE" })));
3801
3894
  }
3895
+ function resolveLink(from, link3) {
3896
+ const bare = link3.replace(/[?#].*$/, "");
3897
+ if (!bare || /^[a-z][a-z0-9+.-]*:/i.test(bare) || bare.startsWith("/") || bare.startsWith("//"))
3898
+ return null;
3899
+ const parts = from.split("/").slice(0, -1);
3900
+ for (const part of decodeSafe(bare).split("/")) {
3901
+ if (part === "" || part === ".")
3902
+ continue;
3903
+ if (part === "..") {
3904
+ if (!parts.length)
3905
+ return null;
3906
+ parts.pop();
3907
+ continue;
3908
+ }
3909
+ if (/[\\:\0]/.test(part))
3910
+ return null;
3911
+ parts.push(part);
3912
+ }
3913
+ return parts.join("/");
3914
+ }
3915
+ var decodeSafe = (value) => {
3916
+ try {
3917
+ return decodeURIComponent(value);
3918
+ } catch {
3919
+ return value;
3920
+ }
3921
+ };
3922
+ function extractLinks(body) {
3923
+ const links = [];
3924
+ let fence;
3925
+ for (const line of body.split("\n")) {
3926
+ if (fence) {
3927
+ if (new RegExp(`^ {0,3}${fence.char}{${fence.size},}\\s*$`).test(line))
3928
+ fence = void 0;
3929
+ continue;
3930
+ }
3931
+ const open6 = /^ {0,3}(`{3,}|~{3,})/.exec(line);
3932
+ if (open6) {
3933
+ fence = { char: open6[1][0], size: open6[1].length };
3934
+ continue;
3935
+ }
3936
+ for (const m of line.replace(/`[^`]*`/g, "").matchAll(/!?\[[^\]]*\]\((?:<([^>]+)>|([^)\s]+))(?:\s+"[^"]*")?\)/g))
3937
+ links.push(m[1] ?? m[2]);
3938
+ }
3939
+ return links;
3940
+ }
3941
+ function documentLinks(bundle) {
3942
+ const out = [];
3943
+ const collect = (from, links) => {
3944
+ for (const link3 of links) {
3945
+ const target = resolveLink(from, link3);
3946
+ if (target !== null)
3947
+ out.push({ from, link: link3, target });
3948
+ }
3949
+ };
3950
+ for (const s of bundle.specs) {
3951
+ collect(s.path, extractLinks(s.description + "\n" + s.requirements.map((r) => r.body).join("\n")));
3952
+ if (s.design) {
3953
+ const path3 = s.path.replace(/requirements\.md$/, "design.md");
3954
+ collect(path3, [...extractLinks(s.design.body), ...s.design.sources.flatMap((x) => x.path ? [x.path] : [])]);
3955
+ }
3956
+ }
3957
+ for (const d of bundle.wiki.documents)
3958
+ collect(d.path, extractLinks(d.body));
3959
+ return out;
3960
+ }
3802
3961
  function parsePreviewFiles(files) {
3803
3962
  const specs = [];
3804
- for (const [path3, source] of files) {
3963
+ for (const [path3, source2] of files) {
3805
3964
  if (documentPathPattern.test(path3) || documentHistoryPattern.test(path3))
3806
3965
  continue;
3807
3966
  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"))));
3967
+ specs.push(parseSpecPreview(path3, source2, files.get(path3.replace(/requirements\.md$/, "history.jsonl")) ?? "", files.get(path3.replace(/requirements\.md$/, "design.md"))));
3809
3968
  } else if (!files.has(path3.replace(/(?:history\.jsonl|design\.md)$/, "requirements.md")))
3810
3969
  fail(t("spec.orphanFile", { path: path3 }));
3811
3970
  }
3812
3971
  validatePreviewSnapshot(specs);
3813
3972
  return specs;
3814
3973
  }
3815
- function parseSpecPreview(path3, source, history = "", designSource) {
3974
+ function parseSpecPreview(path3, source2, history = "", designSource) {
3816
3975
  if (!/^\.(?:gitifact|tryce)\/spec\/[^/]+\/requirements\.md$/.test(path3))
3817
3976
  fail(t("spec.unsupportedPath", { path: path3 }));
3818
- if (source.includes("\0") || source.includes("\r") && /\r(?!\n)/.test(source))
3977
+ if (source2.includes("\0") || source2.includes("\r") && /\r(?!\n)/.test(source2))
3819
3978
  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 }));
3979
+ const lines = normalized(source2).split("\n");
3980
+ const specId = parseFrontmatter(lines, "spec", "S").id;
3824
3981
  let title = "";
3825
3982
  let description = "";
3826
3983
  let current;
@@ -3833,7 +3990,7 @@ function parseSpecPreview(path3, source, history = "", designSource) {
3833
3990
  else
3834
3991
  description += line + "\n";
3835
3992
  };
3836
- for (let i = 1; i < lines.length; i++) {
3993
+ for (let i = 0; i < lines.length; i++) {
3837
3994
  const line = lines[i];
3838
3995
  if (waitingId) {
3839
3996
  const match = new RegExp(`^<!-- ${marker}-req: (R-${token}) -->$`).exec(line);
@@ -3890,13 +4047,13 @@ function parseSpecPreview(path3, source, history = "", designSource) {
3890
4047
  fail(t("spec.emptyOrDuplicateRequirement", { id: r.id }));
3891
4048
  ids.add(r.id);
3892
4049
  }
3893
- return { id: specMatch[1], path: path3, title, description: normalized(description), requirements, history: parsePreviewHistory(history), ...designSource === void 0 ? {} : { design: parseDesignPreview(designSource, specMatch[1]) } };
4050
+ return { id: specId, path: path3, title, description: normalized(description), requirements, history: parsePreviewHistory(history), ...designSource === void 0 ? {} : { design: parseDesignPreview(designSource, specId) } };
3894
4051
  }
3895
- function parsePreviewHistory(source, scope = "spec") {
4052
+ function parsePreviewHistory(source2, scope = "spec") {
3896
4053
  const records = [];
3897
4054
  const ids = /* @__PURE__ */ new Set();
3898
4055
  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")) {
4056
+ for (const line of source2.replace(/\r\n/g, "\n").split("\n")) {
3900
4057
  if (!line.trim())
3901
4058
  continue;
3902
4059
  let value;
@@ -3908,7 +4065,7 @@ function parsePreviewHistory(source, scope = "spec") {
3908
4065
  if (!value || typeof value !== "object" || Array.isArray(value))
3909
4066
  fail(t("history.invalidRecord"));
3910
4067
  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)
4068
+ 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
4069
  fail(t("history.invalidFields"));
3913
4070
  ids.add(r.id);
3914
4071
  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 +4092,7 @@ function validatePreviewSnapshot(specs) {
3935
4092
  }
3936
4093
  }
3937
4094
  }
4095
+ var sameEntry = (a, b) => a.title === b.title && a.body === b.body && JSON.stringify(a.sources ?? []) === JSON.stringify(b.sources ?? []);
3938
4096
  function compareSpecPreviews(before, after) {
3939
4097
  validatePreviewSnapshot(before);
3940
4098
  validatePreviewSnapshot(after);
@@ -3944,7 +4102,7 @@ function compareSpecPreviews(before, after) {
3944
4102
  for (const [specs, map2] of [[before, prev], [after, next]])
3945
4103
  for (const s of specs)
3946
4104
  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") });
4105
+ 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
4106
  const previousHistory = new Map(before.flatMap((s) => s.history.map((h) => [h.id, h])));
3949
4107
  const reasons = after.flatMap((s) => s.history).filter((h) => {
3950
4108
  const old = previousHistory.get(h.id);
@@ -3963,7 +4121,7 @@ function compareSpecPreviews(before, after) {
3963
4121
  else {
3964
4122
  if (from.specId !== to.specId || id.startsWith("S-") && storeRelative(from.path) !== storeRelative(to.path))
3965
4123
  types.push("moved");
3966
- if (from.title !== to.title || from.body !== to.body)
4124
+ if (!sameEntry(from, to))
3967
4125
  types.push("modified");
3968
4126
  }
3969
4127
  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 +4143,14 @@ function compareSpecPreviews(before, after) {
3985
4143
  });
3986
4144
  return { specChanges, changes };
3987
4145
  }
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 }])));
4146
+ function compareWikis(before, after) {
4147
+ validateWiki(before);
4148
+ validateWiki(after);
4149
+ 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
4150
  const prev = index(before);
3993
4151
  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) => {
4152
+ const previousHistory = new Map(before.history.map((h) => [h.id, h]));
4153
+ const reasons = after.history.filter((h) => {
3996
4154
  const old = previousHistory.get(h.id);
3997
4155
  if (old && JSON.stringify(old) !== JSON.stringify(h))
3998
4156
  fail(t("history.changed", { id: h.id }));
@@ -4012,8 +4170,7 @@ function compareDocumentSets(before, after) {
4012
4170
  if (from.title !== to.title || from.body !== to.body)
4013
4171
  types.push("modified");
4014
4172
  }
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)) }] : [];
4173
+ return types.length ? [{ id, types, before: from, after: to, kind: "wiki", reasons: reasons.filter((h) => (h.documents ?? []).includes(id)) }] : [];
4017
4174
  });
4018
4175
  const changed = new Set(changes.map((c) => c.id));
4019
4176
  for (const h of reasons)
@@ -4027,7 +4184,7 @@ function comparePreviewBundles(before, after) {
4027
4184
  validateBundle(a);
4028
4185
  validateBundle(b);
4029
4186
  const specs = compareSpecPreviews(a.specs, b.specs);
4030
- return { specChanges: specs.specChanges, changes: [...specs.changes, ...compareDocumentSets(a.documents, b.documents)] };
4187
+ return { specChanges: specs.specChanges, changes: [...specs.changes, ...compareWikis(a.wiki, b.wiki)] };
4031
4188
  }
4032
4189
 
4033
4190
  // ../../packages/core/dist/formats/changelog.js
@@ -4106,8 +4263,7 @@ function previewPath(feature2) {
4106
4263
  return `.gitifact/spec/${feature2}/requirements.md`;
4107
4264
  }
4108
4265
  function renderSpecPreview(spec2) {
4109
- return `<!-- gitifact-spec: ${spec2.id} -->
4110
-
4266
+ return renderFrontmatter({ id: spec2.id }) + `
4111
4267
  # ${spec2.title}
4112
4268
 
4113
4269
  ` + (spec2.description ? spec2.description + "\n\n" : "") + spec2.requirements.map((r) => `## ${r.title}
@@ -4119,11 +4275,11 @@ ${r.body}
4119
4275
  function editSpecPreview(original, input2, generate) {
4120
4276
  if (!Array.isArray(input2) || !input2.length || input2.length > 100)
4121
4277
  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))]);
4278
+ const source2 = asBundle(original);
4279
+ const specs = source2.specs.map((s) => ({ ...s, requirements: s.requirements.map((r) => ({ ...r })), history: s.history.map((h) => ({ ...h, requirements: [...h.requirements] })) }));
4280
+ const wiki = { documents: source2.wiki.documents.map((d) => ({ ...d })), history: source2.wiki.history.map((h) => ({ ...h })) };
4281
+ validateBundle({ specs, wiki });
4282
+ const used = /* @__PURE__ */ new Set([...specs.flatMap((s) => [s.id, ...s.requirements.map((r) => r.id)]), ...wiki.documents.map((x) => x.id)]);
4127
4283
  const allocate = (prefix) => {
4128
4284
  for (let attempt = 0; attempt < 100; attempt++) {
4129
4285
  const id = generate(prefix);
@@ -4147,14 +4303,12 @@ function editSpecPreview(original, input2, generate) {
4147
4303
  update: ["type", "id", "title", "body"],
4148
4304
  move: ["type", "id", "feature"],
4149
4305
  "rename-spec": ["type", "id", "title"],
4150
- "set-design": ["type", "feature", "title", "body"],
4306
+ "set-design": ["type", "feature", "title", "body", "sources"],
4151
4307
  "delete-design": ["type", "feature"],
4152
4308
  "create-doc": ["type", "path", "title", "body"],
4153
4309
  "update-doc": ["type", "id", "title", "body"],
4154
4310
  "move-doc": ["type", "id", "path"],
4155
- "delete-doc": ["type", "id"],
4156
- "set-product": ["type", "title", "body"],
4157
- "delete-product": ["type"]
4311
+ "delete-doc": ["type", "id"]
4158
4312
  };
4159
4313
  const type = String(op.type);
4160
4314
  const fields2 = schemas[type];
@@ -4174,41 +4328,24 @@ function editSpecPreview(original, input2, generate) {
4174
4328
  const path3 = previewPath(str("feature"));
4175
4329
  return specs.find((s) => s.path === path3) ?? fail2(t("edit.specNotFound", { path: path3 }));
4176
4330
  };
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
4331
  if (type.endsWith("-doc")) {
4193
- const all = () => documents.filter((set2) => set2.kind === "guide").flatMap((set2) => set2.documents);
4194
- const documentPath = (kind, self) => {
4332
+ const documentPath = (self) => {
4195
4333
  const relative = str("path");
4196
4334
  validateDocumentRelativePath(relative);
4197
- const path3 = `${DOCUMENT_DIRS[kind]}/${relative}`;
4335
+ const path3 = `${WIKI_DIR}/${relative}`;
4198
4336
  if (self && self.path === path3)
4199
4337
  fail2(t("edit.samePath"));
4200
- if (all().some((d) => d !== self && d.path.toLowerCase() === path3.toLowerCase()))
4338
+ if (wiki.documents.some((d) => d !== self && d.path.toLowerCase() === path3.toLowerCase()))
4201
4339
  fail2(t("edit.documentPathExists", { path: path3 }));
4202
4340
  return path3;
4203
4341
  };
4204
4342
  const find = () => {
4205
4343
  const id = str("id");
4206
- return all().find((d) => d.id === id) ?? fail2(t("edit.guideIdNotFound", { id }));
4344
+ return wiki.documents.find((d) => d.id === id) ?? fail2(t("edit.documentIdNotFound", { id }));
4207
4345
  };
4208
4346
  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);
4347
+ const doc = { id: allocate("W"), path: documentPath(), title: str("title"), body: str("body") };
4348
+ wiki.documents.push(doc);
4212
4349
  results.push({ type, id: doc.id });
4213
4350
  } else if (type === "update-doc") {
4214
4351
  const doc = find();
@@ -4217,12 +4354,11 @@ function editSpecPreview(original, input2, generate) {
4217
4354
  results.push({ type, id: doc.id });
4218
4355
  } else if (type === "move-doc") {
4219
4356
  const doc = find();
4220
- doc.path = documentPath(doc.kind, doc);
4357
+ doc.path = documentPath(doc);
4221
4358
  results.push({ type, id: doc.id });
4222
4359
  } else {
4223
4360
  const doc = find();
4224
- const set2 = documents.find((s) => s.kind === doc.kind);
4225
- set2.documents = set2.documents.filter((d) => d.id !== doc.id);
4361
+ wiki.documents = wiki.documents.filter((d) => d.id !== doc.id);
4226
4362
  results.push({ type, id: doc.id });
4227
4363
  }
4228
4364
  continue;
@@ -4245,8 +4381,16 @@ function editSpecPreview(original, input2, generate) {
4245
4381
  if (!spec2.design)
4246
4382
  fail2(t("edit.noDesignToDelete"));
4247
4383
  delete spec2.design;
4248
- } else
4249
- spec2.design = parseDesignPreview(renderDesignPreview(spec2.id, { title: str("title"), body: str("body") }), spec2.id);
4384
+ } else {
4385
+ let sources = [];
4386
+ const list = op.sources;
4387
+ if (list !== void 0) {
4388
+ if (!Array.isArray(list) || list.length > 50)
4389
+ return fail2(t("edit.invalidSources"));
4390
+ sources = list.map(validateSource);
4391
+ }
4392
+ spec2.design = parseDesignPreview(renderDesignPreview(spec2.id, { title: str("title"), body: str("body"), sources }), spec2.id);
4393
+ }
4250
4394
  results.push({ type, id: spec2.id });
4251
4395
  } else if (type === "rename-spec") {
4252
4396
  const spec2 = specs.find((s) => s.id === str("id")) ?? fail2(t("edit.specIdNotFound"));
@@ -4254,13 +4398,13 @@ function editSpecPreview(original, input2, generate) {
4254
4398
  results.push({ type, id: spec2.id });
4255
4399
  } else {
4256
4400
  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);
4401
+ const source3 = specs.find((s) => s.requirements.some((r) => r.id === id)) ?? fail2(t("edit.requirementIdNotFound"));
4402
+ const req = source3.requirements.find((r) => r.id === id);
4259
4403
  if (type === "move") {
4260
4404
  const destination = target();
4261
- if (source2 === destination)
4405
+ if (source3 === destination)
4262
4406
  fail2(t("edit.sameSpec"));
4263
- source2.requirements = source2.requirements.filter((r) => r.id !== id);
4407
+ source3.requirements = source3.requirements.filter((r) => r.id !== id);
4264
4408
  destination.requirements.push(req);
4265
4409
  } else {
4266
4410
  req.title = str("title");
@@ -4274,14 +4418,12 @@ function editSpecPreview(original, input2, generate) {
4274
4418
  if (JSON.stringify({ ...parsed, history: spec2.history }) !== JSON.stringify(spec2))
4275
4419
  fail2(t("edit.bodyChangesSpecStructure"));
4276
4420
  }
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 };
4421
+ wiki.documents.sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
4422
+ for (const doc of wiki.documents)
4423
+ if (JSON.stringify(parseDocument(doc.path, renderDocument(doc))) !== JSON.stringify(doc))
4424
+ fail2(t("edit.bodyChangesDocumentStructure", { path: doc.path }));
4425
+ validateBundle({ specs, wiki });
4426
+ return { specs, wiki, results };
4285
4427
  }
4286
4428
 
4287
4429
  // ../../packages/core/dist/use-cases/prepare-spec-preview.js
@@ -4295,7 +4437,7 @@ function pendingPreviewReasons(before, current) {
4295
4437
  const b = asBundle(current);
4296
4438
  return [
4297
4439
  ...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 })))
4440
+ ...b.wiki.history.slice(a.wiki.history.length).map((h) => ({ kind: "wiki", path: WIKI_HISTORY_PATH, ...h }))
4299
4441
  ];
4300
4442
  }
4301
4443
  function finalSpecPreviewChanges(beforeValue, currentValue) {
@@ -4310,14 +4452,11 @@ function finalSpecPreviewChanges(beforeValue, currentValue) {
4310
4452
  if (next && !equal(next.history.slice(0, previous.history.length), previous.history))
4311
4453
  fail3(t("reason.committedImmutable", { id: previous.id }));
4312
4454
  }
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
- }
4455
+ if (!equal(current.wiki.history.slice(0, before.wiki.history.length), before.wiki.history))
4456
+ fail3(t("reason.committedDocumentImmutable"));
4318
4457
  const clean = {
4319
4458
  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 ?? [] }))
4459
+ wiki: { documents: current.wiki.documents, history: before.wiki.history }
4321
4460
  };
4322
4461
  return comparePreviewBundles(before, clean);
4323
4462
  }
@@ -4332,8 +4471,8 @@ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles,
4332
4471
  const writes = /* @__PURE__ */ new Map();
4333
4472
  const pending = /* @__PURE__ */ new Map();
4334
4473
  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();
4474
+ const used = new Set([...before.flatMap((s) => s.history), ...current.flatMap((s) => s.history), ...beforeBundle.wiki.history, ...currentBundle.wiki.history].map((h) => h.id));
4475
+ const wikiPending = [];
4337
4476
  const allocate = () => {
4338
4477
  for (let attempt = 0; attempt < 100; attempt++) {
4339
4478
  const id = generate();
@@ -4359,21 +4498,16 @@ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles,
4359
4498
  if (raw.requirements.length)
4360
4499
  fail3(t("reason.documentWithRequirements"));
4361
4500
  const ids = raw.documents;
4362
- let kind;
4363
4501
  for (const value2 of ids) {
4364
- if (typeof value2 !== "string" || !/^[PG]-[a-z2-7]{10}$/.test(value2) || covered.has(value2))
4502
+ if (typeof value2 !== "string" || !/^W-[a-z2-7]{10}$/.test(value2) || covered.has(value2))
4365
4503
  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;
4504
+ if (!delta.changes.some((c) => c.id === value2))
4505
+ fail3(t("reason.documentUnchanged", { id: value2 }));
4370
4506
  covered.add(value2);
4371
4507
  }
4372
4508
  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 }]);
4509
+ const reusable = currentBundle.wiki.history.slice(beforeBundle.wiki.history.length).find((h) => h.reason === reason && equal([...h.documents ?? []].sort(), documents));
4510
+ wikiPending.push({ id: reusable?.id ?? allocate(), requirements: [], documents, reason });
4377
4511
  continue;
4378
4512
  }
4379
4513
  for (const value2 of [...raw.requirements, ...raw.designs ?? []]) {
@@ -4401,34 +4535,32 @@ function prepareSpecPreview(beforeValue, currentValue, baseFiles, currentFiles,
4401
4535
  pending.set(specId, [...pending.get(specId) ?? [], record3]);
4402
4536
  }
4403
4537
  }
4538
+ const historyText = (base, records) => {
4539
+ let text2 = base ?? "";
4540
+ if (records.length)
4541
+ text2 += (text2 && !text2.endsWith("\n") ? "\n" : "") + records.map((h) => JSON.stringify(h) + "\n").join("");
4542
+ return base !== void 0 || records.length ? text2 : null;
4543
+ };
4404
4544
  for (const spec2 of current) {
4405
4545
  const old = before.find((s) => s.id === spec2.id);
4406
4546
  const base = old ? baseFiles.get(historyPath(old)) : void 0;
4407
4547
  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
4548
  const path3 = historyPath(spec2);
4412
- const next = base !== void 0 || records.length ? text2 : null;
4549
+ const next = historyText(base, records);
4413
4550
  if ((currentFiles.get(path3) ?? null) !== next)
4414
4551
  writes.set(path3, next);
4415
4552
  }
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);
4553
+ {
4554
+ const records = wikiPending.sort((a, b) => a.documents[0] < b.documents[0] ? -1 : 1);
4555
+ const next = historyText(baseFiles.get(WIKI_HISTORY_PATH), records);
4556
+ if ((currentFiles.get(WIKI_HISTORY_PATH) ?? null) !== next)
4557
+ writes.set(WIKI_HISTORY_PATH, next);
4426
4558
  }
4427
4559
  return {
4428
4560
  writes,
4429
4561
  changes: delta.changes,
4430
4562
  specChanges: delta.specChanges,
4431
- reasons: [...[...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))), ...[...documentPending].flatMap(([kind, records]) => records.map((record3) => ({ kind, ...record3 })))],
4563
+ reasons: [...[...pending].flatMap(([specId, records]) => records.map((record3) => ({ specId, ...record3 }))), ...wikiPending.map((record3) => ({ kind: "wiki", ...record3 }))],
4432
4564
  withoutReason: delta.changes.filter((c) => !covered.has(c.id)).map((c) => c.id)
4433
4565
  };
4434
4566
  }
@@ -4629,7 +4761,7 @@ function createRepositoryReader(cwd, options = {}) {
4629
4761
  return output2;
4630
4762
  };
4631
4763
  const discover = async () => {
4632
- const scalar = async (flag) => decodeGitLine(await invoke(["rev-parse", "--path-format=absolute", flag]));
4764
+ const scalar2 = async (flag) => decodeGitLine(await invoke(["rev-parse", "--path-format=absolute", flag]));
4633
4765
  const flags = ["--is-bare-repository", "--is-inside-git-dir", "--is-inside-work-tree"];
4634
4766
  const paths2 = ["--show-toplevel", "--absolute-git-dir", "--git-common-dir", "--show-object-format"];
4635
4767
  let combined;
@@ -4646,7 +4778,7 @@ function createRepositoryReader(cwd, options = {}) {
4646
4778
  }
4647
4779
  if (bare === "true" || internal === "true" || inside !== "true") throw new RepositoryReadError("UNSUPPORTED_REPOSITORY");
4648
4780
  let values = combined.slice(3);
4649
- if (values.length !== paths2.length) values = await Promise.all(paths2.map(scalar));
4781
+ if (values.length !== paths2.length) values = await Promise.all(paths2.map(scalar2));
4650
4782
  const [rootPath, gitDir, commonDir, objectFormat] = values;
4651
4783
  if (objectFormat !== "sha1" && objectFormat !== "sha256") throw new RepositoryReadError("GIT_UNSUPPORTED");
4652
4784
  return { rootPath, gitDir, commonDir, objectFormat };
@@ -5381,10 +5513,10 @@ function cached(getter) {
5381
5513
  function nullish(input2) {
5382
5514
  return input2 === null || input2 === void 0;
5383
5515
  }
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);
5516
+ function cleanRegex(source2) {
5517
+ const start = source2.startsWith("^") ? 1 : 0;
5518
+ const end = source2.endsWith("$") ? source2.length - 1 : source2.length;
5519
+ return source2.slice(start, end);
5388
5520
  }
5389
5521
  function floatSafeRemainder(val, step) {
5390
5522
  const ratio = val / step;
@@ -5452,28 +5584,28 @@ function putProp(target, key, value) {
5452
5584
  else
5453
5585
  target[key] = value;
5454
5586
  }
5455
- function mirrorShape(target, source, keys2, wrap) {
5456
- const raw = sourceShape(source);
5587
+ function mirrorShape(target, source2, keys2, wrap) {
5588
+ const raw = sourceShape(source2);
5457
5589
  for (const key of keys2) {
5458
5590
  const desc = Object.getOwnPropertyDescriptor(raw, key);
5459
5591
  if (!desc.enumerable)
5460
5592
  continue;
5461
5593
  if (desc.get) {
5462
5594
  deferProp(target, key, () => {
5463
- const value = source._zod.def.shape[key];
5595
+ const value = source2._zod.def.shape[key];
5464
5596
  return wrap ? wrap(value, key) : value;
5465
5597
  });
5466
5598
  } else
5467
5599
  putProp(target, key, wrap ? wrap(desc.value, key) : desc.value);
5468
5600
  }
5469
5601
  }
5470
- function mirrorProps(target, source) {
5471
- for (const key of Reflect.ownKeys(source)) {
5472
- const desc = Object.getOwnPropertyDescriptor(source, key);
5602
+ function mirrorProps(target, source2) {
5603
+ for (const key of Reflect.ownKeys(source2)) {
5604
+ const desc = Object.getOwnPropertyDescriptor(source2, key);
5473
5605
  if (!desc.enumerable)
5474
5606
  continue;
5475
5607
  if (desc.get)
5476
- deferProp(target, key, () => source[key]);
5608
+ deferProp(target, key, () => source2[key]);
5477
5609
  else
5478
5610
  putProp(target, key, desc.value);
5479
5611
  }
@@ -6686,8 +6818,8 @@ var creditCard = /^\d(?:[ -]?\d){11,18}$/;
6686
6818
  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
6819
  var iban = /^[A-Z]{2}(?!00|01|99)\d{2}[A-Z0-9]{11,30}$/;
6688
6820
  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}$`);
6821
+ function anchor(source2) {
6822
+ return new RegExp(`^${source2}$`);
6691
6823
  }
6692
6824
  var date = /* @__PURE__ */ anchor(dateSource);
6693
6825
  function timeSource(args) {
@@ -9510,11 +9642,11 @@ var $ZodTemplateLiteral = /* @__PURE__ */ $constructor("$ZodTemplateLiteral", (i
9510
9642
  const regexParts = [];
9511
9643
  for (const part of def.parts) {
9512
9644
  if (typeof part === "object" && part !== null) {
9513
- const source = partPattern(part);
9514
- if (!source) {
9645
+ const source2 = partPattern(part);
9646
+ if (!source2) {
9515
9647
  throw new Error(`Invalid template literal part, no pattern found: ${[...part._zod.traits].shift()}`);
9516
9648
  }
9517
- regexParts.push(cleanRegex(source));
9649
+ regexParts.push(cleanRegex(source2));
9518
9650
  } else if (part === null || primitiveTypes.has(typeof part)) {
9519
9651
  regexParts.push(escapeRegex(`${part}`));
9520
9652
  } else {
@@ -17640,9 +17772,9 @@ function withParser(schema, parser) {
17640
17772
  installCompiledUserMethods(clone2, schema, parser);
17641
17773
  return clone2;
17642
17774
  }
17643
- function installCompiledUserMethods(target, source, parser) {
17775
+ function installCompiledUserMethods(target, source2, parser) {
17644
17776
  const targetAny = target;
17645
- const sourceAny = source;
17777
+ const sourceAny = source2;
17646
17778
  if (typeof sourceAny.safeParse === "function") {
17647
17779
  const originalSafeParse = sourceAny.safeParse;
17648
17780
  targetAny.safeParse = (data, params) => {
@@ -20237,10 +20369,10 @@ function _stringFormat(Class2, format, fnOrRegex, _params = {}) {
20237
20369
 
20238
20370
  // ../../node_modules/.pnpm/zod@4.6.4/node_modules/zod/v4/core/to-json-schema.js
20239
20371
  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]);
20372
+ for (const source2 of sources) {
20373
+ for (const key of Reflect.ownKeys(source2)) {
20374
+ if (Object.prototype.propertyIsEnumerable.call(source2, key)) {
20375
+ assignProp(target, key, source2[key]);
20244
20376
  }
20245
20377
  }
20246
20378
  }
@@ -24463,7 +24595,7 @@ var projectInitV4 = external_exports.discriminatedUnion("ok", [
24463
24595
  outcome: external_exports.enum(["planned", "created", "already-initialized"]),
24464
24596
  rootPath: external_exports.string(),
24465
24597
  configPath: external_exports.literal(".gitifact/config.json"),
24466
- schemaVersion: external_exports.literal(1),
24598
+ schemaVersion: external_exports.literal(2),
24467
24599
  baseline: external_exports.discriminatedUnion("kind", [
24468
24600
  external_exports.strictObject({ kind: external_exports.literal("empty") }),
24469
24601
  external_exports.strictObject({ kind: external_exports.literal("commit"), objectFormat: external_exports.enum(["sha1", "sha256"]), commit: external_exports.string() })
@@ -24473,25 +24605,26 @@ var projectInitV4 = external_exports.discriminatedUnion("ok", [
24473
24605
  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() }) })
24474
24606
  ]);
24475
24607
 
24476
- // ../../packages/contracts/dist/versions/browser-specs-v1.js
24608
+ // ../../packages/contracts/dist/versions/browser-specs-v2.js
24477
24609
  var requirement = external_exports.strictObject({ id: external_exports.string(), title: external_exports.string(), body: external_exports.string() });
24478
24610
  var contributor = external_exports.strictObject({ email: external_exports.string(), name: external_exports.string(), commits: external_exports.number().int().nonnegative(), latest: external_exports.string() });
24611
+ 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
24612
  var feature = external_exports.strictObject({
24480
24613
  id: external_exports.string(),
24481
24614
  path: external_exports.string(),
24482
24615
  title: external_exports.string(),
24483
24616
  description: external_exports.string(),
24484
24617
  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(),
24618
+ 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
24619
  // Authors of commits touching the feature folder (current or legacy store) and the latest such commit; empty until first committed.
24487
24620
  contributors: external_exports.array(contributor),
24488
24621
  updatedAt: external_exports.string().nullable()
24489
24622
  });
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({
24623
+ var snapshot = requirement.extend({ specId: external_exports.string(), path: external_exports.string(), sources: external_exports.array(source).optional() }).nullable();
24624
+ 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() });
24625
+ var browserSpecsV2 = external_exports.strictObject({
24493
24626
  contract: external_exports.literal("browser-specs"),
24494
- version: external_exports.literal(1),
24627
+ version: external_exports.literal(2),
24495
24628
  sessionId: external_exports.string(),
24496
24629
  head: external_exports.string().nullable(),
24497
24630
  observedAt: external_exports.string(),
@@ -24507,7 +24640,7 @@ var browserSpecsV1 = external_exports.strictObject({
24507
24640
  committer: external_exports.string(),
24508
24641
  message: external_exports.string(),
24509
24642
  id: external_exports.string(),
24510
- kind: external_exports.enum(["requirement", "design", "product", "guide"]).optional(),
24643
+ kind: external_exports.enum(["requirement", "design", "wiki"]).optional(),
24511
24644
  types: external_exports.array(external_exports.enum(["created", "modified", "moved", "deleted"])),
24512
24645
  before: snapshot,
24513
24646
  after: snapshot,
@@ -24563,7 +24696,7 @@ var browserSessionV2 = external_exports.strictObject({
24563
24696
  update: updateStateV1
24564
24697
  });
24565
24698
 
24566
- // ../../packages/contracts/dist/versions/update-v2.js
24699
+ // ../../packages/contracts/dist/versions/update-v3.js
24567
24700
  var commitReason = external_exports.enum(["untracked", "other-changes", "commit-failed"]);
24568
24701
  var agentDocsCommit = external_exports.strictObject({
24569
24702
  state: external_exports.enum(["not-requested", "nothing", "committed", "skipped"]),
@@ -24573,10 +24706,10 @@ var agentDocsCommit = external_exports.strictObject({
24573
24706
  reason: commitReason.nullable(),
24574
24707
  detail: external_exports.string().nullable()
24575
24708
  }).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", [
24709
+ var updateV3 = external_exports.discriminatedUnion("ok", [
24577
24710
  external_exports.strictObject({
24578
24711
  contract: external_exports.literal("update"),
24579
- version: external_exports.literal(2),
24712
+ version: external_exports.literal(3),
24580
24713
  ok: external_exports.literal(true),
24581
24714
  cliVersion: external_exports.string().min(1),
24582
24715
  update: updateStateV1,
@@ -24584,10 +24717,11 @@ var updateV2 = external_exports.discriminatedUnion("ok", [
24584
24717
  install: external_exports.strictObject({ npmGlobal: external_exports.string().min(1) }).nullable(),
24585
24718
  // refreshed: blocks whose version line differed were rewritten. current: every block already matched.
24586
24719
  // 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()) }),
24720
+ // missing: files `gitifact init` would add and update does not create, e.g. CLAUDE.md when only AGENTS.md has the block.
24721
+ 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
24722
  commit: agentDocsCommit
24589
24723
  }),
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() }) })
24724
+ 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
24725
  ]);
24592
24726
 
24593
24727
  // src/shared/i18n/ko/messages.json
@@ -24605,7 +24739,7 @@ var messages_default2 = {
24605
24739
  "help.initRemoveAgents": "GITIFACT \uBE14\uB85D\uC744 \uC81C\uAC70",
24606
24740
  "help.initSkipAgents": "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC74C",
24607
24741
  "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",
24742
+ "help.docsTopic": "workflow, spec, design, wiki, commit \uC911 \uD558\uB098",
24609
24743
  "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
24744
  "help.migrateDryRun": "\uD30C\uC77C\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uC804\uD658 \uACC4\uD68D \uD655\uC778",
24611
24745
  "help.spec": "Markdown \uBA85\uC138 \uC791\uC131\xB7\uC870\uD68C\xB7Git \uAE30\uB85D",
@@ -24622,11 +24756,11 @@ var messages_default2 = {
24622
24756
  "help.specDiffFrom": "\uC774\uC804 \uCEE4\uBC0B",
24623
24757
  "help.specDiffTo": "\uC774\uD6C4 \uCEE4\uBC0B",
24624
24758
  "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",
24759
+ "help.specWorking": "\uD604\uC7AC \uBA85\uC138\xB7\uC704\uD0A4 \uC804\uCCB4\uC640 stamp, \uC785\uB825 \uD30C\uC77C \uACBD\uB85C \uC870\uD68C",
24626
24760
  "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)",
24761
+ "help.specWorkingFeature": "\uD55C \uAE30\uB2A5 \uD3F4\uB354\uC758 \uBA85\uC138\uB9CC \uCD9C\uB825 (\uC704\uD0A4 \uC81C\uC678)",
24628
24762
  "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",
24763
+ "help.specSave": "\uC694\uAD6C\uC0AC\uD56D\xB7\uC124\uACC4\xB7\uC704\uD0A4 \uC800\uC7A5",
24630
24764
  "help.specRead": "\uCEE4\uBC0B \uC2DC\uC810\uC758 \uBA85\uC138 \uC870\uD68C",
24631
24765
  "help.specDiff": "\uB450 \uCEE4\uBC0B \uC0AC\uC774\uC758 \uBA85\uC138 \uBCC0\uACBD \uC870\uD68C",
24632
24766
  "git.operationInProgress": "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: {marker}",
@@ -24722,7 +24856,6 @@ var messages_default2 = {
24722
24856
  "server.crossSite": "\uC678\uBD80 \uD398\uC774\uC9C0\uC758 \uC694\uCCAD\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24723
24857
  "server.noQuery": "\uC870\uD68C \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24724
24858
  "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
24859
  "server.apiNotFound": "API\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24727
24860
  "server.sessionChanged": "\uC11C\uBC84 \uC138\uC158\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.",
24728
24861
  "server.badRange": "\uC870\uD68C \uBC94\uC704\uB97C \uD655\uC778\uD558\uC138\uC694.",
@@ -24820,8 +24953,7 @@ var messages_default2 = {
24820
24953
  "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
24954
  "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
24955
  "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",
24956
+ "docs.summary.design": "design.md \uBAA9\uCC28, \uCC38\uC870 \uC8FC\uC11D\uACFC \uCC38\uACE0 \uBB38\uC11C, \uAC1C\uC815 \uBC29\uC2DD",
24825
24957
  "docs.summary.commit": "spec commit \uC785\uB825, \uC774\uC720 \uAE30\uB85D, \uC2E4\uD328 \uD6C4 \uBCF5\uAD6C",
24826
24958
  "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
24959
  "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 +24972,19 @@ var messages_default2 = {
24840
24972
  "update.text.blockCurrent": "\uC9C0\uCE68 \uBE14\uB85D\uC740 \uC774\uBBF8 \uD604\uC7AC \uBC84\uC804\uC785\uB2C8\uB2E4: {paths}",
24841
24973
  "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
24974
  "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.",
24975
+ "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
24976
  "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
24844
24977
  "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
24845
24978
  "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
24979
  "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
24980
  "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
24981
  "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."
24982
+ "update.failed": "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\uD588\uC2B5\uB2C8\uB2E4.",
24983
+ "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)",
24984
+ "server.assetNotFound": "\uC5D0\uC14B\uC744 \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24985
+ "docs.wikiPolicyProject": "\uC6B4\uC601 \uBC29\uCE68 (.gitifact/wiki/README.md)",
24986
+ "docs.wikiPolicyDefault": "\uC6B4\uC601 \uBC29\uCE68 (\uAE30\uBCF8\uAC12. .gitifact/wiki/README.md\uAC00 \uC5C6\uC5B4 \uB0B4\uC7A5 \uBC29\uCE68\uC744 \uC2E3\uB294\uB2E4)",
24987
+ "init.wikiReadmeTitle": "\uC704\uD0A4 \uC6B4\uC601 \uBC29\uCE68"
24850
24988
  };
24851
24989
 
24852
24990
  // src/shared/i18n/index.ts
@@ -24922,8 +25060,8 @@ async function runStatus(format) {
24922
25060
  import { createServer } from "node:http";
24923
25061
  import { randomUUID as randomUUID4 } from "node:crypto";
24924
25062
  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";
25063
+ import { lstat as lstat4, readFile as readFile4 } from "node:fs/promises";
25064
+ import { join as join5 } from "node:path";
24927
25065
 
24928
25066
  // src/server/assets.ts
24929
25067
  import { readdir, readFile } from "node:fs/promises";
@@ -24956,6 +25094,7 @@ function contentType(path3) {
24956
25094
  if (/\.jpe?g$/.test(path3)) return "image/jpeg";
24957
25095
  if (path3.endsWith(".gif")) return "image/gif";
24958
25096
  if (path3.endsWith(".webp")) return "image/webp";
25097
+ if (path3.endsWith(".pdf")) return "application/pdf";
24959
25098
  return "application/octet-stream";
24960
25099
  }
24961
25100
 
@@ -25062,7 +25201,7 @@ function commandScoped(key, load) {
25062
25201
  }
25063
25202
 
25064
25203
  // src/adapters/git/spec-preview-reader.ts
25065
- var RECORD_PATHSPECS = [...STORE_DIRS.map((d) => d + "/spec/"), ...Object.values(DOCUMENT_DIRS).map((d) => d + "/")];
25204
+ var RECORD_PATHSPECS = [...STORE_DIRS.map((d) => d + "/spec/"), WIKI_DIR + "/"];
25066
25205
  var LegacyBaselineError = class extends SpecPreviewError {
25067
25206
  };
25068
25207
  function specPreviewReader(cwd) {
@@ -25096,7 +25235,7 @@ function specPreviewReader(cwd) {
25096
25235
  const [, mode, oid2, stage, path3] = match;
25097
25236
  if (!["100644", "100755"].includes(mode) || stage !== "0") throw new SpecPreviewError(t2("reader.stagingConflictOrLink"));
25098
25237
  if (!recordPathPattern.test(path3)) {
25099
- if (/^\.gitifact\/(?:product|guides)\//.test(path3)) continue;
25238
+ if (path3.startsWith(WIKI_DIR + "/")) continue;
25100
25239
  throw new SpecPreviewError(t2("reader.stagingUnsupported"));
25101
25240
  }
25102
25241
  files.set(path3, oid2);
@@ -25192,38 +25331,85 @@ function specPreviewReader(cwd) {
25192
25331
  }
25193
25332
 
25194
25333
  // 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";
25334
+ import { lstat as lstat3, readdir as readdir3, readFile as readFile3, mkdir as mkdir2, writeFile, rename, unlink as unlink2, rmdir } from "node:fs/promises";
25335
+ import { join as join4, dirname } from "node:path";
25197
25336
  import { createHash as createHash2, randomBytes } from "node:crypto";
25198
25337
 
25199
- // src/adapters/filesystem/config-file.ts
25200
- import { lstat, readFile as readFile2, realpath, mkdir, open as open2, link, unlink } from "node:fs/promises";
25338
+ // src/adapters/filesystem/working-warnings.ts
25339
+ import { lstat, readdir as readdir2 } from "node:fs/promises";
25201
25340
  import { join as join2 } from "node:path";
25341
+ var info = async (path3) => lstat(path3).catch((e) => {
25342
+ if (e.code === "ENOENT") return void 0;
25343
+ throw e;
25344
+ });
25345
+ var posix = (path3) => path3.split("\\").join("/");
25346
+ async function listAssets(root) {
25347
+ const out = [];
25348
+ async function visit2(relative, depth) {
25349
+ const stat3 = await info(join2(root, relative));
25350
+ if (!stat3 || stat3.isSymbolicLink()) return;
25351
+ if (stat3.isDirectory()) {
25352
+ if (depth > 8 || out.length > 4e3) return;
25353
+ for (const name of (await readdir2(join2(root, relative))).sort()) await visit2(relative + "/" + name, depth + 1);
25354
+ } else if (stat3.isFile()) out.push({ path: posix(relative), bytes: stat3.size });
25355
+ }
25356
+ await visit2(ASSETS_DIR, 0);
25357
+ return out;
25358
+ }
25359
+ async function workingWarnings(root, bundle) {
25360
+ const warnings = designReferenceWarnings(bundle.specs);
25361
+ 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)]);
25362
+ const assets = await listAssets(root);
25363
+ const assetPaths = new Set(assets.map((a) => a.path));
25364
+ const referenced = /* @__PURE__ */ new Set();
25365
+ for (const { from, link: link3, target } of documentLinks(bundle)) {
25366
+ if (records.has(target)) continue;
25367
+ if (assetPaths.has(target)) {
25368
+ referenced.add(target);
25369
+ continue;
25370
+ }
25371
+ const stat3 = target.startsWith(".gitifact/") ? void 0 : await info(join2(root, ...target.split("/")));
25372
+ if (!stat3?.isFile()) warnings.push({ code: "MISSING_LINK_TARGET", path: from, target, link: link3 });
25373
+ }
25374
+ let total = 0;
25375
+ for (const asset of assets) {
25376
+ total += asset.bytes;
25377
+ if (asset.bytes > ASSET_SIZE_LIMIT) warnings.push({ code: "ASSET_SIZE", path: asset.path, bytes: asset.bytes });
25378
+ if (!RECOMMENDED_ASSET_EXTENSIONS.includes(assetExtension(asset.path))) warnings.push({ code: "ASSET_EXTENSION", path: asset.path });
25379
+ if (!referenced.has(asset.path)) warnings.push({ code: "UNREFERENCED_ASSET", path: asset.path });
25380
+ }
25381
+ if (total > ASSETS_TOTAL_LIMIT) warnings.push({ code: "ASSETS_TOTAL_SIZE", bytes: total });
25382
+ return warnings;
25383
+ }
25384
+
25385
+ // src/adapters/filesystem/config-file.ts
25386
+ import { lstat as lstat2, readFile as readFile2, realpath, mkdir, open as open2, link, unlink } from "node:fs/promises";
25387
+ import { join as join3 } from "node:path";
25202
25388
  import { randomUUID as randomUUID3 } from "node:crypto";
25203
25389
  var missing = (error62) => error62.code === "ENOENT";
25204
- var fileInfo = (path3) => lstat(path3).catch((error62) => {
25390
+ var fileInfo = (path3) => lstat2(path3).catch((error62) => {
25205
25391
  if (missing(error62)) return void 0;
25206
25392
  throw error62;
25207
25393
  });
25208
25394
  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;
25395
+ const path3 = join3(root, ".gitifact");
25396
+ const info7 = await fileInfo(path3);
25397
+ if (info7 && (!info7.isDirectory() || info7.isSymbolicLink())) throw new InitError("PATH_CONFLICT", t2("config.storeNotDirectory"));
25398
+ return info7;
25213
25399
  }
25214
25400
  async function readConfigFile(root) {
25215
25401
  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"));
25402
+ const path3 = join3(root, ".gitifact", "config.json");
25403
+ const info7 = await fileInfo(path3);
25404
+ if (!info7) {
25405
+ if (await fileInfo(join3(root, ".tryce", "config.json"))) throw new InitError("MIGRATION_REQUIRED", t2("config.migrateFirst"));
25220
25406
  return void 0;
25221
25407
  }
25222
- if (!info5.isFile() || info5.isSymbolicLink() || info5.size > 65536) throw new InitError("PATH_CONFLICT", t2("config.fileShape"));
25408
+ if (!info7.isFile() || info7.isSymbolicLink() || info7.size > 65536) throw new InitError("PATH_CONFLICT", t2("config.fileShape"));
25223
25409
  const bytes = await readFile2(path3);
25224
25410
  const after = await fileInfo(path3);
25225
25411
  await configDirectory(root);
25226
- if (!after || after.ino !== info5.ino || after.dev !== info5.dev || after.mtimeMs !== info5.mtimeMs || bytes.length > 65536) {
25412
+ if (!after || after.ino !== info7.ino || after.dev !== info7.dev || after.mtimeMs !== info7.mtimeMs || bytes.length > 65536) {
25227
25413
  throw new InitError("INPUT_CHANGED", t2("config.changedWhileReading"));
25228
25414
  }
25229
25415
  try {
@@ -25233,7 +25419,7 @@ async function readConfigFile(root) {
25233
25419
  }
25234
25420
  }
25235
25421
  async function publishConfig(root, text2, recheck, beforePublish) {
25236
- const directory = join2(root, ".gitifact");
25422
+ const directory = join3(root, ".gitifact");
25237
25423
  await configDirectory(root);
25238
25424
  await mkdir(directory).catch((error62) => {
25239
25425
  if (error62.code !== "EEXIST") throw error62;
@@ -25246,7 +25432,7 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25246
25432
  throw new InitError("INPUT_CHANGED", t2("config.directoryChanged"));
25247
25433
  }
25248
25434
  };
25249
- const temporary = join2(directory, ".init-" + randomUUID3() + ".tmp");
25435
+ const temporary = join3(directory, ".init-" + randomUUID3() + ".tmp");
25250
25436
  const handle = await open2(temporary, "wx", 384);
25251
25437
  const tempOwner = await handle.stat();
25252
25438
  try {
@@ -25263,7 +25449,7 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25263
25449
  const bytes = await readFile2(temporary, "utf8");
25264
25450
  if (bytes !== text2) throw new InitError("INPUT_CHANGED", t2("config.temporaryChanged"));
25265
25451
  parseManagedConfig(bytes);
25266
- await link(temporary, join2(directory, "config.json"));
25452
+ await link(temporary, join3(directory, "config.json"));
25267
25453
  } finally {
25268
25454
  await unchanged();
25269
25455
  const current = await fileInfo(temporary);
@@ -25275,14 +25461,14 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25275
25461
  var fail4 = (message) => {
25276
25462
  throw new SpecPreviewError(message);
25277
25463
  };
25278
- var info = async (path3) => lstat2(path3).catch((e) => {
25464
+ var info2 = async (path3) => lstat3(path3).catch((e) => {
25279
25465
  if (e.code === "ENOENT") return void 0;
25280
25466
  throw e;
25281
25467
  });
25282
25468
  var digest = (value) => createHash2("sha256").update(value).digest("hex");
25283
25469
  var LEGACY_LOCKS = ["tryce-spec-preview.lock", "tryce-spec-commit.lock"];
25284
25470
  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) }));
25471
+ for (const name of LEGACY_LOCKS) if (await info2(join4(gitDir, name))) fail4(t2("store.legacyLock", { path: join4(gitDir, name) }));
25286
25472
  }
25287
25473
  var decode3 = (bytes) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
25288
25474
  var generatePreviewId = (prefix) => prefix + "-" + [...randomBytes(10)].map((n) => "abcdefghijklmnopqrstuvwxyz234567"[n & 31]).join("");
@@ -25292,48 +25478,48 @@ async function snapshot2(root) {
25292
25478
  const files = /* @__PURE__ */ new Map();
25293
25479
  let count = 0;
25294
25480
  let bytes = 0;
25295
- const parent = await info(join3(root, ".gitifact"));
25481
+ const parent = await info2(join4(root, ".gitifact"));
25296
25482
  if (parent && (!parent.isDirectory() || parent.isSymbolicLink())) fail4(t2("store.storeNotDirectory"));
25297
- if (!parent && await info(join3(root, ".tryce"))) fail4(t2("store.migrateFirst"));
25483
+ if (!parent && await info2(join4(root, ".tryce"))) fail4(t2("store.migrateFirst"));
25298
25484
  const config2 = await readConfigFile(root);
25299
- if (config2 !== void 0 && !("schemaVersion" in parseManagedConfig(config2)) || await info(join3(root, "specs"))) fail4(t2("store.legacyProject"));
25485
+ if (config2 !== void 0 && !("schemaVersion" in parseManagedConfig(config2)) || await info2(join4(root, "specs"))) fail4(t2("store.legacyProject"));
25300
25486
  async function visit2(path3, depth, documents) {
25301
- const stat3 = await info(join3(root, path3));
25487
+ const stat3 = await info2(join4(root, path3));
25302
25488
  if (!stat3) return;
25303
25489
  if (stat3.isSymbolicLink() || !stat3.isDirectory() && !stat3.isFile()) fail4(t2("store.linkOrSpecial", { path: path3 }));
25304
25490
  if (++count > 4e3) fail4(t2("store.tooManyFiles"));
25305
25491
  if (stat3.isDirectory()) {
25306
25492
  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);
25493
+ for (const name of (await readdir3(join4(root, path3))).sort()) await visit2(path3 + "/" + name, depth + 1, documents);
25308
25494
  } else {
25309
25495
  if (path3.endsWith("/tryce.json")) fail4(t2("store.legacyJson"));
25310
25496
  if (documents ? !(path3.endsWith(".md") || depth === 1 && path3.endsWith("/history.jsonl")) : !/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
25311
25497
  if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/")) fail4(t2("store.unsupportedPath", { path: path3 }));
25312
25498
  if (stat3.nlink !== 1 || stat3.size > 1024 * 1024) fail4(t2("store.hardLinkOrSize"));
25313
- const raw = await readFile3(join3(root, path3));
25499
+ const raw = await readFile3(join4(root, path3));
25314
25500
  bytes += raw.length;
25315
25501
  if (raw.length > 1024 * 1024 || bytes > 16 * 1024 * 1024) fail4(t2("store.sizeLimit"));
25316
25502
  files.set(path3, decode3(raw));
25317
25503
  }
25318
25504
  }
25319
25505
  await visit2(".gitifact/spec", 0, false);
25320
- for (const dir of Object.values(DOCUMENT_DIRS)) await visit2(dir, 0, true);
25506
+ await visit2(WIKI_DIR, 0, true);
25321
25507
  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 ?? "")) };
25508
+ 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
25509
  }
25324
25510
  async function readWorkingPreviewState(cwd) {
25325
25511
  const { root, gitDir } = await specPreviewReader(cwd).location();
25326
25512
  await failOnLegacyLock(gitDir);
25327
- if (await info(join3(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.locked"));
25513
+ if (await info2(join4(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.locked"));
25328
25514
  const first = await snapshot2(root);
25329
25515
  const second = await snapshot2(root);
25330
25516
  if (first.stamp !== second.stamp) fail4(t2("store.changedWhileReading"));
25331
- if (await info(join3(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.lockedWhileReading"));
25517
+ if (await info2(join4(gitDir, "gitifact-spec-preview.lock"))) fail4(t2("store.lockedWhileReading"));
25332
25518
  return first;
25333
25519
  }
25334
25520
  async function readWorkingPreview(cwd) {
25335
- const { stamp, specs, documents } = await readWorkingPreviewState(cwd);
25336
- return { stamp, specs, documents, warnings: designReferenceWarnings(specs) };
25521
+ const { root, stamp, specs, wiki, bundle } = await readWorkingPreviewState(cwd);
25522
+ return { stamp, specs, wiki, warnings: await workingWarnings(root, bundle) };
25337
25523
  }
25338
25524
  async function saveWorkingPreview(cwd, input2, publish = rename) {
25339
25525
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) fail4(t2("store.invalidInput"));
@@ -25347,8 +25533,8 @@ async function saveWorkingPreview(cwd, input2, publish = rename) {
25347
25533
  if (!old || renderSpecPreview(old) !== renderSpecPreview(spec2)) writes.set(spec2.path, renderSpecPreview(spec2));
25348
25534
  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
25535
  }
25350
- const previousDocs = before.documents.flatMap((s) => s.documents);
25351
- const nextDocs = result.documents.flatMap((s) => s.documents);
25536
+ const previousDocs = before.wiki.documents;
25537
+ const nextDocs = result.wiki.documents;
25352
25538
  for (const doc of nextDocs) {
25353
25539
  const old = previousDocs.find((d) => d.id === doc.id);
25354
25540
  if (old && old.path !== doc.path) writes.set(old.path, null);
@@ -25362,7 +25548,7 @@ var readLockedPreviewState = (root) => snapshot2(root);
25362
25548
  async function previewTransaction(cwd, expected2, build, publish = rename, afterPublish) {
25363
25549
  const { root, gitDir } = await specPreviewReader(cwd).location();
25364
25550
  await failOnLegacyLock(gitDir);
25365
- const lock = join3(gitDir, "gitifact-spec-preview.lock");
25551
+ const lock = join4(gitDir, "gitifact-spec-preview.lock");
25366
25552
  try {
25367
25553
  await mkdir2(lock);
25368
25554
  } catch (e) {
@@ -25375,7 +25561,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25375
25561
  const published = [];
25376
25562
  let keepRecovery = false;
25377
25563
  async function directory(path3) {
25378
- const stat3 = await info(path3);
25564
+ const stat3 = await info2(path3);
25379
25565
  if (stat3) {
25380
25566
  if (!stat3.isDirectory() || stat3.isSymbolicLink()) fail4(t2("store.notDirectory", { path: path3 }));
25381
25567
  return;
@@ -25399,14 +25585,14 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25399
25585
  else finalFiles.set(c.path, c.after);
25400
25586
  }
25401
25587
  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" });
25588
+ await writeFile(join4(lock, "recovery.json"), JSON.stringify({ root, changed }), { flag: "wx" });
25403
25589
  for (const c of changed) {
25404
25590
  if (c.after === null) {
25405
25591
  temporary.push("");
25406
25592
  continue;
25407
25593
  }
25408
- await directory(dirname(join3(root, c.path)));
25409
- const temp = join3(dirname(join3(root, c.path)), ".gitifact-save-" + randomBytes(12).toString("hex"));
25594
+ await directory(dirname(join4(root, c.path)));
25595
+ const temp = join4(dirname(join4(root, c.path)), ".gitifact-save-" + randomBytes(12).toString("hex"));
25410
25596
  await writeFile(temp, c.after, { flag: "wx" });
25411
25597
  temporary.push(temp);
25412
25598
  }
@@ -25414,8 +25600,8 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25414
25600
  await result.recheck?.();
25415
25601
  for (let i = 0; i < changed.length; i++) {
25416
25602
  const c = changed[i];
25417
- const path3 = join3(root, c.path);
25418
- const stat3 = await info(path3);
25603
+ const path3 = join4(root, c.path);
25604
+ const stat3 = await info2(path3);
25419
25605
  if (stat3?.isSymbolicLink() || stat3 && !stat3.isFile() || (stat3 ? decode3(await readFile3(path3)) : null) !== c.before) fail4(t2("store.changedBeforeWrite"));
25420
25606
  if (c.after === null) await unlink2(path3);
25421
25607
  else await publish(temporary[i], path3);
@@ -25425,7 +25611,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25425
25611
  if (after.stamp !== digest(JSON.stringify([...finalFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (before.config ?? ""))) fail4(t2("store.changedWhileWriting"));
25426
25612
  await result.recheck?.();
25427
25613
  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) };
25614
+ 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
25615
  } catch (error62) {
25430
25616
  if (error62 instanceof PreservedPreviewError) {
25431
25617
  keepRecovery = true;
@@ -25433,15 +25619,15 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25433
25619
  }
25434
25620
  for (const c of [...published].reverse()) {
25435
25621
  try {
25436
- const path3 = join3(root, c.path);
25437
- const stat3 = await info(path3);
25622
+ const path3 = join4(root, c.path);
25623
+ const stat3 = await info2(path3);
25438
25624
  if (c.after === null ? !!stat3 : !stat3?.isFile() || stat3.isSymbolicLink() || decode3(await readFile3(path3)) !== c.after) {
25439
25625
  keepRecovery = true;
25440
25626
  continue;
25441
25627
  }
25442
25628
  if (c.before === null) await unlink2(path3);
25443
25629
  else {
25444
- const temp = join3(dirname(path3), ".gitifact-restore-" + randomBytes(12).toString("hex"));
25630
+ const temp = join4(dirname(path3), ".gitifact-restore-" + randomBytes(12).toString("hex"));
25445
25631
  temporary.push(temp);
25446
25632
  await writeFile(temp, c.before, { flag: "wx" });
25447
25633
  await rename(temp, path3);
@@ -25459,7 +25645,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25459
25645
  for (const path3 of [...createdDirs].reverse()) await rmdir(path3).catch(() => {
25460
25646
  });
25461
25647
  if (!keepRecovery) {
25462
- await unlink2(join3(lock, "recovery.json")).catch((e) => {
25648
+ await unlink2(join4(lock, "recovery.json")).catch((e) => {
25463
25649
  if (e.code !== "ENOENT") throw e;
25464
25650
  });
25465
25651
  await rmdir(lock);
@@ -25501,8 +25687,8 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25501
25687
  if (cursor > 0 && expectedHead !== base.head) throw new SpecPreviewError(t2("specReader.historyChanged"));
25502
25688
  const current = await readWorkingPreviewState(root);
25503
25689
  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 });
25690
+ const bareDocuments = current.wiki.documents.map((d) => ({ ...d, updatedAt: null }));
25691
+ 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
25692
  const head = base.head;
25507
25693
  const features = await Promise.all(bare.map(async (feature2) => {
25508
25694
  const folder = feature2.path.replace(/^\.(?:gitifact|tryce)\/(spec\/[^/]+)\/requirements\.md$/, "$1");
@@ -25518,7 +25704,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25518
25704
  return { ...feature2, contributors: [...people.values()].sort((a, b) => b.commits - a.commits), updatedAt: lines[0]?.split("\0")[2] ?? null };
25519
25705
  }));
25520
25706
  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);
25707
+ const documentDirs = [WIKI_DIR];
25522
25708
  const [rows, dirty] = await Promise.all([
25523
25709
  git([
25524
25710
  "log",
@@ -25552,7 +25738,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25552
25738
  const [commit, parents, author, email3, date5, committer, message] = row.split("\0");
25553
25739
  if (!commit || author === void 0 || email3 === void 0 || !date5 || committer === void 0 || message === void 0) throw new SpecPreviewError(t2("specReader.historyUnreadable"));
25554
25740
  const after = await snapshot3(commit);
25555
- let before = { specs: [], documents: [{ kind: "product", documents: [], history: [] }, { kind: "guide", documents: [], history: [] }] };
25741
+ let before = emptyBundle();
25556
25742
  const parent = parents?.split(" ")[0];
25557
25743
  if (parent) {
25558
25744
  try {
@@ -25579,9 +25765,9 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25579
25765
  }));
25580
25766
  }));
25581
25767
  if (await readHead() !== head || (await readWorkingPreviewState(root)).stamp !== current.stamp) throw new SpecPreviewError(t2("specReader.projectChanged"));
25582
- return browserSpecsV1.parse({
25768
+ return browserSpecsV2.parse({
25583
25769
  contract: "browser-specs",
25584
- version: 1,
25770
+ version: 2,
25585
25771
  sessionId,
25586
25772
  head,
25587
25773
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -25675,18 +25861,27 @@ async function startBrowserServer(options) {
25675
25861
  if (api) {
25676
25862
  if (request.headers["sec-fetch-site"] === "cross-site" && !allowedOrigin) return fail8(response, 403, "FORBIDDEN", t2("server.crossSite"));
25677
25863
  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);
25864
+ if (path3.startsWith("/api/v1/assets/")) {
25865
+ const relative = path3.slice("/api/v1/assets/".length);
25866
+ const assetPath2 = ASSETS_DIR + "/" + relative;
25680
25867
  if (request.method !== "GET") {
25681
25868
  response.setHeader("Allow", "GET");
25682
25869
  return fail8(response, 405, "METHOD_NOT_ALLOWED", t2("server.methodNotAllowed"));
25683
25870
  }
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"));
25871
+ if (!isAssetPath(assetPath2)) return fail8(response, 404, "NOT_FOUND", t2("server.assetNotFound"));
25872
+ const file2 = join5(initial.repository.rootPath, ...assetPath2.split("/"));
25873
+ const stat3 = await lstat4(file2).catch(() => void 0);
25874
+ if (!stat3?.isFile() || stat3.isSymbolicLink() || stat3.size > 20 * 1024 * 1024) return fail8(response, 404, "NOT_FOUND", t2("server.assetNotFound"));
25688
25875
  const bytes = await readFile4(file2);
25689
- response.writeHead(200, { "Content-Type": contentType(name.toLowerCase()), "Cache-Control": "no-cache", "Content-Length": bytes.length });
25876
+ const extension = assetExtension(relative);
25877
+ const inline = ["png", "jpg", "jpeg", "gif", "webp", "svg"].includes(extension);
25878
+ response.writeHead(200, {
25879
+ "Content-Type": contentType("." + extension),
25880
+ "Cache-Control": "no-cache",
25881
+ "Content-Length": bytes.length,
25882
+ "Content-Security-Policy": "default-src 'none'; sandbox",
25883
+ "Content-Disposition": inline ? "inline" : 'attachment; filename="' + encodeURIComponent(relative.split("/").pop()) + '"'
25884
+ });
25690
25885
  response.end(bytes);
25691
25886
  return;
25692
25887
  }
@@ -25850,13 +26045,13 @@ async function runBrowser(options, version2) {
25850
26045
  }
25851
26046
 
25852
26047
  // src/commands/spec-init.ts
25853
- import { readdir as readdir3 } from "node:fs/promises";
25854
- import { join as join7 } from "node:path";
26048
+ import { mkdir as mkdir4, readdir as readdir4, writeFile as writeFile2 } from "node:fs/promises";
26049
+ import { join as join9 } from "node:path";
25855
26050
 
25856
26051
  // src/adapters/git/init-repository.ts
25857
26052
  import { createHash as createHash3 } from "node:crypto";
25858
26053
  import { readFile as readFile5 } from "node:fs/promises";
25859
- import { join as join5 } from "node:path";
26054
+ import { join as join6 } from "node:path";
25860
26055
  function initRepository(cwd, inherited = process.env) {
25861
26056
  const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
25862
26057
  const runner = createGitRunner();
@@ -25876,7 +26071,7 @@ function initRepository(cwd, inherited = process.env) {
25876
26071
  const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
25877
26072
  const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
25878
26073
  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 }));
26074
+ if (await fileInfo(join6(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
25880
26075
  }
25881
26076
  if (state.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
25882
26077
  const index = await readFile5(indexPath).catch((error62) => {
@@ -25914,22 +26109,22 @@ function initRepository(cwd, inherited = process.env) {
25914
26109
 
25915
26110
  // src/adapters/filesystem/managed-file.ts
25916
26111
  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";
26112
+ import { join as join7, dirname as dirname2 } from "node:path";
25918
26113
  import { randomUUID as randomUUID5 } from "node:crypto";
25919
26114
  var DEFAULT_LIMIT = 1024 * 1024;
25920
26115
  async function managedPath(root, path3, create = false) {
25921
26116
  let full = root;
25922
26117
  const parts = path3.split("/");
25923
26118
  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) {
26119
+ full = join7(full, parts[i]);
26120
+ let info7 = await fileInfo(full);
26121
+ if (!info7 && create && i < parts.length - 1) {
25927
26122
  await mkdir3(full).catch((error62) => {
25928
26123
  if (error62.code !== "EEXIST") throw error62;
25929
26124
  });
25930
- info5 = await fileInfo(full);
26125
+ info7 = await fileInfo(full);
25931
26126
  }
25932
- if (info5 && (info5.isSymbolicLink() || (i < parts.length - 1 ? !info5.isDirectory() : !info5.isFile()))) {
26127
+ if (info7 && (info7.isSymbolicLink() || (i < parts.length - 1 ? !info7.isDirectory() : !info7.isFile()))) {
25933
26128
  throw new InitError("PATH_CONFLICT", t2("managedFile.notRegularPath", { path: path3 }));
25934
26129
  }
25935
26130
  }
@@ -25969,7 +26164,7 @@ async function managedWrite(root, path3, previous, next, recheck) {
25969
26164
  if (previous !== null) await unlink3(full);
25970
26165
  return;
25971
26166
  }
25972
- const temp = join6(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
26167
+ const temp = join7(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
25973
26168
  const handle = await open3(temp, "wx", 384);
25974
26169
  const tempOwner = await handle.stat();
25975
26170
  try {
@@ -25995,14 +26190,53 @@ async function managedWrite(root, path3, previous, next, recheck) {
25995
26190
  import { readFile as readFile8 } from "node:fs/promises";
25996
26191
 
25997
26192
  // src/commands/docs.ts
25998
- import { readFile as readFile7 } from "node:fs/promises";
25999
- var docTopics = ["workflow", "spec", "design", "product", "commit"];
26193
+ import { lstat as lstat5, readFile as readFile7 } from "node:fs/promises";
26194
+ import { dirname as dirname3, join as join8 } from "node:path";
26195
+ var docTopics = ["workflow", "spec", "design", "wiki", "commit"];
26000
26196
  var isDocTopic = (value) => docTopics.includes(value);
26001
- var readBundled = (topic, lang) => readFile7(new URL("./i18n/" + lang + "/docs/" + topic + ".md", import.meta.url), "utf8");
26197
+ var readBundledDoc = (name, lang) => readFile7(new URL("./i18n/" + lang + "/docs/" + name + ".md", import.meta.url), "utf8");
26198
+ var info3 = (path3) => lstat5(path3).catch((e) => {
26199
+ if (e.code === "ENOENT") return void 0;
26200
+ throw e;
26201
+ });
26002
26202
  function listDocTopics(lang = defaultLanguage2) {
26003
26203
  const width = Math.max(...docTopics.map((topic) => topic.length));
26004
26204
  return docTopics.map((topic) => topic.padEnd(width) + " " + t2(`docs.summary.${topic}`, {}, lang)).join("\n") + "\n";
26005
26205
  }
26206
+ async function findProjectRoot(cwd) {
26207
+ for (let dir = cwd; ; dir = dirname3(dir)) {
26208
+ if ((await info3(join8(dir, ".gitifact", "config.json")))?.isFile()) return dir;
26209
+ if (dirname3(dir) === dir) return void 0;
26210
+ }
26211
+ }
26212
+ async function readWikiPolicy(root) {
26213
+ if (!root) return void 0;
26214
+ const path3 = join8(root, ...WIKI_ENTRY_PATH.split("/"));
26215
+ const stat3 = await info3(path3);
26216
+ if (!stat3?.isFile() || stat3.size > 1024 * 1024) return void 0;
26217
+ const lines = (await readFile7(path3, "utf8")).replace(/\r\n/g, "\n").split("\n");
26218
+ if (lines[0] === "---") {
26219
+ const end = lines.indexOf("---", 1);
26220
+ if (end > 0) lines.splice(0, end + 1);
26221
+ }
26222
+ while (lines.length && !lines[0].trim()) lines.shift();
26223
+ if (lines[0]?.startsWith("# ")) lines.shift();
26224
+ let fence = false;
26225
+ const body = lines.map((line) => {
26226
+ if (/^ {0,3}(`{3,}|~{3,})/.test(line)) fence = !fence;
26227
+ return !fence && /^#{1,5} /.test(line) ? "#" + line : line;
26228
+ }).join("\n").trim();
26229
+ return body || void 0;
26230
+ }
26231
+ async function renderDoc(topic, controls = {}, lang = defaultLanguage2) {
26232
+ const read = controls.readDoc ?? readBundledDoc;
26233
+ const format = (await read(topic, lang)).trimEnd();
26234
+ if (topic !== "wiki") return format + "\n";
26235
+ const own2 = await readWikiPolicy(await findProjectRoot(controls.cwd ?? process.cwd()));
26236
+ const policy = own2 ?? (await read("wiki.default", lang)).trim().replace(/^(#{1,5}) /gm, "#$1 ");
26237
+ const heading = own2 ? t2("docs.wikiPolicyProject", {}, lang) : t2("docs.wikiPolicyDefault", {}, lang);
26238
+ return format + "\n\n## " + heading + "\n\n" + policy + "\n";
26239
+ }
26006
26240
  async function runDocs(topic, controls = {}) {
26007
26241
  if (topic === void 0) {
26008
26242
  process.stdout.write(listDocTopics());
@@ -26014,7 +26248,7 @@ async function runDocs(topic, controls = {}) {
26014
26248
  process.exitCode = 1;
26015
26249
  return;
26016
26250
  }
26017
- process.stdout.write(await (controls.readDoc ?? readBundled)(topic, defaultLanguage2));
26251
+ process.stdout.write(await renderDoc(topic, controls));
26018
26252
  }
26019
26253
 
26020
26254
  // src/commands/agent-block.ts
@@ -26055,6 +26289,16 @@ function isWrapperFile(text2) {
26055
26289
  const lines = own2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
26056
26290
  return lines.length > 0 && lines.every((line) => /^@(?:\.\/)?[^\s]+$/.test(line) && candidatePaths.includes(line.replace(/^@(?:\.\/)?/, "")));
26057
26291
  }
26292
+ var CLAUDE_WRAPPER_PATH = "CLAUDE.md";
26293
+ function claudeWrapperFor(agents) {
26294
+ return "@AGENTS.md" + eolOf(agents);
26295
+ }
26296
+ function isGeneratedClaudeWrapper(text2) {
26297
+ return text2.trim() === "@AGENTS.md";
26298
+ }
26299
+ function lacksClaudeFile(existing) {
26300
+ return !existing.has("CLAUDE.md") && !existing.has(".claude/CLAUDE.md");
26301
+ }
26058
26302
  function resolveAgentPaths(preset, existing) {
26059
26303
  if (preset && preset !== "all") {
26060
26304
  const order = agentPresets[preset];
@@ -26088,7 +26332,7 @@ function removeBlock(text2, boilerplate) {
26088
26332
  }
26089
26333
 
26090
26334
  // src/commands/agent-docs.ts
26091
- var skippedAgentDocs = { mode: "skip", paths: [], writes: [] };
26335
+ var skippedAgentDocs = { mode: "skip", paths: [], writes: [], missing: [] };
26092
26336
  async function planAgentDocs(root, options) {
26093
26337
  if (options.skip) return skippedAgentDocs;
26094
26338
  const existing = /* @__PURE__ */ new Map();
@@ -26106,7 +26350,12 @@ async function planAgentDocs(root, options) {
26106
26350
  paths2.push(path3);
26107
26351
  writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
26108
26352
  }
26109
- return { mode: "remove", paths: paths2, writes };
26353
+ const wrapper = existing.get(CLAUDE_WRAPPER_PATH);
26354
+ if (writes.some((write) => write.path === "AGENTS.md" && write.next === null) && wrapper !== void 0 && isGeneratedClaudeWrapper(wrapper)) {
26355
+ paths2.push(CLAUDE_WRAPPER_PATH);
26356
+ writes.push({ path: CLAUDE_WRAPPER_PATH, previous: wrapper, next: null });
26357
+ }
26358
+ return { mode: "remove", paths: paths2, writes, missing: [] };
26110
26359
  }
26111
26360
  const block = await renderAgentBlock(options.version, options);
26112
26361
  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 +26369,19 @@ async function planAgentDocs(root, options) {
26120
26369
  paths2.push(create);
26121
26370
  writes.push({ path: create, previous: null, next: injectBlock(null, block, boilerplateFor(create)) });
26122
26371
  }
26372
+ const missing2 = [];
26373
+ if (paths2.includes("AGENTS.md") && lacksClaudeFile(existing)) {
26374
+ if (options.onlyExisting) missing2.push(CLAUDE_WRAPPER_PATH);
26375
+ else {
26376
+ paths2.push(CLAUDE_WRAPPER_PATH);
26377
+ writes.push({ path: CLAUDE_WRAPPER_PATH, previous: null, next: claudeWrapperFor(existing.get("AGENTS.md") ?? null) });
26378
+ }
26379
+ }
26123
26380
  for (const [path3, text2] of existing) {
26124
26381
  if (paths2.includes(path3) || !isWrapperFile(text2) || !findBlock(text2)) continue;
26125
26382
  writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
26126
26383
  }
26127
- return { mode: "install", paths: paths2, writes };
26384
+ return { mode: "install", paths: paths2, writes, missing: missing2 };
26128
26385
  }
26129
26386
  async function applyAgentDocs(root, plan, recheck) {
26130
26387
  for (const write of plan.writes) await managedWrite(root, write.path, write.previous, write.next, recheck);
@@ -26132,7 +26389,7 @@ async function applyAgentDocs(root, plan, recheck) {
26132
26389
  }
26133
26390
 
26134
26391
  // src/commands/spec-init.ts
26135
- async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs) {
26392
+ async function initializeSpecProject(cwd, dryRun = false, env = process.env, beforePublish, agentDocs, readDoc = (name) => readBundledDoc(name, defaultLanguage2)) {
26136
26393
  const repo = initRepository(cwd, env);
26137
26394
  const first = await repo.inspect();
26138
26395
  const root = first.state.repository.rootPath;
@@ -26163,14 +26420,14 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
26163
26420
  if (old !== void 0) return existing(old);
26164
26421
  if (first.trackedConfig) throw new InitError("CONFIG_DELETED", t2("init.configDeleted"));
26165
26422
  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) => {
26423
+ if (await fileInfo(join9(root, "specs"))) throw new InitError("EXISTING_RECORDS", t2("init.existingSpecs"));
26424
+ const entries = await readdir4(join9(root, ".gitifact")).catch((e) => {
26168
26425
  if (e.code === "ENOENT") return [];
26169
26426
  throw e;
26170
26427
  });
26171
26428
  if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", t2("init.orphanStore"));
26172
26429
  };
26173
- const config2 = { schemaVersion: 1, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
26430
+ const config2 = { schemaVersion: SCHEMA_VERSION, baseline: first.state.head.commit ? { kind: "commit", objectFormat: first.state.repository.objectFormat, commit: first.state.head.commit } : { kind: "empty" } };
26174
26431
  const text2 = JSON.stringify(config2, null, 2) + "\n";
26175
26432
  const recheck = async () => {
26176
26433
  await checkRecords();
@@ -26194,8 +26451,22 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
26194
26451
  };
26195
26452
  await published();
26196
26453
  await applyAgentDocs(root, docsPlan, published);
26454
+ await writeWikiPolicy(root, readDoc);
26197
26455
  return result(config2, "created");
26198
26456
  }
26457
+ async function writeWikiPolicy(root, readDoc) {
26458
+ if (await fileInfo(join9(root, ...WIKI_DIR.split("/")))) return;
26459
+ const template = await readDoc("wiki.default").catch((e) => {
26460
+ if (e.code === "ENOENT") return void 0;
26461
+ throw e;
26462
+ });
26463
+ if (template === void 0) return;
26464
+ const doc = { id: generatePreviewId("W"), path: WIKI_ENTRY_PATH, title: t2("init.wikiReadmeTitle"), body: template.trim() };
26465
+ const text2 = renderDocument(doc);
26466
+ parseDocument(doc.path, text2);
26467
+ await mkdir4(join9(root, ...WIKI_DIR.split("/")), { recursive: true });
26468
+ await writeFile2(join9(root, ...WIKI_ENTRY_PATH.split("/")), text2, { flag: "wx" });
26469
+ }
26199
26470
 
26200
26471
  // src/commands/init.ts
26201
26472
  async function runInit(options, version2) {
@@ -26210,7 +26481,7 @@ async function runInit(options, version2) {
26210
26481
  if (!dto.ok) throw new Error("unreachable");
26211
26482
  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") + ")");
26212
26483
  process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
26213
- ${t2("init.text.storage")}: schemaVersion 1
26484
+ ${t2("init.text.storage")}: schemaVersion ${dto.schemaVersion}
26214
26485
  ${t2("init.text.agentDocs")}: ${docs}
26215
26486
  ` : JSON.stringify(dto) + "\n");
26216
26487
  } catch (error62) {
@@ -26262,19 +26533,20 @@ async function refreshBlocks(cwd, version2, env, controls) {
26262
26533
  first = await repo.inspect();
26263
26534
  config2 = await readConfigFile(first.state.repository.rootPath);
26264
26535
  } catch (error62) {
26265
- if (error62 instanceof RepositoryReadError && error62.code === "NOT_A_REPOSITORY" || error62 instanceof InitError && error62.code === "MIGRATION_REQUIRED") return { state: "not-initialized", paths: [] };
26536
+ if (error62 instanceof RepositoryReadError && error62.code === "NOT_A_REPOSITORY" || error62 instanceof InitError && error62.code === "MIGRATION_REQUIRED") return { state: "not-initialized", paths: [], missing: [] };
26266
26537
  throw error62;
26267
26538
  }
26268
- if (config2 === void 0) return { state: "not-initialized", paths: [] };
26539
+ if (config2 === void 0) return { state: "not-initialized", paths: [], missing: [] };
26269
26540
  const root = first.state.repository.rootPath;
26270
26541
  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 };
26542
+ const { missing: missing2 } = plan;
26543
+ if (plan.paths.length === 0) return { state: "no-block", paths: [], missing: missing2, root };
26544
+ if (plan.writes.length === 0) return { state: "current", paths: plan.paths, missing: missing2, root, candidates: plan.paths };
26273
26545
  const unchanged = async () => {
26274
26546
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== config2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
26275
26547
  };
26276
26548
  const applied = await applyAgentDocs(root, plan, unchanged);
26277
- return { state: "refreshed", paths: applied, root, candidates: plan.paths };
26549
+ return { state: "refreshed", paths: applied, missing: missing2, root, candidates: plan.paths };
26278
26550
  }
26279
26551
  function outsideBlock(text2) {
26280
26552
  const normalized2 = text2.replace(/\r\n/g, "\n");
@@ -26320,14 +26592,14 @@ async function updateCommand(cwd, version2, env = process.env, controls = {}) {
26320
26592
  refreshBlocks(cwd, version2, env, controls)
26321
26593
  ]);
26322
26594
  const commit = controls.commit ? await commitBlocks(agentDocs, version2, env) : commitState("not-requested");
26323
- return updateV2.parse({
26595
+ return updateV3.parse({
26324
26596
  contract: "update",
26325
- version: 2,
26597
+ version: 3,
26326
26598
  ok: true,
26327
26599
  cliVersion: version2,
26328
26600
  update,
26329
26601
  install: update.status === "available" ? { npmGlobal: npmGlobalInstall(update.latestVersion) } : null,
26330
- agentDocs: { state: agentDocs.state, paths: agentDocs.paths },
26602
+ agentDocs: { state: agentDocs.state, paths: agentDocs.paths, missing: agentDocs.missing },
26331
26603
  commit
26332
26604
  });
26333
26605
  }
@@ -26343,6 +26615,7 @@ async function runUpdate(options, version2) {
26343
26615
  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
26616
  if (dto.install) lines.push(t2("update.text.install", { command: dto.install.npmGlobal }), t2("update.text.afterInstall"));
26345
26617
  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"));
26618
+ if (dto.agentDocs.missing.length) lines.push(t2("update.text.missing", { paths: dto.agentDocs.missing.join(", ") }));
26346
26619
  const { commit } = dto;
26347
26620
  if (commit.state === "committed") lines.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
26348
26621
  else if (commit.state === "nothing") lines.push(t2("update.text.commitNothing"));
@@ -26355,18 +26628,18 @@ async function runUpdate(options, version2) {
26355
26628
  } catch (error62) {
26356
26629
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
26357
26630
  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");
26631
+ 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
26632
  process.exitCode = 1;
26360
26633
  }
26361
26634
  }
26362
26635
 
26363
26636
  // src/commands/spec-preview.ts
26364
- import { join as join13 } from "node:path";
26637
+ import { join as join15 } from "node:path";
26365
26638
  import { readFile as readFile12, stat as stat2 } from "node:fs/promises";
26366
26639
 
26367
26640
  // src/adapters/filesystem/spec-preview-prepare.ts
26368
- import { lstat as lstat4 } from "node:fs/promises";
26369
- import { join as join8 } from "node:path";
26641
+ import { lstat as lstat6 } from "node:fs/promises";
26642
+ import { join as join10 } from "node:path";
26370
26643
  import { createHash as createHash4 } from "node:crypto";
26371
26644
  var fail5 = (message) => {
26372
26645
  throw new SpecPreviewError(message);
@@ -26379,7 +26652,7 @@ async function readPreviewContext(cwd) {
26379
26652
  const { gitDir } = await reader.location();
26380
26653
  const checkOperation = async () => {
26381
26654
  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) => {
26655
+ const exists = await lstat6(join10(gitDir, name)).catch((e) => {
26383
26656
  if (e.code === "ENOENT") return void 0;
26384
26657
  throw e;
26385
26658
  });
@@ -26433,9 +26706,9 @@ async function verifyPreparedPreview(cwd, input2, staged) {
26433
26706
  if (staged) {
26434
26707
  const index = await c.reader.index();
26435
26708
  if (index.size !== current.files.size) fail5(t2("prepare.stagingScope"));
26436
- for (const [path3, source] of current.files) {
26709
+ for (const [path3, source2] of current.files) {
26437
26710
  const oid2 = index.get(path3);
26438
- const bytes = Buffer.from(source);
26711
+ const bytes = Buffer.from(source2);
26439
26712
  const hash3 = createHash4(oid2?.length === 64 ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
26440
26713
  if (oid2 !== hash3) fail5(t2("prepare.stagingContent", { path: path3 }));
26441
26714
  }
@@ -26448,26 +26721,26 @@ async function verifyPreparedPreview(cwd, input2, staged) {
26448
26721
 
26449
26722
  // src/adapters/filesystem/agent-inputs.ts
26450
26723
  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";
26724
+ import { lstat as lstat7, mkdir as mkdir5, readdir as readdir5, realpath as realpath3, unlink as unlink4, writeFile as writeFile3 } from "node:fs/promises";
26452
26725
  import { tmpdir } from "node:os";
26453
- import { basename, dirname as dirname3, join as join9 } from "node:path";
26726
+ import { basename, dirname as dirname4, join as join11 } from "node:path";
26454
26727
  var maxAge = 7 * 24 * 60 * 60 * 1e3;
26455
- var info2 = (path3) => lstat5(path3).catch((e) => {
26728
+ var info4 = (path3) => lstat7(path3).catch((e) => {
26456
26729
  if (e.code === "ENOENT") return void 0;
26457
26730
  throw e;
26458
26731
  });
26459
26732
  var comparable = (path3) => process.platform === "win32" ? path3.toLowerCase() : path3;
26460
26733
  async function candidates(root, controls) {
26461
26734
  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") };
26735
+ return { system: join11((controls.tmpdir ?? tmpdir)(), "gitifact", key), project: join11(root, ".gitifact", "tmp") };
26463
26736
  }
26464
26737
  async function writable(directory) {
26465
26738
  try {
26466
- await mkdir4(directory, { recursive: true });
26467
- const stat3 = await info2(directory);
26739
+ await mkdir5(directory, { recursive: true });
26740
+ const stat3 = await info4(directory);
26468
26741
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) return false;
26469
- const probe = join9(directory, ".probe-" + randomBytes2(8).toString("hex"));
26470
- await writeFile2(probe, "", { flag: "wx" });
26742
+ const probe = join11(directory, ".probe-" + randomBytes2(8).toString("hex"));
26743
+ await writeFile3(probe, "", { flag: "wx" });
26471
26744
  await unlink4(probe);
26472
26745
  return true;
26473
26746
  } catch {
@@ -26480,28 +26753,28 @@ async function prepareAgentInputs(root, controls = {}) {
26480
26753
  if (await writable(system)) directory = system;
26481
26754
  else if (await writable(project)) {
26482
26755
  directory = project;
26483
- await writeFile2(join9(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
26756
+ await writeFile3(join11(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
26484
26757
  if (e.code !== "EEXIST") throw e;
26485
26758
  });
26486
26759
  }
26487
26760
  if (!directory) return void 0;
26488
26761
  const now = (controls.now ?? Date.now)();
26489
- for (const name of await readdir4(directory).catch(() => [])) {
26762
+ for (const name of await readdir5(directory).catch(() => [])) {
26490
26763
  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);
26764
+ const stat3 = await info4(join11(directory, name)).catch(() => void 0);
26765
+ if (stat3?.isFile() && now - stat3.mtimeMs > maxAge) await unlink4(join11(directory, name)).catch(() => void 0);
26493
26766
  }
26494
- return { save: join9(directory, "save.json"), commit: join9(directory, "commit.json") };
26767
+ return { save: join11(directory, "save.json"), commit: join11(directory, "commit.json") };
26495
26768
  }
26496
26769
  async function discardAgentInput(root, file2, controls = {}) {
26497
26770
  try {
26498
- const stat3 = await info2(file2);
26771
+ const stat3 = await info4(file2);
26499
26772
  if (!stat3?.isFile() || stat3.isSymbolicLink()) return false;
26500
- const parent = comparable(await realpath3(dirname3(file2)));
26773
+ const parent = comparable(await realpath3(dirname4(file2)));
26501
26774
  const { system, project } = await candidates(root, controls);
26502
26775
  let managed = false;
26503
26776
  for (const directory of [system, project]) {
26504
- if (await info2(directory) && comparable(await realpath3(directory)) === parent) managed = true;
26777
+ if (await info4(directory) && comparable(await realpath3(directory)) === parent) managed = true;
26505
26778
  }
26506
26779
  if (!managed || basename(file2) === ".gitignore") return false;
26507
26780
  await unlink4(file2);
@@ -26513,13 +26786,13 @@ async function discardAgentInput(root, file2, controls = {}) {
26513
26786
 
26514
26787
  // src/commands/spec-preview-commit.ts
26515
26788
  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";
26789
+ import { readFile as readFile10, mkdir as mkdir6, open as open4, rename as rename3, unlink as unlink5, rmdir as rmdir2, writeFile as writeFile4 } from "node:fs/promises";
26790
+ import { join as join13 } from "node:path";
26518
26791
 
26519
26792
  // src/commands/spec-commit-files.ts
26520
26793
  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";
26794
+ import { lstat as lstat8, readFile as readFile9 } from "node:fs/promises";
26795
+ import { join as join12 } from "node:path";
26523
26796
  var fail6 = (message) => {
26524
26797
  throw new SpecPreviewError(message);
26525
26798
  };
@@ -26528,7 +26801,7 @@ var optional2 = (path3) => readFile9(path3).catch((e) => {
26528
26801
  if (e.code === "ENOENT") return null;
26529
26802
  throw e;
26530
26803
  });
26531
- var info3 = (path3) => lstat6(path3).catch((e) => {
26804
+ var info5 = (path3) => lstat8(path3).catch((e) => {
26532
26805
  if (e.code === "ENOENT") return void 0;
26533
26806
  throw e;
26534
26807
  });
@@ -26540,11 +26813,11 @@ async function fingerprint(root, path3) {
26540
26813
  validPath2(path3);
26541
26814
  const parts = path3.split("/");
26542
26815
  for (let i = 1; i <= parts.length; i++) {
26543
- const target = join10(root, ...parts.slice(0, i));
26544
- const stat3 = await info3(target);
26816
+ const target = join12(root, ...parts.slice(0, i));
26817
+ const stat3 = await info5(target);
26545
26818
  if (!stat3) return null;
26546
26819
  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"));
26820
+ if (i < parts.length && await info5(join12(target, ".git"))) fail6(t2("commitFiles.nestedRepository"));
26548
26821
  if (i === parts.length) {
26549
26822
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail6(t2("commitFiles.hardLinkOrSize"));
26550
26823
  return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile9(target)]));
@@ -26575,9 +26848,9 @@ function policyPaths(files) {
26575
26848
  return [...all].sort();
26576
26849
  }
26577
26850
  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)$/;
26851
+ 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
26852
  for (const p of selected.filter((p2) => p2.startsWith(".gitifact/") && !record2(p2) && p2 !== ".gitifact/config.json")) {
26580
- if (isProductAssetPath(p)) continue;
26853
+ if (isAssetPath(p)) continue;
26581
26854
  if (!legacyJson.test(p) || await fingerprint(root, p) !== null) fail6(t2("commitFiles.legacyDeleteOnly", { path: p }));
26582
26855
  }
26583
26856
  for (const p of selected.filter((p2) => p2.startsWith(".tryce/"))) {
@@ -26610,8 +26883,8 @@ async function previewCommit(cwd, action, input2) {
26610
26883
  });
26611
26884
  const indexPath = (await git(["rev-parse", "--path-format=absolute", "--git-path", "index"])).toString("utf8").trim();
26612
26885
  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 }));
26886
+ const busy = join13(gitDir, "gitifact-spec-commit.lock");
26887
+ if (await info5(busy)) fail6(t2("commit.busy", { path: busy }));
26615
26888
  if ((await staged()).length) fail6(t2("commit.existingStagingPlan"));
26616
26889
  const request = object3(input2);
26617
26890
  let plan;
@@ -26668,33 +26941,33 @@ async function previewCommit(cwd, action, input2) {
26668
26941
  if (!("plan" in refreshed) || refreshed.plan.digest !== plan.digest) fail6(t2("commit.planStale"));
26669
26942
  const before = await reader.baseline();
26670
26943
  const original = await optional2(indexPath);
26671
- await mkdir5(busy);
26672
- const draftLock = join11(gitDir, "gitifact-spec-preview.lock");
26944
+ await mkdir6(busy);
26945
+ const draftLock = join13(gitDir, "gitifact-spec-preview.lock");
26673
26946
  let draftOwned = false;
26674
26947
  let indexLock;
26675
26948
  let uncertain = false;
26676
26949
  let commitStarted = false;
26677
- const temporary = join11(gitDir, "gitifact-commit-index-" + randomUUID6());
26950
+ const temporary = join13(gitDir, "gitifact-commit-index-" + randomUUID6());
26678
26951
  const checkFiles = async () => {
26679
26952
  for (const f of [...plan.files, ...plan.context]) if (f.hash !== await fingerprint(root, f.path)) fail6(t2("commit.fileChangedPreparing", { path: f.path }));
26680
26953
  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"));
26954
+ 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
26955
  };
26683
26956
  try {
26684
26957
  await verifyPreparedPreview(cwd, plan.verification, false);
26685
- await mkdir5(draftLock);
26958
+ await mkdir6(draftLock);
26686
26959
  draftOwned = true;
26687
26960
  indexLock = await open4(indexPath + ".lock", "wx", 384);
26688
26961
  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" });
26962
+ await writeFile4(join13(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
26963
+ if (original) await writeFile4(temporary, original, { flag: "wx" });
26691
26964
  else await git(["read-tree", "--empty"], temporary);
26692
26965
  await git(["add", "--", ...selected], temporary);
26693
26966
  const actual = await staged(temporary);
26694
26967
  if (!actual.length || actual.some((p) => !selected.includes(p))) fail6(t2("commit.unexpectedFiles"));
26695
26968
  for (const file2 of plan.files.filter((f) => record2(f.path) && f.hash !== null)) {
26696
26969
  const stagedBytes = await git(["show", ":" + file2.path], temporary);
26697
- if (!stagedBytes.equals(await readFile10(join11(root, file2.path)))) fail6(t2("commit.filterChanged"));
26970
+ if (!stagedBytes.equals(await readFile10(join13(root, file2.path)))) fail6(t2("commit.filterChanged"));
26698
26971
  }
26699
26972
  await checkFiles();
26700
26973
  if (JSON.stringify(before) !== JSON.stringify(await reader.baseline())) fail6(t2("commit.headChanged"));
@@ -26733,7 +27006,7 @@ async function previewCommit(cwd, action, input2) {
26733
27006
  });
26734
27007
  await unlink5(temporary).catch(() => {
26735
27008
  });
26736
- await unlink5(join11(busy, "recovery.json")).catch(() => {
27009
+ await unlink5(join13(busy, "recovery.json")).catch(() => {
26737
27010
  });
26738
27011
  await rmdir2(busy);
26739
27012
  if (draftOwned) await rmdir2(draftLock);
@@ -26743,8 +27016,8 @@ async function previewCommit(cwd, action, input2) {
26743
27016
 
26744
27017
  // src/commands/spec-commit.ts
26745
27018
  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";
27019
+ import { mkdir as mkdir7, open as open5, readFile as readFile11, rename as rename4, rmdir as rmdir3, unlink as unlink6, writeFile as writeFile5 } from "node:fs/promises";
27020
+ import { join as join14 } from "node:path";
26748
27021
  var fields = ["reasons", "paths", "message", "authorization", "requirements", "policyFiles", "expected"];
26749
27022
  var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
26750
27023
  var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -26783,9 +27056,9 @@ async function specCommit(cwd, input2, dryRun) {
26783
27056
  ...stdin ? { input: stdin } : {}
26784
27057
  });
26785
27058
  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");
27059
+ const busy = join14(gitDir, "gitifact-spec-commit.lock");
26787
27060
  await failOnLegacyLock(gitDir);
26788
- if (await info3(busy)) fail6(t2("commit.busy", { path: busy }));
27061
+ if (await info5(busy)) fail6(t2("commit.busy", { path: busy }));
26789
27062
  if ((await staged()).length) fail6(t2("commit.existingStaging"));
26790
27063
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
26791
27064
  const current = c.working;
@@ -26835,16 +27108,16 @@ async function specCommit(cwd, input2, dryRun) {
26835
27108
  const trailers = [
26836
27109
  ...requirements.map((id) => "Gitifact-Req: " + id),
26837
27110
  ...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)
27111
+ ...prepared.changes.filter((c2) => c2.kind === "wiki").map((c2) => "Gitifact-Doc: " + c2.id)
26839
27112
  ];
26840
- const temporary = join12(gitDir, "gitifact-commit-index-" + randomUUID7());
27113
+ const temporary = join14(gitDir, "gitifact-commit-index-" + randomUUID7());
26841
27114
  let owned = false;
26842
27115
  let uncertain = false;
26843
27116
  let indexLock;
26844
27117
  let committed;
26845
27118
  try {
26846
27119
  try {
26847
- await mkdir6(busy);
27120
+ await mkdir7(busy);
26848
27121
  owned = true;
26849
27122
  } catch (e) {
26850
27123
  if (e.code === "EEXIST") fail6(t2("commit.busy", { path: busy }));
@@ -26858,8 +27131,8 @@ async function specCommit(cwd, input2, dryRun) {
26858
27131
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail6(t2("commit.indexChanged"));
26859
27132
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected, ...contextPaths])]);
26860
27133
  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" });
27134
+ await writeFile5(join14(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
27135
+ if (original) await writeFile5(temporary, original, { flag: "wx" });
26863
27136
  else await git(["read-tree", "--empty"], temporary);
26864
27137
  await git(["add", "--", ...stageable], temporary);
26865
27138
  const actual = await staged(temporary);
@@ -26874,7 +27147,7 @@ async function specCommit(cwd, input2, dryRun) {
26874
27147
  const size = Number(header[2]);
26875
27148
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail6(t2("commit.stagedUnreadable", { path: path3 }));
26876
27149
  const staged2 = output2.subarray(end + 1, end + 1 + size);
26877
- const file2 = await readFile11(join12(root, path3));
27150
+ const file2 = await readFile11(join14(root, path3));
26878
27151
  if (!staged2.equals(file2) && !staged2.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail6(t2("commit.filterChanged"));
26879
27152
  offset = end + size + 2;
26880
27153
  }
@@ -26882,7 +27155,7 @@ async function specCommit(cwd, input2, dryRun) {
26882
27155
  const again = await hashes([...locked.keys()]);
26883
27156
  for (const [path3, value] of locked) if (again.get(path3) !== value) fail6(t2("commit.fileChangedPreparing", { path: path3 }));
26884
27157
  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"));
27158
+ for (const name of integration) if (await info5(join14(gitDir, name))) fail6(t2("commit.integrationStarted"));
26886
27159
  if (!same2(c.base, await c.reader.baseline())) fail6(t2("commit.headChanged"));
26887
27160
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
26888
27161
  commitStarted = true;
@@ -26924,7 +27197,7 @@ async function specCommit(cwd, input2, dryRun) {
26924
27197
  await unlink6(temporary).catch(() => {
26925
27198
  });
26926
27199
  if (owned) {
26927
- await unlink6(join12(busy, "recovery.json")).catch(() => {
27200
+ await unlink6(join14(busy, "recovery.json")).catch(() => {
26928
27201
  });
26929
27202
  await rmdir3(busy);
26930
27203
  }
@@ -26941,7 +27214,7 @@ async function execute(action, options, controls) {
26941
27214
  try {
26942
27215
  const guard = specPreviewReader(process.cwd());
26943
27216
  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 }));
27217
+ for (const marker2 of operations) if (await fileInfo(join15(gitDir, marker2))) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker2 }));
26945
27218
  if (await guard.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
26946
27219
  const raw = await readConfigFile(root);
26947
27220
  if (raw === void 0) throw new SpecPreviewError(t2("preview.notInitialized"));
@@ -26973,7 +27246,7 @@ async function execute(action, options, controls) {
26973
27246
  if (action === "read") {
26974
27247
  const commit = await reader.resolve(options.ref ?? "HEAD");
26975
27248
  const bundle = await reader.readBundle(commit);
26976
- process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs: bundle.specs, documents: bundle.documents }) + "\n");
27249
+ process.stdout.write(JSON.stringify({ ...envelope, ok: true, commit, specs: bundle.specs, wiki: bundle.wiki }) + "\n");
26977
27250
  } else {
26978
27251
  const [from, to] = await Promise.all([reader.resolve(options.from), reader.resolve(options.to)]);
26979
27252
  const [before, after] = await Promise.all([reader.readBundle(from), reader.readBundle(to)]);
@@ -26986,8 +27259,8 @@ async function execute(action, options, controls) {
26986
27259
  }
26987
27260
  }
26988
27261
  async function readInputFile(path3) {
26989
- const source = await stat2(path3);
26990
- if (!source.isFile() || source.size > 1024 * 1024) throw new SpecPreviewError(t2("preview.inputFile"));
27262
+ const source2 = await stat2(path3);
27263
+ if (!source2.isFile() || source2.size > 1024 * 1024) throw new SpecPreviewError(t2("preview.inputFile"));
26991
27264
  return readFile12(path3);
26992
27265
  }
26993
27266
  async function readStdin(stream) {
@@ -27002,29 +27275,29 @@ async function readStdin(stream) {
27002
27275
  }
27003
27276
  function narrowWorking(working, options, inputs) {
27004
27277
  if (options.stamp) return { stamp: working.stamp, inputs };
27005
- let { specs, documents, warnings } = working;
27278
+ let { specs, wiki, warnings } = working;
27006
27279
  if (options.feature !== void 0) {
27007
27280
  specs = specs.filter((s) => s.path === ".gitifact/spec/" + options.feature + "/requirements.md");
27008
27281
  if (!specs.length) throw new SpecPreviewError(t2("preview.unknownFeature", { feature: options.feature }));
27009
- documents = [];
27010
- warnings = warnings.filter((w) => w.specId === specs[0].id);
27282
+ wiki = { documents: [], history: [] };
27283
+ warnings = warnings.filter((w) => w.code === "MISSING_DESIGN_REFERENCE" && w.specId === specs[0].id);
27011
27284
  }
27012
- if (!options.ids) return { stamp: working.stamp, inputs, specs, documents, warnings };
27285
+ if (!options.ids) return { stamp: working.stamp, inputs, specs, wiki, warnings };
27013
27286
  return {
27014
27287
  stamp: working.stamp,
27015
27288
  inputs,
27016
27289
  warnings,
27017
27290
  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 })) }))
27291
+ wiki: { documents: wiki.documents.map((d) => ({ id: d.id, path: d.path, title: d.title })) }
27019
27292
  };
27020
27293
  }
27021
27294
 
27022
27295
  // 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";
27296
+ import { lstat as lstat10, readdir as readdir6, readFile as readFile13, rename as rename5, writeFile as writeFile6, unlink as unlink7 } from "node:fs/promises";
27297
+ import { join as join16 } from "node:path";
27025
27298
  import { randomBytes as randomBytes3 } from "node:crypto";
27026
27299
  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) => {
27300
+ var info6 = (path3) => lstat10(path3).catch((e) => {
27028
27301
  if (e.code === "ENOENT") return void 0;
27029
27302
  throw e;
27030
27303
  });
@@ -27035,40 +27308,40 @@ var fail7 = (code, message) => {
27035
27308
  async function migrateStore(cwd, dryRun = false) {
27036
27309
  const reader = specPreviewReader(cwd);
27037
27310
  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 }));
27311
+ for (const marker3 of operations2) if (await info6(join16(gitDir, marker3))) fail7("GIT_OPERATION_IN_PROGRESS", t2("git.operationInProgress", { marker: marker3 }));
27039
27312
  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"));
27313
+ 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) }));
27314
+ const from = join16(root, ".tryce");
27315
+ const to = join16(root, ".gitifact");
27316
+ const fromInfo = await info6(from);
27317
+ if (await info6(to)) fail7("ALREADY_MIGRATED", t2("migrate.alreadyMigrated"));
27045
27318
  if (!fromInfo) return fail7("NOTHING_TO_MIGRATE", t2("migrate.nothingToMigrate"));
27046
27319
  if (!fromInfo.isDirectory() || fromInfo.isSymbolicLink()) fail7("PATH_CONFLICT", t2("migrate.notDirectory"));
27047
- const configInfo = await info4(join14(from, "config.json"));
27320
+ const configInfo = await info6(join16(from, "config.json"));
27048
27321
  if (!configInfo?.isFile() || configInfo.isSymbolicLink()) fail7("NOT_INITIALIZED", t2("migrate.noConfig"));
27049
- const config2 = parseManagedConfig(decode4(await readFile13(join14(from, "config.json"))));
27322
+ const config2 = parseManagedConfig(decode4(await readFile13(join16(from, "config.json"))));
27050
27323
  if (!("schemaVersion" in config2)) fail7("UNSUPPORTED_FORMAT", t2("migrate.legacyJson"));
27051
- const top = (await readdir5(from)).sort();
27324
+ const top = (await readdir6(from)).sort();
27052
27325
  if (top.some((name) => name !== "config.json" && name !== "spec")) fail7("UNSUPPORTED_FORMAT", t2("migrate.extraEntries"));
27053
27326
  const files = /* @__PURE__ */ new Map();
27054
27327
  const rewrites = [];
27055
27328
  const legacyRecords = [];
27056
27329
  const marker2 = /^<!-- tryce-(spec|req|design|ref): /;
27057
27330
  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);
27331
+ for (const feature2 of (await readdir6(join16(from, "spec"))).sort()) {
27332
+ const dir = join16(from, "spec", feature2);
27333
+ const stat3 = await info6(dir);
27061
27334
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) fail7("UNSUPPORTED_FORMAT", t2("migrate.notSpecFolder", { feature: feature2 }));
27062
- for (const name of (await readdir5(dir)).sort()) {
27335
+ for (const name of (await readdir6(dir)).sort()) {
27063
27336
  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));
27337
+ const fileStat = await info6(join16(dir, name));
27065
27338
  if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.nlink !== 1 || fileStat.size > 1024 * 1024) fail7("UNSUPPORTED_FORMAT", t2("migrate.fileTooLarge", { name }));
27066
27339
  const newPath = `.gitifact/spec/${feature2}/${name}`;
27067
27340
  if (name === "tryce.json") {
27068
27341
  legacyRecords.push(newPath);
27069
27342
  continue;
27070
27343
  }
27071
- const text2 = decode4(await readFile13(join14(dir, name)));
27344
+ const text2 = decode4(await readFile13(join16(dir, name)));
27072
27345
  if (name === "history.jsonl") {
27073
27346
  files.set(newPath, text2);
27074
27347
  continue;
@@ -27113,9 +27386,9 @@ async function migrateStore(cwd, dryRun = false) {
27113
27386
  if (dryRun) return result("planned");
27114
27387
  await rename5(from, to);
27115
27388
  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" });
27389
+ const full = join16(root, r.path);
27390
+ const temp = join16(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes3(8).toString("hex"));
27391
+ await writeFile6(temp, r.content, { flag: "wx" });
27119
27392
  try {
27120
27393
  await rename5(temp, full);
27121
27394
  } finally {
@@ -27137,16 +27410,16 @@ async function runMigrate(options) {
27137
27410
  }
27138
27411
 
27139
27412
  // 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());
27413
+ var program2 = new Command().name("gitifact").description(t2("help.program")).version("0.5.0").allowExcessArguments(false).addHelpText("after", "\n" + t2("help.notYet")).action(() => program2.outputHelp());
27141
27414
  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
27415
  await runStatus(options.format);
27143
27416
  });
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"));
27417
+ 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.0"));
27418
+ 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.0"));
27146
27419
  program2.command("docs").description(t2("help.docs")).argument("[topic]", t2("help.docsTopic")).allowExcessArguments(false).action(async (topic) => {
27147
27420
  await runDocs(topic);
27148
27421
  });
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"));
27422
+ 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.0"));
27150
27423
  program2.command("migrate").description(t2("help.migrate")).allowExcessArguments(false).option("--dry-run", t2("help.migrateDryRun")).action(runMigrate);
27151
27424
  var spec = program2.command("spec").description(t2("help.spec"));
27152
27425
  var replaced = t2("help.deprecated");