@ornexus/neocortex 4.60.66 → 4.60.68

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 (236) hide show
  1. package/dist/release-source.json +1 -1
  2. package/dist/sbom.cdx.json +1598 -1640
  3. package/install.ps1 +1 -1
  4. package/install.sh +2 -1
  5. package/package.json +10 -7
  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 +303 -3
  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.d.ts +135 -0
  37. package/packages/client/dist/commands/doctor.js +635 -0
  38. package/packages/client/dist/commands/invoke.d.ts +18 -0
  39. package/packages/client/dist/commands/invoke.js +7140 -142
  40. package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
  41. package/packages/client/dist/commands/legacy-story-branch.js +96 -1
  42. package/packages/client/dist/commands/legacy-story-pr.js +276 -1
  43. package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
  44. package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
  45. package/packages/client/dist/commands/mutation-qa-git.js +41 -1
  46. package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
  47. package/packages/client/dist/commands/mutation-qa-run.js +219 -3
  48. package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
  49. package/packages/client/dist/commands/mutation-qa.js +220 -1
  50. package/packages/client/dist/commands/native-agent.js +136 -2
  51. package/packages/client/dist/commands/planning-diagnosis.js +43 -1
  52. package/packages/client/dist/commands/planning-generation.js +53 -1
  53. package/packages/client/dist/commands/planning-memory.js +40 -1
  54. package/packages/client/dist/commands/planning-research.js +42 -1
  55. package/packages/client/dist/commands/private-session.js +75 -1
  56. package/packages/client/dist/commands/property-qa.js +139 -1
  57. package/packages/client/dist/commands/qa-control.js +16 -1
  58. package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
  59. package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
  60. package/packages/client/dist/commands/qa-scan-capture.js +663 -5
  61. package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
  62. package/packages/client/dist/commands/qa-scan-review.js +826 -5
  63. package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
  64. package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
  65. package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
  66. package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
  67. package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
  68. package/packages/client/dist/commands/refactor-characterization.js +671 -21
  69. package/packages/client/dist/commands/refactor-commit.js +399 -13
  70. package/packages/client/dist/commands/refactor-control.js +142 -3
  71. package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
  72. package/packages/client/dist/commands/refactor-review.js +283 -5
  73. package/packages/client/dist/commands/refactor-root.js +588 -7
  74. package/packages/client/dist/commands/refresh-memory.js +156 -3
  75. package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
  76. package/packages/client/dist/commands/story-checkpoint.js +171 -1
  77. package/packages/client/dist/config/profile-directory.js +13 -1
  78. package/packages/client/dist/config/resolver-selection.js +278 -1
  79. package/packages/client/dist/config/secure-config.js +281 -12
  80. package/packages/client/dist/config.js +223 -2
  81. package/packages/client/dist/constants.js +25 -1
  82. package/packages/client/dist/context/context-collector.js +222 -2
  83. package/packages/client/dist/context/context-sanitizer.js +145 -1
  84. package/packages/client/dist/continuity/autonomous-governance.js +285 -1
  85. package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
  86. package/packages/client/dist/continuity/invoke-hooks.js +129 -1
  87. package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
  88. package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
  89. package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
  90. package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
  91. package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
  92. package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
  93. package/packages/client/dist/continuity/native-agent-store.js +69 -1
  94. package/packages/client/dist/continuity/native-agent-types.js +90 -1
  95. package/packages/client/dist/continuity/outbox.js +117 -1
  96. package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
  97. package/packages/client/dist/continuity/sqlite-store.js +2536 -81
  98. package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
  99. package/packages/client/dist/errors/error-messages.js +228 -2
  100. package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
  101. package/packages/client/dist/evidence/collector.js +347 -1
  102. package/packages/client/dist/evidence/index.js +1 -1
  103. package/packages/client/dist/evidence/types.js +2 -0
  104. package/packages/client/dist/fs/atomic-state-replace.js +152 -1
  105. package/packages/client/dist/generated/command-registry.generated.js +4076 -1
  106. package/packages/client/dist/generated/public-projections.generated.js +149 -1
  107. package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
  108. package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
  109. package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
  110. package/packages/client/dist/git/git-lifecycle-store.js +1938 -9
  111. package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
  112. package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
  113. package/packages/client/dist/git/legacy-story-pr.js +3538 -70
  114. package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
  115. package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
  116. package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
  117. package/packages/client/dist/i18n/first-run.js +60 -2
  118. package/packages/client/dist/index.js +47 -1
  119. package/packages/client/dist/invocation/invoke-input.js +24 -1
  120. package/packages/client/dist/license/index.js +5 -1
  121. package/packages/client/dist/license/license-client.js +304 -1
  122. package/packages/client/dist/machine/fingerprint.js +163 -2
  123. package/packages/client/dist/machine/index.js +5 -1
  124. package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
  125. package/packages/client/dist/memory/project-memory-writer.js +1007 -40
  126. package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
  127. package/packages/client/dist/migrations/project-identity-capability.js +50 -1
  128. package/packages/client/dist/migrations/project-identity.js +493 -7
  129. package/packages/client/dist/migrations/state-json.js +344 -4
  130. package/packages/client/dist/policy/architecture-policy.js +184 -2
  131. package/packages/client/dist/policy/index.js +7 -1
  132. package/packages/client/dist/policy/shared-policy-types.js +38 -0
  133. package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
  134. package/packages/client/dist/private-execution/clean-baseline.js +279 -2
  135. package/packages/client/dist/private-execution/codex-executor.js +574 -4
  136. package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
  137. package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
  138. package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
  139. package/packages/client/dist/private-execution/delivery.js +410 -13
  140. package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
  141. package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
  142. package/packages/client/dist/private-execution/implementation-validation.js +106 -2
  143. package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
  144. package/packages/client/dist/private-execution/isolated-session.js +725 -5
  145. package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
  146. package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
  147. package/packages/client/dist/private-execution/pact-test-source.js +258 -1
  148. package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
  149. package/packages/client/dist/private-execution/pact-write-set.js +172 -1
  150. package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
  151. package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
  152. package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
  153. package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
  154. package/packages/client/dist/private-execution/private-report-store.js +42 -1
  155. package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
  156. package/packages/client/dist/private-execution/property-test-choices.js +147 -3
  157. package/packages/client/dist/private-execution/property-test-plan.js +271 -7
  158. package/packages/client/dist/private-execution/property-test-review.js +258 -4
  159. package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
  160. package/packages/client/dist/private-execution/property-test-source.js +139 -1
  161. package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
  162. package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
  163. package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
  164. package/packages/client/dist/private-execution/root-memory-request.js +29 -1
  165. package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
  166. package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
  167. package/packages/client/dist/private-execution/session-lease.js +63 -1
  168. package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
  169. package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
  170. package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
  171. package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
  172. package/packages/client/dist/private-execution/ux-write-set.js +191 -2
  173. package/packages/client/dist/resilience/circuit-breaker.js +170 -1
  174. package/packages/client/dist/resilience/degradation-manager.js +164 -1
  175. package/packages/client/dist/resilience/freshness-indicator.js +100 -1
  176. package/packages/client/dist/resilience/index.js +8 -1
  177. package/packages/client/dist/resilience/recovery-detector.js +74 -1
  178. package/packages/client/dist/resolvers/asset-resolver.js +13 -0
  179. package/packages/client/dist/resolvers/local-resolver.js +255 -8
  180. package/packages/client/dist/resolvers/remote-resolver.js +233 -1
  181. package/packages/client/dist/runner/binary.js +404 -1
  182. package/packages/client/dist/runner/cli.js +1571 -17
  183. package/packages/client/dist/runner/headless/adapters.js +49 -1
  184. package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
  185. package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
  186. package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
  187. package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
  188. package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
  189. package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
  190. package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
  191. package/packages/client/dist/runner/headless/managed-profile.js +132 -1
  192. package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
  193. package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
  194. package/packages/client/dist/runner/headless/resolver.js +231 -1
  195. package/packages/client/dist/runner/headless/run-yolo.js +885 -1
  196. package/packages/client/dist/runner/headless/run-yoloop.d.ts +4 -1
  197. package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
  198. package/packages/client/dist/runner/headless/types.js +574 -2
  199. package/packages/client/dist/runner/local-console.js +396 -13
  200. package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
  201. package/packages/client/dist/runner/project-identity.js +106 -1
  202. package/packages/client/dist/runner/scheduler.js +995 -7
  203. package/packages/client/dist/runner-cli.js +18 -2
  204. package/packages/client/dist/state/invocation-shape.js +13 -1
  205. package/packages/client/dist/state/project-state-snapshot.js +1677 -1
  206. package/packages/client/dist/state/state-json-repair.js +785 -3
  207. package/packages/client/dist/state/structural-dag-projection.js +118 -1
  208. package/packages/client/dist/telemetry/index.js +5 -1
  209. package/packages/client/dist/telemetry/offline-queue.js +131 -1
  210. package/packages/client/dist/tier/index.js +5 -1
  211. package/packages/client/dist/tier/tier-aware-client.js +405 -1
  212. package/packages/client/dist/types/index.js +38 -1
  213. package/packages/client/dist/version.js +27 -1
  214. package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
  215. package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
  216. package/packages/client/dist/yolo/spec-envelope-collector.js +185 -5
  217. package/packages/client/dist/yoloop/discovery-hook.js +300 -2
  218. package/packages/client/dist/yoloop/index.js +8 -1
  219. package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
  220. package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
  221. package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
  222. package/packages/client/dist/yoloop/target-normalization.js +180 -1
  223. package/packages/client/dist/yoloop/yoloop-client-state-store.js +1723 -1
  224. package/packages/shared/dist/compatibility/index.js +476 -0
  225. package/packages/shared/dist/yoloop/release-blocked.js +653 -0
  226. package/targets-stubs/antigravity/gemini.md +1 -1
  227. package/targets-stubs/antigravity/skill/SKILL.md +3 -2
  228. package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
  229. package/targets-stubs/claude-code/neocortex-root.md +5 -5
  230. package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
  231. package/targets-stubs/claude-code/neocortex.md +8 -9
  232. package/targets-stubs/codex/AGENTS.md +3 -3
  233. package/targets-stubs/cursor/agent.md +4 -3
  234. package/targets-stubs/gemini-cli/agent.md +5 -4
  235. package/targets-stubs/opencode/neocortex-root.md +3 -3
  236. package/targets-stubs/vscode/neocortex.agent.md +5 -4
@@ -1,4 +1,17 @@
1
- import{spawn as b}from"node:child_process";import{randomBytes as B}from"node:crypto";import{lstat as w,stat as x}from"node:fs/promises";const T=String.raw`
1
+ /**
2
+ * Handle-anchored filesystem mutations for private lifecycle namespaces.
3
+ *
4
+ * Node does not expose openat(2). A short-lived helper is started with the
5
+ * validated parent as its cwd, verifies the cwd device/inode before effects,
6
+ * and performs every final mutation with relative basenames. Once the OS has
7
+ * handed the cwd to the helper, renaming/replacing the public parent path can
8
+ * no longer redirect the effect. Platforms without that POSIX guarantee fail
9
+ * closed instead of using a pathname fallback.
10
+ */
11
+ import { spawn } from 'node:child_process';
12
+ import { randomBytes } from 'node:crypto';
13
+ import { lstat, stat } from 'node:fs/promises';
14
+ const HELPER = String.raw `
2
15
  const fs = require('node:fs');
3
16
  const crypto = require('node:crypto');
4
17
  const [op, expectedDev, expectedIno, name, temporary, expectedSha] = process.argv.slice(1);
@@ -93,7 +106,8 @@ if (op === 'mkdir' || op === 'mkdir-exclusive') {
93
106
  if (safeExisting(name, 'directory')) fail('secure-filesystem-directory-remained');
94
107
  } else fail('secure-filesystem-operation-invalid');
95
108
  try { const parent = fs.openSync('.', fs.constants.O_RDONLY); fs.fsyncSync(parent); fs.closeSync(parent); } catch {}
96
- `,k=String.raw`
109
+ `;
110
+ const GIT_HELPER = String.raw `
97
111
  const fs = require('node:fs');
98
112
  const cp = require('node:child_process');
99
113
  const [expectedParentDev, expectedParentIno, name, expectedChildDev, expectedChildIno, encodedArgs, handshake, binary] = process.argv.slice(1);
@@ -145,6 +159,169 @@ if (result.error) fail('secure-git-command-error:' + String(result.error.code ||
145
159
  if (result.status !== 0) fail('secure-git-command-failed:' + String(Number.isInteger(result.status) ? result.status : 1));
146
160
  if (result.stdout && result.stdout.length > maxOutputBytes) fail('secure-git-output-too-large');
147
161
  if (result.stdout) process.stdout.write(result.stdout);
148
- `;function v(r=process.platform){return r!=="win32"}async function G(r,t,e,i={}){const s=`.${t}.${process.pid}.${B(8).toString("hex")}.tmp`;await y(r,["write",t,s],Buffer.isBuffer(e)?e:Buffer.from(e),i)}async function P(r,t,e,i,s={}){const n=`.${t}.${process.pid}.${B(8).toString("hex")}.tmp`;await y(r,["write-cas",t,n,i],Buffer.isBuffer(e)?e:Buffer.from(e),s)}async function H(r,t,e={}){await y(r,["remove",t],void 0,e)}async function W(r,t,e={}){await y(r,["mkdir",t],void 0,e)}async function $(r,t){const i=(await y(r,["mkdir-exclusive",t])).toString("utf8").match(/^secure-filesystem-identity:([0-9]+):([0-9]+)$/);if(!i)throw new Error("secure-filesystem-created-identity-missing");return Object.freeze({dev:i[1],ino:i[2]})}async function X(r){if(!v())throw new Error("secure-filesystem-platform-unsupported");const t=await w(r,{bigint:!0}),e=await x(r,{bigint:!0});if(t.isSymbolicLink()||!t.isDirectory()||!e.isDirectory()||t.dev!==e.dev||t.ino!==e.ino)throw new Error("secure-filesystem-directory-unsafe");return Object.freeze({dev:String(e.dev),ino:String(e.ino)})}async function Y(r,t,e,i,s,n={}){return O("git",r,t,e,i,s,n)}async function M(r,t,e,i,s,n={}){return O("node",r,t,e,i,s,n)}async function K(r,t,e,i){return O("npm",r,t,e,i)}async function O(r,t,e,i,s,n,f={}){if(!v(f.platform??process.platform))throw new Error("secure-filesystem-platform-unsupported");if(s.length===0||s.length>512||s.some(a=>typeof a!="string"||a.length>4096||a.includes("\0")))throw new Error("secure-git-args-invalid");const d=await x(t,{bigint:!0}),o=await w(t,{bigint:!0});if(!d.isDirectory()||o.isSymbolicLink()||!o.isDirectory()||d.dev!==o.dev||d.ino!==o.ino)throw new Error("secure-filesystem-parent-invalid");await f.beforeHandoff?.();const m=r==="git"?16*1024*1024:64*1024*1024;return await new Promise((a,p)=>{const l=f.afterHandoff?"1":"0",g=Buffer.from(JSON.stringify([...s])).toString("base64url"),c=b(process.execPath,["-e",k,String(d.dev),String(d.ino),e,i.dev,i.ino,g,l,r],{cwd:t,stdio:["pipe","pipe","pipe"]}),h=[],E=[];let S=0,_=!f.afterHandoff,I=!1;const N=async()=>{if(!I){I=!0;try{await f.afterHandoff?.(),_=!0,c.stdin.end(n===void 0?void 0:Buffer.isBuffer(n)?n:Buffer.from(n))}catch(u){c.kill(),p(u)}}};c.stdout.on("data",u=>{S+=u.length,S>m?c.kill():h.push(u)}),c.stderr.on("data",u=>{E.push(u),!_&&Buffer.concat(E).includes(Buffer.from(`secure-git-handoff-ready
149
- `))&&N()}),c.once("error",p),c.once("close",u=>{if(u===0&&S<=m)return a(Buffer.concat(h));const R=Buffer.concat(E).toString("utf8").split(`
150
- `).find(D=>D.startsWith("secure-git-")&&D!=="secure-git-handoff-ready")??(S>m?"secure-git-output-too-large":"secure-git-helper-failed");p(new Error(R))}),f.afterHandoff||N()})}async function q(r,t,e){await y(r,["create-owned-directory",t],Buffer.isBuffer(e)?e:Buffer.from(e))}async function J(r,t,e,i={}){await y(r,["rename-directory",t,e],void 0,i)}async function U(r,t,e={}){await y(r,["remove-directory",t],void 0,e)}async function y(r,t,e,i={}){if(!v(i.platform??process.platform))throw new Error("secure-filesystem-platform-unsupported");const s=await x(r,{bigint:!0});if(!s.isDirectory())throw new Error("secure-filesystem-parent-invalid");if((await w(r,{bigint:!0})).isSymbolicLink())throw new Error("secure-filesystem-parent-symlink");return await i.beforeHandoff?.(),await new Promise((f,d)=>{const o=b(process.execPath,["-e",T,...t.slice(0,1),String(s.dev),String(s.ino),...t.slice(1)],{cwd:r,stdio:["pipe","pipe","pipe"]}),m=[];let a=0;const p=[];o.stdout.on("data",l=>{a+=l.length,a>1024?o.kill():m.push(l)}),o.stderr.on("data",l=>p.push(l)),o.once("error",d),o.once("close",l=>{if(l===0&&a<=1024)return f(Buffer.concat(m));const g=Buffer.concat(p).toString("utf8")||"secure-filesystem-helper-failed",c=new Error(g),h=g.match(/^secure-filesystem-os:(E[A-Z0-9]+)$/)?.[1];h&&(c.code=h),g==="secure-filesystem-entry-exists"&&(c.code="EEXIST"),d(c)}),o.stdin.end(e)})}export{G as anchoredAtomicWriteFile,P as anchoredCompareAndSwapFile,$ as anchoredCreateDirectory,q as anchoredCreateOwnedDirectory,X as anchoredDirectoryIdentity,W as anchoredEnsureDirectory,Y as anchoredExecuteGit,M as anchoredExecuteNode,K as anchoredExecuteNpm,U as anchoredRemoveDirectory,H as anchoredRemoveFile,J as anchoredRenameDirectory,v as secureFilesystemBoundaryAvailable};
162
+ `;
163
+ export function secureFilesystemBoundaryAvailable(platform = process.platform) {
164
+ return platform !== 'win32';
165
+ }
166
+ export async function anchoredAtomicWriteFile(parent, name, content, options = {}) {
167
+ const temporary = `.${name}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
168
+ await runAnchored(parent, ['write', name, temporary], Buffer.isBuffer(content) ? content : Buffer.from(content), options);
169
+ }
170
+ /** Replace a regular tracked file only while its exact preimage still matches. */
171
+ export async function anchoredCompareAndSwapFile(parent, name, content, expectedSha256, options = {}) {
172
+ const temporary = `.${name}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`;
173
+ await runAnchored(parent, ['write-cas', name, temporary, expectedSha256], Buffer.isBuffer(content) ? content : Buffer.from(content), options);
174
+ }
175
+ export async function anchoredRemoveFile(parent, name, options = {}) {
176
+ await runAnchored(parent, ['remove', name], undefined, options);
177
+ }
178
+ export async function anchoredEnsureDirectory(parent, name, options = {}) {
179
+ await runAnchored(parent, ['mkdir', name], undefined, options);
180
+ }
181
+ export async function anchoredCreateDirectory(parent, name) {
182
+ const output = (await runAnchored(parent, ['mkdir-exclusive', name])).toString('utf8');
183
+ const match = output.match(/^secure-filesystem-identity:([0-9]+):([0-9]+)$/);
184
+ if (!match)
185
+ throw new Error('secure-filesystem-created-identity-missing');
186
+ return Object.freeze({ dev: match[1], ino: match[2] });
187
+ }
188
+ export async function anchoredDirectoryIdentity(path) {
189
+ if (!secureFilesystemBoundaryAvailable())
190
+ throw new Error('secure-filesystem-platform-unsupported');
191
+ const entry = await lstat(path, { bigint: true });
192
+ const identity = await stat(path, { bigint: true });
193
+ if (entry.isSymbolicLink() || !entry.isDirectory() || !identity.isDirectory()
194
+ || entry.dev !== identity.dev || entry.ino !== identity.ino) {
195
+ throw new Error('secure-filesystem-directory-unsafe');
196
+ }
197
+ return Object.freeze({ dev: String(identity.dev), ino: String(identity.ino) });
198
+ }
199
+ /** Execute Git only after a helper owns and revalidates the sandbox directory cwd. */
200
+ export async function anchoredExecuteGit(parent, name, expectedChild, args, input, options = {}) {
201
+ return runAnchoredCommand('git', parent, name, expectedChild, args, input, options);
202
+ }
203
+ /** Gate commands inherit a pinned cwd, never a mutable sandbox pathname. */
204
+ export async function anchoredExecuteNode(parent, name, expectedChild, args, input, options = {}) {
205
+ return runAnchoredCommand('node', parent, name, expectedChild, args, input, options);
206
+ }
207
+ export async function anchoredExecuteNpm(parent, name, expectedChild, args) {
208
+ return runAnchoredCommand('npm', parent, name, expectedChild, args);
209
+ }
210
+ async function runAnchoredCommand(binary, parent, name, expectedChild, args, input, options = {}) {
211
+ if (!secureFilesystemBoundaryAvailable(options.platform ?? process.platform)) {
212
+ throw new Error('secure-filesystem-platform-unsupported');
213
+ }
214
+ if (args.length === 0 || args.length > 512
215
+ || args.some((arg) => typeof arg !== 'string' || arg.length > 4096 || arg.includes('\0'))) {
216
+ throw new Error('secure-git-args-invalid');
217
+ }
218
+ const identity = await stat(parent, { bigint: true });
219
+ const entry = await lstat(parent, { bigint: true });
220
+ if (!identity.isDirectory() || entry.isSymbolicLink() || !entry.isDirectory()
221
+ || identity.dev !== entry.dev || identity.ino !== entry.ino) {
222
+ throw new Error('secure-filesystem-parent-invalid');
223
+ }
224
+ await options.beforeHandoff?.();
225
+ const maxOutputBytes = binary === 'git' ? 16 * 1024 * 1024 : 64 * 1024 * 1024;
226
+ return await new Promise((resolveProcess, rejectProcess) => {
227
+ const handshake = options.afterHandoff ? '1' : '0';
228
+ const encodedArgs = Buffer.from(JSON.stringify([...args])).toString('base64url');
229
+ const child = spawn(process.execPath, [
230
+ '-e', GIT_HELPER, String(identity.dev), String(identity.ino), name,
231
+ expectedChild.dev, expectedChild.ino, encodedArgs, handshake, binary,
232
+ ], { cwd: parent, stdio: ['pipe', 'pipe', 'pipe'] });
233
+ const output = [];
234
+ const errors = [];
235
+ let outputBytes = 0;
236
+ let handoffObserved = !options.afterHandoff;
237
+ let hookStarted = false;
238
+ const finishInput = async () => {
239
+ if (hookStarted)
240
+ return;
241
+ hookStarted = true;
242
+ try {
243
+ await options.afterHandoff?.();
244
+ handoffObserved = true;
245
+ child.stdin.end(input === undefined ? undefined : Buffer.isBuffer(input) ? input : Buffer.from(input));
246
+ }
247
+ catch (error) {
248
+ child.kill();
249
+ rejectProcess(error);
250
+ }
251
+ };
252
+ child.stdout.on('data', (chunk) => {
253
+ outputBytes += chunk.length;
254
+ if (outputBytes > maxOutputBytes)
255
+ child.kill();
256
+ else
257
+ output.push(chunk);
258
+ });
259
+ child.stderr.on('data', (chunk) => {
260
+ errors.push(chunk);
261
+ if (!handoffObserved && Buffer.concat(errors).includes(Buffer.from('secure-git-handoff-ready\n')))
262
+ void finishInput();
263
+ });
264
+ child.once('error', rejectProcess);
265
+ child.once('close', (code) => {
266
+ if (code === 0 && outputBytes <= maxOutputBytes)
267
+ return resolveProcess(Buffer.concat(output));
268
+ const raw = Buffer.concat(errors).toString('utf8');
269
+ const reason = raw.split('\n').find((line) => line.startsWith('secure-git-') && line !== 'secure-git-handoff-ready')
270
+ ?? (outputBytes > maxOutputBytes ? 'secure-git-output-too-large' : 'secure-git-helper-failed');
271
+ rejectProcess(new Error(reason));
272
+ });
273
+ if (!options.afterHandoff)
274
+ void finishInput();
275
+ });
276
+ }
277
+ export async function anchoredCreateOwnedDirectory(parent, name, owner) {
278
+ await runAnchored(parent, ['create-owned-directory', name], Buffer.isBuffer(owner) ? owner : Buffer.from(owner));
279
+ }
280
+ export async function anchoredRenameDirectory(parent, sourceName, destinationName, options = {}) {
281
+ await runAnchored(parent, ['rename-directory', sourceName, destinationName], undefined, options);
282
+ }
283
+ export async function anchoredRemoveDirectory(parent, name, options = {}) {
284
+ await runAnchored(parent, ['remove-directory', name], undefined, options);
285
+ }
286
+ async function runAnchored(parent, args, input, options = {}) {
287
+ if (!secureFilesystemBoundaryAvailable(options.platform ?? process.platform))
288
+ throw new Error('secure-filesystem-platform-unsupported');
289
+ const identity = await stat(parent, { bigint: true });
290
+ if (!identity.isDirectory())
291
+ throw new Error('secure-filesystem-parent-invalid');
292
+ const entry = await lstat(parent, { bigint: true });
293
+ if (entry.isSymbolicLink())
294
+ throw new Error('secure-filesystem-parent-symlink');
295
+ await options.beforeHandoff?.();
296
+ return await new Promise((resolveProcess, rejectProcess) => {
297
+ const child = spawn(process.execPath, ['-e', HELPER, ...args.slice(0, 1), String(identity.dev), String(identity.ino), ...args.slice(1)], {
298
+ cwd: parent,
299
+ stdio: ['pipe', 'pipe', 'pipe'],
300
+ });
301
+ const output = [];
302
+ let outputBytes = 0;
303
+ const errors = [];
304
+ child.stdout.on('data', (chunk) => {
305
+ outputBytes += chunk.length;
306
+ if (outputBytes > 1024)
307
+ child.kill();
308
+ else
309
+ output.push(chunk);
310
+ });
311
+ child.stderr.on('data', (chunk) => errors.push(chunk));
312
+ child.once('error', rejectProcess);
313
+ child.once('close', (code) => {
314
+ if (code === 0 && outputBytes <= 1024)
315
+ return resolveProcess(Buffer.concat(output));
316
+ const message = Buffer.concat(errors).toString('utf8') || 'secure-filesystem-helper-failed';
317
+ const error = new Error(message);
318
+ const osCode = message.match(/^secure-filesystem-os:(E[A-Z0-9]+)$/)?.[1];
319
+ if (osCode)
320
+ error.code = osCode;
321
+ if (message === 'secure-filesystem-entry-exists')
322
+ error.code = 'EEXIST';
323
+ rejectProcess(error);
324
+ });
325
+ child.stdin.end(input);
326
+ });
327
+ }
@@ -1 +1,284 @@
1
- import{createHash as v}from"node:crypto";import{existsSync as T,readFileSync as I,statSync as E}from"node:fs";import{join as h}from"node:path";import{DEFAULT_GRAPH_RETRIEVAL_BUDGETS as y,createGraphRetrievalMetadata as g,normalizeGraphRetrievalPath as i,resolveClientCommandContextPolicy as P}from"./shared-graph-retrieval-contract.js";import{admitInvocation as A}from"../../../shared/dist/invocation/index.js";const u="client-pre-command-v1",x="1970-01-01T00:00:00.000Z",R=new Set([".archive","_archive","_archives","archive","archives","archived"]),S=Object.freeze({exists:T,stat:E,readText:e=>I(e,"utf8")});function k(e){return A(e)}function $(e){const t=k(e);if(t.outcome!=="resolved")return null;const o=P(t.selection.commandId);return o?{policy:o,selection:t.selection}:null}function p(e){return v("sha256").update(e).digest("hex")}function m(e){const t=i(e),o=t.split("/").some(s=>R.has(s.toLowerCase()));return t.length===0||t.startsWith("/")||t.startsWith("..")||t.includes("/../")||o||/(^|\/)\.env(?:$|[./-])/i.test(t)||/(^|\/)(?:credentials?|secrets?|tokens?|private-prompts?|raw-logs?)(?:\.|\/|$)/i.test(t)||/\.(?:sqlite|db|pem|key|p12|pfx|crt|cer|der|bin)$/i.test(t)}function D(e,t,o){const s=i(t);if(m(s))return null;const r=h(e,s);if(!o.exists(r))return null;const n=o.stat(r);if(!n.isFile()||n.size>96*1024)return null;const a=o.readText(r);return{kind:"repo-path",path:s,contentHash:p(a).slice(0,16),mtimeMs:Math.floor(n.mtimeMs)}}function b(e){let t=e;const o=t;return t=t.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi,"[REDACTED_EMAIL]"),t=t.replace(/\b(api[-_ ]?key|license[-_ ]?key|token|password|secret)\b\s*[:=]\s*[^\s`'"\])}]+/gi,"$1=[REDACTED_SECRET]"),t=t.replace(/\b(?:sk|pk|ghp|github_pat|pat)_[A-Za-z0-9_\-]{16,}\b/g,"[REDACTED_TOKEN]"),t=t.replace(/\b[A-Za-z0-9+/]{32,}={0,2}\b/g,"[REDACTED_TOKEN]"),t=t.replace(/<!--\s*(?:server|private|proprietary)[\s\S]*?-->/gi,"[REDACTED_PRIVATE_PROMPT]"),t=t.replace(/\b(?:private|proprietary|protected)\s+(?:prompt|workflow|step\s*body|orchestration)\b[^.!?\n]*/gi,"[REDACTED_PRIVATE_PROMPT]"),{text:t,redacted:t!==o}}function w(e,t=y.maxSnippetChars){const o=e.split(/\r?\n/).map(r=>r.trim()).filter(r=>r.length>0&&!r.startsWith("---")&&!r.startsWith("<!--")).slice(0,8).join(" "),s=b(o);return{summary:s.text.slice(0,t),redacted:s.redacted}}function G(e,t,o){const s=D(e,t.path,o);if(!s)return null;const r=o.readText(h(e,s.path)),n=w(r,t.maxSnippetChars);return{nodeType:t.nodeType,stableId:t.stableId,source:s,confidence:n.redacted?Math.min(t.confidence,.72):t.confidence,reasonCode:t.reasonCode,budgetCost:t.budgetCost,summary:n.summary,warnings:n.redacted?["redacted_sensitive_content"]:void 0,score:t.score}}function M(e){const t=`Public command ${e.commandId}; graphRetrieval profile ${e.retrievalProfile}; context is auxiliary, redacted, budgeted and non-authoritative.`;return{nodeType:"Command",stableId:`command:${e.commandId}`,source:{kind:"command",path:e.commandId,contentHash:p(t).slice(0,16)},confidence:.88,reasonCode:"command_surface_match",budgetCost:1,summary:t.slice(0,e.retrievalBudget.maxSnippetChars),score:90}}function z(e){const t=e.uiUxReview?.architectureDir;return t?[...e.positionals,t]:e.positionals}function _(e){return e.find(o=>/^@?docs\/stories\/[A-Za-z0-9._-]+\.story\.md$/u.test(o))?.replace(/^@/u,"")??null}function B(e){return e.find(o=>/^@?docs\/epics\/[A-Za-z0-9._-]+\.md$/u.test(o))?.replace(/^@/u,"")??null}function O(e){const s=_(e)?.split("/").pop()?.replace(/\.story\.md$/,"")?.match(/^(P\d+)\./i)?.[1];if(s)return s.toUpperCase();const r=e.map(n=>n.match(/^@?(?:docs\/epics\/epic-)?(P\d+)(?:\.md)?$/iu)?.[1]).find(n=>!!n);return r?r.toUpperCase():null}function j(e){return e.map(t=>t.match(/^@?((?:docs|core)\/[A-Za-z0-9_./-]+\.md)$/u)?.[1]).filter(t=>!!t).map(t=>i(t)).filter(t=>!m(t))}function H(e){return e.startsWith("docs/stories/")?{nodeType:"Story",reasonCode:"story_scope_match"}:e.startsWith("docs/epics/")?{nodeType:"Epic",reasonCode:"epic_scope_match"}:e.includes("security")||e.includes("policy")||e.includes("standard")?{nodeType:"Guardrail",reasonCode:"guardrail_match"}:{nodeType:"Doc",reasonCode:"architecture_reference"}}function c(e,t){const o=i(t.path);if(m(o))return;e.some(r=>r.nodeType===t.nodeType&&i(r.path)===o)||e.push({...t,path:o})}function L(e,t,o){const s=_(t);if(s){const a=s.split("/").pop()?.replace(/\.story\.md$/,"")??s;c(e,{path:s,nodeType:"Story",stableId:a,reasonCode:"story_scope_match",confidence:.96,budgetCost:3,score:96,maxSnippetChars:o})}const r=B(t),n=O(t);if(r){const a=r.split("/").pop()?.replace(/\.md$/,"")??r;c(e,{path:r,nodeType:"Epic",stableId:a,reasonCode:"epic_scope_match",confidence:.92,budgetCost:3,score:92,maxSnippetChars:o})}else n&&c(e,{path:`docs/epics/epic-${n}.md`,nodeType:"Epic",stableId:n,reasonCode:"epic_scope_match",confidence:.9,budgetCost:3,score:90,maxSnippetChars:o});for(const a of j(t)){const d=a.split("/").pop()?.replace(/\.md$/,"").replace(/\.story$/,"")??a,l=H(a);c(e,{path:a,stableId:d,...l,confidence:.84,budgetCost:3,score:82,maxSnippetChars:o})}}function N(e,t,o,s){const r=e.retrievalBudget.maxSnippetChars,n=[];return c(n,{path:"NEOCORTEX.md",nodeType:"CanonicalMemory",stableId:"canonical-memory",reasonCode:"canonical_memory_seed",confidence:.92,budgetCost:4,score:100,maxSnippetChars:r}),c(n,{path:"AGENTS.md",nodeType:"PlatformMemory",stableId:"platform-memory:agents",reasonCode:"platform_memory_reference",confidence:.7,budgetCost:2,score:66,maxSnippetChars:r}),c(n,{path:"package.json",nodeType:"Package",stableId:"root-package",reasonCode:"package_surface_match",confidence:.78,budgetCost:2,score:72,maxSnippetChars:r}),(e.retrievalProfile!=="utility"||e.commandId==="ui-ux-review")&&c(n,{path:"docs/arquitetura-software/deterministic-graph-retrieval.md",nodeType:"Doc",stableId:"deterministic-graph-retrieval",reasonCode:"architecture_reference",confidence:.9,budgetCost:4,score:84,maxSnippetChars:r}),L(n,z(t),r),(["story-execution","diagnostic","research","review","qa","recovery","planning"].includes(e.retrievalProfile)||e.commandId==="tech-debt")&&c(n,{path:".neocortex/tech-debt-registry.json",nodeType:"Debt",stableId:"tech-debt-registry",reasonCode:"debt_area_match",confidence:.74,budgetCost:3,score:76,maxSnippetChars:r}),["story-execution","diagnostic","research","review","qa","recovery","planning","architecture"].includes(e.retrievalProfile)&&c(n,{path:"core/standards/spec-driven-development.md",nodeType:"Guardrail",stableId:"spec-driven-development",reasonCode:"guardrail_match",confidence:.82,budgetCost:3,score:78,maxSnippetChars:r}),e.retrievalProfile==="security"&&c(n,{path:"docs/architecture/solyd-security-system-contract.md",nodeType:"Guardrail",stableId:"solyd-security-system-contract",reasonCode:"guardrail_match",confidence:.86,budgetCost:3,score:84,maxSnippetChars:r}),e.retrievalProfile==="continuity"&&(c(n,{path:"core/standards/enterprise-continuity-plane.md",nodeType:"Guardrail",stableId:"enterprise-continuity-plane",reasonCode:"guardrail_match",confidence:.86,budgetCost:3,score:84,maxSnippetChars:r}),c(n,{path:"docs/runbooks/enterprise-continuity-runner.md",nodeType:"Doc",stableId:"enterprise-continuity-runner",reasonCode:"architecture_reference",confidence:.78,budgetCost:3,score:72,maxSnippetChars:r})),["utility","continuity","recovery"].includes(e.retrievalProfile)&&c(n,{path:".neocortex/state.json",nodeType:"State",stableId:"project-state",reasonCode:"state_status_match",confidence:.68,budgetCost:3,score:70,maxSnippetChars:r}),[M(e),...n.map(a=>G(o,a,s)).filter(a=>a!==null)]}function Z(e){let t=[...e.items],o=e;const s=Math.min(e.budget.maxPayloadBytes,y.maxPayloadBytes);for(;Buffer.byteLength(JSON.stringify(o),"utf8")>s&&t.length>0;){const r=t.pop(),n=g({retrievalId:e.retrievalId,query:e.query,items:t,generatedAt:e.generatedAt,warnings:e.warnings,budgets:e.budget});o={...n,skipped:r?[...n.skipped,{stableId:r.stableId,source:r.source,reasonCode:"budget_exceeded"}]:n.skipped,contractVersion:u,command:e.command}}return o}async function K(e){const t=$(e.args);if(!t)return{applied:!1,reason:"command_not_allowlisted"};const{policy:o,selection:s}=t,r=o.commandId;try{const n=N(o,s,e.projectRoot,e.fileReader??S);if(n.length===0)return{applied:!1,reason:"no_items",command:r,profile:o.retrievalProfile};const a=b(s.positionals.join(" ").slice(0,240)).text,d=p(`${u}:${r}:${a}:${n.map(f=>`${f.source.path}:${f.source.contentHash??""}`).sort().join("|")}`).slice(0,24),l=g({retrievalId:d,query:a,items:n,generatedAt:e.nowIso??x,budgets:o.retrievalBudget}),C=Z({...l,contractVersion:u,command:r});return{applied:!0,reason:"applied",command:r,profile:o.retrievalProfile,metadata:C}}catch(n){const a=n instanceof Error?n.message:String(n);return e.logger?.warn?.(`[neocortex] graph retrieval hook failed: ${a}. Proceeding without enrichment.`),{applied:!1,reason:"hook_error",command:r,profile:o.retrievalProfile,warning:a}}}export{k as admitGraphInvocation,K as runGraphRetrievalHook};
1
+ /**
2
+ * P158.06 -- optional pre-command deterministic graph retrieval enrichment.
3
+ *
4
+ * The client collects a tiny, public, redacted context envelope for commands
5
+ * resolved by the shared command context policy shadow. It never constructs
6
+ * workflow prompts or decides orchestration; the server validates and decides
7
+ * whether to consume this auxiliary metadata.
8
+ */
9
+ import { createHash } from 'node:crypto';
10
+ import { existsSync, readFileSync, statSync } from 'node:fs';
11
+ import { join } from 'node:path';
12
+ import { DEFAULT_GRAPH_RETRIEVAL_BUDGETS, createGraphRetrievalMetadata, normalizeGraphRetrievalPath, resolveClientCommandContextPolicy, } from './shared-graph-retrieval-contract.js';
13
+ import { admitInvocation, } from '../../../shared/dist/invocation/index.js';
14
+ const CONTRACT_VERSION = 'client-pre-command-v1';
15
+ const DEFAULT_GENERATED_AT = '1970-01-01T00:00:00.000Z';
16
+ const ARCHIVE_PATH_SEGMENTS = new Set(['.archive', '_archive', '_archives', 'archive', 'archives', 'archived']);
17
+ const DEFAULT_FILE_READER = Object.freeze({
18
+ exists: existsSync,
19
+ stat: statSync,
20
+ readText: (path) => readFileSync(path, 'utf8'),
21
+ });
22
+ export function admitGraphInvocation(args) {
23
+ return admitInvocation(args);
24
+ }
25
+ function resolveAdmission(args) {
26
+ const admission = admitGraphInvocation(args);
27
+ if (admission.outcome !== 'resolved')
28
+ return null;
29
+ const policy = resolveClientCommandContextPolicy(admission.selection.commandId);
30
+ return policy ? { policy, selection: admission.selection } : null;
31
+ }
32
+ function stableHash(input) {
33
+ return createHash('sha256').update(input).digest('hex');
34
+ }
35
+ function isUnsafeGraphRetrievalPath(relativePath) {
36
+ const normalized = normalizeGraphRetrievalPath(relativePath);
37
+ const containsArchiveSegment = normalized
38
+ .split('/')
39
+ .some((segment) => ARCHIVE_PATH_SEGMENTS.has(segment.toLowerCase()));
40
+ return normalized.length === 0 ||
41
+ normalized.startsWith('/') ||
42
+ normalized.startsWith('..') ||
43
+ normalized.includes('/../') ||
44
+ containsArchiveSegment ||
45
+ /(^|\/)\.env(?:$|[./-])/i.test(normalized) ||
46
+ /(^|\/)(?:credentials?|secrets?|tokens?|private-prompts?|raw-logs?)(?:\.|\/|$)/i.test(normalized) ||
47
+ /\.(?:sqlite|db|pem|key|p12|pfx|crt|cer|der|bin)$/i.test(normalized);
48
+ }
49
+ function sourceFor(projectRoot, relativePath, fileReader) {
50
+ const normalized = normalizeGraphRetrievalPath(relativePath);
51
+ if (isUnsafeGraphRetrievalPath(normalized)) {
52
+ return null;
53
+ }
54
+ const absolute = join(projectRoot, normalized);
55
+ if (!fileReader.exists(absolute))
56
+ return null;
57
+ const stat = fileReader.stat(absolute);
58
+ if (!stat.isFile() || stat.size > 96 * 1024)
59
+ return null;
60
+ const content = fileReader.readText(absolute);
61
+ return {
62
+ kind: 'repo-path',
63
+ path: normalized,
64
+ contentHash: stableHash(content).slice(0, 16),
65
+ mtimeMs: Math.floor(stat.mtimeMs),
66
+ };
67
+ }
68
+ function redactPublicText(input) {
69
+ let text = input;
70
+ const before = text;
71
+ text = text.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[REDACTED_EMAIL]');
72
+ text = text.replace(/\b(api[-_ ]?key|license[-_ ]?key|token|password|secret)\b\s*[:=]\s*[^\s`'"\])}]+/gi, '$1=[REDACTED_SECRET]');
73
+ text = text.replace(/\b(?:sk|pk|ghp|github_pat|pat)_[A-Za-z0-9_\-]{16,}\b/g, '[REDACTED_TOKEN]');
74
+ text = text.replace(/\b[A-Za-z0-9+/]{32,}={0,2}\b/g, '[REDACTED_TOKEN]');
75
+ text = text.replace(/<!--\s*(?:server|private|proprietary)[\s\S]*?-->/gi, '[REDACTED_PRIVATE_PROMPT]');
76
+ text = text.replace(/\b(?:private|proprietary|protected)\s+(?:prompt|workflow|step\s*body|orchestration)\b[^.!?\n]*/gi, '[REDACTED_PRIVATE_PROMPT]');
77
+ return { text, redacted: text !== before };
78
+ }
79
+ function compactSummary(content, maxChars = DEFAULT_GRAPH_RETRIEVAL_BUDGETS.maxSnippetChars) {
80
+ const lines = content
81
+ .split(/\r?\n/)
82
+ .map((line) => line.trim())
83
+ .filter((line) => line.length > 0 && !line.startsWith('---') && !line.startsWith('<!--'))
84
+ .slice(0, 8)
85
+ .join(' ');
86
+ const redacted = redactPublicText(lines);
87
+ return {
88
+ summary: redacted.text.slice(0, maxChars),
89
+ redacted: redacted.redacted,
90
+ };
91
+ }
92
+ function readItem(projectRoot, params, fileReader) {
93
+ const source = sourceFor(projectRoot, params.path, fileReader);
94
+ if (!source)
95
+ return null;
96
+ const content = fileReader.readText(join(projectRoot, source.path));
97
+ const summary = compactSummary(content, params.maxSnippetChars);
98
+ return {
99
+ nodeType: params.nodeType,
100
+ stableId: params.stableId,
101
+ source,
102
+ confidence: summary.redacted ? Math.min(params.confidence, 0.72) : params.confidence,
103
+ reasonCode: params.reasonCode,
104
+ budgetCost: params.budgetCost,
105
+ summary: summary.summary,
106
+ warnings: summary.redacted ? ['redacted_sensitive_content'] : undefined,
107
+ score: params.score,
108
+ };
109
+ }
110
+ function generatedCommandItem(policy) {
111
+ const summary = `Public command ${policy.commandId}; graphRetrieval profile ${policy.retrievalProfile}; context is auxiliary, redacted, budgeted and non-authoritative.`;
112
+ return {
113
+ nodeType: 'Command',
114
+ stableId: `command:${policy.commandId}`,
115
+ source: {
116
+ kind: 'command',
117
+ path: policy.commandId,
118
+ contentHash: stableHash(summary).slice(0, 16),
119
+ },
120
+ confidence: 0.88,
121
+ reasonCode: 'command_surface_match',
122
+ budgetCost: 1,
123
+ summary: summary.slice(0, policy.retrievalBudget.maxSnippetChars),
124
+ score: 90,
125
+ };
126
+ }
127
+ function targetTokens(selection) {
128
+ const typedPath = selection.uiUxReview?.architectureDir;
129
+ return typedPath ? [...selection.positionals, typedPath] : selection.positionals;
130
+ }
131
+ function extractStoryPath(tokens) {
132
+ const token = tokens.find((value) => /^@?docs\/stories\/[A-Za-z0-9._-]+\.story\.md$/u.test(value));
133
+ return token?.replace(/^@/u, '') ?? null;
134
+ }
135
+ function extractEpicPath(tokens) {
136
+ const token = tokens.find((value) => /^@?docs\/epics\/[A-Za-z0-9._-]+\.md$/u.test(value));
137
+ return token?.replace(/^@/u, '') ?? null;
138
+ }
139
+ function extractEpicId(tokens) {
140
+ const storyPath = extractStoryPath(tokens);
141
+ const storyId = storyPath?.split('/').pop()?.replace(/\.story\.md$/, '');
142
+ const fromStory = storyId?.match(/^(P\d+)\./i)?.[1];
143
+ if (fromStory)
144
+ return fromStory.toUpperCase();
145
+ const direct = tokens
146
+ .map((value) => value.match(/^@?(?:docs\/epics\/epic-)?(P\d+)(?:\.md)?$/iu)?.[1])
147
+ .find((value) => Boolean(value));
148
+ return direct ? direct.toUpperCase() : null;
149
+ }
150
+ function extractMarkdownPaths(tokens) {
151
+ return tokens
152
+ .map((value) => value.match(/^@?((?:docs|core)\/[A-Za-z0-9_./-]+\.md)$/u)?.[1])
153
+ .filter((value) => Boolean(value))
154
+ .map((value) => normalizeGraphRetrievalPath(value))
155
+ .filter((path) => !isUnsafeGraphRetrievalPath(path));
156
+ }
157
+ function candidateKindForPath(path) {
158
+ if (path.startsWith('docs/stories/'))
159
+ return { nodeType: 'Story', reasonCode: 'story_scope_match' };
160
+ if (path.startsWith('docs/epics/'))
161
+ return { nodeType: 'Epic', reasonCode: 'epic_scope_match' };
162
+ if (path.includes('security') || path.includes('policy') || path.includes('standard'))
163
+ return { nodeType: 'Guardrail', reasonCode: 'guardrail_match' };
164
+ return { nodeType: 'Doc', reasonCode: 'architecture_reference' };
165
+ }
166
+ function addSpec(specs, spec) {
167
+ const normalized = normalizeGraphRetrievalPath(spec.path);
168
+ if (isUnsafeGraphRetrievalPath(normalized))
169
+ return;
170
+ const duplicate = specs.some((existing) => existing.nodeType === spec.nodeType && normalizeGraphRetrievalPath(existing.path) === normalized);
171
+ if (!duplicate)
172
+ specs.push({ ...spec, path: normalized });
173
+ }
174
+ function addTargetSpecs(specs, tokens, maxSnippetChars) {
175
+ const storyPath = extractStoryPath(tokens);
176
+ if (storyPath) {
177
+ const storyId = storyPath.split('/').pop()?.replace(/\.story\.md$/, '') ?? storyPath;
178
+ addSpec(specs, { path: storyPath, nodeType: 'Story', stableId: storyId, reasonCode: 'story_scope_match', confidence: 0.96, budgetCost: 3, score: 96, maxSnippetChars });
179
+ }
180
+ const epicPath = extractEpicPath(tokens);
181
+ const epicId = extractEpicId(tokens);
182
+ if (epicPath) {
183
+ const stableId = epicPath.split('/').pop()?.replace(/\.md$/, '') ?? epicPath;
184
+ addSpec(specs, { path: epicPath, nodeType: 'Epic', stableId, reasonCode: 'epic_scope_match', confidence: 0.92, budgetCost: 3, score: 92, maxSnippetChars });
185
+ }
186
+ else if (epicId) {
187
+ addSpec(specs, { path: `docs/epics/epic-${epicId}.md`, nodeType: 'Epic', stableId: epicId, reasonCode: 'epic_scope_match', confidence: 0.9, budgetCost: 3, score: 90, maxSnippetChars });
188
+ }
189
+ for (const path of extractMarkdownPaths(tokens)) {
190
+ const stableId = path.split('/').pop()?.replace(/\.md$/, '').replace(/\.story$/, '') ?? path;
191
+ const kind = candidateKindForPath(path);
192
+ addSpec(specs, { path, stableId, ...kind, confidence: 0.84, budgetCost: 3, score: 82, maxSnippetChars });
193
+ }
194
+ }
195
+ function collectCandidateItems(policy, selection, projectRoot, fileReader) {
196
+ const maxSnippetChars = policy.retrievalBudget.maxSnippetChars;
197
+ const specs = [];
198
+ addSpec(specs, { path: 'NEOCORTEX.md', nodeType: 'CanonicalMemory', stableId: 'canonical-memory', reasonCode: 'canonical_memory_seed', confidence: 0.92, budgetCost: 4, score: 100, maxSnippetChars });
199
+ addSpec(specs, { path: 'AGENTS.md', nodeType: 'PlatformMemory', stableId: 'platform-memory:agents', reasonCode: 'platform_memory_reference', confidence: 0.7, budgetCost: 2, score: 66, maxSnippetChars });
200
+ addSpec(specs, { path: 'package.json', nodeType: 'Package', stableId: 'root-package', reasonCode: 'package_surface_match', confidence: 0.78, budgetCost: 2, score: 72, maxSnippetChars });
201
+ if (policy.retrievalProfile !== 'utility' || policy.commandId === 'ui-ux-review') {
202
+ addSpec(specs, { path: 'docs/arquitetura-software/deterministic-graph-retrieval.md', nodeType: 'Doc', stableId: 'deterministic-graph-retrieval', reasonCode: 'architecture_reference', confidence: 0.9, budgetCost: 4, score: 84, maxSnippetChars });
203
+ }
204
+ addTargetSpecs(specs, targetTokens(selection), maxSnippetChars);
205
+ if (['story-execution', 'diagnostic', 'research', 'review', 'qa', 'recovery', 'planning'].includes(policy.retrievalProfile) || policy.commandId === 'tech-debt') {
206
+ addSpec(specs, { path: '.neocortex/tech-debt-registry.json', nodeType: 'Debt', stableId: 'tech-debt-registry', reasonCode: 'debt_area_match', confidence: 0.74, budgetCost: 3, score: 76, maxSnippetChars });
207
+ }
208
+ if (['story-execution', 'diagnostic', 'research', 'review', 'qa', 'recovery', 'planning', 'architecture'].includes(policy.retrievalProfile)) {
209
+ addSpec(specs, { path: 'core/standards/spec-driven-development.md', nodeType: 'Guardrail', stableId: 'spec-driven-development', reasonCode: 'guardrail_match', confidence: 0.82, budgetCost: 3, score: 78, maxSnippetChars });
210
+ }
211
+ if (policy.retrievalProfile === 'security') {
212
+ addSpec(specs, { path: 'docs/architecture/solyd-security-system-contract.md', nodeType: 'Guardrail', stableId: 'solyd-security-system-contract', reasonCode: 'guardrail_match', confidence: 0.86, budgetCost: 3, score: 84, maxSnippetChars });
213
+ }
214
+ if (policy.retrievalProfile === 'continuity') {
215
+ addSpec(specs, { path: 'core/standards/enterprise-continuity-plane.md', nodeType: 'Guardrail', stableId: 'enterprise-continuity-plane', reasonCode: 'guardrail_match', confidence: 0.86, budgetCost: 3, score: 84, maxSnippetChars });
216
+ addSpec(specs, { path: 'docs/runbooks/enterprise-continuity-runner.md', nodeType: 'Doc', stableId: 'enterprise-continuity-runner', reasonCode: 'architecture_reference', confidence: 0.78, budgetCost: 3, score: 72, maxSnippetChars });
217
+ }
218
+ if (['utility', 'continuity', 'recovery'].includes(policy.retrievalProfile)) {
219
+ addSpec(specs, { path: '.neocortex/state.json', nodeType: 'State', stableId: 'project-state', reasonCode: 'state_status_match', confidence: 0.68, budgetCost: 3, score: 70, maxSnippetChars });
220
+ }
221
+ return [generatedCommandItem(policy), ...specs
222
+ .map((spec) => readItem(projectRoot, spec, fileReader))
223
+ .filter((item) => item !== null)];
224
+ }
225
+ function enforceSerializedPayloadLimit(metadata) {
226
+ let items = [...metadata.items];
227
+ let next = metadata;
228
+ const maxPayloadBytes = Math.min(metadata.budget.maxPayloadBytes, DEFAULT_GRAPH_RETRIEVAL_BUDGETS.maxPayloadBytes);
229
+ while (Buffer.byteLength(JSON.stringify(next), 'utf8') > maxPayloadBytes && items.length > 0) {
230
+ const removed = items.pop();
231
+ const base = createGraphRetrievalMetadata({
232
+ retrievalId: metadata.retrievalId,
233
+ query: metadata.query,
234
+ items,
235
+ generatedAt: metadata.generatedAt,
236
+ warnings: metadata.warnings,
237
+ budgets: metadata.budget,
238
+ });
239
+ next = {
240
+ ...base,
241
+ skipped: removed
242
+ ? [...base.skipped, { stableId: removed.stableId, source: removed.source, reasonCode: 'budget_exceeded' }]
243
+ : base.skipped,
244
+ contractVersion: CONTRACT_VERSION,
245
+ command: metadata.command,
246
+ };
247
+ }
248
+ return next;
249
+ }
250
+ export async function runGraphRetrievalHook(ctx) {
251
+ const resolved = resolveAdmission(ctx.args);
252
+ if (!resolved) {
253
+ return { applied: false, reason: 'command_not_allowlisted' };
254
+ }
255
+ const { policy, selection } = resolved;
256
+ const command = policy.commandId;
257
+ try {
258
+ const items = collectCandidateItems(policy, selection, ctx.projectRoot, ctx.fileReader ?? DEFAULT_FILE_READER);
259
+ if (items.length === 0)
260
+ return { applied: false, reason: 'no_items', command, profile: policy.retrievalProfile };
261
+ // Query and target projection consume only values emitted by the exact
262
+ // admission authority. Raw quoting/option syntax is never reparsed here.
263
+ const query = redactPublicText(selection.positionals.join(' ').slice(0, 240)).text;
264
+ const retrievalId = stableHash(`${CONTRACT_VERSION}:${command}:${query}:${items.map((item) => `${item.source.path}:${item.source.contentHash ?? ''}`).sort().join('|')}`).slice(0, 24);
265
+ const base = createGraphRetrievalMetadata({
266
+ retrievalId,
267
+ query,
268
+ items,
269
+ generatedAt: ctx.nowIso ?? DEFAULT_GENERATED_AT,
270
+ budgets: policy.retrievalBudget,
271
+ });
272
+ const metadata = enforceSerializedPayloadLimit({
273
+ ...base,
274
+ contractVersion: CONTRACT_VERSION,
275
+ command,
276
+ });
277
+ return { applied: true, reason: 'applied', command, profile: policy.retrievalProfile, metadata };
278
+ }
279
+ catch (err) {
280
+ const msg = err instanceof Error ? err.message : String(err);
281
+ ctx.logger?.warn?.(`[neocortex] graph retrieval hook failed: ${msg}. Proceeding without enrichment.`);
282
+ return { applied: false, reason: 'hook_error', command, profile: policy.retrievalProfile, warning: msg };
283
+ }
284
+ }