gitifact 0.4.3 → 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 +10 -6
  2. package/dist/browser/assets/{Markdown-GTI3nRCA.js → Markdown-C8C4nHPG.js} +3 -3
  3. package/dist/browser/assets/{MetadataListItem-c6t7SM9J.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-BWWTOV27.js → activity-B7x648pR.js} +1 -1
  7. package/dist/browser/assets/changelog-DaxXahXm.js +2 -0
  8. package/dist/browser/assets/{contributors._email-DUoflTLI.js → contributors._email-BZevZqRL.js} +1 -1
  9. package/dist/browser/assets/{contributors.index-DfuU03rG.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-QHPB6OmC.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-PaoXpYJM.js → requirements-wk9Joy7M.js} +1 -1
  22. package/dist/browser/assets/{settings-DN1DDBYj.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 +27 -13
  28. package/dist/i18n/ko/changelog.md +30 -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 +21 -11
  35. package/dist/main.js +810 -439
  36. package/package.json +1 -1
  37. package/dist/browser/assets/Table-BJ_vfcZE.js +0 -3
  38. package/dist/browser/assets/about-5hmfP7Oq.js +0 -3
  39. package/dist/browser/assets/changelog-jpn_8GXM.js +0 -2
  40. package/dist/browser/assets/document-D41OCO3e.css +0 -1
  41. package/dist/browser/assets/document-DeNuHzWM.js +0 -1
  42. package/dist/browser/assets/features._featureId-mcSjZyhs.js +0 -1
  43. package/dist/browser/assets/git-Cj31Qjk8.js +0 -1
  44. package/dist/browser/assets/guides._documentId-BuOa1m0p.js +0 -1
  45. package/dist/browser/assets/guides.index-BNK_skIs.js +0 -1
  46. package/dist/browser/assets/index-CdO3EM0n.js +0 -56
  47. package/dist/browser/assets/page-header-CBWpQAOT.js +0 -126
  48. package/dist/browser/assets/product-BrvAWtBo.css +0 -1
  49. package/dist/browser/assets/product-DZ2_YsMy.js +0 -8
  50. package/dist/browser/assets/product.document-C5VnB7EK.js +0 -1
  51. package/dist/browser/assets/product.index-DybzHICn.js +0 -1
  52. package/dist/browser/assets/request-state-CtVGSFDZ.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,11 +24696,20 @@ var browserSessionV2 = external_exports.strictObject({
24563
24696
  update: updateStateV1
24564
24697
  });
24565
24698
 
24566
- // ../../packages/contracts/dist/versions/update-v1.js
24567
- var updateV1 = external_exports.discriminatedUnion("ok", [
24699
+ // ../../packages/contracts/dist/versions/update-v3.js
24700
+ var commitReason = external_exports.enum(["untracked", "other-changes", "commit-failed"]);
24701
+ var agentDocsCommit = external_exports.strictObject({
24702
+ state: external_exports.enum(["not-requested", "nothing", "committed", "skipped"]),
24703
+ commit: external_exports.string().nullable(),
24704
+ paths: external_exports.array(external_exports.string()),
24705
+ message: external_exports.string().nullable(),
24706
+ reason: commitReason.nullable(),
24707
+ detail: external_exports.string().nullable()
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.");
24709
+ var updateV3 = external_exports.discriminatedUnion("ok", [
24568
24710
  external_exports.strictObject({
24569
24711
  contract: external_exports.literal("update"),
24570
- version: external_exports.literal(1),
24712
+ version: external_exports.literal(3),
24571
24713
  ok: external_exports.literal(true),
24572
24714
  cliVersion: external_exports.string().min(1),
24573
24715
  update: updateStateV1,
@@ -24575,9 +24717,11 @@ var updateV1 = external_exports.discriminatedUnion("ok", [
24575
24717
  install: external_exports.strictObject({ npmGlobal: external_exports.string().min(1) }).nullable(),
24576
24718
  // refreshed: blocks whose version line differed were rewritten. current: every block already matched.
24577
24719
  // not-initialized: no .gitifact config here, so only the version check ran. no-block: no file carries a block.
24578
- 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()) }),
24722
+ commit: agentDocsCommit
24579
24723
  }),
24580
- external_exports.strictObject({ contract: external_exports.literal("update"), version: external_exports.literal(1), 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() }) })
24581
24725
  ]);
24582
24726
 
24583
24727
  // src/shared/i18n/ko/messages.json
@@ -24595,7 +24739,7 @@ var messages_default2 = {
24595
24739
  "help.initRemoveAgents": "GITIFACT \uBE14\uB85D\uC744 \uC81C\uAC70",
24596
24740
  "help.initSkipAgents": "\uC9C0\uCE68 \uD30C\uC77C\uC744 \uAC74\uB4DC\uB9AC\uC9C0 \uC54A\uC74C",
24597
24741
  "help.docs": "\uC5D0\uC774\uC804\uD2B8\uC6A9 \uC791\uC5C5 \uC9C0\uCE68 \uCD9C\uB825 (\uC8FC\uC81C \uC5C6\uC774 \uC2E4\uD589\uD558\uBA74 \uBAA9\uB85D)",
24598
- "help.docsTopic": "workflow, spec, design, product, commit \uC911 \uD558\uB098",
24742
+ "help.docsTopic": "workflow, spec, design, wiki, commit \uC911 \uD558\uB098",
24599
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)",
24600
24744
  "help.migrateDryRun": "\uD30C\uC77C\uC744 \uBC14\uAFB8\uC9C0 \uC54A\uACE0 \uC804\uD658 \uACC4\uD68D \uD655\uC778",
24601
24745
  "help.spec": "Markdown \uBA85\uC138 \uC791\uC131\xB7\uC870\uD68C\xB7Git \uAE30\uB85D",
@@ -24612,11 +24756,11 @@ var messages_default2 = {
24612
24756
  "help.specDiffFrom": "\uC774\uC804 \uCEE4\uBC0B",
24613
24757
  "help.specDiffTo": "\uC774\uD6C4 \uCEE4\uBC0B",
24614
24758
  "help.specChanges": "HEAD \uB300\uBE44 \uCD5C\uC885 \uBA85\uC138 \uCC28\uC774\uC640 \uBBF8\uCEE4\uBC0B \uC774\uC720 \uC870\uD68C",
24615
- "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",
24616
24760
  "help.specWorkingStamp": "stamp\uC640 \uC785\uB825 \uD30C\uC77C \uACBD\uB85C\uB9CC \uCD9C\uB825",
24617
- "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)",
24618
24762
  "help.specWorkingIds": "\uBCF8\uBB38 \uC5C6\uC774 ID\xB7\uC81C\uBAA9\xB7\uACBD\uB85C\uB9CC \uCD9C\uB825",
24619
- "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",
24620
24764
  "help.specRead": "\uCEE4\uBC0B \uC2DC\uC810\uC758 \uBA85\uC138 \uC870\uD68C",
24621
24765
  "help.specDiff": "\uB450 \uCEE4\uBC0B \uC0AC\uC774\uC758 \uBA85\uC138 \uBCC0\uACBD \uC870\uD68C",
24622
24766
  "git.operationInProgress": "Git \uC791\uC5C5\uC774 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4: {marker}",
@@ -24712,7 +24856,6 @@ var messages_default2 = {
24712
24856
  "server.crossSite": "\uC678\uBD80 \uD398\uC774\uC9C0\uC758 \uC694\uCCAD\uC744 \uD5C8\uC6A9\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24713
24857
  "server.noQuery": "\uC870\uD68C \uC778\uC790\uB97C \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.",
24714
24858
  "server.methodNotAllowed": "\uD5C8\uC6A9\uD558\uC9C0 \uC54A\uB294 \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4.",
24715
- "server.productImageNotFound": "\uC81C\uD488 \uC774\uBBF8\uC9C0\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24716
24859
  "server.apiNotFound": "API\uB97C \uCC3E\uC744 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.",
24717
24860
  "server.sessionChanged": "\uC11C\uBC84 \uC138\uC158\uC774 \uBCC0\uACBD\uB410\uC2B5\uB2C8\uB2E4. \uB2E4\uC2DC \uC5F0\uACB0\uD558\uC138\uC694.",
24718
24861
  "server.badRange": "\uC870\uD68C \uBC94\uC704\uB97C \uD655\uC778\uD558\uC138\uC694.",
@@ -24810,10 +24953,10 @@ var messages_default2 = {
24810
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.",
24811
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",
24812
24955
  "docs.summary.spec": "\uC0AC\uC6A9\uC790 \uC2A4\uD1A0\uB9AC\xB7\uC218\uC6A9 \uC870\uAC74 \uD615\uC2DD, ID \uADDC\uCE59, spec save \uC785\uB825",
24813
- "docs.summary.design": "design.md \uBAA9\uCC28, \uCC38\uC870 \uC8FC\uC11D, \uAC1C\uC815 \uBC29\uC2DD",
24814
- "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",
24815
24957
  "docs.summary.commit": "spec commit \uC785\uB825, \uC774\uC720 \uAE30\uB85D, \uC2E4\uD328 \uD6C4 \uBCF5\uAD6C",
24816
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)",
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",
24817
24960
  "help.update": "\uC0C8 \uBC84\uC804 \uD655\uC778\uACFC \uC124\uCE58 \uBC29\uBC95 \uC548\uB0B4, \uD504\uB85C\uC81D\uD2B8 \uC9C0\uCE68 \uD30C\uC77C\uC758 GITIFACT \uBE14\uB85D\uC744 \uD604\uC7AC \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0",
24818
24961
  "server.badLanguage": "\uC5B8\uC5B4 \uCF54\uB4DC\uB97C \uD655\uC778\uD558\uC138\uC694.",
24819
24962
  "server.changelogNotFound": "\uD328\uCE58\uB178\uD2B8\uB97C \uCC3E\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4.",
@@ -24824,12 +24967,24 @@ var messages_default2 = {
24824
24967
  "update.text.disabled": "\uC0C8 \uBC84\uC804 \uD655\uC778\uC774 \uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4.",
24825
24968
  "update.text.unavailable": "\uC0C8 \uBC84\uC804\uC744 \uD655\uC778\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4. \uB124\uD2B8\uC6CC\uD06C \uC5F0\uACB0\uC744 \uD655\uC778\uD558\uC138\uC694.",
24826
24969
  "update.text.install": "npm \uC804\uC5ED \uC124\uCE58\uC6A9: {command}",
24827
- "update.text.afterInstall": "\uC124\uCE58\uD55C \uB4A4 gitifact update\uB97C \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC9C0\uCE68 \uBE14\uB85D\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB429\uB2C8\uB2E4.",
24970
+ "update.text.afterInstall": "\uC124\uCE58\uD55C \uB4A4 gitifact update --commit\uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uBA74 \uC9C0\uCE68 \uBE14\uB85D\uC774 \uC0C8 \uBC84\uC804\uC73C\uB85C \uAC31\uC2E0\uB418\uACE0 \uCEE4\uBC0B\uB429\uB2C8\uB2E4.",
24828
24971
  "update.text.blockRefreshed": "\uC9C0\uCE68 \uBE14\uB85D \uAC31\uC2E0: {paths}",
24829
24972
  "update.text.blockCurrent": "\uC9C0\uCE68 \uBE14\uB85D\uC740 \uC774\uBBF8 \uD604\uC7AC \uBC84\uC804\uC785\uB2C8\uB2E4: {paths}",
24830
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.",
24831
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.",
24832
- "update.failed": "\uC5C5\uB370\uC774\uD2B8 \uD655\uC778\uC5D0 \uC2E4\uD328\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.",
24976
+ "update.text.committed": "\uC9C0\uCE68 \uBE14\uB85D \uCEE4\uBC0B: {commit} ({paths})",
24977
+ "update.text.commitNothing": "\uCEE4\uBC0B\uD560 \uC9C0\uCE68 \uBE14\uB85D \uBCC0\uACBD\uC774 \uC5C6\uC2B5\uB2C8\uB2E4.",
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}",
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}",
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}",
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.",
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"
24833
24988
  };
24834
24989
 
24835
24990
  // src/shared/i18n/index.ts
@@ -24905,8 +25060,8 @@ async function runStatus(format) {
24905
25060
  import { createServer } from "node:http";
24906
25061
  import { randomUUID as randomUUID4 } from "node:crypto";
24907
25062
  import { fileURLToPath } from "node:url";
24908
- import { lstat as lstat3, readFile as readFile4 } from "node:fs/promises";
24909
- 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";
24910
25065
 
24911
25066
  // src/server/assets.ts
24912
25067
  import { readdir, readFile } from "node:fs/promises";
@@ -24939,6 +25094,7 @@ function contentType(path3) {
24939
25094
  if (/\.jpe?g$/.test(path3)) return "image/jpeg";
24940
25095
  if (path3.endsWith(".gif")) return "image/gif";
24941
25096
  if (path3.endsWith(".webp")) return "image/webp";
25097
+ if (path3.endsWith(".pdf")) return "application/pdf";
24942
25098
  return "application/octet-stream";
24943
25099
  }
24944
25100
 
@@ -25045,7 +25201,7 @@ function commandScoped(key, load) {
25045
25201
  }
25046
25202
 
25047
25203
  // src/adapters/git/spec-preview-reader.ts
25048
- 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 + "/"];
25049
25205
  var LegacyBaselineError = class extends SpecPreviewError {
25050
25206
  };
25051
25207
  function specPreviewReader(cwd) {
@@ -25079,7 +25235,7 @@ function specPreviewReader(cwd) {
25079
25235
  const [, mode, oid2, stage, path3] = match;
25080
25236
  if (!["100644", "100755"].includes(mode) || stage !== "0") throw new SpecPreviewError(t2("reader.stagingConflictOrLink"));
25081
25237
  if (!recordPathPattern.test(path3)) {
25082
- if (/^\.gitifact\/(?:product|guides)\//.test(path3)) continue;
25238
+ if (path3.startsWith(WIKI_DIR + "/")) continue;
25083
25239
  throw new SpecPreviewError(t2("reader.stagingUnsupported"));
25084
25240
  }
25085
25241
  files.set(path3, oid2);
@@ -25175,38 +25331,85 @@ function specPreviewReader(cwd) {
25175
25331
  }
25176
25332
 
25177
25333
  // src/adapters/filesystem/spec-preview-store.ts
25178
- import { lstat as lstat2, readdir as readdir2, readFile as readFile3, mkdir as mkdir2, writeFile, rename, unlink as unlink2, rmdir } from "node:fs/promises";
25179
- 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";
25180
25336
  import { createHash as createHash2, randomBytes } from "node:crypto";
25181
25337
 
25182
- // src/adapters/filesystem/config-file.ts
25183
- 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";
25184
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";
25185
25388
  import { randomUUID as randomUUID3 } from "node:crypto";
25186
25389
  var missing = (error62) => error62.code === "ENOENT";
25187
- var fileInfo = (path3) => lstat(path3).catch((error62) => {
25390
+ var fileInfo = (path3) => lstat2(path3).catch((error62) => {
25188
25391
  if (missing(error62)) return void 0;
25189
25392
  throw error62;
25190
25393
  });
25191
25394
  async function configDirectory(root) {
25192
- const path3 = join2(root, ".gitifact");
25193
- const info5 = await fileInfo(path3);
25194
- if (info5 && (!info5.isDirectory() || info5.isSymbolicLink())) throw new InitError("PATH_CONFLICT", t2("config.storeNotDirectory"));
25195
- 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;
25196
25399
  }
25197
25400
  async function readConfigFile(root) {
25198
25401
  await configDirectory(root);
25199
- const path3 = join2(root, ".gitifact", "config.json");
25200
- const info5 = await fileInfo(path3);
25201
- if (!info5) {
25202
- 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"));
25203
25406
  return void 0;
25204
25407
  }
25205
- 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"));
25206
25409
  const bytes = await readFile2(path3);
25207
25410
  const after = await fileInfo(path3);
25208
25411
  await configDirectory(root);
25209
- 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) {
25210
25413
  throw new InitError("INPUT_CHANGED", t2("config.changedWhileReading"));
25211
25414
  }
25212
25415
  try {
@@ -25216,7 +25419,7 @@ async function readConfigFile(root) {
25216
25419
  }
25217
25420
  }
25218
25421
  async function publishConfig(root, text2, recheck, beforePublish) {
25219
- const directory = join2(root, ".gitifact");
25422
+ const directory = join3(root, ".gitifact");
25220
25423
  await configDirectory(root);
25221
25424
  await mkdir(directory).catch((error62) => {
25222
25425
  if (error62.code !== "EEXIST") throw error62;
@@ -25229,7 +25432,7 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25229
25432
  throw new InitError("INPUT_CHANGED", t2("config.directoryChanged"));
25230
25433
  }
25231
25434
  };
25232
- const temporary = join2(directory, ".init-" + randomUUID3() + ".tmp");
25435
+ const temporary = join3(directory, ".init-" + randomUUID3() + ".tmp");
25233
25436
  const handle = await open2(temporary, "wx", 384);
25234
25437
  const tempOwner = await handle.stat();
25235
25438
  try {
@@ -25246,7 +25449,7 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25246
25449
  const bytes = await readFile2(temporary, "utf8");
25247
25450
  if (bytes !== text2) throw new InitError("INPUT_CHANGED", t2("config.temporaryChanged"));
25248
25451
  parseManagedConfig(bytes);
25249
- await link(temporary, join2(directory, "config.json"));
25452
+ await link(temporary, join3(directory, "config.json"));
25250
25453
  } finally {
25251
25454
  await unchanged();
25252
25455
  const current = await fileInfo(temporary);
@@ -25258,14 +25461,14 @@ async function publishConfig(root, text2, recheck, beforePublish) {
25258
25461
  var fail4 = (message) => {
25259
25462
  throw new SpecPreviewError(message);
25260
25463
  };
25261
- var info = async (path3) => lstat2(path3).catch((e) => {
25464
+ var info2 = async (path3) => lstat3(path3).catch((e) => {
25262
25465
  if (e.code === "ENOENT") return void 0;
25263
25466
  throw e;
25264
25467
  });
25265
25468
  var digest = (value) => createHash2("sha256").update(value).digest("hex");
25266
25469
  var LEGACY_LOCKS = ["tryce-spec-preview.lock", "tryce-spec-commit.lock"];
25267
25470
  async function failOnLegacyLock(gitDir) {
25268
- 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) }));
25269
25472
  }
25270
25473
  var decode3 = (bytes) => new TextDecoder("utf-8", { fatal: true, ignoreBOM: true }).decode(bytes);
25271
25474
  var generatePreviewId = (prefix) => prefix + "-" + [...randomBytes(10)].map((n) => "abcdefghijklmnopqrstuvwxyz234567"[n & 31]).join("");
@@ -25275,48 +25478,48 @@ async function snapshot2(root) {
25275
25478
  const files = /* @__PURE__ */ new Map();
25276
25479
  let count = 0;
25277
25480
  let bytes = 0;
25278
- const parent = await info(join3(root, ".gitifact"));
25481
+ const parent = await info2(join4(root, ".gitifact"));
25279
25482
  if (parent && (!parent.isDirectory() || parent.isSymbolicLink())) fail4(t2("store.storeNotDirectory"));
25280
- if (!parent && await info(join3(root, ".tryce"))) fail4(t2("store.migrateFirst"));
25483
+ if (!parent && await info2(join4(root, ".tryce"))) fail4(t2("store.migrateFirst"));
25281
25484
  const config2 = await readConfigFile(root);
25282
- 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"));
25283
25486
  async function visit2(path3, depth, documents) {
25284
- const stat3 = await info(join3(root, path3));
25487
+ const stat3 = await info2(join4(root, path3));
25285
25488
  if (!stat3) return;
25286
25489
  if (stat3.isSymbolicLink() || !stat3.isDirectory() && !stat3.isFile()) fail4(t2("store.linkOrSpecial", { path: path3 }));
25287
25490
  if (++count > 4e3) fail4(t2("store.tooManyFiles"));
25288
25491
  if (stat3.isDirectory()) {
25289
25492
  if (depth > (documents ? 8 : 1)) fail4(t2("store.tooDeep", { path: path3 }));
25290
- 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);
25291
25494
  } else {
25292
25495
  if (path3.endsWith("/tryce.json")) fail4(t2("store.legacyJson"));
25293
25496
  if (documents ? !(path3.endsWith(".md") || depth === 1 && path3.endsWith("/history.jsonl")) : !/\/(requirements\.md|design\.md|history\.jsonl)$/.test(path3)) return;
25294
25497
  if (!recordPathPattern.test(path3) || path3.startsWith(".tryce/")) fail4(t2("store.unsupportedPath", { path: path3 }));
25295
25498
  if (stat3.nlink !== 1 || stat3.size > 1024 * 1024) fail4(t2("store.hardLinkOrSize"));
25296
- const raw = await readFile3(join3(root, path3));
25499
+ const raw = await readFile3(join4(root, path3));
25297
25500
  bytes += raw.length;
25298
25501
  if (raw.length > 1024 * 1024 || bytes > 16 * 1024 * 1024) fail4(t2("store.sizeLimit"));
25299
25502
  files.set(path3, decode3(raw));
25300
25503
  }
25301
25504
  }
25302
25505
  await visit2(".gitifact/spec", 0, false);
25303
- for (const dir of Object.values(DOCUMENT_DIRS)) await visit2(dir, 0, true);
25506
+ await visit2(WIKI_DIR, 0, true);
25304
25507
  const bundle = parsePreviewBundle(files);
25305
- 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 ?? "")) };
25306
25509
  }
25307
25510
  async function readWorkingPreviewState(cwd) {
25308
25511
  const { root, gitDir } = await specPreviewReader(cwd).location();
25309
25512
  await failOnLegacyLock(gitDir);
25310
- 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"));
25311
25514
  const first = await snapshot2(root);
25312
25515
  const second = await snapshot2(root);
25313
25516
  if (first.stamp !== second.stamp) fail4(t2("store.changedWhileReading"));
25314
- 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"));
25315
25518
  return first;
25316
25519
  }
25317
25520
  async function readWorkingPreview(cwd) {
25318
- const { stamp, specs, documents } = await readWorkingPreviewState(cwd);
25319
- 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) };
25320
25523
  }
25321
25524
  async function saveWorkingPreview(cwd, input2, publish = rename) {
25322
25525
  if (!input2 || typeof input2 !== "object" || Array.isArray(input2)) fail4(t2("store.invalidInput"));
@@ -25330,8 +25533,8 @@ async function saveWorkingPreview(cwd, input2, publish = rename) {
25330
25533
  if (!old || renderSpecPreview(old) !== renderSpecPreview(spec2)) writes.set(spec2.path, renderSpecPreview(spec2));
25331
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);
25332
25535
  }
25333
- const previousDocs = before.documents.flatMap((s) => s.documents);
25334
- const nextDocs = result.documents.flatMap((s) => s.documents);
25536
+ const previousDocs = before.wiki.documents;
25537
+ const nextDocs = result.wiki.documents;
25335
25538
  for (const doc of nextDocs) {
25336
25539
  const old = previousDocs.find((d) => d.id === doc.id);
25337
25540
  if (old && old.path !== doc.path) writes.set(old.path, null);
@@ -25345,7 +25548,7 @@ var readLockedPreviewState = (root) => snapshot2(root);
25345
25548
  async function previewTransaction(cwd, expected2, build, publish = rename, afterPublish) {
25346
25549
  const { root, gitDir } = await specPreviewReader(cwd).location();
25347
25550
  await failOnLegacyLock(gitDir);
25348
- const lock = join3(gitDir, "gitifact-spec-preview.lock");
25551
+ const lock = join4(gitDir, "gitifact-spec-preview.lock");
25349
25552
  try {
25350
25553
  await mkdir2(lock);
25351
25554
  } catch (e) {
@@ -25358,7 +25561,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25358
25561
  const published = [];
25359
25562
  let keepRecovery = false;
25360
25563
  async function directory(path3) {
25361
- const stat3 = await info(path3);
25564
+ const stat3 = await info2(path3);
25362
25565
  if (stat3) {
25363
25566
  if (!stat3.isDirectory() || stat3.isSymbolicLink()) fail4(t2("store.notDirectory", { path: path3 }));
25364
25567
  return;
@@ -25382,14 +25585,14 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25382
25585
  else finalFiles.set(c.path, c.after);
25383
25586
  }
25384
25587
  if (finalFiles.size > 2e3 || [...finalFiles.values()].reduce((n, s) => n + Buffer.byteLength(s), 0) > 16 * 1024 * 1024) fail4(t2("store.totalLimit"));
25385
- 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" });
25386
25589
  for (const c of changed) {
25387
25590
  if (c.after === null) {
25388
25591
  temporary.push("");
25389
25592
  continue;
25390
25593
  }
25391
- await directory(dirname(join3(root, c.path)));
25392
- 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"));
25393
25596
  await writeFile(temp, c.after, { flag: "wx" });
25394
25597
  temporary.push(temp);
25395
25598
  }
@@ -25397,8 +25600,8 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25397
25600
  await result.recheck?.();
25398
25601
  for (let i = 0; i < changed.length; i++) {
25399
25602
  const c = changed[i];
25400
- const path3 = join3(root, c.path);
25401
- const stat3 = await info(path3);
25603
+ const path3 = join4(root, c.path);
25604
+ const stat3 = await info2(path3);
25402
25605
  if (stat3?.isSymbolicLink() || stat3 && !stat3.isFile() || (stat3 ? decode3(await readFile3(path3)) : null) !== c.before) fail4(t2("store.changedBeforeWrite"));
25403
25606
  if (c.after === null) await unlink2(path3);
25404
25607
  else await publish(temporary[i], path3);
@@ -25408,7 +25611,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25408
25611
  if (after.stamp !== digest(JSON.stringify([...finalFiles].sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0)) + (before.config ?? ""))) fail4(t2("store.changedWhileWriting"));
25409
25612
  await result.recheck?.();
25410
25613
  await afterPublish?.(after);
25411
- 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) };
25412
25615
  } catch (error62) {
25413
25616
  if (error62 instanceof PreservedPreviewError) {
25414
25617
  keepRecovery = true;
@@ -25416,15 +25619,15 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25416
25619
  }
25417
25620
  for (const c of [...published].reverse()) {
25418
25621
  try {
25419
- const path3 = join3(root, c.path);
25420
- const stat3 = await info(path3);
25622
+ const path3 = join4(root, c.path);
25623
+ const stat3 = await info2(path3);
25421
25624
  if (c.after === null ? !!stat3 : !stat3?.isFile() || stat3.isSymbolicLink() || decode3(await readFile3(path3)) !== c.after) {
25422
25625
  keepRecovery = true;
25423
25626
  continue;
25424
25627
  }
25425
25628
  if (c.before === null) await unlink2(path3);
25426
25629
  else {
25427
- const temp = join3(dirname(path3), ".gitifact-restore-" + randomBytes(12).toString("hex"));
25630
+ const temp = join4(dirname(path3), ".gitifact-restore-" + randomBytes(12).toString("hex"));
25428
25631
  temporary.push(temp);
25429
25632
  await writeFile(temp, c.before, { flag: "wx" });
25430
25633
  await rename(temp, path3);
@@ -25442,7 +25645,7 @@ async function previewTransaction(cwd, expected2, build, publish = rename, after
25442
25645
  for (const path3 of [...createdDirs].reverse()) await rmdir(path3).catch(() => {
25443
25646
  });
25444
25647
  if (!keepRecovery) {
25445
- await unlink2(join3(lock, "recovery.json")).catch((e) => {
25648
+ await unlink2(join4(lock, "recovery.json")).catch((e) => {
25446
25649
  if (e.code !== "ENOENT") throw e;
25447
25650
  });
25448
25651
  await rmdir(lock);
@@ -25484,8 +25687,8 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25484
25687
  if (cursor > 0 && expectedHead !== base.head) throw new SpecPreviewError(t2("specReader.historyChanged"));
25485
25688
  const current = await readWorkingPreviewState(root);
25486
25689
  const bare = current.specs.map(({ history: _history, ...s }) => ({ ...s, contributors: [], updatedAt: null }));
25487
- const bareDocuments = current.documents.flatMap((set2) => set2.documents).map((d) => ({ ...d, updatedAt: null }));
25488
- 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 });
25489
25692
  const head = base.head;
25490
25693
  const features = await Promise.all(bare.map(async (feature2) => {
25491
25694
  const folder = feature2.path.replace(/^\.(?:gitifact|tryce)\/(spec\/[^/]+)\/requirements\.md$/, "$1");
@@ -25501,7 +25704,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25501
25704
  return { ...feature2, contributors: [...people.values()].sort((a, b) => b.commits - a.commits), updatedAt: lines[0]?.split("\0")[2] ?? null };
25502
25705
  }));
25503
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 })));
25504
- const documentDirs = Object.values(DOCUMENT_DIRS);
25707
+ const documentDirs = [WIKI_DIR];
25505
25708
  const [rows, dirty] = await Promise.all([
25506
25709
  git([
25507
25710
  "log",
@@ -25535,7 +25738,7 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25535
25738
  const [commit, parents, author, email3, date5, committer, message] = row.split("\0");
25536
25739
  if (!commit || author === void 0 || email3 === void 0 || !date5 || committer === void 0 || message === void 0) throw new SpecPreviewError(t2("specReader.historyUnreadable"));
25537
25740
  const after = await snapshot3(commit);
25538
- let before = { specs: [], documents: [{ kind: "product", documents: [], history: [] }, { kind: "guide", documents: [], history: [] }] };
25741
+ let before = emptyBundle();
25539
25742
  const parent = parents?.split(" ")[0];
25540
25743
  if (parent) {
25541
25744
  try {
@@ -25562,9 +25765,9 @@ function createSpecBrowserReader(root, sessionId, inherited = process.env) {
25562
25765
  }));
25563
25766
  }));
25564
25767
  if (await readHead() !== head || (await readWorkingPreviewState(root)).stamp !== current.stamp) throw new SpecPreviewError(t2("specReader.projectChanged"));
25565
- return browserSpecsV1.parse({
25768
+ return browserSpecsV2.parse({
25566
25769
  contract: "browser-specs",
25567
- version: 1,
25770
+ version: 2,
25568
25771
  sessionId,
25569
25772
  head,
25570
25773
  observedAt: (/* @__PURE__ */ new Date()).toISOString(),
@@ -25658,18 +25861,27 @@ async function startBrowserServer(options) {
25658
25861
  if (api) {
25659
25862
  if (request.headers["sec-fetch-site"] === "cross-site" && !allowedOrigin) return fail8(response, 403, "FORBIDDEN", t2("server.crossSite"));
25660
25863
  if (url2.search && path3 !== "/api/v1/specs" && path3 !== "/api/v1/changelog") return fail8(response, 400, "BAD_REQUEST", t2("server.noQuery"));
25661
- if (path3.startsWith("/api/v1/product/assets/")) {
25662
- 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;
25663
25867
  if (request.method !== "GET") {
25664
25868
  response.setHeader("Allow", "GET");
25665
25869
  return fail8(response, 405, "METHOD_NOT_ALLOWED", t2("server.methodNotAllowed"));
25666
25870
  }
25667
- if (!isProductAssetPath(".gitifact/product/" + name)) return fail8(response, 404, "NOT_FOUND", t2("server.productImageNotFound"));
25668
- const file2 = join4(initial.repository.rootPath, ".gitifact", "product", name);
25669
- const stat3 = await lstat3(file2).catch(() => void 0);
25670
- 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"));
25671
25875
  const bytes = await readFile4(file2);
25672
- 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
+ });
25673
25885
  response.end(bytes);
25674
25886
  return;
25675
25887
  }
@@ -25833,13 +26045,13 @@ async function runBrowser(options, version2) {
25833
26045
  }
25834
26046
 
25835
26047
  // src/commands/spec-init.ts
25836
- import { readdir as readdir3 } from "node:fs/promises";
25837
- 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";
25838
26050
 
25839
26051
  // src/adapters/git/init-repository.ts
25840
26052
  import { createHash as createHash3 } from "node:crypto";
25841
26053
  import { readFile as readFile5 } from "node:fs/promises";
25842
- import { join as join5 } from "node:path";
26054
+ import { join as join6 } from "node:path";
25843
26055
  function initRepository(cwd, inherited = process.env) {
25844
26056
  const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
25845
26057
  const runner = createGitRunner();
@@ -25859,7 +26071,7 @@ function initRepository(cwd, inherited = process.env) {
25859
26071
  const gitDir = locations.length === 2 ? locations[0] : decodeGitLine(await git(["rev-parse", "--absolute-git-dir"], root));
25860
26072
  const indexPath = locations.length === 2 ? locations[1] : decodeGitLine(await git(["rev-parse", "--path-format=absolute", "--git-path", "index"], root));
25861
26073
  for (const marker2 of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"]) {
25862
- 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 }));
25863
26075
  }
25864
26076
  if (state.changes.some((change2) => change2.kind === "unmerged")) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
25865
26077
  const index = await readFile5(indexPath).catch((error62) => {
@@ -25897,22 +26109,22 @@ function initRepository(cwd, inherited = process.env) {
25897
26109
 
25898
26110
  // src/adapters/filesystem/managed-file.ts
25899
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";
25900
- import { join as join6, dirname as dirname2 } from "node:path";
26112
+ import { join as join7, dirname as dirname2 } from "node:path";
25901
26113
  import { randomUUID as randomUUID5 } from "node:crypto";
25902
26114
  var DEFAULT_LIMIT = 1024 * 1024;
25903
26115
  async function managedPath(root, path3, create = false) {
25904
26116
  let full = root;
25905
26117
  const parts = path3.split("/");
25906
26118
  for (let i = 0; i < parts.length; i++) {
25907
- full = join6(full, parts[i]);
25908
- let info5 = await fileInfo(full);
25909
- 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) {
25910
26122
  await mkdir3(full).catch((error62) => {
25911
26123
  if (error62.code !== "EEXIST") throw error62;
25912
26124
  });
25913
- info5 = await fileInfo(full);
26125
+ info7 = await fileInfo(full);
25914
26126
  }
25915
- 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()))) {
25916
26128
  throw new InitError("PATH_CONFLICT", t2("managedFile.notRegularPath", { path: path3 }));
25917
26129
  }
25918
26130
  }
@@ -25952,7 +26164,7 @@ async function managedWrite(root, path3, previous, next, recheck) {
25952
26164
  if (previous !== null) await unlink3(full);
25953
26165
  return;
25954
26166
  }
25955
- const temp = join6(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
26167
+ const temp = join7(directory, ".gitifact-managed-" + randomUUID5() + ".tmp");
25956
26168
  const handle = await open3(temp, "wx", 384);
25957
26169
  const tempOwner = await handle.stat();
25958
26170
  try {
@@ -25978,14 +26190,53 @@ async function managedWrite(root, path3, previous, next, recheck) {
25978
26190
  import { readFile as readFile8 } from "node:fs/promises";
25979
26191
 
25980
26192
  // src/commands/docs.ts
25981
- import { readFile as readFile7 } from "node:fs/promises";
25982
- 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"];
25983
26196
  var isDocTopic = (value) => docTopics.includes(value);
25984
- 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
+ });
25985
26202
  function listDocTopics(lang = defaultLanguage2) {
25986
26203
  const width = Math.max(...docTopics.map((topic) => topic.length));
25987
26204
  return docTopics.map((topic) => topic.padEnd(width) + " " + t2(`docs.summary.${topic}`, {}, lang)).join("\n") + "\n";
25988
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
+ }
25989
26240
  async function runDocs(topic, controls = {}) {
25990
26241
  if (topic === void 0) {
25991
26242
  process.stdout.write(listDocTopics());
@@ -25997,7 +26248,7 @@ async function runDocs(topic, controls = {}) {
25997
26248
  process.exitCode = 1;
25998
26249
  return;
25999
26250
  }
26000
- process.stdout.write(await (controls.readDoc ?? readBundled)(topic, defaultLanguage2));
26251
+ process.stdout.write(await renderDoc(topic, controls));
26001
26252
  }
26002
26253
 
26003
26254
  // src/commands/agent-block.ts
@@ -26038,6 +26289,16 @@ function isWrapperFile(text2) {
26038
26289
  const lines = own2.split(/\r?\n/).map((line) => line.trim()).filter(Boolean);
26039
26290
  return lines.length > 0 && lines.every((line) => /^@(?:\.\/)?[^\s]+$/.test(line) && candidatePaths.includes(line.replace(/^@(?:\.\/)?/, "")));
26040
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
+ }
26041
26302
  function resolveAgentPaths(preset, existing) {
26042
26303
  if (preset && preset !== "all") {
26043
26304
  const order = agentPresets[preset];
@@ -26071,7 +26332,7 @@ function removeBlock(text2, boilerplate) {
26071
26332
  }
26072
26333
 
26073
26334
  // src/commands/agent-docs.ts
26074
- var skippedAgentDocs = { mode: "skip", paths: [], writes: [] };
26335
+ var skippedAgentDocs = { mode: "skip", paths: [], writes: [], missing: [] };
26075
26336
  async function planAgentDocs(root, options) {
26076
26337
  if (options.skip) return skippedAgentDocs;
26077
26338
  const existing = /* @__PURE__ */ new Map();
@@ -26089,7 +26350,12 @@ async function planAgentDocs(root, options) {
26089
26350
  paths2.push(path3);
26090
26351
  writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
26091
26352
  }
26092
- 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: [] };
26093
26359
  }
26094
26360
  const block = await renderAgentBlock(options.version, options);
26095
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);
@@ -26103,11 +26369,19 @@ async function planAgentDocs(root, options) {
26103
26369
  paths2.push(create);
26104
26370
  writes.push({ path: create, previous: null, next: injectBlock(null, block, boilerplateFor(create)) });
26105
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
+ }
26106
26380
  for (const [path3, text2] of existing) {
26107
26381
  if (paths2.includes(path3) || !isWrapperFile(text2) || !findBlock(text2)) continue;
26108
26382
  writes.push({ path: path3, previous: text2, next: removeBlock(text2, boilerplateFor(path3)) });
26109
26383
  }
26110
- return { mode: "install", paths: paths2, writes };
26384
+ return { mode: "install", paths: paths2, writes, missing: missing2 };
26111
26385
  }
26112
26386
  async function applyAgentDocs(root, plan, recheck) {
26113
26387
  for (const write of plan.writes) await managedWrite(root, write.path, write.previous, write.next, recheck);
@@ -26115,7 +26389,7 @@ async function applyAgentDocs(root, plan, recheck) {
26115
26389
  }
26116
26390
 
26117
26391
  // src/commands/spec-init.ts
26118
- 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)) {
26119
26393
  const repo = initRepository(cwd, env);
26120
26394
  const first = await repo.inspect();
26121
26395
  const root = first.state.repository.rootPath;
@@ -26146,14 +26420,14 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
26146
26420
  if (old !== void 0) return existing(old);
26147
26421
  if (first.trackedConfig) throw new InitError("CONFIG_DELETED", t2("init.configDeleted"));
26148
26422
  const checkRecords = async () => {
26149
- if (await fileInfo(join7(root, "specs"))) throw new InitError("EXISTING_RECORDS", t2("init.existingSpecs"));
26150
- 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) => {
26151
26425
  if (e.code === "ENOENT") return [];
26152
26426
  throw e;
26153
26427
  });
26154
26428
  if (entries.some((name) => !/^\.init-[a-f0-9-]+\.tmp$/.test(name))) throw new InitError("EXISTING_RECORDS", t2("init.orphanStore"));
26155
26429
  };
26156
- 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" } };
26157
26431
  const text2 = JSON.stringify(config2, null, 2) + "\n";
26158
26432
  const recheck = async () => {
26159
26433
  await checkRecords();
@@ -26177,8 +26451,22 @@ async function initializeSpecProject(cwd, dryRun = false, env = process.env, bef
26177
26451
  };
26178
26452
  await published();
26179
26453
  await applyAgentDocs(root, docsPlan, published);
26454
+ await writeWikiPolicy(root, readDoc);
26180
26455
  return result(config2, "created");
26181
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
+ }
26182
26470
 
26183
26471
  // src/commands/init.ts
26184
26472
  async function runInit(options, version2) {
@@ -26193,7 +26481,7 @@ async function runInit(options, version2) {
26193
26481
  if (!dto.ok) throw new Error("unreachable");
26194
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") + ")");
26195
26483
  process.stdout.write(options.format === "text" ? `${dto.outcome}: ${dto.rootPath}/.gitifact/config.json
26196
- ${t2("init.text.storage")}: schemaVersion 1
26484
+ ${t2("init.text.storage")}: schemaVersion ${dto.schemaVersion}
26197
26485
  ${t2("init.text.agentDocs")}: ${docs}
26198
26486
  ` : JSON.stringify(dto) + "\n");
26199
26487
  } catch (error62) {
@@ -26204,7 +26492,39 @@ ${t2("init.text.agentDocs")}: ${docs}
26204
26492
  }
26205
26493
  }
26206
26494
 
26495
+ // src/adapters/git/agent-docs-commit.ts
26496
+ function agentDocsGit(root, inherited = process.env) {
26497
+ const env = { ...inherited, GIT_OPTIONAL_LOCKS: "0", GIT_NO_LAZY_FETCH: "1", GIT_TERMINAL_PROMPT: "0", GIT_LITERAL_PATHSPECS: "1", LC_ALL: "C" };
26498
+ const runner = createGitRunner();
26499
+ const git = (args, acceptedExitCodes = [0]) => runner(
26500
+ ["-c", "core.fsmonitor=false", ...args],
26501
+ { cwd: root, env, timeoutMs: 12e4, maxBytes: 16 * 1024 * 1024, acceptedExitCodes }
26502
+ );
26503
+ return {
26504
+ async head() {
26505
+ return (await git(["rev-parse", "-q", "--verify", "HEAD^{commit}"], [0, 1])).toString("utf8").trim() || null;
26506
+ },
26507
+ // The committed text of a path, or null when HEAD does not track it.
26508
+ async committed(head, path3) {
26509
+ if ((await git(["ls-tree", "-z", head, "--", path3])).length === 0) return null;
26510
+ return (await git(["cat-file", "blob", head + ":" + path3])).toString("utf8");
26511
+ },
26512
+ // `--only` commits the working content of these paths and leaves every other staged entry staged. Hooks and signing stay on.
26513
+ async commitOnly(paths2, message) {
26514
+ await git(["commit", "--only", "-m", message, "--", ...paths2]);
26515
+ },
26516
+ async changedPaths(commit) {
26517
+ return (await git(["diff-tree", "--no-commit-id", "--name-only", "--no-renames", "-r", "-z", "--root", commit])).toString("utf8").split("\0").filter(Boolean).sort();
26518
+ },
26519
+ async parents(commit) {
26520
+ return (await git(["rev-list", "--parents", "-n", "1", commit])).toString("utf8").trim().split(" ").slice(1);
26521
+ }
26522
+ };
26523
+ }
26524
+
26207
26525
  // src/commands/update.ts
26526
+ var blockCommitMessage = (version2) => "chore(gitifact): refresh GITIFACT block to v" + version2;
26527
+ var commitState = (state, extra = {}) => ({ state, commit: null, paths: [], message: null, reason: null, detail: null, ...extra });
26208
26528
  async function refreshBlocks(cwd, version2, env, controls) {
26209
26529
  const repo = initRepository(cwd, env);
26210
26530
  let first;
@@ -26213,37 +26533,79 @@ async function refreshBlocks(cwd, version2, env, controls) {
26213
26533
  first = await repo.inspect();
26214
26534
  config2 = await readConfigFile(first.state.repository.rootPath);
26215
26535
  } catch (error62) {
26216
- 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: [] };
26217
26537
  throw error62;
26218
26538
  }
26219
- if (config2 === void 0) return { state: "not-initialized", paths: [] };
26539
+ if (config2 === void 0) return { state: "not-initialized", paths: [], missing: [] };
26220
26540
  const root = first.state.repository.rootPath;
26221
26541
  const plan = await planAgentDocs(root, { version: version2, onlyExisting: true, ...controls });
26222
- if (plan.paths.length === 0) return { state: "no-block", paths: [] };
26223
- if (plan.writes.length === 0) return { state: "current", paths: 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 };
26224
26545
  const unchanged = async () => {
26225
26546
  if ((await repo.inspect()).stamp !== first.stamp || await readConfigFile(root) !== config2) throw new InitError("INPUT_CHANGED", t2("init.inputChanged"));
26226
26547
  };
26227
- return { state: "refreshed", paths: await applyAgentDocs(root, plan, unchanged) };
26548
+ const applied = await applyAgentDocs(root, plan, unchanged);
26549
+ return { state: "refreshed", paths: applied, missing: missing2, root, candidates: plan.paths };
26550
+ }
26551
+ function outsideBlock(text2) {
26552
+ const normalized2 = text2.replace(/\r\n/g, "\n");
26553
+ try {
26554
+ const found = findBlock(normalized2);
26555
+ return found ? normalized2.slice(0, found.start) + AGENT_START + normalized2.slice(found.end) : null;
26556
+ } catch {
26557
+ return null;
26558
+ }
26559
+ }
26560
+ async function commitBlocks(docs, version2, env) {
26561
+ if (!docs.root || !docs.candidates?.length) return commitState("nothing");
26562
+ const git = agentDocsGit(docs.root, env);
26563
+ const head = await git.head();
26564
+ const changed = [];
26565
+ for (const path3 of docs.candidates) {
26566
+ const working = await managedRead(docs.root, path3);
26567
+ const committed = head ? await git.committed(head, path3) : null;
26568
+ if (working === null) continue;
26569
+ if (committed === null) return commitState("skipped", { paths: [path3], reason: "untracked" });
26570
+ if (committed.replace(/\r\n/g, "\n") === working.replace(/\r\n/g, "\n")) continue;
26571
+ const before = outsideBlock(committed);
26572
+ if (before === null || before !== outsideBlock(working)) return commitState("skipped", { paths: [path3], reason: "other-changes" });
26573
+ changed.push(path3);
26574
+ }
26575
+ if (changed.length === 0) return commitState("nothing");
26576
+ const message = blockCommitMessage(version2);
26577
+ let failure2;
26578
+ try {
26579
+ await git.commitOnly(changed, message);
26580
+ } catch (error62) {
26581
+ failure2 = error62;
26582
+ }
26583
+ const after = await git.head();
26584
+ if (!after || after === head) return commitState("skipped", { paths: changed, reason: "commit-failed", detail: failure2 instanceof Error ? failure2.message : null });
26585
+ const parents = await git.parents(after);
26586
+ if (parents.length !== 1 || parents[0] !== head) throw new InitError("COMMIT_RESULT_UNKNOWN", t2("update.commitResultUnknown", { commit: after }));
26587
+ return commitState("committed", { commit: after, paths: await git.changedPaths(after), message });
26228
26588
  }
26229
26589
  async function updateCommand(cwd, version2, env = process.env, controls = {}) {
26230
26590
  const [update, agentDocs] = await Promise.all([
26231
26591
  updateCheckDisabled(env) ? disabledUpdate : resolveUpdate(version2, controls.fetchLatest ?? fetchLatestVersion, void 0, controls.timeoutMs),
26232
26592
  refreshBlocks(cwd, version2, env, controls)
26233
26593
  ]);
26234
- return updateV1.parse({
26594
+ const commit = controls.commit ? await commitBlocks(agentDocs, version2, env) : commitState("not-requested");
26595
+ return updateV3.parse({
26235
26596
  contract: "update",
26236
- version: 1,
26597
+ version: 3,
26237
26598
  ok: true,
26238
26599
  cliVersion: version2,
26239
26600
  update,
26240
26601
  install: update.status === "available" ? { npmGlobal: npmGlobalInstall(update.latestVersion) } : null,
26241
- agentDocs
26602
+ agentDocs: { state: agentDocs.state, paths: agentDocs.paths, missing: agentDocs.missing },
26603
+ commit
26242
26604
  });
26243
26605
  }
26244
26606
  async function runUpdate(options, version2) {
26245
26607
  try {
26246
- const dto = await updateCommand(process.cwd(), version2);
26608
+ const dto = await updateCommand(process.cwd(), version2, process.env, { commit: !!options.commit });
26247
26609
  if (!dto.ok) throw new Error("unreachable");
26248
26610
  if (options.format !== "text") {
26249
26611
  process.stdout.write(JSON.stringify(dto) + "\n");
@@ -26253,22 +26615,31 @@ async function runUpdate(options, version2) {
26253
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"));
26254
26616
  if (dto.install) lines.push(t2("update.text.install", { command: dto.install.npmGlobal }), t2("update.text.afterInstall"));
26255
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(", ") }));
26619
+ const { commit } = dto;
26620
+ if (commit.state === "committed") lines.push(t2("update.text.committed", { commit: commit.commit.slice(0, 12), paths: commit.paths.join(", ") }));
26621
+ else if (commit.state === "nothing") lines.push(t2("update.text.commitNothing"));
26622
+ else if (commit.state === "skipped") {
26623
+ const paths2 = commit.paths.join(", ");
26624
+ const text2 = commit.reason === "untracked" ? t2("update.text.commitSkipped.untracked", { paths: paths2 }) : commit.reason === "other-changes" ? t2("update.text.commitSkipped.other-changes", { paths: paths2 }) : t2("update.text.commitSkipped.commit-failed", { paths: paths2 });
26625
+ lines.push(text2 + (commit.detail ? " (" + commit.detail + ")" : ""));
26626
+ }
26256
26627
  process.stdout.write(lines.join("\n") + "\n");
26257
26628
  } catch (error62) {
26258
26629
  const known = error62 instanceof InitError || error62 instanceof RepositoryReadError;
26259
26630
  const failure2 = { code: known ? error62.code : "UPDATE_FAILED", message: known ? error62.message : t2("update.failed") };
26260
- process.stderr.write(options.format === "text" ? failure2.code + ": " + failure2.message + "\n" : JSON.stringify(updateV1.parse({ contract: "update", version: 1, 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");
26261
26632
  process.exitCode = 1;
26262
26633
  }
26263
26634
  }
26264
26635
 
26265
26636
  // src/commands/spec-preview.ts
26266
- import { join as join13 } from "node:path";
26637
+ import { join as join15 } from "node:path";
26267
26638
  import { readFile as readFile12, stat as stat2 } from "node:fs/promises";
26268
26639
 
26269
26640
  // src/adapters/filesystem/spec-preview-prepare.ts
26270
- import { lstat as lstat4 } from "node:fs/promises";
26271
- import { join as join8 } from "node:path";
26641
+ import { lstat as lstat6 } from "node:fs/promises";
26642
+ import { join as join10 } from "node:path";
26272
26643
  import { createHash as createHash4 } from "node:crypto";
26273
26644
  var fail5 = (message) => {
26274
26645
  throw new SpecPreviewError(message);
@@ -26281,7 +26652,7 @@ async function readPreviewContext(cwd) {
26281
26652
  const { gitDir } = await reader.location();
26282
26653
  const checkOperation = async () => {
26283
26654
  for (const name of ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"]) {
26284
- const exists = await lstat4(join8(gitDir, name)).catch((e) => {
26655
+ const exists = await lstat6(join10(gitDir, name)).catch((e) => {
26285
26656
  if (e.code === "ENOENT") return void 0;
26286
26657
  throw e;
26287
26658
  });
@@ -26335,9 +26706,9 @@ async function verifyPreparedPreview(cwd, input2, staged) {
26335
26706
  if (staged) {
26336
26707
  const index = await c.reader.index();
26337
26708
  if (index.size !== current.files.size) fail5(t2("prepare.stagingScope"));
26338
- for (const [path3, source] of current.files) {
26709
+ for (const [path3, source2] of current.files) {
26339
26710
  const oid2 = index.get(path3);
26340
- const bytes = Buffer.from(source);
26711
+ const bytes = Buffer.from(source2);
26341
26712
  const hash3 = createHash4(oid2?.length === 64 ? "sha256" : "sha1").update(`blob ${bytes.length}\0`).update(bytes).digest("hex");
26342
26713
  if (oid2 !== hash3) fail5(t2("prepare.stagingContent", { path: path3 }));
26343
26714
  }
@@ -26350,26 +26721,26 @@ async function verifyPreparedPreview(cwd, input2, staged) {
26350
26721
 
26351
26722
  // src/adapters/filesystem/agent-inputs.ts
26352
26723
  import { createHash as createHash5, randomBytes as randomBytes2 } from "node:crypto";
26353
- 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";
26354
26725
  import { tmpdir } from "node:os";
26355
- import { basename, dirname as dirname3, join as join9 } from "node:path";
26726
+ import { basename, dirname as dirname4, join as join11 } from "node:path";
26356
26727
  var maxAge = 7 * 24 * 60 * 60 * 1e3;
26357
- var info2 = (path3) => lstat5(path3).catch((e) => {
26728
+ var info4 = (path3) => lstat7(path3).catch((e) => {
26358
26729
  if (e.code === "ENOENT") return void 0;
26359
26730
  throw e;
26360
26731
  });
26361
26732
  var comparable = (path3) => process.platform === "win32" ? path3.toLowerCase() : path3;
26362
26733
  async function candidates(root, controls) {
26363
26734
  const key = createHash5("sha256").update(comparable(await realpath3(root))).digest("hex").slice(0, 16);
26364
- 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") };
26365
26736
  }
26366
26737
  async function writable(directory) {
26367
26738
  try {
26368
- await mkdir4(directory, { recursive: true });
26369
- const stat3 = await info2(directory);
26739
+ await mkdir5(directory, { recursive: true });
26740
+ const stat3 = await info4(directory);
26370
26741
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) return false;
26371
- const probe = join9(directory, ".probe-" + randomBytes2(8).toString("hex"));
26372
- await writeFile2(probe, "", { flag: "wx" });
26742
+ const probe = join11(directory, ".probe-" + randomBytes2(8).toString("hex"));
26743
+ await writeFile3(probe, "", { flag: "wx" });
26373
26744
  await unlink4(probe);
26374
26745
  return true;
26375
26746
  } catch {
@@ -26382,28 +26753,28 @@ async function prepareAgentInputs(root, controls = {}) {
26382
26753
  if (await writable(system)) directory = system;
26383
26754
  else if (await writable(project)) {
26384
26755
  directory = project;
26385
- await writeFile2(join9(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
26756
+ await writeFile3(join11(project, ".gitignore"), "*\n", { flag: "wx" }).catch((e) => {
26386
26757
  if (e.code !== "EEXIST") throw e;
26387
26758
  });
26388
26759
  }
26389
26760
  if (!directory) return void 0;
26390
26761
  const now = (controls.now ?? Date.now)();
26391
- for (const name of await readdir4(directory).catch(() => [])) {
26762
+ for (const name of await readdir5(directory).catch(() => [])) {
26392
26763
  if (name === ".gitignore") continue;
26393
- const stat3 = await info2(join9(directory, name)).catch(() => void 0);
26394
- 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);
26395
26766
  }
26396
- return { save: join9(directory, "save.json"), commit: join9(directory, "commit.json") };
26767
+ return { save: join11(directory, "save.json"), commit: join11(directory, "commit.json") };
26397
26768
  }
26398
26769
  async function discardAgentInput(root, file2, controls = {}) {
26399
26770
  try {
26400
- const stat3 = await info2(file2);
26771
+ const stat3 = await info4(file2);
26401
26772
  if (!stat3?.isFile() || stat3.isSymbolicLink()) return false;
26402
- const parent = comparable(await realpath3(dirname3(file2)));
26773
+ const parent = comparable(await realpath3(dirname4(file2)));
26403
26774
  const { system, project } = await candidates(root, controls);
26404
26775
  let managed = false;
26405
26776
  for (const directory of [system, project]) {
26406
- if (await info2(directory) && comparable(await realpath3(directory)) === parent) managed = true;
26777
+ if (await info4(directory) && comparable(await realpath3(directory)) === parent) managed = true;
26407
26778
  }
26408
26779
  if (!managed || basename(file2) === ".gitignore") return false;
26409
26780
  await unlink4(file2);
@@ -26415,13 +26786,13 @@ async function discardAgentInput(root, file2, controls = {}) {
26415
26786
 
26416
26787
  // src/commands/spec-preview-commit.ts
26417
26788
  import { randomUUID as randomUUID6 } from "node:crypto";
26418
- 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";
26419
- 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";
26420
26791
 
26421
26792
  // src/commands/spec-commit-files.ts
26422
26793
  import { createHash as createHash6 } from "node:crypto";
26423
- import { lstat as lstat6, readFile as readFile9 } from "node:fs/promises";
26424
- 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";
26425
26796
  var fail6 = (message) => {
26426
26797
  throw new SpecPreviewError(message);
26427
26798
  };
@@ -26430,7 +26801,7 @@ var optional2 = (path3) => readFile9(path3).catch((e) => {
26430
26801
  if (e.code === "ENOENT") return null;
26431
26802
  throw e;
26432
26803
  });
26433
- var info3 = (path3) => lstat6(path3).catch((e) => {
26804
+ var info5 = (path3) => lstat8(path3).catch((e) => {
26434
26805
  if (e.code === "ENOENT") return void 0;
26435
26806
  throw e;
26436
26807
  });
@@ -26442,11 +26813,11 @@ async function fingerprint(root, path3) {
26442
26813
  validPath2(path3);
26443
26814
  const parts = path3.split("/");
26444
26815
  for (let i = 1; i <= parts.length; i++) {
26445
- const target = join10(root, ...parts.slice(0, i));
26446
- const stat3 = await info3(target);
26816
+ const target = join12(root, ...parts.slice(0, i));
26817
+ const stat3 = await info5(target);
26447
26818
  if (!stat3) return null;
26448
26819
  if (stat3.isSymbolicLink() || (i < parts.length ? !stat3.isDirectory() : !stat3.isFile())) fail6(t2("commitFiles.linkOrDirectory"));
26449
- 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"));
26450
26821
  if (i === parts.length) {
26451
26822
  if (stat3.nlink !== 1 || stat3.size > 16 * 1024 * 1024) fail6(t2("commitFiles.hardLinkOrSize"));
26452
26823
  return hash2(Buffer.concat([Buffer.from(String(stat3.mode & 73) + "\0"), await readFile9(target)]));
@@ -26477,9 +26848,9 @@ function policyPaths(files) {
26477
26848
  return [...all].sort();
26478
26849
  }
26479
26850
  async function checkLegacySelection(root, selected) {
26480
- 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)\/.+$/;
26481
26852
  for (const p of selected.filter((p2) => p2.startsWith(".gitifact/") && !record2(p2) && p2 !== ".gitifact/config.json")) {
26482
- if (isProductAssetPath(p)) continue;
26853
+ if (isAssetPath(p)) continue;
26483
26854
  if (!legacyJson.test(p) || await fingerprint(root, p) !== null) fail6(t2("commitFiles.legacyDeleteOnly", { path: p }));
26484
26855
  }
26485
26856
  for (const p of selected.filter((p2) => p2.startsWith(".tryce/"))) {
@@ -26512,8 +26883,8 @@ async function previewCommit(cwd, action, input2) {
26512
26883
  });
26513
26884
  const indexPath = (await git(["rev-parse", "--path-format=absolute", "--git-path", "index"])).toString("utf8").trim();
26514
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();
26515
- const busy = join11(gitDir, "gitifact-spec-commit.lock");
26516
- 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 }));
26517
26888
  if ((await staged()).length) fail6(t2("commit.existingStagingPlan"));
26518
26889
  const request = object3(input2);
26519
26890
  let plan;
@@ -26570,33 +26941,33 @@ async function previewCommit(cwd, action, input2) {
26570
26941
  if (!("plan" in refreshed) || refreshed.plan.digest !== plan.digest) fail6(t2("commit.planStale"));
26571
26942
  const before = await reader.baseline();
26572
26943
  const original = await optional2(indexPath);
26573
- await mkdir5(busy);
26574
- const draftLock = join11(gitDir, "gitifact-spec-preview.lock");
26944
+ await mkdir6(busy);
26945
+ const draftLock = join13(gitDir, "gitifact-spec-preview.lock");
26575
26946
  let draftOwned = false;
26576
26947
  let indexLock;
26577
26948
  let uncertain = false;
26578
26949
  let commitStarted = false;
26579
- const temporary = join11(gitDir, "gitifact-commit-index-" + randomUUID6());
26950
+ const temporary = join13(gitDir, "gitifact-commit-index-" + randomUUID6());
26580
26951
  const checkFiles = async () => {
26581
26952
  for (const f of [...plan.files, ...plan.context]) if (f.hash !== await fingerprint(root, f.path)) fail6(t2("commit.fileChangedPreparing", { path: f.path }));
26582
26953
  if ((await readLockedPreviewState(root)).stamp !== object3(plan.verification).stamp) fail6(t2("commit.specsChangedPreparing"));
26583
- 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"));
26584
26955
  };
26585
26956
  try {
26586
26957
  await verifyPreparedPreview(cwd, plan.verification, false);
26587
- await mkdir5(draftLock);
26958
+ await mkdir6(draftLock);
26588
26959
  draftOwned = true;
26589
26960
  indexLock = await open4(indexPath + ".lock", "wx", 384);
26590
26961
  if (plan.index !== hash2(await optional2(indexPath) ?? Buffer.alloc(0)) || (await staged()).length) fail6(t2("commit.indexChangedLocked"));
26591
- await writeFile3(join11(busy, "recovery.json"), JSON.stringify({ before, originalIndex: original?.toString("base64") ?? null, temporary, plan }));
26592
- 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" });
26593
26964
  else await git(["read-tree", "--empty"], temporary);
26594
26965
  await git(["add", "--", ...selected], temporary);
26595
26966
  const actual = await staged(temporary);
26596
26967
  if (!actual.length || actual.some((p) => !selected.includes(p))) fail6(t2("commit.unexpectedFiles"));
26597
26968
  for (const file2 of plan.files.filter((f) => record2(f.path) && f.hash !== null)) {
26598
26969
  const stagedBytes = await git(["show", ":" + file2.path], temporary);
26599
- 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"));
26600
26971
  }
26601
26972
  await checkFiles();
26602
26973
  if (JSON.stringify(before) !== JSON.stringify(await reader.baseline())) fail6(t2("commit.headChanged"));
@@ -26635,7 +27006,7 @@ async function previewCommit(cwd, action, input2) {
26635
27006
  });
26636
27007
  await unlink5(temporary).catch(() => {
26637
27008
  });
26638
- await unlink5(join11(busy, "recovery.json")).catch(() => {
27009
+ await unlink5(join13(busy, "recovery.json")).catch(() => {
26639
27010
  });
26640
27011
  await rmdir2(busy);
26641
27012
  if (draftOwned) await rmdir2(draftLock);
@@ -26645,8 +27016,8 @@ async function previewCommit(cwd, action, input2) {
26645
27016
 
26646
27017
  // src/commands/spec-commit.ts
26647
27018
  import { createHash as createHash8, randomUUID as randomUUID7 } from "node:crypto";
26648
- 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";
26649
- 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";
26650
27021
  var fields = ["reasons", "paths", "message", "authorization", "requirements", "policyFiles", "expected"];
26651
27022
  var integration = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply"];
26652
27023
  var same2 = (a, b) => JSON.stringify(a) === JSON.stringify(b);
@@ -26685,9 +27056,9 @@ async function specCommit(cwd, input2, dryRun) {
26685
27056
  ...stdin ? { input: stdin } : {}
26686
27057
  });
26687
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();
26688
- const busy = join12(gitDir, "gitifact-spec-commit.lock");
27059
+ const busy = join14(gitDir, "gitifact-spec-commit.lock");
26689
27060
  await failOnLegacyLock(gitDir);
26690
- if (await info3(busy)) fail6(t2("commit.busy", { path: busy }));
27061
+ if (await info5(busy)) fail6(t2("commit.busy", { path: busy }));
26691
27062
  if ((await staged()).length) fail6(t2("commit.existingStaging"));
26692
27063
  const indexHash = hash2(await optional2(indexPath) ?? Buffer.alloc(0));
26693
27064
  const current = c.working;
@@ -26737,16 +27108,16 @@ async function specCommit(cwd, input2, dryRun) {
26737
27108
  const trailers = [
26738
27109
  ...requirements.map((id) => "Gitifact-Req: " + id),
26739
27110
  ...prepared.changes.filter((c2) => c2.kind === "design").map((c2) => "Gitifact-Design: " + c2.id),
26740
- ...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)
26741
27112
  ];
26742
- const temporary = join12(gitDir, "gitifact-commit-index-" + randomUUID7());
27113
+ const temporary = join14(gitDir, "gitifact-commit-index-" + randomUUID7());
26743
27114
  let owned = false;
26744
27115
  let uncertain = false;
26745
27116
  let indexLock;
26746
27117
  let committed;
26747
27118
  try {
26748
27119
  try {
26749
- await mkdir6(busy);
27120
+ await mkdir7(busy);
26750
27121
  owned = true;
26751
27122
  } catch (e) {
26752
27123
  if (e.code === "EEXIST") fail6(t2("commit.busy", { path: busy }));
@@ -26760,8 +27131,8 @@ async function specCommit(cwd, input2, dryRun) {
26760
27131
  if (hash2(original ?? Buffer.alloc(0)) !== indexHash || (await staged()).length) fail6(t2("commit.indexChanged"));
26761
27132
  const locked = await hashes([.../* @__PURE__ */ new Set([...selected, ...contextPaths])]);
26762
27133
  for (const [path3, value] of started) if (locked.get(path3) !== value) fail6(t2("commit.fileChangedRunning", { path: path3 }));
26763
- await writeFile4(join12(busy, "recovery.json"), JSON.stringify({ before: c.base, originalIndex: original?.toString("base64") ?? null, temporary, paths: selected, message, requirements }));
26764
- 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" });
26765
27136
  else await git(["read-tree", "--empty"], temporary);
26766
27137
  await git(["add", "--", ...stageable], temporary);
26767
27138
  const actual = await staged(temporary);
@@ -26776,7 +27147,7 @@ async function specCommit(cwd, input2, dryRun) {
26776
27147
  const size = Number(header[2]);
26777
27148
  if (end < 0 || header[1] !== "blob" || !Number.isSafeInteger(size) || output2[end + 1 + size] !== 10) fail6(t2("commit.stagedUnreadable", { path: path3 }));
26778
27149
  const staged2 = output2.subarray(end + 1, end + 1 + size);
26779
- const file2 = await readFile11(join12(root, path3));
27150
+ const file2 = await readFile11(join14(root, path3));
26780
27151
  if (!staged2.equals(file2) && !staged2.equals(Buffer.from(file2.toString("latin1").replace(/\r\n/g, "\n"), "latin1"))) fail6(t2("commit.filterChanged"));
26781
27152
  offset = end + size + 2;
26782
27153
  }
@@ -26784,7 +27155,7 @@ async function specCommit(cwd, input2, dryRun) {
26784
27155
  const again = await hashes([...locked.keys()]);
26785
27156
  for (const [path3, value] of locked) if (again.get(path3) !== value) fail6(t2("commit.fileChangedPreparing", { path: path3 }));
26786
27157
  if ((await readLockedPreviewState(root)).stamp !== published.stamp) fail6(t2("commit.specsChangedPreparing"));
26787
- 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"));
26788
27159
  if (!same2(c.base, await c.reader.baseline())) fail6(t2("commit.headChanged"));
26789
27160
  const tree = (await git(["write-tree"], temporary)).toString("utf8").trim();
26790
27161
  commitStarted = true;
@@ -26826,7 +27197,7 @@ async function specCommit(cwd, input2, dryRun) {
26826
27197
  await unlink6(temporary).catch(() => {
26827
27198
  });
26828
27199
  if (owned) {
26829
- await unlink6(join12(busy, "recovery.json")).catch(() => {
27200
+ await unlink6(join14(busy, "recovery.json")).catch(() => {
26830
27201
  });
26831
27202
  await rmdir3(busy);
26832
27203
  }
@@ -26843,7 +27214,7 @@ async function execute(action, options, controls) {
26843
27214
  try {
26844
27215
  const guard = specPreviewReader(process.cwd());
26845
27216
  const { root, gitDir, objectFormat } = await guard.location();
26846
- 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 }));
26847
27218
  if (await guard.hasUnmerged()) throw new InitError("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
26848
27219
  const raw = await readConfigFile(root);
26849
27220
  if (raw === void 0) throw new SpecPreviewError(t2("preview.notInitialized"));
@@ -26875,7 +27246,7 @@ async function execute(action, options, controls) {
26875
27246
  if (action === "read") {
26876
27247
  const commit = await reader.resolve(options.ref ?? "HEAD");
26877
27248
  const bundle = await reader.readBundle(commit);
26878
- 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");
26879
27250
  } else {
26880
27251
  const [from, to] = await Promise.all([reader.resolve(options.from), reader.resolve(options.to)]);
26881
27252
  const [before, after] = await Promise.all([reader.readBundle(from), reader.readBundle(to)]);
@@ -26888,8 +27259,8 @@ async function execute(action, options, controls) {
26888
27259
  }
26889
27260
  }
26890
27261
  async function readInputFile(path3) {
26891
- const source = await stat2(path3);
26892
- 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"));
26893
27264
  return readFile12(path3);
26894
27265
  }
26895
27266
  async function readStdin(stream) {
@@ -26904,29 +27275,29 @@ async function readStdin(stream) {
26904
27275
  }
26905
27276
  function narrowWorking(working, options, inputs) {
26906
27277
  if (options.stamp) return { stamp: working.stamp, inputs };
26907
- let { specs, documents, warnings } = working;
27278
+ let { specs, wiki, warnings } = working;
26908
27279
  if (options.feature !== void 0) {
26909
27280
  specs = specs.filter((s) => s.path === ".gitifact/spec/" + options.feature + "/requirements.md");
26910
27281
  if (!specs.length) throw new SpecPreviewError(t2("preview.unknownFeature", { feature: options.feature }));
26911
- documents = [];
26912
- 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);
26913
27284
  }
26914
- if (!options.ids) return { stamp: working.stamp, inputs, specs, documents, warnings };
27285
+ if (!options.ids) return { stamp: working.stamp, inputs, specs, wiki, warnings };
26915
27286
  return {
26916
27287
  stamp: working.stamp,
26917
27288
  inputs,
26918
27289
  warnings,
26919
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 })) })),
26920
- 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 })) }
26921
27292
  };
26922
27293
  }
26923
27294
 
26924
27295
  // src/commands/migrate.ts
26925
- import { lstat as lstat8, readdir as readdir5, readFile as readFile13, rename as rename5, writeFile as writeFile5, unlink as unlink7 } from "node:fs/promises";
26926
- 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";
26927
27298
  import { randomBytes as randomBytes3 } from "node:crypto";
26928
27299
  var operations2 = ["MERGE_HEAD", "CHERRY_PICK_HEAD", "REVERT_HEAD", "rebase-merge", "rebase-apply", "sequencer", "BISECT_START", "index.lock"];
26929
- var info4 = (path3) => lstat8(path3).catch((e) => {
27300
+ var info6 = (path3) => lstat10(path3).catch((e) => {
26930
27301
  if (e.code === "ENOENT") return void 0;
26931
27302
  throw e;
26932
27303
  });
@@ -26937,40 +27308,40 @@ var fail7 = (code, message) => {
26937
27308
  async function migrateStore(cwd, dryRun = false) {
26938
27309
  const reader = specPreviewReader(cwd);
26939
27310
  const { root, gitDir } = await reader.location();
26940
- 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 }));
26941
27312
  if (await reader.hasUnmerged()) fail7("GIT_OPERATION_IN_PROGRESS", t2("git.resolveConflicts"));
26942
- 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) }));
26943
- const from = join14(root, ".tryce");
26944
- const to = join14(root, ".gitifact");
26945
- const fromInfo = await info4(from);
26946
- 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"));
26947
27318
  if (!fromInfo) return fail7("NOTHING_TO_MIGRATE", t2("migrate.nothingToMigrate"));
26948
27319
  if (!fromInfo.isDirectory() || fromInfo.isSymbolicLink()) fail7("PATH_CONFLICT", t2("migrate.notDirectory"));
26949
- const configInfo = await info4(join14(from, "config.json"));
27320
+ const configInfo = await info6(join16(from, "config.json"));
26950
27321
  if (!configInfo?.isFile() || configInfo.isSymbolicLink()) fail7("NOT_INITIALIZED", t2("migrate.noConfig"));
26951
- const config2 = parseManagedConfig(decode4(await readFile13(join14(from, "config.json"))));
27322
+ const config2 = parseManagedConfig(decode4(await readFile13(join16(from, "config.json"))));
26952
27323
  if (!("schemaVersion" in config2)) fail7("UNSUPPORTED_FORMAT", t2("migrate.legacyJson"));
26953
- const top = (await readdir5(from)).sort();
27324
+ const top = (await readdir6(from)).sort();
26954
27325
  if (top.some((name) => name !== "config.json" && name !== "spec")) fail7("UNSUPPORTED_FORMAT", t2("migrate.extraEntries"));
26955
27326
  const files = /* @__PURE__ */ new Map();
26956
27327
  const rewrites = [];
26957
27328
  const legacyRecords = [];
26958
27329
  const marker2 = /^<!-- tryce-(spec|req|design|ref): /;
26959
27330
  if (top.includes("spec")) {
26960
- for (const feature2 of (await readdir5(join14(from, "spec"))).sort()) {
26961
- const dir = join14(from, "spec", feature2);
26962
- 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);
26963
27334
  if (!stat3?.isDirectory() || stat3.isSymbolicLink()) fail7("UNSUPPORTED_FORMAT", t2("migrate.notSpecFolder", { feature: feature2 }));
26964
- for (const name of (await readdir5(dir)).sort()) {
27335
+ for (const name of (await readdir6(dir)).sort()) {
26965
27336
  if (!["requirements.md", "design.md", "history.jsonl", "tryce.json"].includes(name)) fail7("UNSUPPORTED_FORMAT", t2("migrate.unsupportedFile", { feature: feature2, name }));
26966
- const fileStat = await info4(join14(dir, name));
27337
+ const fileStat = await info6(join16(dir, name));
26967
27338
  if (!fileStat?.isFile() || fileStat.isSymbolicLink() || fileStat.nlink !== 1 || fileStat.size > 1024 * 1024) fail7("UNSUPPORTED_FORMAT", t2("migrate.fileTooLarge", { name }));
26968
27339
  const newPath = `.gitifact/spec/${feature2}/${name}`;
26969
27340
  if (name === "tryce.json") {
26970
27341
  legacyRecords.push(newPath);
26971
27342
  continue;
26972
27343
  }
26973
- const text2 = decode4(await readFile13(join14(dir, name)));
27344
+ const text2 = decode4(await readFile13(join16(dir, name)));
26974
27345
  if (name === "history.jsonl") {
26975
27346
  files.set(newPath, text2);
26976
27347
  continue;
@@ -27015,9 +27386,9 @@ async function migrateStore(cwd, dryRun = false) {
27015
27386
  if (dryRun) return result("planned");
27016
27387
  await rename5(from, to);
27017
27388
  for (const r of rewrites) {
27018
- const full = join14(root, r.path);
27019
- const temp = join14(root, ".gitifact", "spec", ".gitifact-migrate-" + randomBytes3(8).toString("hex"));
27020
- 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" });
27021
27392
  try {
27022
27393
  await rename5(temp, full);
27023
27394
  } finally {
@@ -27039,16 +27410,16 @@ async function runMigrate(options) {
27039
27410
  }
27040
27411
 
27041
27412
  // src/main.ts
27042
- var program2 = new Command().name("gitifact").description(t2("help.program")).version("0.4.3").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());
27043
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) => {
27044
27415
  await runStatus(options.format);
27045
27416
  });
27046
- 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.3"));
27047
- 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.3"));
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"));
27048
27419
  program2.command("docs").description(t2("help.docs")).argument("[topic]", t2("help.docsTopic")).allowExcessArguments(false).action(async (topic) => {
27049
27420
  await runDocs(topic);
27050
27421
  });
27051
- program2.command("update").description(t2("help.update")).allowExcessArguments(false).addOption(new Option("--format <format>", t2("help.format")).choices(["json", "text"]).default("json")).action((options) => runUpdate(options, "0.4.3"));
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"));
27052
27423
  program2.command("migrate").description(t2("help.migrate")).allowExcessArguments(false).option("--dry-run", t2("help.migrateDryRun")).action(runMigrate);
27053
27424
  var spec = program2.command("spec").description(t2("help.spec"));
27054
27425
  var replaced = t2("help.deprecated");