@ornexus/neocortex 4.60.71 → 4.60.73

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 (243) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +1211 -1217
  3. package/install.ps1 +1 -1
  4. package/install.sh +1 -1
  5. package/package.json +2 -2
  6. package/packages/client/dist/adapters/adapter-registry.js +108 -1
  7. package/packages/client/dist/adapters/antigravity-adapter.js +77 -2
  8. package/packages/client/dist/adapters/claude-code-adapter.js +79 -3
  9. package/packages/client/dist/adapters/codex-adapter.js +80 -2
  10. package/packages/client/dist/adapters/cursor-adapter.js +115 -4
  11. package/packages/client/dist/adapters/gemini-adapter.js +71 -2
  12. package/packages/client/dist/adapters/grok-adapter.js +42 -2
  13. package/packages/client/dist/adapters/index.js +22 -1
  14. package/packages/client/dist/adapters/platform-detector.js +212 -1
  15. package/packages/client/dist/adapters/target-adapter.js +12 -0
  16. package/packages/client/dist/adapters/vscode-adapter.js +72 -2
  17. package/packages/client/dist/agent/refresh-stubs.js +417 -2
  18. package/packages/client/dist/agent/update-agent-yaml.js +102 -1
  19. package/packages/client/dist/agent/update-description.js +251 -1
  20. package/packages/client/dist/cache/crypto-utils.js +78 -1
  21. package/packages/client/dist/cache/encrypted-cache.js +108 -1
  22. package/packages/client/dist/cache/in-memory-asset-cache.js +73 -1
  23. package/packages/client/dist/cache/index.js +13 -1
  24. package/packages/client/dist/cache/protected-pi-boundary.js +44 -1
  25. package/packages/client/dist/checkpoint/checkpoint-client-reader.js +127 -2
  26. package/packages/client/dist/checkpoint/index.js +11 -1
  27. package/packages/client/dist/checkpoint/shared-checkpoint-types.js +32 -1
  28. package/packages/client/dist/cli.js +358 -5
  29. package/packages/client/dist/commands/activate.js +394 -8
  30. package/packages/client/dist/commands/architecture-answers.js +274 -7
  31. package/packages/client/dist/commands/architecture-memory-terminal.js +253 -4
  32. package/packages/client/dist/commands/architecture-non-ui-continuation.js +100 -1
  33. package/packages/client/dist/commands/architecture-root-binding.js +51 -1
  34. package/packages/client/dist/commands/cache-status.js +112 -2
  35. package/packages/client/dist/commands/config.js +88 -1
  36. package/packages/client/dist/commands/doctor.js +635 -2
  37. package/packages/client/dist/commands/invoke.js +7200 -143
  38. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  39. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  40. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  41. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  42. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  43. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  44. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  45. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  46. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  47. package/packages/client/dist/commands/mutation-qa.js +220 -1
  48. package/packages/client/dist/commands/native-agent.js +136 -2
  49. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  50. package/packages/client/dist/commands/planning-generation.js +53 -1
  51. package/packages/client/dist/commands/planning-memory.js +40 -1
  52. package/packages/client/dist/commands/planning-research.js +42 -1
  53. package/packages/client/dist/commands/private-session.js +75 -1
  54. package/packages/client/dist/commands/property-qa.js +139 -1
  55. package/packages/client/dist/commands/qa-control.js +16 -1
  56. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  57. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  58. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  59. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  60. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  61. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  62. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  63. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  64. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  65. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  66. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  67. package/packages/client/dist/commands/refactor-commit.js +399 -13
  68. package/packages/client/dist/commands/refactor-control.js +142 -3
  69. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  70. package/packages/client/dist/commands/refactor-review.js +283 -5
  71. package/packages/client/dist/commands/refactor-root.js +588 -7
  72. package/packages/client/dist/commands/refresh-memory.js +156 -3
  73. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  74. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  75. package/packages/client/dist/config/profile-directory.js +13 -1
  76. package/packages/client/dist/config/resolver-selection.js +278 -1
  77. package/packages/client/dist/config/secure-config.js +281 -12
  78. package/packages/client/dist/config.js +223 -2
  79. package/packages/client/dist/constants.js +25 -1
  80. package/packages/client/dist/context/context-collector.js +222 -2
  81. package/packages/client/dist/context/context-sanitizer.js +145 -1
  82. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  83. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  84. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  85. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  86. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  87. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  88. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  89. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  90. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  91. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  92. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  93. package/packages/client/dist/continuity/outbox.js +117 -1
  94. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  95. package/packages/client/dist/continuity/sqlite-store.d.ts +22 -1
  96. package/packages/client/dist/continuity/sqlite-store.js +2612 -81
  97. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  98. package/packages/client/dist/errors/error-messages.js +228 -2
  99. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  100. package/packages/client/dist/evidence/collector.js +347 -1
  101. package/packages/client/dist/evidence/index.js +1 -1
  102. package/packages/client/dist/evidence/types.js +2 -0
  103. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  104. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  105. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  106. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  107. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  108. package/packages/client/dist/git/epic-operational-state.d.ts +198 -0
  109. package/packages/client/dist/git/epic-operational-state.js +672 -0
  110. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  111. package/packages/client/dist/git/git-lifecycle-store.d.ts +9 -1
  112. package/packages/client/dist/git/git-lifecycle-store.js +1981 -9
  113. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  114. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  115. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  116. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  117. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  118. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  119. package/packages/client/dist/i18n/first-run.js +60 -2
  120. package/packages/client/dist/index.js +47 -1
  121. package/packages/client/dist/invocation/invoke-input.js +24 -1
  122. package/packages/client/dist/license/index.js +5 -1
  123. package/packages/client/dist/license/license-client.js +304 -1
  124. package/packages/client/dist/machine/fingerprint.js +163 -2
  125. package/packages/client/dist/machine/index.js +5 -1
  126. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  127. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  128. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  129. package/packages/client/dist/migrations/epic-lifecycle-backfill.d.ts +97 -0
  130. package/packages/client/dist/migrations/epic-lifecycle-backfill.js +1060 -0
  131. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  132. package/packages/client/dist/migrations/project-identity.js +493 -7
  133. package/packages/client/dist/migrations/state-json.d.ts +22 -0
  134. package/packages/client/dist/migrations/state-json.js +393 -4
  135. package/packages/client/dist/policy/architecture-policy.js +184 -2
  136. package/packages/client/dist/policy/index.js +7 -1
  137. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  138. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  139. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  140. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  141. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  142. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  143. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  144. package/packages/client/dist/private-execution/delivery.js +410 -13
  145. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  146. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  147. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  148. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  149. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  150. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  151. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  152. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  153. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  154. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  155. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  156. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  157. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  158. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  159. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  160. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  161. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  162. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  163. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  164. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  165. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  166. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  167. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  168. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  169. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  170. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  171. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  172. package/packages/client/dist/private-execution/session-lease.js +63 -1
  173. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  174. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  175. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  176. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  177. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  178. package/packages/client/dist/registry/registry-reconciliation.js +234 -1
  179. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  180. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  181. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  182. package/packages/client/dist/resilience/index.js +8 -1
  183. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  184. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  185. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  186. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  187. package/packages/client/dist/runner/binary.js +404 -1
  188. package/packages/client/dist/runner/cli.js +1571 -17
  189. package/packages/client/dist/runner/headless/adapters.js +49 -1
  190. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  191. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  192. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  193. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  194. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  195. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  196. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  197. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  198. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  199. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  200. package/packages/client/dist/runner/headless/resolver.js +231 -1
  201. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  202. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  203. package/packages/client/dist/runner/headless/types.js +574 -2
  204. package/packages/client/dist/runner/local-console.js +396 -13
  205. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  206. package/packages/client/dist/runner/project-identity.js +106 -1
  207. package/packages/client/dist/runner/scheduler.js +995 -7
  208. package/packages/client/dist/runner-cli.js +18 -2
  209. package/packages/client/dist/state/invocation-shape.js +13 -1
  210. package/packages/client/dist/state/project-state-snapshot.d.ts +8 -0
  211. package/packages/client/dist/state/project-state-snapshot.js +1731 -1
  212. package/packages/client/dist/state/state-json-repair.js +785 -3
  213. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  214. package/packages/client/dist/telemetry/index.js +5 -1
  215. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  216. package/packages/client/dist/tier/index.js +5 -1
  217. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  218. package/packages/client/dist/types/index.js +38 -1
  219. package/packages/client/dist/version.js +27 -1
  220. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  221. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  222. package/packages/client/dist/yolo/spec-envelope-collector.js +184 -5
  223. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  224. package/packages/client/dist/yoloop/index.js +8 -1
  225. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  226. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  227. package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +65 -1
  228. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  229. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  230. package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +8 -0
  231. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1920 -1
  232. package/packages/shared/dist/yolo/spec-envelope.js +3 -6
  233. package/targets-stubs/antigravity/gemini.md +1 -1
  234. package/targets-stubs/antigravity/skill/SKILL.md +1 -1
  235. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  236. package/targets-stubs/claude-code/neocortex-root.md +2 -2
  237. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  238. package/targets-stubs/claude-code/neocortex.md +2 -2
  239. package/targets-stubs/codex/AGENTS.md +1 -1
  240. package/targets-stubs/cursor/agent.md +2 -2
  241. package/targets-stubs/gemini-cli/agent.md +2 -2
  242. package/targets-stubs/opencode/neocortex-root.md +1 -1
  243. package/targets-stubs/vscode/neocortex.agent.md +2 -2
@@ -1 +1,347 @@
1
- import{createHash as q}from"node:crypto";import{lstatSync as A,readdirSync as G,readFileSync as z,realpathSync as E,statSync as O}from"node:fs";import{isAbsolute as F,relative as B,resolve as $}from"node:path";const J=Object.freeze({maxItems:48,maxFileBytes:96*1024,maxTotalBytes:512*1024,maxSummaryChars:280,maxEnvelopeBytes:64*1024,maxExclusions:64}),S=512,D=/(?:\.tsx?|\.jsx?|\.vue|\.svelte|\.css|\.scss|\.json|\.ya?ml|\.mdx?|\.html)$/i,V=/\.(?:png|jpe?g|webp)$/i,X=new Set([".git",".neocortex","node_modules","dist","build","coverage","core","private","secrets","credentials","raw-logs"]),I=/(?:[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}|\b(?:api[-_ ]?key|license[-_ ]?key|password|secret|token)\b\s*[:=]|\b(?:sk|pk|ghp|github_pat)_[A-Za-z0-9_-]{12,}|https?:\/\/(?:localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|[^/\s]+\.(?:local|internal))\b|private\s+(?:prompt|workflow|customer data)|-----BEGIN [A-Z ]+PRIVATE KEY-----)/i,R=new Set(["metadata-only-v1","visual-pii-v1","strict-v1"]);function _(t){return q("sha256").update(t).digest("hex")}function j(t){if(Array.isArray(t))return`[${t.map(j).join(",")}]`;if(t&&typeof t=="object"){const e=t;return`{${Object.keys(e).sort().map(s=>`${JSON.stringify(s)}:${j(e[s])}`).join(",")}}`}return JSON.stringify(t)??"null"}function h(t){return t.trim().replace(/\\/g,"/").replace(/\/+/g,"/").replace(/^\.\//,"")}function N(t){const e=h(t);return!e||F(t)||e.startsWith("/")||/^[a-z]:[\\/]/i.test(t)?"absolute_path_rejected":e===".."||e.startsWith("../")||e.includes("/../")?"traversal_path_rejected":null}function K(t){return`evidence:${_(t).slice(0,16)}`}function c(t,e){return{sourceId:K(t),reasonCode:e}}function k(t){return h(t).toLowerCase().split("/").some(e=>e.startsWith(".env")||X.has(e))}function b(t,e){const s=B(t,e);return s===""||!s.startsWith("..")&&!F(s)}function w(t,e,s,r,u=S){if(!(s.length>=u))for(const i of G(e,{withFileTypes:!0}).sort((a,l)=>a.name.localeCompare(l.name))){if(s.length>=u)break;const a=$(e,i.name),l=h(B(t,a));if(!k(l)){if(i.isSymbolicLink()){r.push(c(l,"symlink_escape_rejected"));continue}if(i.isDirectory()){w(t,a,s,r,u);continue}i.isFile()&&D.test(i.name)&&s.push(l)}}}function U(t,e,s,r){const u=N(e);if(u){r.push(c(e,u));return}const i=h(e);if(k(i)||I.test(i)){r.push(c(e,"private_evidence_rejected"));return}const a=$(t,i);if(!b(t,a)){r.push(c(e,"traversal_path_rejected"));return}const l=A(a);if(l.isSymbolicLink()){r.push(c(e,"symlink_escape_rejected"));return}const p=E(a);if(!b(t,p)){r.push(c(e,"symlink_escape_rejected"));return}if(l.isDirectory()){w(t,p,s,r);return}l.isFile()&&s.push(i)}function Y(t){const e=t.toLowerCase();return/\.(?:stories|story)\.(?:tsx?|jsx?|mdx?)$/.test(e)||e.includes("/.storybook/")?"storybook":/(?:^|\/)(?:__tests__|tests?|e2e)(?:\/|$)|\.(?:test|spec)\.(?:tsx?|jsx?)$|playwright|vitest|jest/.test(e)?"validation":/(?:^|\/)(?:routes?|pages?|app)(?:\/|$)|(?:^|\/)router\./.test(e)?"route":/(?:^|\/)(?:components?|ui)(?:\/|$)|[A-Z][A-Za-z0-9_-]*\.(?:tsx|jsx|vue|svelte)$/.test(t)?"component":/(?:tokens?|theme|tailwind|design-system)|\.(?:css|scss)$/.test(e)?"token":/(?:openapi|asyncapi|contracts?|schemas?|events?)|\.(?:graphql|proto)$/.test(e)?"contract":e==="package.json"||e.startsWith("docs/architecture")||e.startsWith("docs/arquitetura-software")?"source_summary":null}function Q(t,e){let s=0;const r=[/\bexport\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g,/\b(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/g,/--([a-z][a-z0-9-]{1,64})\s*:/gi];for(const u of r)for(const i of e.matchAll(u)){const a=(i[1]??i[0]).replace(/^--/,"");if(/^[A-Za-z_$][\w$-]{0,64}$/.test(a)&&(s+=1),s>=12)break}return`${t}; lines=${e.split(/\r?\n/).length}; structuralIdentifiers=${s}`}function H(t,e){const s=e.match(/\.[A-Za-z0-9]+$/)?.[0].toLowerCase()??".json";return`evidence/${t}/${_(e).slice(0,20)}${s}`}function ee(t,e){const s=t.consent??{},r=s.redactionPolicy&&R.has(s.redactionPolicy)?s.redactionPolicy:void 0,u=typeof s.expiresAt=="string"?s.expiresAt:void 0,i=Date.parse(e),a=u?Date.parse(u):Number.NaN,l=Number.isFinite(i)&&Number.isFinite(a)&&a>i&&a-i<=3600*1e3;return{richEvidence:s.richEvidence===!0&&!!r&&l,screenshots:s.screenshots===!0&&s.richEvidence===!0&&!!r&&l,liveEvidence:s.liveEvidence===!0&&s.richEvidence===!0&&!!r&&l,...r?{redactionPolicy:r}:{},...u?{expiresAt:u}:{}}}function te(t,e,s,r){const u=N(e);if(u)return{exclusion:c(e,u)};if(k(e)||I.test(e))return{exclusion:c(e,"private_evidence_rejected")};if(!s||!D.test(e))return{exclusion:c(e,"unsupported_file_type")};const i=$(t,e);if(!b(t,i))return{exclusion:c(e,"traversal_path_rejected")};if(A(i).isSymbolicLink())return{exclusion:c(e,"symlink_escape_rejected")};const l=E(i);if(!b(t,l))return{exclusion:c(e,"symlink_escape_rejected")};const p=O(l);if(!p.isFile())return{exclusion:c(e,"unsupported_file_type")};if(p.size>r.maxFileBytes)return{exclusion:c(e,"oversized_evidence")};const y=z(l);if(y.includes(0)||I.test(y.toString("utf8")))return{exclusion:c(e,"private_evidence_rejected")};const f=h(e),d=_(y),g=H(s,f);return{item:{id:`${s}:${_(`${f}:${d}`).slice(0,20)}`,kind:s,source:{path:g,contentHash:d,byteLength:p.size},summary:Q(s,y.toString("utf8")).slice(0,r.maxSummaryChars)}}}function se(t,e,s,r){const u=N(e.path);if(u)return{exclusion:c(e.path,u)};if(!V.test(e.path))return{exclusion:c(e.path,"unsupported_file_type")};if(!s.richEvidence||(e.kind==="screenshot"?!s.screenshots:!s.liveEvidence))return{exclusion:c(e.path,"consent_required")};if(!e.redactionPolicy||!R.has(e.redactionPolicy)||e.redactionPolicy!==s.redactionPolicy)return{exclusion:c(e.path,"redaction_policy_required")};if(!e.expiresAt||e.expiresAt!==s.expiresAt)return{exclusion:c(e.path,"ttl_invalid")};if(k(e.path))return{exclusion:c(e.path,"private_evidence_rejected")};const i=$(t,e.path);if(!b(t,i))return{exclusion:c(e.path,"traversal_path_rejected")};if(A(i).isSymbolicLink())return{exclusion:c(e.path,"symlink_escape_rejected")};const l=E(i);if(!b(t,l))return{exclusion:c(e.path,"symlink_escape_rejected")};const p=O(l);if(!p.isFile())return{exclusion:c(e.path,"unsupported_file_type")};if(p.size>r.maxFileBytes)return{exclusion:c(e.path,"oversized_evidence")};const y=z(l),f=h(e.path),d={path:H(e.kind,f),contentHash:_(y),byteLength:p.size};return{item:{id:`${e.kind}:${_(`${f}:${d.contentHash}`).slice(0,20)}`,kind:e.kind,source:d,summary:`${e.kind}; metadata-only; bytes=${d.byteLength}`,consent:{redactionPolicy:e.redactionPolicy,expiresAt:e.expiresAt}}}}function ce(t){const e=E(t.projectRoot),s=new Date(t.collectedAt??Date.now()).toISOString(),r={...J,...t.budget},u=ee(t,s),i=[],a=[];for(const n of[...t.candidatePaths??[]].sort()){try{U(e,n,a,i)}catch{i.push(c(n,"malformed_evidence"))}if(a.length>=S)break}const l=[];(!t.candidatePaths||t.includeFallbackInventory===!0)&&w(e,e,l,i,Math.max(0,S-a.length));const p=new Set(a.map(h)),y=[...new Set([...a,...l])].slice(0,S),f=[];for(const n of y){let o;try{o=te(e,n,Y(h(n)),r)}catch{o={exclusion:c(n,"malformed_evidence")}}o.exclusion&&i.push(o.exclusion),o.item&&f.push({candidate:n,explicit:p.has(h(n)),item:o.item})}const d=new Map;for(const n of f)d.set(n.item.kind,[...d.get(n.item.kind)??[],n]);const g=(n,o)=>Number(o.explicit)-Number(n.explicit)||n.candidate.localeCompare(o.candidate);for(const n of d.values())n.sort(g);const C=[...d.keys()].sort().flatMap(n=>d.get(n)?.slice(0,1)??[]),P=new Set(C.map(n=>n.item.id)),Z=f.filter(n=>!P.has(n.item.id)).sort(g),x=[];let v=0;for(const n of[...C,...Z]){if(x.length>=r.maxItems||v+n.item.source.byteLength>r.maxTotalBytes){i.push(c(n.candidate,"budget_exceeded"));continue}x.push(n.item),v+=n.item.source.byteLength}for(const n of t.richCandidates??[]){let o;try{o=se(e,n,u,r)}catch{o={exclusion:c(n.path,"malformed_evidence")}}if(o.exclusion&&i.push(o.exclusion),!!o.item){if(x.length>=r.maxItems||v+o.item.source.byteLength>r.maxTotalBytes){i.push(c(n.path,"budget_exceeded"));continue}x.push(o.item),v+=o.item.source.byteLength}}x.sort((n,o)=>n.kind.localeCompare(o.kind)||n.source.path.localeCompare(o.source.path));const T=_(j(x));let m={schemaVersion:1,envelopeId:`evidence-${T.slice(0,24)}`,evidenceHash:T,collectedAt:s,consent:u,budget:{...r,acceptedItems:x.length,acceptedBytes:v},evidence:x,exclusions:i.slice(0,r.maxExclusions)};for(;Buffer.byteLength(JSON.stringify(m),"utf8")>r.maxEnvelopeBytes&&m.evidence.length>0;){const n=m.evidence.at(-1),o=m.evidence.slice(0,-1),L=_(j(o));m={...m,envelopeId:`evidence-${L.slice(0,24)}`,evidenceHash:L,budget:{...m.budget,acceptedItems:o.length,acceptedBytes:o.reduce((M,W)=>M+W.source.byteLength,0)},evidence:o,exclusions:[...m.exclusions,c(n.id,"budget_exceeded")].slice(0,r.maxExclusions)}}return m}export{J as CLIENT_PROJECT_EVIDENCE_BUDGET,ce as collectProjectEvidence};
1
+ import { createHash } from 'node:crypto';
2
+ import { lstatSync, readdirSync, readFileSync, realpathSync, statSync } from 'node:fs';
3
+ import { isAbsolute, relative, resolve } from 'node:path';
4
+ export const CLIENT_PROJECT_EVIDENCE_BUDGET = Object.freeze({
5
+ maxItems: 48,
6
+ maxFileBytes: 96 * 1024,
7
+ maxTotalBytes: 512 * 1024,
8
+ maxSummaryChars: 280,
9
+ maxEnvelopeBytes: 64 * 1024,
10
+ maxExclusions: 64,
11
+ });
12
+ const MAX_SCAN_ENTRIES = 512;
13
+ const ALLOWED_EXTENSIONS = /(?:\.tsx?|\.jsx?|\.vue|\.svelte|\.css|\.scss|\.json|\.ya?ml|\.mdx?|\.html)$/i;
14
+ const RICH_EXTENSIONS = /\.(?:png|jpe?g|webp)$/i;
15
+ const IGNORED_SEGMENTS = new Set(['.git', '.neocortex', 'node_modules', 'dist', 'build', 'coverage', 'core', 'private', 'secrets', 'credentials', 'raw-logs']);
16
+ const PRIVATE_CONTENT = /(?:[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}|\b(?:api[-_ ]?key|license[-_ ]?key|password|secret|token)\b\s*[:=]|\b(?:sk|pk|ghp|github_pat)_[A-Za-z0-9_-]{12,}|https?:\/\/(?:localhost|127\.0\.0\.1|10\.\d+\.\d+\.\d+|192\.168\.\d+\.\d+|172\.(?:1[6-9]|2\d|3[01])\.\d+\.\d+|[^/\s]+\.(?:local|internal))\b|private\s+(?:prompt|workflow|customer data)|-----BEGIN [A-Z ]+PRIVATE KEY-----)/i;
17
+ const REDACTION_POLICIES = new Set(['metadata-only-v1', 'visual-pii-v1', 'strict-v1']);
18
+ function sha256(value) {
19
+ return createHash('sha256').update(value).digest('hex');
20
+ }
21
+ function stableJson(value) {
22
+ if (Array.isArray(value))
23
+ return `[${value.map(stableJson).join(',')}]`;
24
+ if (value && typeof value === 'object') {
25
+ const record = value;
26
+ return `{${Object.keys(record).sort().map((key) => `${JSON.stringify(key)}:${stableJson(record[key])}`).join(',')}}`;
27
+ }
28
+ return JSON.stringify(value) ?? 'null';
29
+ }
30
+ function normalizePath(value) {
31
+ return value.trim().replace(/\\/g, '/').replace(/\/+/g, '/').replace(/^\.\//, '');
32
+ }
33
+ function pathRejection(value) {
34
+ const normalized = normalizePath(value);
35
+ if (!normalized || isAbsolute(value) || normalized.startsWith('/') || /^[a-z]:[\\/]/i.test(value))
36
+ return 'absolute_path_rejected';
37
+ if (normalized === '..' || normalized.startsWith('../') || normalized.includes('/../'))
38
+ return 'traversal_path_rejected';
39
+ return null;
40
+ }
41
+ function sourceId(value) {
42
+ return `evidence:${sha256(value).slice(0, 16)}`;
43
+ }
44
+ function excluded(value, reasonCode) {
45
+ return { sourceId: sourceId(value), reasonCode };
46
+ }
47
+ function isIgnored(relativePath) {
48
+ return normalizePath(relativePath).toLowerCase().split('/').some((segment) => segment.startsWith('.env') || IGNORED_SEGMENTS.has(segment));
49
+ }
50
+ function insideRoot(root, candidate) {
51
+ const rel = relative(root, candidate);
52
+ return rel === '' || (!rel.startsWith('..') && !isAbsolute(rel));
53
+ }
54
+ function inventory(root, dir, output, exclusions, maxEntries = MAX_SCAN_ENTRIES) {
55
+ if (output.length >= maxEntries)
56
+ return;
57
+ for (const entry of readdirSync(dir, { withFileTypes: true }).sort((a, b) => a.name.localeCompare(b.name))) {
58
+ if (output.length >= maxEntries)
59
+ break;
60
+ const absolute = resolve(dir, entry.name);
61
+ const relativePath = normalizePath(relative(root, absolute));
62
+ if (isIgnored(relativePath))
63
+ continue;
64
+ if (entry.isSymbolicLink()) {
65
+ exclusions.push(excluded(relativePath, 'symlink_escape_rejected'));
66
+ continue;
67
+ }
68
+ if (entry.isDirectory()) {
69
+ inventory(root, absolute, output, exclusions, maxEntries);
70
+ continue;
71
+ }
72
+ if (entry.isFile() && ALLOWED_EXTENSIONS.test(entry.name))
73
+ output.push(relativePath);
74
+ }
75
+ }
76
+ function expandExplicitCandidate(root, candidate, output, exclusions) {
77
+ const rejection = pathRejection(candidate);
78
+ if (rejection) {
79
+ exclusions.push(excluded(candidate, rejection));
80
+ return;
81
+ }
82
+ const normalized = normalizePath(candidate);
83
+ if (isIgnored(normalized) || PRIVATE_CONTENT.test(normalized)) {
84
+ exclusions.push(excluded(candidate, 'private_evidence_rejected'));
85
+ return;
86
+ }
87
+ const absolute = resolve(root, normalized);
88
+ if (!insideRoot(root, absolute)) {
89
+ exclusions.push(excluded(candidate, 'traversal_path_rejected'));
90
+ return;
91
+ }
92
+ const stat = lstatSync(absolute);
93
+ if (stat.isSymbolicLink()) {
94
+ exclusions.push(excluded(candidate, 'symlink_escape_rejected'));
95
+ return;
96
+ }
97
+ const real = realpathSync(absolute);
98
+ if (!insideRoot(root, real)) {
99
+ exclusions.push(excluded(candidate, 'symlink_escape_rejected'));
100
+ return;
101
+ }
102
+ if (stat.isDirectory()) {
103
+ inventory(root, real, output, exclusions);
104
+ return;
105
+ }
106
+ if (stat.isFile())
107
+ output.push(normalized);
108
+ }
109
+ function kindFor(path) {
110
+ const lower = path.toLowerCase();
111
+ if (/\.(?:stories|story)\.(?:tsx?|jsx?|mdx?)$/.test(lower) || lower.includes('/.storybook/'))
112
+ return 'storybook';
113
+ if (/(?:^|\/)(?:__tests__|tests?|e2e)(?:\/|$)|\.(?:test|spec)\.(?:tsx?|jsx?)$|playwright|vitest|jest/.test(lower))
114
+ return 'validation';
115
+ if (/(?:^|\/)(?:routes?|pages?|app)(?:\/|$)|(?:^|\/)router\./.test(lower))
116
+ return 'route';
117
+ if (/(?:^|\/)(?:components?|ui)(?:\/|$)|[A-Z][A-Za-z0-9_-]*\.(?:tsx|jsx|vue|svelte)$/.test(path))
118
+ return 'component';
119
+ if (/(?:tokens?|theme|tailwind|design-system)|\.(?:css|scss)$/.test(lower))
120
+ return 'token';
121
+ if (/(?:openapi|asyncapi|contracts?|schemas?|events?)|\.(?:graphql|proto)$/.test(lower))
122
+ return 'contract';
123
+ if (lower === 'package.json' || lower.startsWith('docs/architecture') || lower.startsWith('docs/arquitetura-software'))
124
+ return 'source_summary';
125
+ return null;
126
+ }
127
+ function structuralSummary(kind, content) {
128
+ let identifierCount = 0;
129
+ const patterns = [
130
+ /\bexport\s+(?:default\s+)?(?:async\s+)?(?:function|class|const|let|var|interface|type|enum)\s+([A-Za-z_$][\w$]*)/g,
131
+ /\b(?:GET|POST|PUT|PATCH|DELETE|OPTIONS|HEAD)\b/g,
132
+ /--([a-z][a-z0-9-]{1,64})\s*:/gi,
133
+ ];
134
+ for (const pattern of patterns) {
135
+ for (const match of content.matchAll(pattern)) {
136
+ const identifier = (match[1] ?? match[0]).replace(/^--/, '');
137
+ if (/^[A-Za-z_$][\w$-]{0,64}$/.test(identifier))
138
+ identifierCount += 1;
139
+ if (identifierCount >= 12)
140
+ break;
141
+ }
142
+ }
143
+ return `${kind}; lines=${content.split(/\r?\n/).length}; structuralIdentifiers=${identifierCount}`;
144
+ }
145
+ function opaqueEvidencePath(kind, relativePath) {
146
+ const extension = relativePath.match(/\.[A-Za-z0-9]+$/)?.[0].toLowerCase() ?? '.json';
147
+ return `evidence/${kind}/${sha256(relativePath).slice(0, 20)}${extension}`;
148
+ }
149
+ function consentFor(options, collectedAt) {
150
+ const consent = options.consent ?? {};
151
+ const redactionPolicy = consent.redactionPolicy && REDACTION_POLICIES.has(consent.redactionPolicy) ? consent.redactionPolicy : undefined;
152
+ const expiresAt = typeof consent.expiresAt === 'string' ? consent.expiresAt : undefined;
153
+ const collectedMs = Date.parse(collectedAt);
154
+ const expiresMs = expiresAt ? Date.parse(expiresAt) : Number.NaN;
155
+ const ttlValid = Number.isFinite(collectedMs) && Number.isFinite(expiresMs) && expiresMs > collectedMs && expiresMs - collectedMs <= 60 * 60 * 1000;
156
+ return {
157
+ richEvidence: consent.richEvidence === true && !!redactionPolicy && ttlValid,
158
+ screenshots: consent.screenshots === true && consent.richEvidence === true && !!redactionPolicy && ttlValid,
159
+ liveEvidence: consent.liveEvidence === true && consent.richEvidence === true && !!redactionPolicy && ttlValid,
160
+ ...(redactionPolicy ? { redactionPolicy } : {}),
161
+ ...(expiresAt ? { expiresAt } : {}),
162
+ };
163
+ }
164
+ function collectFile(root, relativePath, kind, budget) {
165
+ const rejection = pathRejection(relativePath);
166
+ if (rejection)
167
+ return { exclusion: excluded(relativePath, rejection) };
168
+ if (isIgnored(relativePath) || PRIVATE_CONTENT.test(relativePath))
169
+ return { exclusion: excluded(relativePath, 'private_evidence_rejected') };
170
+ if (!kind || !ALLOWED_EXTENSIONS.test(relativePath))
171
+ return { exclusion: excluded(relativePath, 'unsupported_file_type') };
172
+ const absolute = resolve(root, relativePath);
173
+ if (!insideRoot(root, absolute))
174
+ return { exclusion: excluded(relativePath, 'traversal_path_rejected') };
175
+ const stat = lstatSync(absolute);
176
+ if (stat.isSymbolicLink())
177
+ return { exclusion: excluded(relativePath, 'symlink_escape_rejected') };
178
+ const real = realpathSync(absolute);
179
+ if (!insideRoot(root, real))
180
+ return { exclusion: excluded(relativePath, 'symlink_escape_rejected') };
181
+ const realStat = statSync(real);
182
+ if (!realStat.isFile())
183
+ return { exclusion: excluded(relativePath, 'unsupported_file_type') };
184
+ if (realStat.size > budget.maxFileBytes)
185
+ return { exclusion: excluded(relativePath, 'oversized_evidence') };
186
+ const content = readFileSync(real);
187
+ if (content.includes(0) || PRIVATE_CONTENT.test(content.toString('utf8')))
188
+ return { exclusion: excluded(relativePath, 'private_evidence_rejected') };
189
+ const normalized = normalizePath(relativePath);
190
+ const contentHash = sha256(content);
191
+ const publicPath = opaqueEvidencePath(kind, normalized);
192
+ return {
193
+ item: {
194
+ id: `${kind}:${sha256(`${normalized}:${contentHash}`).slice(0, 20)}`,
195
+ kind,
196
+ source: { path: publicPath, contentHash, byteLength: realStat.size },
197
+ summary: structuralSummary(kind, content.toString('utf8')).slice(0, budget.maxSummaryChars),
198
+ },
199
+ };
200
+ }
201
+ function collectRichCandidate(root, candidate, consent, budget) {
202
+ const rejection = pathRejection(candidate.path);
203
+ if (rejection)
204
+ return { exclusion: excluded(candidate.path, rejection) };
205
+ if (!RICH_EXTENSIONS.test(candidate.path))
206
+ return { exclusion: excluded(candidate.path, 'unsupported_file_type') };
207
+ if (!consent.richEvidence || (candidate.kind === 'screenshot' ? !consent.screenshots : !consent.liveEvidence))
208
+ return { exclusion: excluded(candidate.path, 'consent_required') };
209
+ if (!candidate.redactionPolicy || !REDACTION_POLICIES.has(candidate.redactionPolicy) || candidate.redactionPolicy !== consent.redactionPolicy)
210
+ return { exclusion: excluded(candidate.path, 'redaction_policy_required') };
211
+ if (!candidate.expiresAt || candidate.expiresAt !== consent.expiresAt)
212
+ return { exclusion: excluded(candidate.path, 'ttl_invalid') };
213
+ if (isIgnored(candidate.path))
214
+ return { exclusion: excluded(candidate.path, 'private_evidence_rejected') };
215
+ const absolute = resolve(root, candidate.path);
216
+ if (!insideRoot(root, absolute))
217
+ return { exclusion: excluded(candidate.path, 'traversal_path_rejected') };
218
+ const stat = lstatSync(absolute);
219
+ if (stat.isSymbolicLink())
220
+ return { exclusion: excluded(candidate.path, 'symlink_escape_rejected') };
221
+ const real = realpathSync(absolute);
222
+ if (!insideRoot(root, real))
223
+ return { exclusion: excluded(candidate.path, 'symlink_escape_rejected') };
224
+ const realStat = statSync(real);
225
+ if (!realStat.isFile())
226
+ return { exclusion: excluded(candidate.path, 'unsupported_file_type') };
227
+ if (realStat.size > budget.maxFileBytes)
228
+ return { exclusion: excluded(candidate.path, 'oversized_evidence') };
229
+ const content = readFileSync(real);
230
+ const normalized = normalizePath(candidate.path);
231
+ const source = { path: opaqueEvidencePath(candidate.kind, normalized), contentHash: sha256(content), byteLength: realStat.size };
232
+ return {
233
+ item: {
234
+ id: `${candidate.kind}:${sha256(`${normalized}:${source.contentHash}`).slice(0, 20)}`,
235
+ kind: candidate.kind,
236
+ source,
237
+ summary: `${candidate.kind}; metadata-only; bytes=${source.byteLength}`,
238
+ consent: { redactionPolicy: candidate.redactionPolicy, expiresAt: candidate.expiresAt },
239
+ },
240
+ };
241
+ }
242
+ export function collectProjectEvidence(options) {
243
+ const root = realpathSync(options.projectRoot);
244
+ const collectedAt = new Date(options.collectedAt ?? Date.now()).toISOString();
245
+ const budget = { ...CLIENT_PROJECT_EVIDENCE_BUDGET, ...options.budget };
246
+ const consent = consentFor(options, collectedAt);
247
+ const exclusions = [];
248
+ const explicitCandidates = [];
249
+ for (const candidate of [...(options.candidatePaths ?? [])].sort()) {
250
+ try {
251
+ expandExplicitCandidate(root, candidate, explicitCandidates, exclusions);
252
+ }
253
+ catch {
254
+ exclusions.push(excluded(candidate, 'malformed_evidence'));
255
+ }
256
+ if (explicitCandidates.length >= MAX_SCAN_ENTRIES)
257
+ break;
258
+ }
259
+ const fallbackCandidates = [];
260
+ if (!options.candidatePaths || options.includeFallbackInventory === true) {
261
+ inventory(root, root, fallbackCandidates, exclusions, Math.max(0, MAX_SCAN_ENTRIES - explicitCandidates.length));
262
+ }
263
+ const explicitSet = new Set(explicitCandidates.map(normalizePath));
264
+ const candidates = [...new Set([...explicitCandidates, ...fallbackCandidates])].slice(0, MAX_SCAN_ENTRIES);
265
+ const collected = [];
266
+ for (const candidate of candidates) {
267
+ let result;
268
+ try {
269
+ result = collectFile(root, candidate, kindFor(normalizePath(candidate)), budget);
270
+ }
271
+ catch {
272
+ result = { exclusion: excluded(candidate, 'malformed_evidence') };
273
+ }
274
+ if (result.exclusion)
275
+ exclusions.push(result.exclusion);
276
+ if (result.item)
277
+ collected.push({ candidate, explicit: explicitSet.has(normalizePath(candidate)), item: result.item });
278
+ }
279
+ const byKind = new Map();
280
+ for (const entry of collected)
281
+ byKind.set(entry.item.kind, [...(byKind.get(entry.item.kind) ?? []), entry]);
282
+ const priority = (left, right) => Number(right.explicit) - Number(left.explicit) || left.candidate.localeCompare(right.candidate);
283
+ for (const entries of byKind.values())
284
+ entries.sort(priority);
285
+ const reserved = [...byKind.keys()].sort().flatMap((kind) => byKind.get(kind)?.slice(0, 1) ?? []);
286
+ const reservedIds = new Set(reserved.map((entry) => entry.item.id));
287
+ const fill = collected.filter((entry) => !reservedIds.has(entry.item.id)).sort(priority);
288
+ const evidence = [];
289
+ let acceptedBytes = 0;
290
+ for (const entry of [...reserved, ...fill]) {
291
+ if (evidence.length >= budget.maxItems || acceptedBytes + entry.item.source.byteLength > budget.maxTotalBytes) {
292
+ exclusions.push(excluded(entry.candidate, 'budget_exceeded'));
293
+ continue;
294
+ }
295
+ evidence.push(entry.item);
296
+ acceptedBytes += entry.item.source.byteLength;
297
+ }
298
+ for (const candidate of options.richCandidates ?? []) {
299
+ let result;
300
+ try {
301
+ result = collectRichCandidate(root, candidate, consent, budget);
302
+ }
303
+ catch {
304
+ result = { exclusion: excluded(candidate.path, 'malformed_evidence') };
305
+ }
306
+ if (result.exclusion)
307
+ exclusions.push(result.exclusion);
308
+ if (!result.item)
309
+ continue;
310
+ if (evidence.length >= budget.maxItems || acceptedBytes + result.item.source.byteLength > budget.maxTotalBytes) {
311
+ exclusions.push(excluded(candidate.path, 'budget_exceeded'));
312
+ continue;
313
+ }
314
+ evidence.push(result.item);
315
+ acceptedBytes += result.item.source.byteLength;
316
+ }
317
+ evidence.sort((left, right) => left.kind.localeCompare(right.kind) || left.source.path.localeCompare(right.source.path));
318
+ const evidenceHash = sha256(stableJson(evidence));
319
+ let envelope = {
320
+ schemaVersion: 1,
321
+ envelopeId: `evidence-${evidenceHash.slice(0, 24)}`,
322
+ evidenceHash,
323
+ collectedAt,
324
+ consent,
325
+ budget: { ...budget, acceptedItems: evidence.length, acceptedBytes },
326
+ evidence,
327
+ exclusions: exclusions.slice(0, budget.maxExclusions),
328
+ };
329
+ while (Buffer.byteLength(JSON.stringify(envelope), 'utf8') > budget.maxEnvelopeBytes && envelope.evidence.length > 0) {
330
+ const removed = envelope.evidence.at(-1);
331
+ const nextEvidence = envelope.evidence.slice(0, -1);
332
+ const nextHash = sha256(stableJson(nextEvidence));
333
+ envelope = {
334
+ ...envelope,
335
+ envelopeId: `evidence-${nextHash.slice(0, 24)}`,
336
+ evidenceHash: nextHash,
337
+ budget: {
338
+ ...envelope.budget,
339
+ acceptedItems: nextEvidence.length,
340
+ acceptedBytes: nextEvidence.reduce((total, item) => total + item.source.byteLength, 0),
341
+ },
342
+ evidence: nextEvidence,
343
+ exclusions: [...envelope.exclusions, excluded(removed.id, 'budget_exceeded')].slice(0, budget.maxExclusions),
344
+ };
345
+ }
346
+ return envelope;
347
+ }
@@ -1 +1 @@
1
- import{CLIENT_PROJECT_EVIDENCE_BUDGET as c,collectProjectEvidence as o}from"./collector.js";export{c as CLIENT_PROJECT_EVIDENCE_BUDGET,o as collectProjectEvidence};
1
+ export { CLIENT_PROJECT_EVIDENCE_BUDGET, collectProjectEvidence } from './collector.js';
@@ -0,0 +1,2 @@
1
+ /** Runtime-local mirror of the project evidence contract. */
2
+ export {};
@@ -1 +1,152 @@
1
- import{randomUUID as C}from"node:crypto";import{closeSync as E,existsSync as f,fsyncSync as y,lstatSync as p,mkdirSync as w,openSync as u,readdirSync as R,renameSync as l,unlinkSync as d,writeFileSync as A}from"node:fs";import{basename as S,dirname as s,join as h}from"node:path";const _=/^\d+\.[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\.(tmp|swap)$/i;function k(t){const n=s(t);if(!f(n))return"none";const o=`${S(t)}.`,e=R(n).filter(r=>r.startsWith(o)&&_.test(r.slice(o.length))).map(r=>({entry:r,path:h(n,r),kind:r.endsWith(".swap")?"swap":"tmp"}));if(f(t)){if(!p(t).isFile())throw new Error("ATOMIC_REPLACE_RECOVERY_UNSAFE");for(const r of e)if(!p(r.path).isFile())throw new Error("ATOMIC_REPLACE_RECOVERY_UNSAFE");for(const r of e)d(r.path);return e.length>0&&m(n),"none"}const c=e.filter(r=>r.kind==="swap"),a=e.filter(r=>r.kind==="tmp");if(c.length>1||c.length===0&&a.length>1)throw new Error("ATOMIC_REPLACE_RECOVERY_AMBIGUOUS");const i=c[0]??a[0];if(!i)return"none";if(!p(i.path).isFile())throw new Error("ATOMIC_REPLACE_RECOVERY_UNSAFE");l(i.path,t);for(const r of e)if(!(r.path===i.path||!f(r.path)))try{d(r.path)}catch{}return m(n),i.kind==="swap"?"restored-swap":"promoted-temp"}function m(t){let n;try{n=u(t,"r"),y(n)}catch{}finally{n!==void 0&&E(n)}}function x(t,n){w(s(t),{recursive:!0,mode:448});let o;try{o=u(t,"wx",384),A(o,n,"utf8"),y(o)}catch(e){if(o!==void 0){try{E(o)}catch{}o=void 0}try{f(t)&&d(t)}catch{}throw m(s(t)),e}finally{o!==void 0&&E(o)}m(s(t))}function $(t,n){w(s(t),{recursive:!0,mode:448});const o=`${process.pid}.${C()}`,e=`${t}.${o}.tmp`,c=`${t}.${o}.swap`;A(e,n,{encoding:"utf8",mode:384,flag:"wx"});const a=u(e,"r");try{y(a)}finally{E(a)}try{l(e,t)}catch{let i=!1;try{f(t)&&(l(t,c),i=!0),l(e,t)}catch(r){i&&!f(t)&&f(c)&&l(c,t);try{f(e)&&d(e)}catch{}throw r}if(i)try{d(c)}catch{}}m(s(t))}export{$ as atomicReplaceUtf8,x as durableCreateUtf8,m as fsyncDirectoryBestEffort,k as recoverAtomicReplaceUtf8};
1
+ import { randomUUID } from 'node:crypto';
2
+ import { closeSync, existsSync, fsyncSync, lstatSync, mkdirSync, openSync, readdirSync, renameSync, unlinkSync, writeFileSync, } from 'node:fs';
3
+ import { basename, dirname, join } from 'node:path';
4
+ const ATOMIC_TOKEN = /^\d+\.[a-f0-9]{8}-[a-f0-9]{4}-[1-5][a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}\.(tmp|swap)$/i;
5
+ /**
6
+ * Recover a fully-fsynced artifact left by a process crash. A swap is the
7
+ * conservative authority because it restores the last committed destination;
8
+ * without a swap, one unambiguous temp can be promoted for first creation.
9
+ */
10
+ export function recoverAtomicReplaceUtf8(path) {
11
+ const directory = dirname(path);
12
+ if (!existsSync(directory))
13
+ return 'none';
14
+ const prefix = `${basename(path)}.`;
15
+ const artifacts = readdirSync(directory)
16
+ .filter((entry) => entry.startsWith(prefix) && ATOMIC_TOKEN.test(entry.slice(prefix.length)))
17
+ .map((entry) => ({
18
+ entry,
19
+ path: join(directory, entry),
20
+ kind: entry.endsWith('.swap') ? 'swap' : 'tmp',
21
+ }));
22
+ if (existsSync(path)) {
23
+ if (!lstatSync(path).isFile())
24
+ throw new Error('ATOMIC_REPLACE_RECOVERY_UNSAFE');
25
+ for (const artifact of artifacts) {
26
+ if (!lstatSync(artifact.path).isFile())
27
+ throw new Error('ATOMIC_REPLACE_RECOVERY_UNSAFE');
28
+ }
29
+ for (const artifact of artifacts)
30
+ unlinkSync(artifact.path);
31
+ if (artifacts.length > 0)
32
+ fsyncDirectoryBestEffort(directory);
33
+ return 'none';
34
+ }
35
+ const swaps = artifacts.filter((artifact) => artifact.kind === 'swap');
36
+ const temps = artifacts.filter((artifact) => artifact.kind === 'tmp');
37
+ if (swaps.length > 1 || (swaps.length === 0 && temps.length > 1)) {
38
+ throw new Error('ATOMIC_REPLACE_RECOVERY_AMBIGUOUS');
39
+ }
40
+ const selected = swaps[0] ?? temps[0];
41
+ if (!selected)
42
+ return 'none';
43
+ if (!lstatSync(selected.path).isFile())
44
+ throw new Error('ATOMIC_REPLACE_RECOVERY_UNSAFE');
45
+ renameSync(selected.path, path);
46
+ for (const artifact of artifacts) {
47
+ if (artifact.path === selected.path || !existsSync(artifact.path))
48
+ continue;
49
+ try {
50
+ unlinkSync(artifact.path);
51
+ }
52
+ catch { /* recovered destination is authoritative */ }
53
+ }
54
+ fsyncDirectoryBestEffort(directory);
55
+ return selected.kind === 'swap' ? 'restored-swap' : 'promoted-temp';
56
+ }
57
+ /** Best-effort directory durability; Windows can reject directory handles. */
58
+ export function fsyncDirectoryBestEffort(path) {
59
+ let fd;
60
+ try {
61
+ fd = openSync(path, 'r');
62
+ fsyncSync(fd);
63
+ }
64
+ catch {
65
+ // The temp file itself is always fsynced before replacement. On hosts that
66
+ // cannot fsync directories this is the strongest portable boundary.
67
+ }
68
+ finally {
69
+ if (fd !== undefined)
70
+ closeSync(fd);
71
+ }
72
+ }
73
+ /** Create a new UTF-8 file and make its contents durable before returning. */
74
+ export function durableCreateUtf8(path, contents) {
75
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
76
+ let fd;
77
+ try {
78
+ fd = openSync(path, 'wx', 0o600);
79
+ writeFileSync(fd, contents, 'utf8');
80
+ fsyncSync(fd);
81
+ }
82
+ catch (error) {
83
+ if (fd !== undefined) {
84
+ try {
85
+ closeSync(fd);
86
+ }
87
+ catch { /* best effort before cleanup */ }
88
+ fd = undefined;
89
+ }
90
+ try {
91
+ if (existsSync(path))
92
+ unlinkSync(path);
93
+ }
94
+ catch { /* best effort */ }
95
+ fsyncDirectoryBestEffort(dirname(path));
96
+ throw error;
97
+ }
98
+ finally {
99
+ if (fd !== undefined)
100
+ closeSync(fd);
101
+ }
102
+ fsyncDirectoryBestEffort(dirname(path));
103
+ }
104
+ /**
105
+ * Replace one UTF-8 state file without truncating the live destination.
106
+ * The fallback swap exists for Windows rename semantics and restores the
107
+ * original if installing the new file fails.
108
+ */
109
+ export function atomicReplaceUtf8(path, contents) {
110
+ mkdirSync(dirname(path), { recursive: true, mode: 0o700 });
111
+ const token = `${process.pid}.${randomUUID()}`;
112
+ const temp = `${path}.${token}.tmp`;
113
+ const swap = `${path}.${token}.swap`;
114
+ writeFileSync(temp, contents, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
115
+ const fd = openSync(temp, 'r');
116
+ try {
117
+ fsyncSync(fd);
118
+ }
119
+ finally {
120
+ closeSync(fd);
121
+ }
122
+ try {
123
+ renameSync(temp, path);
124
+ }
125
+ catch {
126
+ let movedOriginal = false;
127
+ try {
128
+ if (existsSync(path)) {
129
+ renameSync(path, swap);
130
+ movedOriginal = true;
131
+ }
132
+ renameSync(temp, path);
133
+ }
134
+ catch (fallbackError) {
135
+ if (movedOriginal && !existsSync(path) && existsSync(swap))
136
+ renameSync(swap, path);
137
+ try {
138
+ if (existsSync(temp))
139
+ unlinkSync(temp);
140
+ }
141
+ catch { /* best effort */ }
142
+ throw fallbackError;
143
+ }
144
+ if (movedOriginal) {
145
+ try {
146
+ unlinkSync(swap);
147
+ }
148
+ catch { /* stale swap is safer than an uncertain replace */ }
149
+ }
150
+ }
151
+ fsyncDirectoryBestEffort(dirname(path));
152
+ }