@ornexus/neocortex 4.60.72 → 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.
- package/dist/release-source.json +1 -1
- package/dist/sbom.cdx.json +24 -24
- package/install.ps1 +1 -1
- package/install.sh +1 -1
- package/package.json +2 -2
- package/packages/client/dist/adapters/adapter-registry.js +108 -1
- package/packages/client/dist/adapters/antigravity-adapter.js +77 -2
- package/packages/client/dist/adapters/claude-code-adapter.js +79 -3
- package/packages/client/dist/adapters/codex-adapter.js +80 -2
- package/packages/client/dist/adapters/cursor-adapter.js +115 -4
- package/packages/client/dist/adapters/gemini-adapter.js +71 -2
- package/packages/client/dist/adapters/grok-adapter.js +42 -2
- package/packages/client/dist/adapters/index.js +22 -1
- package/packages/client/dist/adapters/platform-detector.js +212 -1
- package/packages/client/dist/adapters/target-adapter.js +12 -0
- package/packages/client/dist/adapters/vscode-adapter.js +72 -2
- package/packages/client/dist/agent/refresh-stubs.js +417 -2
- package/packages/client/dist/agent/update-agent-yaml.js +102 -1
- package/packages/client/dist/agent/update-description.js +251 -1
- package/packages/client/dist/cache/crypto-utils.js +78 -1
- package/packages/client/dist/cache/encrypted-cache.js +108 -1
- package/packages/client/dist/cache/in-memory-asset-cache.js +73 -1
- package/packages/client/dist/cache/index.js +13 -1
- package/packages/client/dist/cache/protected-pi-boundary.js +44 -1
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +127 -2
- package/packages/client/dist/checkpoint/index.js +11 -1
- package/packages/client/dist/checkpoint/shared-checkpoint-types.js +32 -1
- package/packages/client/dist/cli.js +358 -5
- package/packages/client/dist/commands/activate.js +394 -8
- package/packages/client/dist/commands/architecture-answers.js +274 -7
- package/packages/client/dist/commands/architecture-memory-terminal.js +253 -4
- package/packages/client/dist/commands/architecture-non-ui-continuation.js +100 -1
- package/packages/client/dist/commands/architecture-root-binding.js +51 -1
- package/packages/client/dist/commands/cache-status.js +112 -2
- package/packages/client/dist/commands/config.js +88 -1
- package/packages/client/dist/commands/doctor.js +635 -2
- package/packages/client/dist/commands/invoke.js +7200 -143
- package/packages/client/dist/commands/legacy-epic-terminal.js +71 -1
- package/packages/client/dist/commands/legacy-story-branch.js +96 -1
- package/packages/client/dist/commands/legacy-story-pr.js +276 -1
- package/packages/client/dist/commands/mutation-qa-checkpoint.js +285 -5
- package/packages/client/dist/commands/mutation-qa-gate.js +54 -1
- package/packages/client/dist/commands/mutation-qa-git.js +41 -1
- package/packages/client/dist/commands/mutation-qa-policy.js +102 -1
- package/packages/client/dist/commands/mutation-qa-run.js +219 -3
- package/packages/client/dist/commands/mutation-qa-verdict.js +149 -1
- package/packages/client/dist/commands/mutation-qa.js +220 -1
- package/packages/client/dist/commands/native-agent.js +136 -2
- package/packages/client/dist/commands/planning-diagnosis.js +43 -1
- package/packages/client/dist/commands/planning-generation.js +53 -1
- package/packages/client/dist/commands/planning-memory.js +40 -1
- package/packages/client/dist/commands/planning-research.js +42 -1
- package/packages/client/dist/commands/private-session.js +75 -1
- package/packages/client/dist/commands/property-qa.js +139 -1
- package/packages/client/dist/commands/qa-control.js +16 -1
- package/packages/client/dist/commands/qa-scan-browser-launch.js +55 -1
- package/packages/client/dist/commands/qa-scan-browser-route.js +28 -1
- package/packages/client/dist/commands/qa-scan-capture.js +663 -5
- package/packages/client/dist/commands/qa-scan-discovery.js +376 -3
- package/packages/client/dist/commands/qa-scan-review.js +826 -5
- package/packages/client/dist/commands/qa-synthetic-backend-capture.js +266 -1
- package/packages/client/dist/commands/qa-synthetic-backend-review.js +297 -1
- package/packages/client/dist/commands/qa-synthetic-backend.js +340 -2
- package/packages/client/dist/commands/qa-synthetic-broker-port.js +85 -1
- package/packages/client/dist/commands/qa-synthetic-browser.js +39 -1
- package/packages/client/dist/commands/refactor-characterization.js +671 -21
- package/packages/client/dist/commands/refactor-commit.js +399 -13
- package/packages/client/dist/commands/refactor-control.js +142 -3
- package/packages/client/dist/commands/refactor-cpp-assertions.js +93 -3
- package/packages/client/dist/commands/refactor-review.js +283 -5
- package/packages/client/dist/commands/refactor-root.js +588 -7
- package/packages/client/dist/commands/refresh-memory.js +156 -3
- package/packages/client/dist/commands/shared-architecture-non-ui.js +24 -1
- package/packages/client/dist/commands/story-checkpoint.js +171 -1
- package/packages/client/dist/config/profile-directory.js +13 -1
- package/packages/client/dist/config/resolver-selection.js +278 -1
- package/packages/client/dist/config/secure-config.js +281 -12
- package/packages/client/dist/config.js +223 -2
- package/packages/client/dist/constants.js +25 -1
- package/packages/client/dist/context/context-collector.js +222 -2
- package/packages/client/dist/context/context-sanitizer.js +145 -1
- package/packages/client/dist/continuity/autonomous-governance.js +285 -1
- package/packages/client/dist/continuity/continuity-client-state-store.js +1587 -1
- package/packages/client/dist/continuity/invoke-hooks.js +129 -1
- package/packages/client/dist/continuity/migrations/001-initial-schema.js +12 -2
- package/packages/client/dist/continuity/migrations/002-processing-output-summary-indexes.js +12 -2
- package/packages/client/dist/continuity/migrations/003-headless-execution-attempts.js +12 -2
- package/packages/client/dist/continuity/migrations/004-outbox-delivery-state.js +12 -2
- package/packages/client/dist/continuity/migrations/005-bounded-headless-queue.js +12 -2
- package/packages/client/dist/continuity/migrations/006-native-agent-dispatches.js +10 -2
- package/packages/client/dist/continuity/native-agent-store.js +69 -1
- package/packages/client/dist/continuity/native-agent-types.js +90 -1
- package/packages/client/dist/continuity/outbox.js +117 -1
- package/packages/client/dist/continuity/runner-bootstrap-policy.js +172 -1
- package/packages/client/dist/continuity/sqlite-store.d.ts +22 -1
- package/packages/client/dist/continuity/sqlite-store.js +2612 -81
- package/packages/client/dist/effects/invoke-effect-gateways.js +62 -1
- package/packages/client/dist/errors/error-messages.js +228 -2
- package/packages/client/dist/errors/frontmatter-diagnostics.js +125 -1
- package/packages/client/dist/evidence/collector.js +347 -1
- package/packages/client/dist/evidence/index.js +1 -1
- package/packages/client/dist/evidence/types.js +2 -0
- package/packages/client/dist/fs/atomic-state-replace.js +152 -1
- package/packages/client/dist/generated/command-registry.generated.js +4076 -1
- package/packages/client/dist/generated/public-projections.generated.js +149 -1
- package/packages/client/dist/git/direct-yolo-c01-retry.js +138 -4
- package/packages/client/dist/git/direct-yolo-checkpoint.js +1234 -4
- package/packages/client/dist/git/epic-operational-state.d.ts +198 -0
- package/packages/client/dist/git/epic-operational-state.js +672 -0
- package/packages/client/dist/git/git-lifecycle-step-ids.js +14 -1
- package/packages/client/dist/git/git-lifecycle-store.d.ts +9 -1
- package/packages/client/dist/git/git-lifecycle-store.js +1981 -9
- package/packages/client/dist/git/legacy-story-branch-checkpoint.js +259 -9
- package/packages/client/dist/git/legacy-story-branch-setup.js +348 -4
- package/packages/client/dist/git/legacy-story-pr.js +3538 -70
- package/packages/client/dist/git/secure-filesystem-boundary.js +182 -5
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +284 -1
- package/packages/client/dist/graph-retrieval/shared-graph-retrieval-contract.js +237 -1
- package/packages/client/dist/i18n/first-run.js +60 -2
- package/packages/client/dist/index.js +47 -1
- package/packages/client/dist/invocation/invoke-input.js +24 -1
- package/packages/client/dist/license/index.js +5 -1
- package/packages/client/dist/license/license-client.js +304 -1
- package/packages/client/dist/machine/fingerprint.js +163 -2
- package/packages/client/dist/machine/index.js +5 -1
- package/packages/client/dist/memory/project-memory-path-scope.js +62 -1
- package/packages/client/dist/memory/project-memory-writer.js +1007 -40
- package/packages/client/dist/memory/shared-project-memory-types.js +253 -2
- package/packages/client/dist/migrations/epic-lifecycle-backfill.d.ts +97 -0
- package/packages/client/dist/migrations/epic-lifecycle-backfill.js +1060 -0
- package/packages/client/dist/migrations/project-identity-capability.js +50 -1
- package/packages/client/dist/migrations/project-identity.js +493 -7
- package/packages/client/dist/migrations/state-json.d.ts +22 -0
- package/packages/client/dist/migrations/state-json.js +393 -4
- package/packages/client/dist/policy/architecture-policy.js +184 -2
- package/packages/client/dist/policy/index.js +7 -1
- package/packages/client/dist/policy/shared-policy-types.js +38 -0
- package/packages/client/dist/policy/subagent-approval-policy.js +55 -1
- package/packages/client/dist/private-execution/clean-baseline.js +279 -2
- package/packages/client/dist/private-execution/codex-executor.js +574 -4
- package/packages/client/dist/private-execution/codex-host-runtime.js +137 -1
- package/packages/client/dist/private-execution/codex-lifecycle-guard.js +97 -1
- package/packages/client/dist/private-execution/codex-scoped-permissions.js +128 -1
- package/packages/client/dist/private-execution/delivery.js +410 -13
- package/packages/client/dist/private-execution/diagnosis-root-review.js +294 -2
- package/packages/client/dist/private-execution/frozen-handoff.js +19 -6
- package/packages/client/dist/private-execution/implementation-validation.js +106 -2
- package/packages/client/dist/private-execution/isolated-admission.js +1432 -23
- package/packages/client/dist/private-execution/isolated-session.js +725 -5
- package/packages/client/dist/private-execution/isolated-workspace.js +122 -1
- package/packages/client/dist/private-execution/pact-consumer-runtime.js +409 -3
- package/packages/client/dist/private-execution/pact-test-source.js +258 -1
- package/packages/client/dist/private-execution/pact-toolchain.js +290 -3
- package/packages/client/dist/private-execution/pact-write-set.js +172 -1
- package/packages/client/dist/private-execution/planning-diagnosis-checkpoint.js +406 -5
- package/packages/client/dist/private-execution/planning-generation-checkpoint.js +462 -5
- package/packages/client/dist/private-execution/planning-memory-checkpoint.js +139 -3
- package/packages/client/dist/private-execution/planning-research-checkpoint.js +325 -4
- package/packages/client/dist/private-execution/private-report-store.js +42 -1
- package/packages/client/dist/private-execution/property-test-candidate.js +103 -1
- package/packages/client/dist/private-execution/property-test-choices.js +147 -3
- package/packages/client/dist/private-execution/property-test-plan.js +271 -7
- package/packages/client/dist/private-execution/property-test-review.js +258 -4
- package/packages/client/dist/private-execution/property-test-runtime.js +372 -9
- package/packages/client/dist/private-execution/property-test-source.js +139 -1
- package/packages/client/dist/private-execution/root-checkpoint.js +80 -1
- package/packages/client/dist/private-execution/root-memory-git-evidence.js +135 -3
- package/packages/client/dist/private-execution/root-memory-recovery.js +93 -1
- package/packages/client/dist/private-execution/root-memory-request.js +29 -1
- package/packages/client/dist/private-execution/root-memory-scope.js +178 -1
- package/packages/client/dist/private-execution/root-memory-writer.js +102 -1
- package/packages/client/dist/private-execution/session-lease.js +63 -1
- package/packages/client/dist/private-execution/shared-private-admission-types.js +50 -1
- package/packages/client/dist/private-execution/shared-project-validation.js +66 -1
- package/packages/client/dist/private-execution/shared-root-memory-types.js +61 -1
- package/packages/client/dist/private-execution/ux-source-validation.js +372 -1
- package/packages/client/dist/private-execution/ux-write-set.js +191 -2
- package/packages/client/dist/registry/registry-reconciliation.js +234 -1
- package/packages/client/dist/resilience/circuit-breaker.js +170 -1
- package/packages/client/dist/resilience/degradation-manager.js +164 -1
- package/packages/client/dist/resilience/freshness-indicator.js +100 -1
- package/packages/client/dist/resilience/index.js +8 -1
- package/packages/client/dist/resilience/recovery-detector.js +74 -1
- package/packages/client/dist/resolvers/asset-resolver.js +13 -0
- package/packages/client/dist/resolvers/local-resolver.js +255 -8
- package/packages/client/dist/resolvers/remote-resolver.js +233 -1
- package/packages/client/dist/runner/binary.js +404 -1
- package/packages/client/dist/runner/cli.js +1571 -17
- package/packages/client/dist/runner/headless/adapters.js +49 -1
- package/packages/client/dist/runner/headless/claude-code-adapter.js +79 -1
- package/packages/client/dist/runner/headless/continuous-story-workspace.js +1628 -11
- package/packages/client/dist/runner/headless/epic-git-reconciliation-inventory.js +69 -1
- package/packages/client/dist/runner/headless/epic-merge-candidate-qa.js +110 -2
- package/packages/client/dist/runner/headless/epic-terminal-git-cli-adapter.js +1236 -8
- package/packages/client/dist/runner/headless/epic-terminal-git-runner.js +318 -2
- package/packages/client/dist/runner/headless/fake-adapter.js +62 -1
- package/packages/client/dist/runner/headless/managed-profile.js +132 -1
- package/packages/client/dist/runner/headless/opencode-adapter.js +145 -2
- package/packages/client/dist/runner/headless/prepared-story-bundle.js +43 -1
- package/packages/client/dist/runner/headless/resolver.js +231 -1
- package/packages/client/dist/runner/headless/run-yolo.js +885 -1
- package/packages/client/dist/runner/headless/run-yoloop.js +2574 -2
- package/packages/client/dist/runner/headless/types.js +574 -2
- package/packages/client/dist/runner/local-console.js +396 -13
- package/packages/client/dist/runner/portal-sync-worker.js +1487 -1
- package/packages/client/dist/runner/project-identity.js +106 -1
- package/packages/client/dist/runner/scheduler.js +995 -7
- package/packages/client/dist/runner-cli.js +18 -2
- package/packages/client/dist/state/invocation-shape.js +13 -1
- package/packages/client/dist/state/project-state-snapshot.d.ts +8 -0
- package/packages/client/dist/state/project-state-snapshot.js +1731 -1
- package/packages/client/dist/state/state-json-repair.js +785 -3
- package/packages/client/dist/state/structural-dag-projection.js +118 -1
- package/packages/client/dist/telemetry/index.js +5 -1
- package/packages/client/dist/telemetry/offline-queue.js +131 -1
- package/packages/client/dist/tier/index.js +5 -1
- package/packages/client/dist/tier/tier-aware-client.js +405 -1
- package/packages/client/dist/types/index.js +38 -1
- package/packages/client/dist/version.js +27 -1
- package/packages/client/dist/yolo/change-evidence-collector.js +126 -1
- package/packages/client/dist/yolo/invoke-envelope-capability.js +62 -1
- package/packages/client/dist/yolo/spec-envelope-collector.js +184 -5
- package/packages/client/dist/yoloop/discovery-hook.js +300 -2
- package/packages/client/dist/yoloop/index.js +8 -1
- package/packages/client/dist/yoloop/invoke-hooks.js +669 -12
- package/packages/client/dist/yoloop/shared-discover-epics.js +1007 -1
- package/packages/client/dist/yoloop/shared-yoloop-types.d.ts +65 -1
- package/packages/client/dist/yoloop/shared-yoloop-types.js +33 -1
- package/packages/client/dist/yoloop/target-normalization.js +180 -1
- package/packages/client/dist/yoloop/yoloop-client-state-store.d.ts +8 -0
- package/packages/client/dist/yoloop/yoloop-client-state-store.js +1920 -1
- package/packages/shared/dist/yolo/spec-envelope.js +3 -6
- package/targets-stubs/antigravity/gemini.md +1 -1
- package/targets-stubs/antigravity/skill/SKILL.md +1 -1
- package/targets-stubs/claude-code/neocortex-root.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex-root.md +2 -2
- package/targets-stubs/claude-code/neocortex.agent.yaml +1 -1
- package/targets-stubs/claude-code/neocortex.md +2 -2
- package/targets-stubs/codex/AGENTS.md +1 -1
- package/targets-stubs/cursor/agent.md +2 -2
- package/targets-stubs/gemini-cli/agent.md +2 -2
- package/targets-stubs/opencode/neocortex-root.md +1 -1
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
|
@@ -1,4 +1,17 @@
|
|
|
1
|
-
|
|
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
|
-
|
|
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
|
-
`;
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
+
}
|