@stacksjs/defaults 0.75.360 → 0.75.362

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 (209) hide show
  1. package/ai/skills/stacks-ai/SKILL.md +22 -0
  2. package/ai/skills/stacks-native/CATALOG.md +4 -4
  3. package/ai/skills/stacks-payments/SKILL.md +28 -0
  4. package/ai/skills/stacks-technical-diagrams/LICENSE +202 -22
  5. package/ai/skills/stacks-technical-diagrams/LICENSE-MIT +22 -0
  6. package/ai/skills/stacks-technical-diagrams/NOTICE +26 -0
  7. package/ai/skills/stacks-technical-diagrams/SKILL.md +106 -266
  8. package/ai/skills/stacks-technical-diagrams/THIRD_PARTY_NOTICES.md +11 -2
  9. package/ai/skills/stacks-technical-diagrams/UPSTREAM.json +12 -0
  10. package/ai/skills/stacks-technical-diagrams/agents/openai.yaml +2 -2
  11. package/ai/skills/stacks-technical-diagrams/assets/template.html +3655 -3798
  12. package/ai/skills/stacks-technical-diagrams/bin/archify.mjs +7290 -0
  13. package/ai/skills/stacks-technical-diagrams/bin/delivery-update.mjs +7 -0
  14. package/ai/skills/stacks-technical-diagrams/bin/diagrams +1 -0
  15. package/ai/skills/stacks-technical-diagrams/bin/finalize.mjs +1056 -0
  16. package/ai/skills/stacks-technical-diagrams/bin/open-artifact.mjs +72 -12
  17. package/ai/skills/stacks-technical-diagrams/bin/preview.mjs +348 -31
  18. package/ai/skills/stacks-technical-diagrams/bin/recover-output.mjs +41 -0
  19. package/ai/skills/stacks-technical-diagrams/bin/technical-diagrams.mjs +3 -2097
  20. package/ai/skills/stacks-technical-diagrams/bin/visual-check.mjs +2142 -157
  21. package/ai/skills/stacks-technical-diagrams/brand-marks/README.md +3 -3
  22. package/ai/skills/stacks-technical-diagrams/delta/architecture-delta.mjs +130 -33
  23. package/ai/skills/stacks-technical-diagrams/examples/agent-run.lifecycle.json +25 -30
  24. package/ai/skills/stacks-technical-diagrams/examples/agent-tool-call.workflow.json +0 -20
  25. package/ai/skills/stacks-technical-diagrams/examples/archify-dev-activity.timeline.json +176 -0
  26. package/ai/skills/stacks-technical-diagrams/examples/archify-repository.tree.json +197 -0
  27. package/ai/skills/stacks-technical-diagrams/examples/async-job-roundtrip.sequence.json +1 -6
  28. package/ai/skills/stacks-technical-diagrams/examples/brand-aware-delivery.architecture.json +3 -16
  29. package/ai/skills/stacks-technical-diagrams/examples/cache-miss-request.sequence.json +2 -7
  30. package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.base.architecture.json +2 -1
  31. package/ai/skills/stacks-technical-diagrams/examples/checkout-platform.head.architecture.json +2 -1
  32. package/ai/skills/stacks-technical-diagrams/examples/checkout-request.waterfall.json +20 -0
  33. package/ai/skills/stacks-technical-diagrams/examples/class-payment-processors-rendered.html +14987 -0
  34. package/ai/skills/stacks-technical-diagrams/examples/class-payments-rendered.html +14787 -0
  35. package/ai/skills/stacks-technical-diagrams/examples/dataflow-product-analytics.html +14880 -0
  36. package/ai/skills/stacks-technical-diagrams/examples/delivery.explainer.json +155 -0
  37. package/ai/skills/stacks-technical-diagrams/examples/deployment-release.lifecycle.json +57 -34
  38. package/ai/skills/stacks-technical-diagrams/examples/erd-orders-rendered.html +15112 -0
  39. package/ai/skills/stacks-technical-diagrams/examples/event-stream.dataflow.json +1 -6
  40. package/ai/skills/stacks-technical-diagrams/examples/example-rebuild.waterfall.json +212 -0
  41. package/ai/skills/stacks-technical-diagrams/examples/holiday-planning.workflow.json +143 -0
  42. package/ai/skills/stacks-technical-diagrams/examples/incident-response.workflow.json +1 -6
  43. package/ai/skills/stacks-technical-diagrams/examples/lifecycle-agent-run.html +14892 -0
  44. package/ai/skills/stacks-technical-diagrams/examples/locales/de.json +463 -0
  45. package/ai/skills/stacks-technical-diagrams/examples/locales/fr.json +463 -0
  46. package/ai/skills/stacks-technical-diagrams/examples/locales/fr.partial.json +22 -0
  47. package/ai/skills/stacks-technical-diagrams/examples/locales/it.json +463 -0
  48. package/ai/skills/stacks-technical-diagrams/examples/locales/ja.json +463 -0
  49. package/ai/skills/stacks-technical-diagrams/examples/locales/pt.json +463 -0
  50. package/ai/skills/stacks-technical-diagrams/examples/locales/ru.json +463 -0
  51. package/ai/skills/stacks-technical-diagrams/examples/orders.erd.json +337 -0
  52. package/ai/skills/stacks-technical-diagrams/examples/payment-incident.timeline.json +24 -0
  53. package/ai/skills/stacks-technical-diagrams/examples/payment-platform.tree.json +22 -0
  54. package/ai/skills/stacks-technical-diagrams/examples/payment-processors.class.json +177 -0
  55. package/ai/skills/stacks-technical-diagrams/examples/payments.class.json +85 -0
  56. package/ai/skills/stacks-technical-diagrams/examples/product-analytics.dataflow.json +1 -6
  57. package/ai/skills/stacks-technical-diagrams/examples/production-deployment.architecture.json +3 -23
  58. package/ai/skills/stacks-technical-diagrams/examples/release-delivery.workflow.json +1 -6
  59. package/ai/skills/stacks-technical-diagrams/examples/sequence-cache-miss-request.html +14895 -0
  60. package/ai/skills/stacks-technical-diagrams/examples/subscription-billing-rendered.html +15369 -0
  61. package/ai/skills/stacks-technical-diagrams/examples/subscription-billing.erd.json +548 -0
  62. package/ai/skills/stacks-technical-diagrams/examples/timeline-archify-dev-activity-rendered.html +15002 -0
  63. package/ai/skills/stacks-technical-diagrams/examples/timeline-payment-incident-rendered.html +14772 -0
  64. package/ai/skills/stacks-technical-diagrams/examples/tree-archify-repository-rendered.html +15071 -0
  65. package/ai/skills/stacks-technical-diagrams/examples/tree-payment-platform-rendered.html +14810 -0
  66. package/ai/skills/stacks-technical-diagrams/examples/waterfall-checkout-request-rendered.html +14754 -0
  67. package/ai/skills/stacks-technical-diagrams/examples/waterfall-example-rebuild-rendered.html +14940 -0
  68. package/ai/skills/stacks-technical-diagrams/examples/web-app-rendered.html +14889 -0
  69. package/ai/skills/stacks-technical-diagrams/examples/web-app.architecture.json +1 -6
  70. package/ai/skills/stacks-technical-diagrams/examples/workflow-agent-tool-call-rendered.html +14907 -0
  71. package/ai/skills/stacks-technical-diagrams/explainer/browser-check.mjs +208 -0
  72. package/ai/skills/stacks-technical-diagrams/explainer/charts.mjs +70 -0
  73. package/ai/skills/stacks-technical-diagrams/explainer/check.mjs +168 -0
  74. package/ai/skills/stacks-technical-diagrams/explainer/diagrams.mjs +518 -0
  75. package/ai/skills/stacks-technical-diagrams/explainer/diff.mjs +124 -0
  76. package/ai/skills/stacks-technical-diagrams/explainer/inline.mjs +155 -0
  77. package/ai/skills/stacks-technical-diagrams/explainer/layout.mjs +35 -0
  78. package/ai/skills/stacks-technical-diagrams/explainer/page-bundle.mjs +16 -0
  79. package/ai/skills/stacks-technical-diagrams/explainer/parse.mjs +300 -0
  80. package/ai/skills/stacks-technical-diagrams/explainer/patch.mjs +117 -0
  81. package/ai/skills/stacks-technical-diagrams/explainer/prepare.mjs +131 -0
  82. package/ai/skills/stacks-technical-diagrams/explainer/reader.css +472 -0
  83. package/ai/skills/stacks-technical-diagrams/explainer/reader.js +667 -0
  84. package/ai/skills/stacks-technical-diagrams/explainer/render.mjs +224 -0
  85. package/ai/skills/stacks-technical-diagrams/explainer/revision.mjs +20 -0
  86. package/ai/skills/stacks-technical-diagrams/explainer/sources.mjs +318 -0
  87. package/ai/skills/stacks-technical-diagrams/explainer/validate.mjs +138 -0
  88. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/LICENSE +40 -0
  89. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/README.md +5 -0
  90. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/3DKXF73K.js +4 -0
  91. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/42UTT654.js +3 -0
  92. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/DKTLEL5E.js +1 -0
  93. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EEIX5ZYH.js +2 -0
  94. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/EJ5KOXYD.js +25 -0
  95. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/GUJTEFZK.js +1 -0
  96. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/IZOKQ2GT.js +4 -0
  97. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/L4LY553T.js +3 -0
  98. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/MSKIJHB2.js +2 -0
  99. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/RVARI57O.js +1 -0
  100. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UKSIWDDN.js +2 -0
  101. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/UPTXNAXN.js +2 -0
  102. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/XTDUIGUI.js +9 -0
  103. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/chunks/YAYTP6L5.js +9 -0
  104. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/core.js +1 -0
  105. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/pie.js +1 -0
  106. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/quadrantChart.js +1 -0
  107. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/radar.js +1 -0
  108. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/sankey.js +1 -0
  109. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/venn.js +1 -0
  110. package/ai/skills/stacks-technical-diagrams/explainer/vendor/pele/diagrams/xychart.js +1 -0
  111. package/ai/skills/stacks-technical-diagrams/explainer/writing.mjs +139 -0
  112. package/ai/skills/stacks-technical-diagrams/locales/README.md +40 -0
  113. package/ai/skills/stacks-technical-diagrams/locales/ar.json +488 -0
  114. package/ai/skills/stacks-technical-diagrams/locales/en.json +488 -0
  115. package/ai/skills/stacks-technical-diagrams/locales/es.json +488 -0
  116. package/ai/skills/stacks-technical-diagrams/locales/ko.json +488 -0
  117. package/ai/skills/stacks-technical-diagrams/locales/manifest.json +10 -0
  118. package/ai/skills/stacks-technical-diagrams/locales/zh-CN.json +488 -0
  119. package/ai/skills/stacks-technical-diagrams/locales/zh-TW.json +488 -0
  120. package/ai/skills/stacks-technical-diagrams/migrations/workflow-v2.mjs +1 -0
  121. package/ai/skills/stacks-technical-diagrams/recipes/scenarios.mjs +115 -51
  122. package/ai/skills/stacks-technical-diagrams/references/architecture-layout-repair.md +18 -0
  123. package/ai/skills/stacks-technical-diagrams/references/authoring-contract.md +453 -51
  124. package/ai/skills/stacks-technical-diagrams/references/authoring-defaults.md +57 -0
  125. package/ai/skills/stacks-technical-diagrams/references/brand-marks.md +57 -3
  126. package/ai/skills/stacks-technical-diagrams/references/delivery-contract.md +603 -58
  127. package/ai/skills/stacks-technical-diagrams/references/explainer-authoring.md +274 -0
  128. package/ai/skills/stacks-technical-diagrams/references/repository-authoring.md +126 -0
  129. package/ai/skills/stacks-technical-diagrams/references/update-awareness.md +13 -0
  130. package/ai/skills/stacks-technical-diagrams/references/viewer-runtime.md +18 -11
  131. package/ai/skills/stacks-technical-diagrams/renderers/architecture/grid.mjs +1 -1
  132. package/ai/skills/stacks-technical-diagrams/renderers/architecture/labels.mjs +2 -0
  133. package/ai/skills/stacks-technical-diagrams/renderers/architecture/render-architecture.mjs +550 -424
  134. package/ai/skills/stacks-technical-diagrams/renderers/architecture/routing.mjs +1469 -0
  135. package/ai/skills/stacks-technical-diagrams/renderers/class/README.md +129 -0
  136. package/ai/skills/stacks-technical-diagrams/renderers/class/render-class.mjs +1095 -0
  137. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/README.md +89 -17
  138. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/dataflow-compiler.mjs +1605 -0
  139. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/joint-label-routing.mjs +165 -0
  140. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/label-capacity.mjs +81 -0
  141. package/ai/skills/stacks-technical-diagrams/renderers/dataflow/render-dataflow.mjs +14 -474
  142. package/ai/skills/stacks-technical-diagrams/renderers/erd/README.md +246 -0
  143. package/ai/skills/stacks-technical-diagrams/renderers/erd/grid.mjs +150 -0
  144. package/ai/skills/stacks-technical-diagrams/renderers/erd/render-erd.mjs +1588 -0
  145. package/ai/skills/stacks-technical-diagrams/renderers/explainer/render-explainer.mjs +42 -0
  146. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/README.md +83 -94
  147. package/ai/skills/stacks-technical-diagrams/renderers/lifecycle/render-lifecycle.mjs +990 -417
  148. package/ai/skills/stacks-technical-diagrams/renderers/sequence/README.md +49 -17
  149. package/ai/skills/stacks-technical-diagrams/renderers/sequence/render-sequence.mjs +735 -94
  150. package/ai/skills/stacks-technical-diagrams/renderers/shared/atomic-output.mjs +1982 -0
  151. package/ai/skills/stacks-technical-diagrams/renderers/shared/automatic-labels.mjs +290 -0
  152. package/ai/skills/stacks-technical-diagrams/renderers/shared/brand-marks.mjs +169 -26
  153. package/ai/skills/stacks-technical-diagrams/renderers/shared/cli.mjs +266 -54
  154. package/ai/skills/stacks-technical-diagrams/renderers/shared/desktop-readability.mjs +106 -0
  155. package/ai/skills/stacks-technical-diagrams/renderers/shared/diagnostics.mjs +0 -0
  156. package/ai/skills/stacks-technical-diagrams/renderers/shared/generated-validators.mjs +268 -2
  157. package/ai/skills/stacks-technical-diagrams/renderers/shared/geometry.mjs +774 -124
  158. package/ai/skills/stacks-technical-diagrams/renderers/shared/i18n.mjs +289 -556
  159. package/ai/skills/stacks-technical-diagrams/renderers/shared/layout-report.mjs +18 -1
  160. package/ai/skills/stacks-technical-diagrams/renderers/shared/legend.mjs +16 -6
  161. package/ai/skills/stacks-technical-diagrams/renderers/shared/node-card-style.mjs +32 -0
  162. package/ai/skills/stacks-technical-diagrams/renderers/shared/output-path.mjs +329 -229
  163. package/ai/skills/stacks-technical-diagrams/renderers/shared/owned-temp-directory.mjs +82 -0
  164. package/ai/skills/stacks-technical-diagrams/renderers/shared/path-semantics.mjs +1017 -0
  165. package/ai/skills/stacks-technical-diagrams/renderers/shared/portable-path.mjs +408 -0
  166. package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-evidence.mjs +235 -79
  167. package/ai/skills/stacks-technical-diagrams/renderers/shared/repository-location.mjs +17 -4
  168. package/ai/skills/stacks-technical-diagrams/renderers/shared/route-quality.mjs +670 -0
  169. package/ai/skills/stacks-technical-diagrams/renderers/shared/sidecar-path.mjs +38 -0
  170. package/ai/skills/stacks-technical-diagrams/renderers/shared/software-notices.mjs +6 -0
  171. package/ai/skills/stacks-technical-diagrams/renderers/shared/spatial-grid.mjs +66 -0
  172. package/ai/skills/stacks-technical-diagrams/renderers/shared/svg-path-analysis.mjs +135 -0
  173. package/ai/skills/stacks-technical-diagrams/renderers/shared/text-fit.mjs +59 -3
  174. package/ai/skills/stacks-technical-diagrams/renderers/shared/utils.mjs +63 -35
  175. package/ai/skills/stacks-technical-diagrams/renderers/shared/validator.mjs +2 -5
  176. package/ai/skills/stacks-technical-diagrams/renderers/timeline/README.md +76 -0
  177. package/ai/skills/stacks-technical-diagrams/renderers/timeline/render-timeline.mjs +576 -0
  178. package/ai/skills/stacks-technical-diagrams/renderers/tree/README.md +96 -0
  179. package/ai/skills/stacks-technical-diagrams/renderers/tree/render-tree.mjs +573 -0
  180. package/ai/skills/stacks-technical-diagrams/renderers/waterfall/README.md +73 -0
  181. package/ai/skills/stacks-technical-diagrams/renderers/waterfall/render-waterfall.mjs +413 -0
  182. package/ai/skills/stacks-technical-diagrams/renderers/workflow/README.md +137 -23
  183. package/ai/skills/stacks-technical-diagrams/renderers/workflow/render-workflow.mjs +18 -6
  184. package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-compiler.mjs +2230 -710
  185. package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-migration-geometry.mjs +17 -0
  186. package/ai/skills/stacks-technical-diagrams/renderers/workflow/workflow-placement-repair.mjs +94 -0
  187. package/ai/skills/stacks-technical-diagrams/schemas/README.md +87 -40
  188. package/ai/skills/stacks-technical-diagrams/schemas/architecture.schema.json +7 -33
  189. package/ai/skills/stacks-technical-diagrams/schemas/class.schema.json +345 -0
  190. package/ai/skills/stacks-technical-diagrams/schemas/common.schema.json +68 -2
  191. package/ai/skills/stacks-technical-diagrams/schemas/dataflow.schema.json +14 -3
  192. package/ai/skills/stacks-technical-diagrams/schemas/erd.schema.json +381 -0
  193. package/ai/skills/stacks-technical-diagrams/schemas/explainer.schema.json +927 -0
  194. package/ai/skills/stacks-technical-diagrams/schemas/lifecycle.schema.json +23 -110
  195. package/ai/skills/stacks-technical-diagrams/schemas/sequence.schema.json +15 -4
  196. package/ai/skills/stacks-technical-diagrams/schemas/timeline.schema.json +160 -0
  197. package/ai/skills/stacks-technical-diagrams/schemas/tree.schema.json +141 -0
  198. package/ai/skills/stacks-technical-diagrams/schemas/waterfall.schema.json +147 -0
  199. package/ai/skills/stacks-technical-diagrams/schemas/workflow.schema.json +14 -3
  200. package/ai/skills/stacks-technical-diagrams/scripts/check-render-output.mjs +876 -243
  201. package/ai/skills/stacks-technical-diagrams/scripts/check-update.mjs +1717 -0
  202. package/ai/skills/stacks-technical-diagrams/scripts/delivery-update-child.mjs +26 -0
  203. package/ai/skills/stacks-technical-diagrams/scripts/generate-brand-marks.mjs +141 -0
  204. package/ai/skills/stacks-technical-diagrams/scripts/generate-validators.mjs +105 -0
  205. package/ai/skills/stacks-technical-diagrams/scripts/render-examples.mjs +11 -1
  206. package/ai/skills/stacks-technical-diagrams/scripts/update-contract.mjs +187 -0
  207. package/ai/skills/stacks-technical-diagrams/skill-release.json +10 -0
  208. package/ide/vscode/package.json +1 -1
  209. package/package.json +2 -2
@@ -0,0 +1,1017 @@
1
+ import crypto from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const MAX_SYMLINK_DEPTH = 64;
6
+ const RECENT_PROBE_NAME_LIMIT = 128;
7
+ const recentProbeNames = new Set();
8
+ const WINDOWS_SMB_SHARE_FORBIDDEN = /[\u0000-\u001F"\/\\[\]:|<>+=;,?*]/u;
9
+
10
+ export function isWindowsIpcShare(share) {
11
+ return /^(?:IPC\$|pipe|mailslot)$/iu.test(share);
12
+ }
13
+
14
+ export function isValidWindowsSmbShareName(share) {
15
+ return typeof share === 'string'
16
+ && share.length > 0
17
+ && share.length <= 80
18
+ && !WINDOWS_SMB_SHARE_FORBIDDEN.test(share)
19
+ && !/[ .]$/u.test(share);
20
+ }
21
+
22
+ function result(status, code, details = {}) {
23
+ return {
24
+ status,
25
+ reason: {
26
+ code,
27
+ ...details,
28
+ },
29
+ };
30
+ }
31
+
32
+ function unknown(code, details = {}) {
33
+ return result('unknown', code, details);
34
+ }
35
+
36
+ function systemFailure(code, error, details = {}) {
37
+ return unknown(code, {
38
+ ...details,
39
+ ...(typeof error?.code === 'string' ? { systemCode: error.code } : {}),
40
+ ...(typeof error?.recoveryPath === 'string' ? { recoveryPath: error.recoveryPath } : {}),
41
+ ...(typeof error?.restoredAtTarget === 'boolean'
42
+ ? { restoredAtTarget: error.restoredAtTarget }
43
+ : {}),
44
+ });
45
+ }
46
+
47
+ function hostPath() {
48
+ return process.platform === 'win32' ? path.win32 : path.posix;
49
+ }
50
+
51
+ function absolutePath(input, side, basePath = process.cwd()) {
52
+ if (typeof input !== 'string' || input.length === 0) {
53
+ return {
54
+ ok: false,
55
+ failure: unknown('invalid-path', { side }),
56
+ };
57
+ }
58
+
59
+ try {
60
+ const pathApi = hostPath();
61
+ if (pathApi.isAbsolute(input)) return { ok: true, path: input };
62
+ if (pathApi.parse(input).root) {
63
+ return {
64
+ ok: false,
65
+ failure: unknown('drive-relative-path', { side }),
66
+ };
67
+ }
68
+ const separator = basePath.endsWith(pathApi.sep) ? '' : pathApi.sep;
69
+ return { ok: true, path: `${basePath}${separator}${input}` };
70
+ } catch (error) {
71
+ return {
72
+ ok: false,
73
+ failure: systemFailure('invalid-path', error, { side }),
74
+ };
75
+ }
76
+ }
77
+
78
+ function rootFailure(code, side) {
79
+ return {
80
+ ok: false,
81
+ failure: unknown(code, { side }),
82
+ };
83
+ }
84
+
85
+ function splitWindowsExtendedTail(tail, side, { internalRelativeLinkTarget = false } = {}) {
86
+ const authoredSegments = tail.split('\\');
87
+ // A single final separator is harmless, while an internal empty segment
88
+ // would require normalizing a raw extended path before it reaches Windows.
89
+ if (!internalRelativeLinkTarget
90
+ && authoredSegments.slice(0, -1).some((component) => component.length === 0)) {
91
+ return rootFailure('windows-root-invalid', side);
92
+ }
93
+ const segments = authoredSegments.filter(Boolean);
94
+ if (!internalRelativeLinkTarget
95
+ && segments.some((component) => component === '.' || component === '..')) {
96
+ return rootFailure('windows-root-invalid', side);
97
+ }
98
+ return { ok: true, segments };
99
+ }
100
+
101
+ function splitWindowsAbsolute(absolute, side, { internalRelativeLinkTarget = false } = {}) {
102
+ if (/^[\\/]{2}\.[\\/]/u.test(absolute)) {
103
+ return rootFailure('windows-namespace-unsupported', side);
104
+ }
105
+ if (/^[\\/]{2}\?[\\/]/u.test(absolute) && !absolute.startsWith('\\\\?\\')) {
106
+ return rootFailure('windows-namespace-unsupported', side);
107
+ }
108
+
109
+ if (absolute.startsWith('\\\\?\\')) {
110
+ if (absolute.includes('/')) return rootFailure('windows-root-invalid', side);
111
+ if (
112
+ /^\\\\\?\\(?:GLOBALROOT|Device)(?:\\|$)/iu.test(absolute)
113
+ || /^\\\\\?\\Volume\{/iu.test(absolute)
114
+ ) {
115
+ return rootFailure('windows-namespace-unsupported', side);
116
+ }
117
+
118
+ const extendedUnc = absolute.match(/^(\\\\\?\\UNC\\([^\\]+)\\([^\\]+))(?:\\(.*))?$/iu);
119
+ if (extendedUnc) {
120
+ if (isWindowsIpcShare(extendedUnc[3])) {
121
+ return rootFailure('windows-namespace-unsupported', side);
122
+ }
123
+ if (!isValidWindowsSmbShareName(extendedUnc[3])) {
124
+ return rootFailure('windows-root-invalid', side);
125
+ }
126
+ if ([extendedUnc[2], extendedUnc[3]].some((component) => component === '.' || component === '..')) {
127
+ return rootFailure('windows-root-invalid', side);
128
+ }
129
+ const tail = splitWindowsExtendedTail(
130
+ extendedUnc[4] || '',
131
+ side,
132
+ { internalRelativeLinkTarget },
133
+ );
134
+ if (!tail.ok) return tail;
135
+ return {
136
+ ok: true,
137
+ root: `${extendedUnc[1]}\\`,
138
+ segments: tail.segments,
139
+ };
140
+ }
141
+
142
+ const extendedDrive = absolute.match(/^(\\\\\?\\[A-Za-z]:\\)(.*)$/u);
143
+ if (extendedDrive) {
144
+ const tail = splitWindowsExtendedTail(
145
+ extendedDrive[2],
146
+ side,
147
+ { internalRelativeLinkTarget },
148
+ );
149
+ if (!tail.ok) return tail;
150
+ return {
151
+ ok: true,
152
+ root: extendedDrive[1],
153
+ segments: tail.segments,
154
+ };
155
+ }
156
+ return rootFailure('windows-root-invalid', side);
157
+ }
158
+
159
+ const unc = absolute.match(/^[\\/]{2}([^\\/]+)[\\/]([^\\/]+)(?:[\\/](.*))?$/u);
160
+ if (unc) {
161
+ if (isWindowsIpcShare(unc[2])) {
162
+ return rootFailure('windows-namespace-unsupported', side);
163
+ }
164
+ if (!isValidWindowsSmbShareName(unc[2])) {
165
+ return rootFailure('windows-root-invalid', side);
166
+ }
167
+ if ([unc[1], unc[2]].some((component) => component === '.' || component === '..')) {
168
+ return rootFailure('windows-root-invalid', side);
169
+ }
170
+ return {
171
+ ok: true,
172
+ root: `\\\\${unc[1]}\\${unc[2]}\\`,
173
+ segments: (unc[3] || '').split(/[\\/]+/u).filter(Boolean),
174
+ };
175
+ }
176
+
177
+ const drive = absolute.match(/^([A-Za-z]:)[\\/](.*)$/u);
178
+ if (drive) {
179
+ return {
180
+ ok: true,
181
+ root: `${drive[1]}\\`,
182
+ segments: drive[2].split(/[\\/]+/u).filter(Boolean),
183
+ };
184
+ }
185
+ return rootFailure('windows-root-invalid', side);
186
+ }
187
+
188
+ function splitAbsolute(absolute, side, options) {
189
+ if (process.platform === 'win32') return splitWindowsAbsolute(absolute, side, options);
190
+ const root = path.posix.parse(absolute).root;
191
+ return {
192
+ ok: true,
193
+ root,
194
+ segments: absolute.slice(root.length).split(/\/+/u).filter(Boolean),
195
+ };
196
+ }
197
+
198
+ function statBigInt(targetPath) {
199
+ return fs.statSync(targetPath, { bigint: true });
200
+ }
201
+
202
+ function ordinaryWindowsRoot(extendedRoot) {
203
+ const drive = extendedRoot.match(/^\\\\\?\\([A-Za-z]:\\)$/u);
204
+ if (drive) return drive[1];
205
+
206
+ const unc = extendedRoot.match(/^\\\\\?\\UNC\\([^\\]+)\\([^\\]+)\\$/iu);
207
+ return unc ? `\\\\${unc[1]}\\${unc[2]}\\` : null;
208
+ }
209
+
210
+ function hasStableIdentity(stat) {
211
+ return typeof stat?.dev === 'bigint'
212
+ && typeof stat?.ino === 'bigint'
213
+ && stat.ino !== 0n;
214
+ }
215
+
216
+ function compareKnownEntries(left, right, operation) {
217
+ if (hasStableIdentity(left.stat) && hasStableIdentity(right.stat)) {
218
+ const matches = left.stat.dev === right.stat.dev && left.stat.ino === right.stat.ino;
219
+ return result(matches ? 'match' : 'different', matches ? 'file-identity-match' : 'file-identity-different', {
220
+ operation,
221
+ method: 'bigint-stat',
222
+ });
223
+ }
224
+
225
+ // path-contract-allow: lexical-capability -- Canonical paths are compared inside the identity primitive.
226
+ if (left.realPath === right.realPath) {
227
+ return result('match', 'native-realpath-match', {
228
+ operation,
229
+ method: 'native-realpath',
230
+ });
231
+ }
232
+
233
+ return unknown('file-identity-unavailable', {
234
+ operation,
235
+ method: 'bigint-stat-and-native-realpath',
236
+ });
237
+ }
238
+
239
+ function rememberProbeName(name) {
240
+ recentProbeNames.add(name);
241
+ if (recentProbeNames.size > RECENT_PROBE_NAME_LIMIT) {
242
+ recentProbeNames.delete(recentProbeNames.values().next().value);
243
+ }
244
+ }
245
+
246
+ function probeInterference(message, code = 'ARCHIFY_PROBE_IDENTITY_CHANGED', details = {}) {
247
+ const error = new Error(message);
248
+ error.code = code;
249
+ Object.assign(error, details);
250
+ return error;
251
+ }
252
+
253
+ function sameStableIdentity(left, right) {
254
+ return hasStableIdentity(left)
255
+ && hasStableIdentity(right)
256
+ && left.dev === right.dev
257
+ && left.ino === right.ino;
258
+ }
259
+
260
+ function preserveQuarantinedReplacement(quarantinePath, targetPath) {
261
+ try {
262
+ fs.linkSync(quarantinePath, targetPath);
263
+ return { restoredAtTarget: true };
264
+ } catch {
265
+ // Preserve the quarantined entry when the original name was concurrently
266
+ // occupied or the host cannot create a hard link. Never overwrite either.
267
+ return { restoredAtTarget: false };
268
+ }
269
+ }
270
+
271
+ function removeOwnedProbe(targetPath, expectedStat) {
272
+ let currentStat;
273
+ try {
274
+ currentStat = fs.lstatSync(targetPath, { bigint: true });
275
+ } catch (error) {
276
+ if (error?.code === 'ENOENT') {
277
+ return probeInterference('filesystem probe disappeared before cleanup', 'ARCHIFY_PROBE_MISSING');
278
+ }
279
+ return error;
280
+ }
281
+ if (!sameStableIdentity(expectedStat, currentStat)) {
282
+ return probeInterference('filesystem probe identity changed before cleanup');
283
+ }
284
+
285
+ const quarantineName = `.archify-path-semantics-cleanup-${process.pid}-${crypto.randomBytes(10).toString('hex')}`;
286
+ const quarantinePath = path.join(path.dirname(targetPath), quarantineName);
287
+ rememberProbeName(quarantineName);
288
+ try {
289
+ // Move the checked name into a fresh, unpublished quarantine namespace.
290
+ // A replacement introduced after the check is moved rather than deleted.
291
+ fs.renameSync(targetPath, quarantinePath);
292
+ } catch (error) {
293
+ return error;
294
+ }
295
+
296
+ let quarantineStat;
297
+ try {
298
+ quarantineStat = fs.lstatSync(quarantinePath, { bigint: true });
299
+ } catch (error) {
300
+ error.recoveryPath = quarantinePath;
301
+ error.restoredAtTarget = false;
302
+ return error;
303
+ }
304
+ if (!sameStableIdentity(expectedStat, quarantineStat)) {
305
+ const recovery = preserveQuarantinedReplacement(quarantinePath, targetPath);
306
+ return probeInterference(
307
+ 'filesystem probe identity changed during cleanup',
308
+ 'ARCHIFY_PROBE_IDENTITY_CHANGED',
309
+ { recoveryPath: quarantinePath, ...recovery },
310
+ );
311
+ }
312
+
313
+ try {
314
+ // The quarantine spelling did not exist before the atomic rename and is
315
+ // never returned to callers. Legitimate target-path replacements therefore
316
+ // remain outside the only namespace we unlink.
317
+ fs.unlinkSync(quarantinePath);
318
+ return null;
319
+ } catch (error) {
320
+ error.recoveryPath = quarantinePath;
321
+ error.restoredAtTarget = false;
322
+ return error;
323
+ }
324
+ }
325
+
326
+ function resolvePlan({ root, segments }, side, depth) {
327
+ if (depth > MAX_SYMLINK_DEPTH) {
328
+ return {
329
+ ok: false,
330
+ failure: unknown('symlink-cycle', { side }),
331
+ };
332
+ }
333
+
334
+ let current;
335
+ let currentStat;
336
+ try {
337
+ current = fs.realpathSync.native(root);
338
+ } catch (error) {
339
+ // Node on Windows can report EISDIR while resolving a valid extended
340
+ // filesystem root (for example \\?\C:\). The namespace was already
341
+ // validated by splitWindowsAbsolute, so retain its exact spelling for
342
+ // traversal.
343
+ if (process.platform === 'win32'
344
+ && error?.code === 'EISDIR'
345
+ && ordinaryWindowsRoot(root)) {
346
+ current = root;
347
+ } else {
348
+ return {
349
+ ok: false,
350
+ failure: systemFailure('root-resolution-failed', error, { side }),
351
+ };
352
+ }
353
+ }
354
+
355
+ try {
356
+ currentStat = statBigInt(current);
357
+ } catch (error) {
358
+ // The same Node/Windows edge can occur during stat after realpath succeeds.
359
+ // A drive or share root is short enough for its ordinary spelling, while
360
+ // `current` remains namespaced so descendants keep long-path semantics.
361
+ const ordinaryRoot = process.platform === 'win32'
362
+ && error?.code === 'EISDIR'
363
+ ? ordinaryWindowsRoot(root)
364
+ : null;
365
+ if (ordinaryRoot && ordinaryRoot !== current) {
366
+ try {
367
+ currentStat = statBigInt(ordinaryRoot);
368
+ } catch (statError) {
369
+ return {
370
+ ok: false,
371
+ failure: systemFailure('root-resolution-failed', statError, { side }),
372
+ };
373
+ }
374
+ } else {
375
+ return {
376
+ ok: false,
377
+ failure: systemFailure('root-resolution-failed', error, { side }),
378
+ };
379
+ }
380
+ }
381
+
382
+ for (let index = 0; index < segments.length; index += 1) {
383
+ const segment = segments[index];
384
+ if (segment === '.') continue;
385
+ if (segment === '..') {
386
+ if (!currentStat.isDirectory()) {
387
+ return {
388
+ ok: false,
389
+ failure: unknown('ancestor-not-directory', { side, segmentIndex: index }),
390
+ };
391
+ }
392
+ try {
393
+ current = fs.realpathSync.native(hostPath().dirname(current));
394
+ currentStat = statBigInt(current);
395
+ } catch (error) {
396
+ return {
397
+ ok: false,
398
+ failure: systemFailure('path-resolution-failed', error, { side, segmentIndex: index }),
399
+ };
400
+ }
401
+ continue;
402
+ }
403
+
404
+ const candidate = hostPath().join(current, segment);
405
+ let candidateStat;
406
+ try {
407
+ candidateStat = fs.lstatSync(candidate, { bigint: true });
408
+ } catch (error) {
409
+ if (error?.code === 'ENOENT') {
410
+ if (!currentStat.isDirectory()) {
411
+ return {
412
+ ok: false,
413
+ failure: unknown('ancestor-not-directory', { side, segmentIndex: index }),
414
+ };
415
+ }
416
+ const unresolved = segments.slice(index).filter((item) => item !== '.');
417
+ if (unresolved.includes('..')) {
418
+ return {
419
+ ok: false,
420
+ failure: unknown('future-parent-traversal-indeterminate', {
421
+ side,
422
+ segmentIndex: index,
423
+ }),
424
+ };
425
+ }
426
+ return {
427
+ ok: true,
428
+ existing: false,
429
+ ancestor: {
430
+ realPath: current,
431
+ stat: currentStat,
432
+ },
433
+ unresolved,
434
+ };
435
+ }
436
+ if (error?.code === 'ENOTDIR') {
437
+ return {
438
+ ok: false,
439
+ failure: systemFailure('ancestor-not-directory', error, { side, segmentIndex: index }),
440
+ };
441
+ }
442
+ return {
443
+ ok: false,
444
+ failure: systemFailure('path-resolution-failed', error, { side, segmentIndex: index }),
445
+ };
446
+ }
447
+
448
+ if (candidateStat.isSymbolicLink()) {
449
+ let linkPlan;
450
+ try {
451
+ const authoredTarget = fs.readlinkSync(candidate);
452
+ const pathApi = hostPath();
453
+ const internalRelativeLinkTarget = !pathApi.isAbsolute(authoredTarget)
454
+ && !pathApi.parse(authoredTarget).root;
455
+ const parsedTarget = absolutePath(authoredTarget, side, hostPath().dirname(candidate));
456
+ if (!parsedTarget.ok) return parsedTarget;
457
+ const target = splitAbsolute(parsedTarget.path, side, { internalRelativeLinkTarget });
458
+ if (!target.ok) return target;
459
+ linkPlan = {
460
+ root: target.root,
461
+ segments: [...target.segments, ...segments.slice(index + 1)],
462
+ };
463
+ } catch (error) {
464
+ return {
465
+ ok: false,
466
+ failure: systemFailure('symlink-resolution-failed', error, { side, segmentIndex: index }),
467
+ };
468
+ }
469
+ return resolvePlan(linkPlan, side, depth + 1);
470
+ }
471
+
472
+ if (index < segments.length - 1 && !candidateStat.isDirectory()) {
473
+ return {
474
+ ok: false,
475
+ failure: unknown('ancestor-not-directory', { side, segmentIndex: index }),
476
+ };
477
+ }
478
+
479
+ try {
480
+ current = fs.realpathSync.native(candidate);
481
+ currentStat = statBigInt(current);
482
+ } catch (error) {
483
+ return {
484
+ ok: false,
485
+ failure: systemFailure('path-resolution-failed', error, { side, segmentIndex: index }),
486
+ };
487
+ }
488
+ }
489
+
490
+ return {
491
+ ok: true,
492
+ existing: true,
493
+ entry: {
494
+ realPath: current,
495
+ stat: currentStat,
496
+ },
497
+ unresolved: [],
498
+ };
499
+ }
500
+
501
+ function resolveLocation(input, side) {
502
+ const parsed = absolutePath(input, side);
503
+ if (!parsed.ok) return parsed;
504
+ const plan = splitAbsolute(parsed.path, side);
505
+ if (!plan.ok) return plan;
506
+ return resolvePlan(plan, side, 0);
507
+ }
508
+
509
+ function semanticNameEnvelope(name) {
510
+ const names = new Set([name]);
511
+ const pending = [name];
512
+ while (pending.length > 0 && names.size <= 64) {
513
+ const current = pending.shift();
514
+ const candidates = [
515
+ current.normalize('NFC'),
516
+ current.normalize('NFD'),
517
+ current.toLocaleLowerCase('en-US'),
518
+ current.toLocaleUpperCase('en-US'),
519
+ ];
520
+ for (const candidate of candidates) {
521
+ if (names.has(candidate)) continue;
522
+ names.add(candidate);
523
+ pending.push(candidate);
524
+ }
525
+ }
526
+ return names.size <= 64 ? names : null;
527
+ }
528
+
529
+ function semanticEnvelopesOverlap(leftName, rightName) {
530
+ const left = semanticNameEnvelope(leftName);
531
+ const right = semanticNameEnvelope(rightName);
532
+ if (!left || !right) return true;
533
+ for (const spelling of left) {
534
+ if (right.has(spelling)) return true;
535
+ }
536
+ return false;
537
+ }
538
+
539
+ function windowsDeviceName(name) {
540
+ const stem = name.split('.')[0].replace(/[ .]+$/u, '').toUpperCase();
541
+ return /^(?:CON|PRN|AUX|NUL|CONIN\$|CONOUT\$|COM[1-9¹²³]|LPT[1-9¹²³])$/u.test(stem);
542
+ }
543
+
544
+ function futureWindowsNameRisk(leftName, rightName) {
545
+ if (process.platform !== 'win32') return null;
546
+ const windowsShortName = /~[1-9][0-9]*(?:\.|$)/iu;
547
+ if (windowsShortName.test(leftName) || windowsShortName.test(rightName)) {
548
+ return unknown('future-short-name-indeterminate', { method: 'windows-8dot3' });
549
+ }
550
+ if (windowsDeviceName(leftName) || windowsDeviceName(rightName)) {
551
+ return unknown('future-device-name-indeterminate', { method: 'windows-device-name' });
552
+ }
553
+ if (/[ .]$/.test(leftName) || /[ .]$/.test(rightName)) {
554
+ return unknown('future-trim-name-indeterminate', { method: 'windows-trim-alias' });
555
+ }
556
+ return null;
557
+ }
558
+
559
+ /**
560
+ * Resolve one existing or future path through the host filesystem without
561
+ * exposing a string key for identity comparisons.
562
+ */
563
+ export function resolvePhysicalLocation(targetPath) {
564
+ const resolution = resolveLocation(targetPath, 'target');
565
+ if (!resolution.ok) return resolution.failure;
566
+ if (resolution.existing) {
567
+ return {
568
+ status: 'resolved',
569
+ location: {
570
+ kind: 'existing',
571
+ path: resolution.entry.realPath,
572
+ },
573
+ reason: {
574
+ code: 'existing-native-realpath',
575
+ method: 'native-realpath',
576
+ },
577
+ };
578
+ }
579
+ return {
580
+ status: 'resolved',
581
+ location: {
582
+ kind: 'future',
583
+ ancestorPath: resolution.ancestor.realPath,
584
+ unresolved: [...resolution.unresolved],
585
+ },
586
+ reason: {
587
+ code: 'future-physical-ancestor',
588
+ method: 'native-realpath',
589
+ },
590
+ };
591
+ }
592
+
593
+ function probeNameAlias(directoryPath, leftName, rightName) {
594
+ if (leftName === rightName) {
595
+ return result('match', 'future-name-exact', { method: 'lexical-exact' });
596
+ }
597
+ if (recentProbeNames.has(leftName) || recentProbeNames.has(rightName)) {
598
+ return result('different', 'internal-probe-name', { method: 'probe-suppression' });
599
+ }
600
+ const windowsRisk = futureWindowsNameRisk(leftName, rightName);
601
+ if (windowsRisk) return windowsRisk;
602
+ if (!semanticEnvelopesOverlap(leftName, rightName)) {
603
+ return result('different', 'future-name-distinct', { method: 'semantic-envelope' });
604
+ }
605
+
606
+ const prefix = `.archify-path-semantics-${process.pid}-${crypto.randomBytes(10).toString('hex')}-`;
607
+ const probeName = `${prefix}${leftName}`;
608
+ const lookupName = `${prefix}${rightName}`;
609
+ const componentLength = (name) => process.platform === 'win32'
610
+ ? name.length
611
+ : Buffer.byteLength(name, 'utf8');
612
+ if (componentLength(probeName) > 255 || componentLength(lookupName) > 255) {
613
+ return unknown('future-name-probe-unrepresentable', {
614
+ method: 'direct-filesystem-probe',
615
+ systemCode: 'ENAMETOOLONG',
616
+ });
617
+ }
618
+
619
+ let probePath;
620
+ let probeStat;
621
+ let descriptor;
622
+ let comparison;
623
+ let cleanupError;
624
+ try {
625
+ // Probe entries must live directly in the directory being queried. On
626
+ // Windows case sensitivity is per-directory and a child may not inherit it.
627
+ rememberProbeName(probeName);
628
+ rememberProbeName(lookupName);
629
+ probePath = path.join(directoryPath, probeName);
630
+ const lookupPath = path.join(directoryPath, lookupName);
631
+ descriptor = fs.openSync(probePath, 'wx', 0o600);
632
+ probeStat = fs.fstatSync(descriptor, { bigint: true });
633
+
634
+ let authoredStat;
635
+ let lookupStat;
636
+ try {
637
+ authoredStat = statBigInt(probePath);
638
+ lookupStat = statBigInt(lookupPath);
639
+ } catch (error) {
640
+ if (error?.code === 'ENOENT') {
641
+ comparison = result('different', 'future-name-different', { method: 'filesystem-probe' });
642
+ } else {
643
+ comparison = systemFailure('future-name-probe-failed', error, { method: 'filesystem-probe' });
644
+ }
645
+ }
646
+
647
+ if (!comparison) {
648
+ const identity = compareKnownEntries(
649
+ { realPath: fs.realpathSync.native(probePath), stat: authoredStat },
650
+ { realPath: fs.realpathSync.native(lookupPath), stat: lookupStat },
651
+ 'future-name',
652
+ );
653
+ if (identity.status === 'match') {
654
+ comparison = result('match', 'future-name-alias', { method: 'filesystem-probe' });
655
+ } else if (identity.status === 'different') {
656
+ comparison = result('different', 'future-name-different', { method: 'filesystem-probe' });
657
+ } else {
658
+ comparison = identity;
659
+ }
660
+ }
661
+ } catch (error) {
662
+ comparison = systemFailure('future-name-probe-failed', error, { method: 'filesystem-probe' });
663
+ } finally {
664
+ // Pin the inode until cleanup finishes. Once the final handle is closed,
665
+ // an unlinked probe's inode can be reused by a replacement at the same name.
666
+ if (probeStat) cleanupError = removeOwnedProbe(probePath, probeStat);
667
+ if (descriptor !== undefined) {
668
+ try {
669
+ fs.closeSync(descriptor);
670
+ } catch (error) {
671
+ cleanupError = cleanupError ?? error;
672
+ }
673
+ }
674
+ }
675
+
676
+ if (cleanupError) {
677
+ return systemFailure('future-name-probe-cleanup-failed', cleanupError, {
678
+ method: 'filesystem-probe',
679
+ ...(comparison ? { comparison: comparison.reason } : {}),
680
+ });
681
+ }
682
+ return comparison;
683
+ }
684
+
685
+ function sidecarCaseKey(component) {
686
+ // The candidate is accepted only after the containing filesystem confirms
687
+ // that the original grapheme and this spelling name the same entry.
688
+ return component.toLocaleUpperCase('en-US').toLocaleLowerCase('en-US');
689
+ }
690
+
691
+ function transformSidecarComponent(directoryPath, component, transform, phase) {
692
+ if (typeof Intl.Segmenter !== 'function') {
693
+ return {
694
+ ok: false,
695
+ failure: unknown('sidecar-unicode-segmentation-unavailable', {
696
+ operation: 'sidecar-namespace-key',
697
+ }),
698
+ };
699
+ }
700
+ const segmenter = new Intl.Segmenter('en-US', { granularity: 'grapheme' });
701
+ const comparisons = new Map();
702
+ const output = [];
703
+ for (const { segment } of segmenter.segment(component)) {
704
+ const candidate = transform(segment);
705
+ if (candidate === segment) {
706
+ output.push(segment);
707
+ continue;
708
+ }
709
+ const cacheKey = `${segment}\0${candidate}`;
710
+ let comparison = comparisons.get(cacheKey);
711
+ if (!comparison) {
712
+ comparison = probeNameAlias(directoryPath, segment, candidate);
713
+ comparisons.set(cacheKey, comparison);
714
+ }
715
+ if (comparison.status === 'unknown') {
716
+ return {
717
+ ok: false,
718
+ failure: unknown(`sidecar-${phase}-semantics-indeterminate`, {
719
+ operation: 'sidecar-namespace-key',
720
+ cause: comparison.reason,
721
+ }),
722
+ };
723
+ }
724
+ output.push(comparison.status === 'match' ? candidate : segment);
725
+ }
726
+ return {
727
+ ok: true,
728
+ component: output.join(''),
729
+ };
730
+ }
731
+
732
+ /**
733
+ * Produce a stable component for a sidecar namespace in one existing
734
+ * directory. The returned string is a collision-domain key, not a canonical
735
+ * path and must never be used to decide whether two filesystem entries match.
736
+ */
737
+ export function sidecarNamespaceComponentKey(directoryPath, component) {
738
+ if (typeof component !== 'string' || component.length === 0
739
+ || component === '.' || component === '..'
740
+ || hostPath().basename(component) !== component) {
741
+ return unknown('invalid-sidecar-component', {
742
+ operation: 'sidecar-namespace-key',
743
+ });
744
+ }
745
+
746
+ const directory = resolveLocation(directoryPath, 'sidecar-directory');
747
+ if (!directory.ok) return directory.failure;
748
+ if (!directory.existing) {
749
+ return unknown('sidecar-directory-missing', {
750
+ operation: 'sidecar-namespace-key',
751
+ });
752
+ }
753
+ if (!directory.entry.stat.isDirectory()) {
754
+ return unknown('sidecar-parent-not-directory', {
755
+ operation: 'sidecar-namespace-key',
756
+ });
757
+ }
758
+
759
+ const normalized = transformSidecarComponent(
760
+ directory.entry.realPath,
761
+ component,
762
+ (segment) => segment.normalize('NFC'),
763
+ 'normalization',
764
+ );
765
+ if (!normalized.ok) return normalized.failure;
766
+ const cased = transformSidecarComponent(
767
+ directory.entry.realPath,
768
+ normalized.component,
769
+ sidecarCaseKey,
770
+ 'case',
771
+ );
772
+ if (!cased.ok) return cased.failure;
773
+ const renormalized = transformSidecarComponent(
774
+ directory.entry.realPath,
775
+ cased.component,
776
+ (segment) => segment.normalize('NFC'),
777
+ 'normalization',
778
+ );
779
+ if (!renormalized.ok) return renormalized.failure;
780
+
781
+ return {
782
+ status: 'resolved',
783
+ directoryPath: directory.entry.realPath,
784
+ componentKey: renormalized.component,
785
+ reason: {
786
+ code: 'sidecar-namespace-key',
787
+ method: 'directory-filesystem-probe',
788
+ },
789
+ };
790
+ }
791
+
792
+ function compareUnmaterializedNames(leftName, rightName) {
793
+ if (leftName === rightName) {
794
+ return result('match', 'future-name-exact', { method: 'lexical-exact' });
795
+ }
796
+ const windowsRisk = futureWindowsNameRisk(leftName, rightName);
797
+ if (windowsRisk) return windowsRisk;
798
+ if (!semanticEnvelopesOverlap(leftName, rightName)) {
799
+ return result('different', 'future-name-distinct', { method: 'semantic-envelope' });
800
+ }
801
+ return unknown('future-descendant-semantics-indeterminate', {
802
+ method: 'unmaterialized-directory',
803
+ });
804
+ }
805
+
806
+ function compareFutureLocations(left, right) {
807
+ const ancestor = compareKnownEntries(left.ancestor, right.ancestor, 'future-ancestor');
808
+ if (ancestor.status !== 'match') return ancestor;
809
+
810
+ if (left.unresolved.length !== right.unresolved.length) {
811
+ return result('different', 'future-depth-different', {
812
+ operation: 'same-location',
813
+ leftDepth: left.unresolved.length,
814
+ rightDepth: right.unresolved.length,
815
+ });
816
+ }
817
+
818
+ let probedSegments = 0;
819
+ for (let index = 0; index < left.unresolved.length; index += 1) {
820
+ const names = index === 0
821
+ ? probeNameAlias(
822
+ left.ancestor.realPath,
823
+ left.unresolved[index],
824
+ right.unresolved[index],
825
+ )
826
+ : compareUnmaterializedNames(left.unresolved[index], right.unresolved[index]);
827
+ if (names.reason.method === 'filesystem-probe') probedSegments += 1;
828
+ if (names.status !== 'match') {
829
+ const { code, ...reasonDetails } = names.reason;
830
+ return result(names.status, code, {
831
+ ...reasonDetails,
832
+ operation: 'same-location',
833
+ segmentIndex: index,
834
+ });
835
+ }
836
+ }
837
+
838
+ return result('match', probedSegments > 0 ? 'future-location-alias' : 'future-location-exact', {
839
+ operation: 'same-location',
840
+ method: probedSegments > 0 ? 'filesystem-probe' : 'lexical-exact',
841
+ probedSegments,
842
+ });
843
+ }
844
+
845
+ /**
846
+ * Compare two existing filesystem entries by physical identity.
847
+ * Missing or unidentifiable entries return `unknown`, never `different`.
848
+ */
849
+ export function sameEntry(leftPath, rightPath) {
850
+ const left = resolveLocation(leftPath, 'left');
851
+ if (!left.ok) return left.failure;
852
+ const right = resolveLocation(rightPath, 'right');
853
+ if (!right.ok) return right.failure;
854
+
855
+ if (!left.existing || !right.existing) {
856
+ return unknown('entry-missing', {
857
+ operation: 'same-entry',
858
+ missing: [
859
+ ...(!left.existing ? ['left'] : []),
860
+ ...(!right.existing ? ['right'] : []),
861
+ ],
862
+ });
863
+ }
864
+
865
+ return compareKnownEntries(left.entry, right.entry, 'same-entry');
866
+ }
867
+
868
+ /**
869
+ * Compare two existing or future write targets using the containing filesystem's
870
+ * actual name semantics. Existing hard links and resolved symbolic links match.
871
+ */
872
+ export function sameLocation(leftPath, rightPath) {
873
+ const left = resolveLocation(leftPath, 'left');
874
+ if (!left.ok) return left.failure;
875
+ const right = resolveLocation(rightPath, 'right');
876
+ if (!right.ok) return right.failure;
877
+
878
+ if (left.existing && right.existing) {
879
+ return compareKnownEntries(left.entry, right.entry, 'same-location');
880
+ }
881
+ if (left.existing !== right.existing) {
882
+ return result('different', 'location-existence-different', {
883
+ operation: 'same-location',
884
+ existing: left.existing ? 'left' : 'right',
885
+ });
886
+ }
887
+ return compareFutureLocations(left, right);
888
+ }
889
+
890
+ /** Compare the physical locations of the two paths' parent directories. */
891
+ export function sameParent(leftPath, rightPath) {
892
+ const left = absolutePath(leftPath, 'left');
893
+ if (!left.ok) return left.failure;
894
+ const right = absolutePath(rightPath, 'right');
895
+ if (!right.ok) return right.failure;
896
+
897
+ return sameLocation(hostPath().dirname(left.path), hostPath().dirname(right.path));
898
+ }
899
+
900
+ function parentEntry(entry) {
901
+ const parentPath = hostPath().dirname(entry.realPath);
902
+ // path-contract-allow: lexical-capability -- Both values are canonical paths captured by this primitive.
903
+ if (parentPath === entry.realPath) return null;
904
+ try {
905
+ const realPath = fs.realpathSync.native(parentPath);
906
+ return {
907
+ ok: true,
908
+ entry: {
909
+ realPath,
910
+ stat: statBigInt(realPath),
911
+ },
912
+ };
913
+ } catch (error) {
914
+ return {
915
+ ok: false,
916
+ failure: systemFailure('ancestor-resolution-failed', error, {
917
+ operation: 'contained-by',
918
+ }),
919
+ };
920
+ }
921
+ }
922
+
923
+ /**
924
+ * Determine physical containment in an existing directory. The comparison is
925
+ * inclusive and follows links; the target may exist or name a future location.
926
+ */
927
+ export function containedBy(directoryPath, targetPath) {
928
+ const directory = resolveLocation(directoryPath, 'directory');
929
+ if (!directory.ok) return directory.failure;
930
+ if (directory.existing && !directory.entry.stat.isDirectory()) {
931
+ return unknown('container-not-directory', { operation: 'contained-by' });
932
+ }
933
+
934
+ const target = resolveLocation(targetPath, 'target');
935
+ if (!target.ok) return target.failure;
936
+
937
+ if (!directory.existing) {
938
+ if (target.existing) {
939
+ return unknown('container-missing', { operation: 'contained-by' });
940
+ }
941
+ const ancestor = compareKnownEntries(directory.ancestor, target.ancestor, 'contained-by');
942
+ if (ancestor.status !== 'match') return ancestor;
943
+ if (target.unresolved.length < directory.unresolved.length) {
944
+ return result('different', 'future-container-prefix-too-deep', {
945
+ operation: 'contained-by',
946
+ });
947
+ }
948
+ for (let index = 0; index < directory.unresolved.length; index += 1) {
949
+ const segment = index === 0
950
+ ? probeNameAlias(
951
+ directory.ancestor.realPath,
952
+ directory.unresolved[index],
953
+ target.unresolved[index],
954
+ )
955
+ : compareUnmaterializedNames(
956
+ directory.unresolved[index],
957
+ target.unresolved[index],
958
+ );
959
+ if (segment.status === 'match') continue;
960
+ const { code, ...reasonDetails } = segment.reason;
961
+ return result(segment.status, code, {
962
+ ...reasonDetails,
963
+ operation: 'contained-by',
964
+ segmentIndex: index,
965
+ });
966
+ }
967
+ return result('match', 'future-container-prefix-match', {
968
+ operation: 'contained-by',
969
+ target: 'future',
970
+ depth: target.unresolved.length - directory.unresolved.length,
971
+ });
972
+ }
973
+
974
+ let current;
975
+ if (!target.existing) {
976
+ current = target.ancestor;
977
+ } else if (target.entry.stat.isDirectory()) {
978
+ current = target.entry;
979
+ } else {
980
+ const parent = parentEntry(target.entry);
981
+ if (!parent?.ok) return parent?.failure ?? unknown('ancestor-resolution-failed', {
982
+ operation: 'contained-by',
983
+ });
984
+ current = parent.entry;
985
+ }
986
+
987
+ let depth = 0;
988
+ let identityWasUnknown = false;
989
+ while (current) {
990
+ const comparison = compareKnownEntries(directory.entry, current, 'contained-by');
991
+ if (comparison.status === 'match') {
992
+ return result('match', 'physical-ancestor-match', {
993
+ operation: 'contained-by',
994
+ depth,
995
+ target: target.existing ? 'existing' : 'future',
996
+ });
997
+ }
998
+ if (comparison.status === 'unknown') identityWasUnknown = true;
999
+
1000
+ const parent = parentEntry(current);
1001
+ if (parent === null) break;
1002
+ if (!parent.ok) return parent.failure;
1003
+ current = parent.entry;
1004
+ depth += 1;
1005
+ }
1006
+
1007
+ if (identityWasUnknown) {
1008
+ return unknown('containment-identity-unavailable', {
1009
+ operation: 'contained-by',
1010
+ target: target.existing ? 'existing' : 'future',
1011
+ });
1012
+ }
1013
+ return result('different', 'physical-ancestor-not-found', {
1014
+ operation: 'contained-by',
1015
+ target: target.existing ? 'existing' : 'future',
1016
+ });
1017
+ }