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