@ornexus/neocortex 4.60.16 → 4.63.4
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/sbom.cdx.json +29 -29
- package/docs/install/installer-diagnostics.md +33 -0
- package/install.ps1 +49 -229
- package/install.sh +30 -92
- package/package.json +5 -2
- package/packages/client/dist/adapters/platform-detector.d.ts +7 -6
- package/packages/client/dist/adapters/platform-detector.js +1 -1
- package/packages/client/dist/agent/refresh-stubs.js +1 -1
- package/packages/client/dist/cache/encrypted-cache.d.ts +10 -4
- package/packages/client/dist/cache/encrypted-cache.js +1 -1
- package/packages/client/dist/cache/in-memory-asset-cache.d.ts +5 -4
- package/packages/client/dist/cache/in-memory-asset-cache.js +1 -1
- package/packages/client/dist/cache/protected-pi-boundary.d.ts +6 -2
- package/packages/client/dist/cache/protected-pi-boundary.js +1 -1
- package/packages/client/dist/checkpoint/checkpoint-client-reader.d.ts +31 -30
- package/packages/client/dist/checkpoint/checkpoint-client-reader.js +2 -2
- package/packages/client/dist/checkpoint/shared-checkpoint-types.d.ts +2 -0
- package/packages/client/dist/cli.js +3 -3
- package/packages/client/dist/commands/invoke.d.ts +77 -1
- package/packages/client/dist/commands/invoke.js +44 -59
- package/packages/client/dist/config/resolver-selection.d.ts +12 -31
- package/packages/client/dist/config/resolver-selection.js +1 -1
- package/packages/client/dist/errors/error-messages.js +1 -1
- package/packages/client/dist/evidence/collector.d.ts +18 -0
- package/packages/client/dist/evidence/collector.js +1 -0
- package/packages/client/dist/evidence/index.d.ts +3 -0
- package/packages/client/dist/evidence/index.js +1 -0
- package/packages/client/dist/evidence/types.d.ts +59 -0
- package/packages/client/dist/evidence/types.js +0 -0
- package/packages/client/dist/graph-retrieval/pre-command-hook.d.ts +21 -0
- package/packages/client/dist/graph-retrieval/pre-command-hook.js +1 -1
- package/packages/client/dist/index.d.ts +10 -11
- package/packages/client/dist/index.js +1 -1
- package/packages/client/dist/license/license-client.d.ts +17 -0
- package/packages/client/dist/license/license-client.js +1 -1
- package/packages/client/dist/memory/project-memory-writer.js +13 -13
- package/packages/client/dist/memory/shared-project-memory-types.d.ts +1 -1
- package/packages/client/dist/memory/shared-project-memory-types.js +2 -2
- package/packages/client/dist/resolvers/asset-resolver.d.ts +8 -71
- package/packages/client/dist/resolvers/remote-resolver.d.ts +7 -69
- package/packages/client/dist/resolvers/remote-resolver.js +1 -1
- package/packages/client/dist/runner-cli.js +0 -0
- package/packages/client/dist/state/project-state-snapshot.js +1 -1
- package/packages/client/dist/telemetry/index.d.ts +1 -1
- package/packages/client/dist/telemetry/index.js +1 -1
- package/packages/client/dist/telemetry/offline-queue.d.ts +12 -2
- package/packages/client/dist/telemetry/offline-queue.js +1 -1
- package/packages/client/dist/types/index.d.ts +26 -8
- package/packages/client/dist/types/index.js +1 -1
- package/packages/client/dist/types/shared-public-execution-contract.d.ts +175 -0
- package/packages/client/dist/types/shared-public-execution-contract.js +2 -0
- 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 +16 -6
- package/targets-stubs/codex/README.md +24 -0
- package/targets-stubs/codex/install-codex.sh +70 -1
- package/targets-stubs/codex/neocortex-local.config.toml +17 -0
- package/targets-stubs/codex/neocortex.toml +10 -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 +4 -4
- package/targets-stubs/opencode/neocortex.md +1 -1
- package/targets-stubs/vscode/neocortex.agent.md +2 -2
- package/packages/client/dist/resolvers/local-resolver.d.ts +0 -26
- package/packages/client/dist/resolvers/local-resolver.js +0 -8
package/install.sh
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
# Development Orchestrator
|
|
5
5
|
|
|
6
6
|
# Versao do instalador
|
|
7
|
-
VERSION="4.
|
|
7
|
+
VERSION="4.63.4"
|
|
8
8
|
|
|
9
9
|
# Flags
|
|
10
10
|
MIGRATION_DETECTED=false
|
|
@@ -14,7 +14,8 @@ LEGACY_ITEMS=""
|
|
|
14
14
|
LEGACY_WARNINGS=0
|
|
15
15
|
SELECTED_TARGETS=""
|
|
16
16
|
VALID_TARGETS="claude-code cursor vscode gemini gemini-cli codex antigravity opencode kimi openclaw"
|
|
17
|
-
# LOCAL_MODE removed
|
|
17
|
+
# LOCAL_MODE removed: thin-client mode is mandatory. Customer installer paths never read core/ or
|
|
18
|
+
# development targets/ from a source checkout.
|
|
18
19
|
# SSoT: packages/client/src/constants.ts DEFAULT_SERVER_URL
|
|
19
20
|
NEOCORTEX_SERVER_URL="${NEOCORTEX_SERVER_URL:-$(printf '%s://%s.%s.%s' 'https' 'api' 'neocortex' 'sh')}"
|
|
20
21
|
|
|
@@ -303,7 +304,6 @@ is_neocortex_source_project() {
|
|
|
303
304
|
|
|
304
305
|
[ -f "$package_file" ] || return 1
|
|
305
306
|
grep -q '"name"[[:space:]]*:[[:space:]]*"@ornexus/neocortex"' "$package_file" 2>/dev/null || return 1
|
|
306
|
-
[ -d "$candidate_dir/core" ] || return 1
|
|
307
307
|
[ -d "$candidate_dir/packages/server" ] || return 1
|
|
308
308
|
[ -d "$candidate_dir/targets-stubs" ] || return 1
|
|
309
309
|
[ -f "$candidate_dir/install.sh" ] || return 1
|
|
@@ -314,22 +314,15 @@ is_neocortex_source_project() {
|
|
|
314
314
|
resolve_target_dir() {
|
|
315
315
|
local target="$1"
|
|
316
316
|
local stub_dir="$SOURCE_DIR/targets-stubs/$target"
|
|
317
|
-
local dev_dir="$SOURCE_DIR/targets/$target"
|
|
318
317
|
|
|
319
|
-
[ "$target" = "gemini" ] && stub_dir="$SOURCE_DIR/targets-stubs/gemini-cli"
|
|
318
|
+
[ "$target" = "gemini" ] && stub_dir="$SOURCE_DIR/targets-stubs/gemini-cli"
|
|
320
319
|
|
|
321
320
|
if [ -d "$stub_dir" ]; then
|
|
322
321
|
printf '%s\n' "$stub_dir"
|
|
323
322
|
return 0
|
|
324
323
|
fi
|
|
325
324
|
|
|
326
|
-
|
|
327
|
-
debug "Fallback dev targets/ para $target: $dev_dir"
|
|
328
|
-
printf '%s\n' "$dev_dir"
|
|
329
|
-
return 0
|
|
330
|
-
fi
|
|
331
|
-
|
|
332
|
-
warn "$target ${DIM}(adapter nao encontrado; paths tentados: $stub_dir, $dev_dir)${NC}"
|
|
325
|
+
warn "$target ${DIM}(adapter publico nao encontrado: $stub_dir)${NC}"
|
|
333
326
|
return 1
|
|
334
327
|
}
|
|
335
328
|
|
|
@@ -1015,36 +1008,9 @@ install_core() {
|
|
|
1015
1008
|
# =============================================================================
|
|
1016
1009
|
|
|
1017
1010
|
install_skills() {
|
|
1018
|
-
local errors=0
|
|
1019
|
-
|
|
1020
|
-
SKILLS_DIR="${CLAUDE_DIR}/skills"
|
|
1021
|
-
SKILLS_DEST="${SKILLS_DIR}/neocortex"
|
|
1022
|
-
SKILLS_SOURCE="${SOURCE_DIR}/core/skills"
|
|
1023
|
-
|
|
1024
1011
|
# Thin-client: skills delivered by remote server, never copied
|
|
1025
1012
|
ok "Skills: delivered by remote server"
|
|
1026
1013
|
return 0
|
|
1027
|
-
|
|
1028
|
-
mkdir -p "$SKILLS_DIR" 2>/dev/null || { fail "Falha ao criar $SKILLS_DIR"; return 1; }
|
|
1029
|
-
|
|
1030
|
-
# Clean previous
|
|
1031
|
-
[ -d "$SKILLS_DEST" ] && rm -rf "$SKILLS_DEST" 2>/dev/null
|
|
1032
|
-
|
|
1033
|
-
if cp -r "$SKILLS_SOURCE" "$SKILLS_DEST" 2>/dev/null; then
|
|
1034
|
-
# Count skills
|
|
1035
|
-
local skill_count=0
|
|
1036
|
-
for skill_file in "$SKILLS_DEST"/step-skills/*/*.md "$SKILLS_DEST"/domain-skills/*/*.md; do
|
|
1037
|
-
if [ -f "$skill_file" ] && [[ "$(basename "$skill_file")" != "_template.md" ]]; then
|
|
1038
|
-
((skill_count++))
|
|
1039
|
-
fi
|
|
1040
|
-
done
|
|
1041
|
-
ok "Skills instaladas ${DIM}($skill_count skills) [modo local]${NC}"
|
|
1042
|
-
else
|
|
1043
|
-
fail "Falha ao copiar skills"
|
|
1044
|
-
((errors++))
|
|
1045
|
-
fi
|
|
1046
|
-
|
|
1047
|
-
return $errors
|
|
1048
1014
|
}
|
|
1049
1015
|
|
|
1050
1016
|
# =============================================================================
|
|
@@ -1055,9 +1021,6 @@ install_agent() {
|
|
|
1055
1021
|
local errors=0
|
|
1056
1022
|
|
|
1057
1023
|
CLAUDE_TARGET_DIR="$SOURCE_DIR/targets-stubs/claude-code"
|
|
1058
|
-
if [ ! -d "$CLAUDE_TARGET_DIR" ] && is_neocortex_source_project "$SOURCE_DIR" && [ -d "$SOURCE_DIR/targets/claude-code" ]; then
|
|
1059
|
-
CLAUDE_TARGET_DIR="$SOURCE_DIR/targets/claude-code"
|
|
1060
|
-
fi
|
|
1061
1024
|
if [ ! -d "$CLAUDE_TARGET_DIR" ]; then
|
|
1062
1025
|
CLAUDE_TARGET_DIR="$SOURCE_DIR"
|
|
1063
1026
|
fi
|
|
@@ -1487,56 +1450,26 @@ verify_installation() {
|
|
|
1487
1450
|
fi
|
|
1488
1451
|
done
|
|
1489
1452
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
if [ ! -d "$dir_path" ]; then
|
|
1495
|
-
report="${report}FAIL ${dir}/ (diretorio nao encontrado)\n"
|
|
1496
|
-
fails=$((fails + 1))
|
|
1497
|
-
else
|
|
1498
|
-
local md_count=0
|
|
1499
|
-
for f in "$dir_path"/*.md; do
|
|
1500
|
-
[ -f "$f" ] && md_count=$((md_count + 1))
|
|
1501
|
-
done
|
|
1502
|
-
if [ $md_count -eq 0 ]; then
|
|
1503
|
-
report="${report}WARN ${dir}/ (vazio - nenhum arquivo .md)\n"
|
|
1504
|
-
warns=$((warns + 1))
|
|
1505
|
-
else
|
|
1506
|
-
report="${report}OK ${dir}/ (${md_count} arquivos)\n"
|
|
1507
|
-
fi
|
|
1508
|
-
fi
|
|
1509
|
-
done
|
|
1510
|
-
|
|
1511
|
-
# ─── Layer 3b: Core directory (local mode only) ──────────────────
|
|
1512
|
-
if [ ! -d "$DEST_DIR/core" ]; then
|
|
1513
|
-
report="${report}FAIL core/ (diretorio nao encontrado)\n"
|
|
1514
|
-
fails=$((fails + 1))
|
|
1515
|
-
else
|
|
1516
|
-
report="${report}OK core/\n"
|
|
1517
|
-
fi
|
|
1453
|
+
# ─── Layer 3: Thin client config (remote mode) ─────────────────────
|
|
1454
|
+
local config_file="${HOME}/.neocortex/config.json"
|
|
1455
|
+
if [ -f "$config_file" ]; then
|
|
1456
|
+
report="${report}OK ~/.neocortex/config.json (thin client configured)\n"
|
|
1518
1457
|
else
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
report="${report}OK ~/.neocortex/config.json (thin client configured)\n"
|
|
1523
|
-
else
|
|
1524
|
-
report="${report}WARN ~/.neocortex/config.json (nao encontrado)\n"
|
|
1525
|
-
warns=$((warns + 1))
|
|
1526
|
-
fi
|
|
1458
|
+
report="${report}WARN ~/.neocortex/config.json (nao encontrado)\n"
|
|
1459
|
+
warns=$((warns + 1))
|
|
1460
|
+
fi
|
|
1527
1461
|
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
fi
|
|
1536
|
-
done
|
|
1537
|
-
if [ "$ip_found" = false ]; then
|
|
1538
|
-
report="${report}OK Zero IP no filesystem (modo remoto)\n"
|
|
1462
|
+
# Verify NO IP directories exist
|
|
1463
|
+
local ip_found=false
|
|
1464
|
+
for dir in core steps-c steps-e steps-p steps-r steps-u; do
|
|
1465
|
+
if [ -d "$DEST_DIR/$dir" ]; then
|
|
1466
|
+
report="${report}WARN ${dir}/ ainda existe (deveria ter sido removido)\n"
|
|
1467
|
+
warns=$((warns + 1))
|
|
1468
|
+
ip_found=true
|
|
1539
1469
|
fi
|
|
1470
|
+
done
|
|
1471
|
+
if [ "$ip_found" = false ]; then
|
|
1472
|
+
report="${report}OK Zero IP no filesystem (modo remoto)\n"
|
|
1540
1473
|
fi
|
|
1541
1474
|
|
|
1542
1475
|
# ─── Display report ─────────────────────────────────────────────────
|
|
@@ -1680,10 +1613,15 @@ create_project_dirs() {
|
|
|
1680
1613
|
"$project_dir/docs/epics" \
|
|
1681
1614
|
"$project_dir/docs/proposals" 2>/dev/null
|
|
1682
1615
|
|
|
1683
|
-
#
|
|
1616
|
+
# Create a public state shell if needed. Never source it from core/.
|
|
1684
1617
|
if [ ! -f "$project_dir/.neocortex/state.json" ]; then
|
|
1685
|
-
|
|
1686
|
-
|
|
1618
|
+
cat > "$project_dir/.neocortex/state.json" <<'EOFSTATE'
|
|
1619
|
+
{
|
|
1620
|
+
"schema_version": 1,
|
|
1621
|
+
"stories": {},
|
|
1622
|
+
"epics": {}
|
|
1623
|
+
}
|
|
1624
|
+
EOFSTATE
|
|
1687
1625
|
fi
|
|
1688
1626
|
|
|
1689
1627
|
# Thin-client: never copy core/ to project
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ornexus/neocortex",
|
|
3
|
-
"version": "4.
|
|
4
|
-
"description": "Neocortex v4.
|
|
3
|
+
"version": "4.63.4",
|
|
4
|
+
"description": "Neocortex v4.63.4 - Orquestrador de Desenvolvimento de Epics & Stories para Claude Code",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
7
7
|
"claude-code",
|
|
@@ -67,6 +67,9 @@
|
|
|
67
67
|
],
|
|
68
68
|
"scripts": {
|
|
69
69
|
"test": "node scripts/test-root.mjs",
|
|
70
|
+
"test:fast": "npm --prefix packages/server test -- public-output-ip-dlp protected-execution-worker invoke-e2e arch-plan-brownfield-ux arch-plan-product-experience p201-zero-pi-arch-plan-e2e && npm --prefix packages/client test -- platform-detector invoke project-evidence-collector public-execution/dispatch && npm --prefix packages/shared test -- public-execution-contract project-evidence-contract",
|
|
71
|
+
"test:pr": "npm run test:fast && npm --prefix packages/shared run build && npm --prefix packages/client run build && npm --prefix packages/client test -- tarball-validation stubs-integrity p155-install-regression-suite && npm --prefix packages/server run build -- --noEmit",
|
|
72
|
+
"test:release": "npx vitest run --config vitest.config.ts packages/shared/src packages/core/src && npm run build && npm --prefix packages/client test && npm test -- --runInBand && npm run test:fast && npm --prefix packages/server test -- p201-zero-pi-public-safety p201-downstream-ux-propagation p201-encrypted-asset-bundle p201-encrypted-production-deploy shared-types-sync quota-v2-flags && npm --prefix packages/server run build -- --noEmit && npm --prefix packages/server run bundle && npm run storybook:build && npm run storybook:smoke && npm run sbom && npm run validate && bash scripts/ip-leak-scan.sh --skip-docker --check=1 --version $(node -p \"require('./package.json').version\")",
|
|
70
73
|
"build": "npm run build -w packages/shared && npm run build -w packages/client && node scripts/strip-source-map-comments.js && node scripts/minify-client-dist.mjs",
|
|
71
74
|
"build:clean": "rm -rf packages/shared/dist packages/client/dist && npm run build",
|
|
72
75
|
"build:client": "npm run build -w packages/client",
|
|
@@ -10,13 +10,14 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Detection priority:
|
|
12
12
|
* 1. --target flag (explicit override)
|
|
13
|
-
* 2.
|
|
14
|
-
* 3.
|
|
15
|
-
* 4.
|
|
16
|
-
* 5. .
|
|
13
|
+
* 2. OPENCODE=1 runtime marker -> OpenCode
|
|
14
|
+
* 3. CODEX_CLI=1 or non-empty CODEX_THREAD_ID -> Codex
|
|
15
|
+
* 4. Claude Code runtime marker -> Claude Code
|
|
16
|
+
* 5. .cursorrules file -> Cursor
|
|
17
17
|
* 6. GEMINI_CLI env var -> Gemini CLI
|
|
18
|
-
* 7.
|
|
19
|
-
* 8.
|
|
18
|
+
* 7. CLAUDE.md file -> Claude Code
|
|
19
|
+
* 8. .vscode/ directory -> VS Code
|
|
20
|
+
* 9. Default fallback -> Claude Code
|
|
20
21
|
*
|
|
21
22
|
* Story 42.8
|
|
22
23
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{existsSync as
|
|
1
|
+
import{existsSync as t}from"node:fs";import{join as r}from"node:path";const a=new Set(["claude-code","cursor","vscode","gemini","codex","opencode","github-copilot-cli","kimi","antigravity"]),u=Object.freeze({claude:"claude-code","claude-code":"claude-code",cursor:"cursor",vscode:"vscode","vscode-copilot":"vscode",gemini:"gemini","gemini-cli":"gemini",codex:"codex","codex-cli":"codex",opencode:"opencode","copilot-cli":"github-copilot-cli","github-copilot-cli":"github-copilot-cli",kimi:"kimi",antigravity:"antigravity"});function g(e){return a.has(e)}function l(e){return u[e.trim().toLowerCase()]??null}const s=e=>t(e);function p(e,o=s){if(e.targetFlag){const n=l(e.targetFlag);if(n)return{targetId:n,confidence:1,reason:`Explicit --target=${n} flag`}}const d=e.env.OPENCODE==="1",i=e.env.CODEX_CLI==="1"||!!e.env.CODEX_THREAD_ID?.trim(),c=!!(e.env.CLAUDECODE||e.env.CLAUDE_CODE||e.env.CLAUDE_AGENT);return d?{targetId:"opencode",confidence:.95,reason:"Found OpenCode runtime environment marker"}:i&&o(r(e.cwd,"AGENTS.md"))?{targetId:"codex",confidence:.96,reason:"Found Codex runtime marker and uppercase AGENTS.md in project root"}:i?{targetId:"codex",confidence:.9,reason:"Found trusted Codex runtime environment marker"}:c?{targetId:"claude-code",confidence:.9,reason:"Found Claude Code runtime environment marker"}:o(r(e.cwd,".cursorrules"))?{targetId:"cursor",confidence:.95,reason:"Found .cursorrules file in project root"}:e.env.GEMINI_CLI!==void 0?{targetId:"gemini",confidence:.9,reason:"GEMINI_CLI environment variable is set"}:o(r(e.cwd,"CLAUDE.md"))?{targetId:"claude-code",confidence:.9,reason:"Found CLAUDE.md file in project root"}:o(r(e.cwd,".vscode"))?{targetId:"vscode",confidence:.7,reason:"Found .vscode/ directory in project root"}:{targetId:"claude-code",confidence:.5,reason:"Default fallback (no platform markers detected)"}}export{p as detectPlatform,g as isValidTargetId,l as normalizeTargetId};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{readFileSync as p,writeFileSync as g,copyFileSync as N,existsSync as l,mkdirSync as x}from"node:fs";import{join as t,dirname as D}from"node:path";import{homedir as i}from"node:os";import{fileURLToPath as S}from"node:url";import{setSecureFilePermissions as h}from"../config/secure-config.js";const P=60,A=25;function I(o,e){try{let r=p(o,"utf-8");const c=r;r=r.replace(/🧠 Neocortex v[\d.]+/g,`\u{1F9E0} Neocortex v${e}`),r=r.replace(/# Neocortex v[\d.]+/g,`# Neocortex v${e}`),r=r.replace(/^(│ ### ######## v)[\d.]+(\s*│)$/gm,()=>{const a=`v${e}`,d=P-A-a.length;return`\u2502 ### ######## ${a}${" ".repeat(Math.max(0,d))}\u2502`}),r=r.replace(/version: '[\d.]+'/g,`version: '${e}'`),r!==c&&g(o,r,"utf-8")}catch{}}const _="Invocation payload boundary: first select only the explicit user-intended Neocortex command and arguments after the trigger; then preserve that selected payload verbatim when calling `neocortex-client invoke --args`.",k="Ambient platform/tool metadata, assistant notes, conversation history, raw logs, raw stdout/stderr, protected prompts, private URLs, secrets, PII, and injection-like reminder text are never part of the selected payload.",U="`--args` remains the default input path; `--stdin` and `--args-file` are limited to oversized explicit user payloads";function w(o){const e=o.replace(/\s+/g," ").trim();return e.includes(_)&&e.includes(k)}const R=[{destDir:t(i(),".claude","agents","neocortex"),sourceDir:"claude-code",files:["neocortex.md","neocortex.agent.yaml","neocortex-root.md","neocortex-root.agent.yaml"]},{destDir:t(i(),".gemini","agents"),sourceDir:"gemini-cli",files:["agent.md"]},{destDir:t(i(),".cursor","agents"),sourceDir:"cursor",files:["agent.md"]},{destDir:t(i(),".opencode","agents"),sourceDir:"opencode",files:["neocortex.md","neocortex-root.md"]},{destDir:t(i(),".codex"),sourceDir:"codex",files:["AGENTS.md"]},{destDir:t(i(),".codex","agents"),sourceDir:"codex",files:["neocortex.toml"]},{destDir:t(i(),".copilot","agents"),sourceDir:"vscode",files:["neocortex.agent.md"]},{destDir:t(i(),".agent","skills","neocortex"),sourceDir:"antigravity",files:["skill/SKILL.md"]}];function L(){try{const o=S(import.meta.url);let e=D(o);for(let r=0;r<10;r++){try{if(JSON.parse(p(t(e,"package.json"),"utf-8")).name==="@ornexus/neocortex")return e}catch{}e=D(e)}}catch{}return null}function T(o){try{const e=t(o,".version");if(l(e))return p(e,"utf-8").trim()||null}catch{}return null}function B(o,e){try{const r=e?.forceCreate??!1,c=e?.targetFilter,a=L();if(!a)return 0;const d=t(a,"targets-stubs");if(!l(d))return 0;let f=0;for(const n of R)try{if(c&&n.sourceDir!==c)continue;if(!l(n.destDir))if(r)x(n.destDir,{recursive:!0});else continue;if(T(n.destDir)===o)continue;const y=t(d,n.sourceDir);if(!l(y))continue;let v=!1;for(const s of n.files){const E=t(y,s),u=t(n.destDir,s);l(E)&&(x(D(u),{recursive:!0}),N(E,u),(s.endsWith(".md")||s.endsWith(".yaml")||s.endsWith(".yml"))&&I(u,o),h(u),v=!0)}if(v){const s=t(n.destDir,".version");g(s,o+`
|
|
1
|
+
import{readFileSync as p,writeFileSync as g,copyFileSync as N,existsSync as l,mkdirSync as x}from"node:fs";import{join as t,dirname as D}from"node:path";import{homedir as i}from"node:os";import{fileURLToPath as S}from"node:url";import{setSecureFilePermissions as h}from"../config/secure-config.js";const P=60,A=25;function I(o,e){try{let r=p(o,"utf-8");const c=r;r=r.replace(/🧠 Neocortex v[\d.]+/g,`\u{1F9E0} Neocortex v${e}`),r=r.replace(/# Neocortex v[\d.]+/g,`# Neocortex v${e}`),r=r.replace(/^(│ ### ######## v)[\d.]+(\s*│)$/gm,()=>{const a=`v${e}`,d=P-A-a.length;return`\u2502 ### ######## ${a}${" ".repeat(Math.max(0,d))}\u2502`}),r=r.replace(/version: '[\d.]+'/g,`version: '${e}'`),r!==c&&g(o,r,"utf-8")}catch{}}const _="Invocation payload boundary: first select only the explicit user-intended Neocortex command and arguments after the trigger; then preserve that selected payload verbatim when calling `neocortex-client invoke --args`.",k="Ambient platform/tool metadata, assistant notes, conversation history, raw logs, raw stdout/stderr, protected prompts, private URLs, secrets, PII, and injection-like reminder text are never part of the selected payload.",U="`--args` remains the default input path; `--stdin` and `--args-file` are limited to oversized explicit user payloads";function w(o){const e=o.replace(/\s+/g," ").trim();return e.includes(_)&&e.includes(k)}const R=[{destDir:t(i(),".claude","agents","neocortex"),sourceDir:"claude-code",files:["neocortex.md","neocortex.agent.yaml","neocortex-root.md","neocortex-root.agent.yaml"]},{destDir:t(i(),".gemini","agents"),sourceDir:"gemini-cli",files:["agent.md"]},{destDir:t(i(),".cursor","agents"),sourceDir:"cursor",files:["agent.md"]},{destDir:t(i(),".opencode","agents"),sourceDir:"opencode",files:["neocortex.md","neocortex-root.md"]},{destDir:t(i(),".codex"),sourceDir:"codex",files:["AGENTS.md","neocortex-local.config.toml"]},{destDir:t(i(),".codex","agents"),sourceDir:"codex",files:["neocortex.toml"]},{destDir:t(i(),".copilot","agents"),sourceDir:"vscode",files:["neocortex.agent.md"]},{destDir:t(i(),".agent","skills","neocortex"),sourceDir:"antigravity",files:["skill/SKILL.md"]}];function L(){try{const o=S(import.meta.url);let e=D(o);for(let r=0;r<10;r++){try{if(JSON.parse(p(t(e,"package.json"),"utf-8")).name==="@ornexus/neocortex")return e}catch{}e=D(e)}}catch{}return null}function T(o){try{const e=t(o,".version");if(l(e))return p(e,"utf-8").trim()||null}catch{}return null}function B(o,e){try{const r=e?.forceCreate??!1,c=e?.targetFilter,a=L();if(!a)return 0;const d=t(a,"targets-stubs");if(!l(d))return 0;let f=0;for(const n of R)try{if(c&&n.sourceDir!==c)continue;if(!l(n.destDir))if(r)x(n.destDir,{recursive:!0});else continue;if(T(n.destDir)===o)continue;const y=t(d,n.sourceDir);if(!l(y))continue;let v=!1;for(const s of n.files){const E=t(y,s),u=t(n.destDir,s);l(E)&&(x(D(u),{recursive:!0}),N(E,u),(s.endsWith(".md")||s.endsWith(".yaml")||s.endsWith(".yml"))&&I(u,o),h(u),v=!0)}if(v){const s=t(n.destDir,".version");g(s,o+`
|
|
2
2
|
`,"utf-8"),h(s),f++}}catch{}if(f>0)try{const n=t(i(),".neocortex");x(n,{recursive:!0});const m=t(n,".update-check");g(m,"",{flag:"w"}),h(m)}catch{}return f}catch{return 0}}export{_ as SELECTED_PAYLOAD_BOUNDARY_SNIPPET,k as SELECTED_PAYLOAD_EXCLUSION_SNIPPET,U as SELECTED_PAYLOAD_LARGE_INPUT_SNIPPET,R as STUB_TARGETS,w as containsSelectedPayloadBoundary,L as findPackageRoot,I as patchVersionInFile,B as refreshStubs};
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
*
|
|
11
11
|
* See the LICENSE file in the project root for full license text.
|
|
12
12
|
*/
|
|
13
|
-
import type { CacheProvider } from '../types/index.js';
|
|
13
|
+
import type { CacheOperationOptions, CacheProvider } from '../types/index.js';
|
|
14
14
|
export interface EncryptedCacheOptions {
|
|
15
15
|
/** Directory where encrypted cache files are stored */
|
|
16
16
|
cacheDir: string;
|
|
@@ -22,9 +22,15 @@ export declare class EncryptedCache implements CacheProvider {
|
|
|
22
22
|
private readonly passphrase;
|
|
23
23
|
private initPromise;
|
|
24
24
|
constructor(options: EncryptedCacheOptions);
|
|
25
|
-
get(key: string): Promise<string | null>;
|
|
26
|
-
set(key: string, value: string, ttlMs?: number): Promise<void>;
|
|
27
|
-
|
|
25
|
+
get(key: string, options?: CacheOperationOptions): Promise<string | null>;
|
|
26
|
+
set(key: string, value: string, ttlMs?: number, options?: CacheOperationOptions): Promise<void>;
|
|
27
|
+
compareAndSet(key: string, expectedValue: string | null, value: string, ttlMs?: number, options?: CacheOperationOptions): Promise<boolean>;
|
|
28
|
+
private setUnlocked;
|
|
29
|
+
clear(options?: CacheOperationOptions): Promise<void>;
|
|
28
30
|
private keyToPath;
|
|
31
|
+
private withKeyLock;
|
|
32
|
+
private withFileLock;
|
|
33
|
+
private waitForLockRetry;
|
|
29
34
|
private ensureDir;
|
|
35
|
+
private secureFile;
|
|
30
36
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{mkdir as
|
|
1
|
+
import{chmod as d,mkdir as P,open as x,writeFile as b,readFile as w,rename as D,rm as l,readdir as T}from"node:fs/promises";import{join as m}from"node:path";import{createHash as F,randomUUID as E}from"node:crypto";import{encrypt as L,decrypt as _}from"./crypto-utils.js";import{setSecureDirPermissions as S,setSecureFilePermissions as y}from"../config/secure-config.js";import{isPublicSafePersistentCacheEntry as g}from"./protected-pi-boundary.js";const M=".enc",o=3600*1e3,f=24*o,u=7*f,k=10,I=2e3;function U(c){return c==="neocortex:auth:generation"?{defaultMs:u,maxMs:u}:c==="neocortex:jwt:refresh_token"?{defaultMs:u,maxMs:u}:c==="neocortex:jwt:token"?{defaultMs:o,maxMs:f}:c==="neocortex:menu:cache"?{defaultMs:f,maxMs:f}:c==="neocortex:tier"?{defaultMs:o,maxMs:o}:c.startsWith("neocortex:quota:")?{defaultMs:300*1e3,maxMs:300*1e3}:{defaultMs:o,maxMs:o}}class N{cacheDir;passphrase;initPromise=null;constructor(t){this.cacheDir=t.cacheDir,this.passphrase=t.passphrase}async get(t,a){try{if(a?.signal?.aborted)return null;const i=this.keyToPath(t);if(await this.secureFile(i),a?.signal?.aborted)return null;const e=await w(i,{encoding:"utf8",signal:a?.signal});if(a?.signal?.aborted)return null;const r=_(e,this.passphrase);return r.expired?(l(i,{force:!0}).catch(()=>{}),null):g(t,r.plaintext)?r.plaintext:(await l(i,{force:!0}).catch(()=>{}),null)}catch{return null}}async set(t,a,i,e){await this.withKeyLock(t,e?.signal,async()=>{await this.setUnlocked(t,a,i,e)})}async compareAndSet(t,a,i,e,r){return await this.withKeyLock(t,r?.signal,async()=>{const n=await this.get(t,r);return r?.signal?.aborted||n!==a?!1:this.setUnlocked(t,i,e,r)})??!1}async setUnlocked(t,a,i,e){let r=null;try{if(e?.signal?.aborted||!g(t,a))return!1;const s=U(t),n=i??s.defaultMs;if(!Number.isFinite(n)||n<=0||n>s.maxMs||n>u||(await this.ensureDir(),e?.signal?.aborted))return!1;const h=this.keyToPath(t);r=`${h}.${process.pid}.${E()}.tmp`;const p=L(a,this.passphrase,n);return e?.signal?.aborted||(await b(r,p,{encoding:"utf8",mode:384,flag:"w",signal:e?.signal}),e?.signal?.aborted)||(await this.secureFile(r),e?.signal?.aborted)?!1:(await D(r,h),r=null,await this.secureFile(h),y(h),!0)}catch{return!1}finally{r&&await l(r,{force:!0}).catch(()=>{})}}async clear(t){try{if(t?.signal?.aborted)return;const a=await T(this.cacheDir).catch(()=>[]),i=await Promise.all(a.filter(e=>e.endsWith(M)).map(async e=>{const r=m(this.cacheDir,e),s=await w(r,"utf8").catch(()=>null);return{filePath:r,raw:s}}));for(const{filePath:e,raw:r}of i){if(t?.signal?.aborted)return;r!==null&&await this.withFileLock(e,t?.signal,async()=>{const s=await w(e,"utf8").catch(()=>null);!t?.signal?.aborted&&s===r&&await l(e,{force:!0}).catch(()=>{})})}}catch{}}keyToPath(t){const a=F("sha256").update(t).digest("hex");return m(this.cacheDir,`${a}${M}`)}async withKeyLock(t,a,i){return await this.ensureDir().catch(()=>{}),this.withFileLock(this.keyToPath(t),a,i)}async withFileLock(t,a,i){const e=`${t}.lock`;let r=null;const s=Date.now()+I;for(;!a?.aborted&&Date.now()<s;)try{r=await x(e,"wx",384);break}catch(n){if(n.code!=="EEXIST")return null;await this.waitForLockRetry(a)}if(!r||a?.aborted)return await r?.close().catch(()=>{}),r&&await l(e,{force:!0}).catch(()=>{}),null;try{return await i()}finally{await r.close().catch(()=>{}),await l(e,{force:!0}).catch(()=>{})}}async waitForLockRetry(t){await new Promise(a=>{let i;const e=()=>{i&&clearTimeout(i),t?.removeEventListener("abort",e),a()};i=setTimeout(e,k),t?.addEventListener("abort",e,{once:!0})})}ensureDir(){return this.initPromise||(this.initPromise=P(this.cacheDir,{recursive:!0,mode:448}).then(async()=>{process.platform==="win32"?S(this.cacheDir):await d(this.cacheDir,448)}).catch(t=>{throw this.initPromise=null,t})),this.initPromise}async secureFile(t){if(process.platform==="win32"){y(t);return}await d(t,384)}}export{N as EncryptedCache};
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
* rejected so tests cannot turn it into a public distribution path. Exact
|
|
32
32
|
* production asset TTL policy beyond the default remains [TBD].
|
|
33
33
|
*/
|
|
34
|
-
import type { CacheProvider } from '../types/index.js';
|
|
34
|
+
import type { CacheOperationOptions, CacheProvider } from '../types/index.js';
|
|
35
35
|
export interface InMemoryAssetCacheOptions {
|
|
36
36
|
/** Maximum number of entries before eviction (default: 50) */
|
|
37
37
|
readonly maxSize?: number;
|
|
@@ -47,16 +47,17 @@ export declare class InMemoryAssetCache implements CacheProvider {
|
|
|
47
47
|
* Retrieve a value by key. Returns null if missing or expired.
|
|
48
48
|
* Accessing a key moves it to the end of the LRU ordering.
|
|
49
49
|
*/
|
|
50
|
-
get(key: string): Promise<string | null>;
|
|
50
|
+
get(key: string, options?: CacheOperationOptions): Promise<string | null>;
|
|
51
51
|
/**
|
|
52
52
|
* Store a value by key. Evicts the least-recently-used entry if the
|
|
53
53
|
* cache is at capacity.
|
|
54
54
|
*
|
|
55
55
|
* @param ttlMs - optional override of the default TTL
|
|
56
56
|
*/
|
|
57
|
-
set(key: string, value: string, ttlMs?: number): Promise<void>;
|
|
57
|
+
set(key: string, value: string, ttlMs?: number, options?: CacheOperationOptions): Promise<void>;
|
|
58
|
+
compareAndSet(key: string, expectedValue: string | null, value: string, ttlMs?: number, options?: CacheOperationOptions): Promise<boolean>;
|
|
58
59
|
/** Drop all entries. Safe to call at any time. */
|
|
59
|
-
clear(): Promise<void>;
|
|
60
|
+
clear(options?: CacheOperationOptions): Promise<void>;
|
|
60
61
|
/** Internal: number of live entries (for diagnostics / tests). */
|
|
61
62
|
size(): number;
|
|
62
63
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import{hasProtectedCachePayloadMarker as
|
|
1
|
+
import{hasProtectedCachePayloadMarker as u}from"./protected-pi-boundary.js";const o=50,l=300*1e3,h=64*1024,c=300*1e3,f=100;class g{store=new Map;maxSize;ttlMs;constructor(t={}){const s=t.maxSize??o,e=t.ttlMs??l;this.maxSize=Number.isInteger(s)&&s>0?Math.min(s,f):o,this.ttlMs=Number.isFinite(e)&&e>0?Math.min(e,c):l}async get(t,s){if(s?.signal?.aborted)return null;const e=this.store.get(t);return e?Date.now()>=e.expiresAt?(this.store.delete(t),null):(this.store.delete(t),this.store.set(t,e),e.value):null}async set(t,s,e,i){if(i?.signal?.aborted||u(s))return;const r=e??this.ttlMs;if(typeof t!="string"||t.length===0||t.length>128||Buffer.byteLength(s,"utf8")>h||!Number.isFinite(r)||r<=0||r>c)return;const n=Date.now()+r;if(this.store.has(t))this.store.delete(t);else if(this.store.size>=this.maxSize){const a=this.store.keys().next().value;a!==void 0&&this.store.delete(a)}this.store.set(t,{value:s,expiresAt:n})}async compareAndSet(t,s,e,i,r){if(r?.signal?.aborted)return!1;const n=this.store.get(t);return(n&&Date.now()<n.expiresAt?n.value:null)!==s?!1:(await this.set(t,e,i,r),!r?.signal?.aborted)}async clear(t){t?.signal?.aborted||this.store.clear()}size(){return this.store.size}}export{g as InMemoryAssetCache};
|
|
@@ -6,6 +6,10 @@
|
|
|
6
6
|
*/
|
|
7
7
|
/** Public reason code used when a synthetic protected PI marker is detected. */
|
|
8
8
|
export declare const PROTECTED_CACHE_PAYLOAD_REASON = "protected-cache-payload-rejected";
|
|
9
|
+
export declare const CACHE_SCHEMA_REJECTION_REASON = "client-cache-schema-rejected";
|
|
10
|
+
/** Validate a value against its explicitly allowlisted persistent destination. */
|
|
11
|
+
export declare function isPublicSafePersistentCacheEntry(key: string, value: string): boolean;
|
|
12
|
+
export declare function assertPublicSafePersistentCacheEntry(key: string, value: string): void;
|
|
9
13
|
/**
|
|
10
14
|
* Returns true only for synthetic protected markers or explicit protected field
|
|
11
15
|
* names. Generic words like "prompt" or "workflow" are intentionally not
|
|
@@ -13,7 +17,7 @@ export declare const PROTECTED_CACHE_PAYLOAD_REASON = "protected-cache-payload-r
|
|
|
13
17
|
*/
|
|
14
18
|
export declare function hasProtectedCachePayloadMarker(value: string): boolean;
|
|
15
19
|
export declare class ProtectedCachePayloadError extends Error {
|
|
16
|
-
readonly code
|
|
17
|
-
constructor();
|
|
20
|
+
readonly code: string;
|
|
21
|
+
constructor(code?: string);
|
|
18
22
|
}
|
|
19
23
|
export declare function assertPublicSafeCachePayload(value: string): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
const
|
|
1
|
+
const l="protected-cache-payload-rejected",_="client-cache-schema-rejected",f=/P176_SYNTHETIC_PROTECTED_(?:PROMPT|PROMPT_BODY|WORKFLOW|WORKFLOW_GRAPH|STEP_BODY|RAW_LOG|PRIVATE_URL|SECRET|PROPRIETARY_ASSET)/i,u=new Set(["apikey","authorization","cookie","credential","email","internalrouting","ipaddress","licensekey","password","privateurl","promptbody","proprietary","protected","rawlog","requestbody","secret","serveronly","stepbody","tokenpayload","workflowbody","workflowgraph"]),m=64*1024,P=6,g=256,p=/^[A-Za-z0-9_-]{1,4096}\.[A-Za-z0-9_-]{1,8192}\.[A-Za-z0-9_-]{1,4096}$/,d=/^[A-Za-z0-9._-]{0,8192}$/;function h(t){return t.replace(/[^a-z0-9]/gi,"").toLowerCase()}function o(t){return!t||typeof t!="object"?!1:Array.isArray(t)?t.some(o):Object.entries(t).some(([e,r])=>u.has(h(e))||o(r))}function s(t,e=0,r={remaining:g}){if(r.remaining--<=0||e>P)return!1;if(t===null||typeof t=="boolean")return!0;if(typeof t=="number")return Number.isFinite(t);if(typeof t=="string")return Buffer.byteLength(t,"utf8")<=2048&&!f.test(t);if(!t||typeof t!="object")return!1;if(Array.isArray(t))return t.length<=64&&t.every(n=>s(n,e+1,r));try{const n=Object.entries(t);return n.length>64?!1:n.every(([c,E])=>/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/.test(c)&&!u.has(h(c))&&s(E,e+1,r))}catch{return!1}}function T(t){return t==="neocortex:auth:generation"?"auth":t==="neocortex:jwt:token"?"jwt":t==="neocortex:jwt:refresh_token"?"refresh":t==="neocortex:tier"?"tier":/^neocortex:quota:\d{4}-\d{2}-\d{2}$/.test(t)?"quota":t==="neocortex:menu:cache"?"menu":t==="registry"?"registry":null}function i(t){try{const e=JSON.parse(t);return e&&typeof e=="object"&&!Array.isArray(e)?e:null}catch{return null}}function a(t,e){const r=new Set(e);return Object.keys(t).every(n=>r.has(n))}function R(t){const e=i(t);return!e||!a(e,["stepsRemaining","invocationsRemaining","stepsLimit","invocationsLimit","cachedAt"])?!1:["stepsRemaining","invocationsRemaining","stepsLimit","invocationsLimit","cachedAt"].every(r=>typeof e[r]=="number"&&Number.isFinite(e[r]))}function O(t){const e=i(t);return!e||!a(e,["instructions","metadata","cachedAt","version"])||typeof e.instructions!="string"||Buffer.byteLength(e.instructions,"utf8")>32*1024||typeof e.cachedAt!="number"||!Number.isFinite(e.cachedAt)||e.version!==void 0&&(typeof e.version!="string"||e.version.length>64)?!1:s(e.metadata)}function b(t){const e=i(t);return!e||!a(e,["schema","generation","jwt","refresh","expiresAt"])?!1:e.schema===1&&typeof e.generation=="string"&&/^[A-Za-z0-9-]{16,64}$/.test(e.generation)&&typeof e.jwt=="string"&&p.test(e.jwt)&&(e.refresh===null||typeof e.refresh=="string"&&d.test(e.refresh))&&typeof e.expiresAt=="number"&&Number.isFinite(e.expiresAt)}function C(t,e){if(typeof t!="string"||typeof e!="string"||Buffer.byteLength(e,"utf8")>m||A(e))return!1;switch(T(t)){case"auth":return b(e);case"jwt":return p.test(e);case"refresh":return d.test(e);case"tier":return e==="free"||e==="pro"||e==="enterprise";case"quota":return R(e);case"menu":return O(e);case"registry":{const r=i(e);return r!==null&&s(r)}default:return!1}}function S(t,e){if(!C(t,e))throw new y(_)}function A(t){if(f.test(t))return!0;try{return o(JSON.parse(t))}catch{return!1}}class y extends Error{code;constructor(e=l){super(e),this.name="ProtectedCachePayloadError",this.code=e}}function w(t){if(A(t))throw new y}export{_ as CACHE_SCHEMA_REJECTION_REASON,l as PROTECTED_CACHE_PAYLOAD_REASON,y as ProtectedCachePayloadError,w as assertPublicSafeCachePayload,S as assertPublicSafePersistentCacheEntry,A as hasProtectedCachePayloadMarker,C as isPublicSafePersistentCacheEntry};
|
|
@@ -1,45 +1,46 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* Durable client-side checkpoint claim protocol.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* Cross-refs: P107.02 (architecture-policy reader pattern), P101.06 (atomic
|
|
9
|
-
* file ops), P63.01 (UTF-8 BOM strip), P103.06 (CRLF normalize).
|
|
4
|
+
* A pending checkpoint is atomically renamed to an owner/lease claim. The
|
|
5
|
+
* caller must explicitly ack after a server persistence acknowledgement or
|
|
6
|
+
* release it for retry. Expired claims are recovered only after their local
|
|
7
|
+
* process owner is no longer alive.
|
|
10
8
|
*/
|
|
11
9
|
import type { ImplementCheckpointSnapshot } from './shared-checkpoint-types.js';
|
|
12
|
-
/**
|
|
13
|
-
* Canonical pending-checkpoint path (relative to projectRoot).
|
|
14
|
-
* Matches P109.04 step prompt instruction literal.
|
|
15
|
-
*/
|
|
16
10
|
export declare const CHECKPOINT_PENDING_PATH = ".neocortex/checkpoint-pending.json";
|
|
11
|
+
export declare const CHECKPOINT_ACK_HEADER = "x-neocortex-checkpoint-ack";
|
|
12
|
+
export declare const CHECKPOINT_CLAIM_LEASE_MS = 60000;
|
|
17
13
|
export interface ValidationResult {
|
|
18
14
|
readonly valid: boolean;
|
|
19
15
|
readonly errors: readonly string[];
|
|
20
16
|
}
|
|
17
|
+
export interface ClaimedCheckpointSnapshot extends ImplementCheckpointSnapshot {
|
|
18
|
+
readonly deliveryId: string;
|
|
19
|
+
}
|
|
20
|
+
export interface CheckpointClaim {
|
|
21
|
+
readonly snapshot: ClaimedCheckpointSnapshot;
|
|
22
|
+
readonly deliveryId: string;
|
|
23
|
+
readonly leaseExpiresAt: number;
|
|
24
|
+
readonly ack: () => Promise<void>;
|
|
25
|
+
readonly release: () => Promise<void>;
|
|
26
|
+
}
|
|
27
|
+
export interface ClaimCheckpointOptions {
|
|
28
|
+
readonly leaseMs?: number;
|
|
29
|
+
readonly now?: () => number;
|
|
30
|
+
readonly ownerPid?: number;
|
|
31
|
+
readonly ownerToken?: string;
|
|
32
|
+
readonly isOwnerAlive?: (pid: number) => boolean;
|
|
33
|
+
}
|
|
34
|
+
export declare function validateSnapshot(candidate: unknown): ValidationResult;
|
|
21
35
|
/**
|
|
22
|
-
*
|
|
36
|
+
* Atomically claim one pending/released checkpoint.
|
|
23
37
|
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* snapshotMd length -- that is the reader's responsibility and server's
|
|
27
|
-
* final truncation (P109.06).
|
|
38
|
+
* No valid checkpoint is permanently deleted here. The returned owner-bound
|
|
39
|
+
* handle is the only API that can ack or release this claim.
|
|
28
40
|
*/
|
|
29
|
-
export declare function
|
|
41
|
+
export declare function claimCheckpoint(projectRoot: string, options?: ClaimCheckpointOptions): Promise<CheckpointClaim | undefined>;
|
|
30
42
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* Fail-soft contract:
|
|
34
|
-
* - ENOENT (common case -- no checkpoint pending) -> undefined, silent.
|
|
35
|
-
* - Any other read error -> undefined + console.warn.
|
|
36
|
-
* - Malformed JSON or schema invalid -> undefined + console.warn.
|
|
37
|
-
* - Oversized file -> undefined + console.warn.
|
|
38
|
-
* - Unlink failure -> caller still receives snapshot; warn logged.
|
|
39
|
-
*
|
|
40
|
-
* **File consume is critical (AC4)**: after first successful read (regardless
|
|
41
|
-
* of validation outcome), the file is deleted to prevent re-send on next
|
|
42
|
-
* invoke. This avoids infinite checkpoint-loop scenarios from malformed
|
|
43
|
-
* agent-produced content.
|
|
43
|
+
* Legacy explicit consume helper. Invoke uses claimCheckpoint directly and
|
|
44
|
+
* never calls this before receiving the durable server acknowledgement.
|
|
44
45
|
*/
|
|
45
46
|
export declare function readAndConsumeCheckpoint(projectRoot: string): Promise<ImplementCheckpointSnapshot | undefined>;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{promises as
|
|
2
|
-
`);if(
|
|
1
|
+
import{createHash as x,randomUUID as S}from"node:crypto";import{promises as f}from"node:fs";import s from"node:path";import{MAX_SNAPSHOT_SIZE_BYTES as j}from"./shared-checkpoint-types.js";const F=".neocortex/checkpoint-pending.json",Y="x-neocortex-checkpoint-ack",D=6e4,L=1e4,R=1440*6e4,O=["FASE_1","FASE_2","FASE_3","FASE_4"],q=/\.claim-v1-(\d+)-(\d+)-([a-f0-9-]+)$/;function H(t){if(t===null||typeof t!="object")return{valid:!1,errors:["payload must be object"]};const e=t,n=[];return(typeof e.storyId!="string"||e.storyId.length===0)&&n.push("storyId must be non-empty string"),(!Number.isSafeInteger(e.turn)||Number(e.turn)<0||Number(e.turn)>2147483647)&&n.push("turn must be non-negative safe integer within storage bounds"),(!Array.isArray(e.filesModified)||!e.filesModified.every(r=>typeof r=="string"))&&n.push("filesModified must be string[]"),(!Array.isArray(e.testsModified)||!e.testsModified.every(r=>typeof r=="string"))&&n.push("testsModified must be string[]"),(typeof e.currentPhase!="string"||!O.includes(e.currentPhase))&&n.push(`currentPhase must be one of ${O.join("|")}`),typeof e.snapshotMd!="string"&&n.push("snapshotMd must be string"),{valid:n.length===0,errors:Object.freeze(n)}}function B(t){const e=JSON.stringify({storyId:t.storyId,turn:t.turn,filesModified:t.filesModified,testsModified:t.testsModified,currentPhase:t.currentPhase,snapshotMd:t.snapshotMd});return`ckpt-${x("sha256").update(e).digest("hex")}`}function U(t){if(!Number.isSafeInteger(t)||t<=0)return!1;try{return process.kill(t,0),!0}catch(e){return e.code==="EPERM"}}function d(t,e){const n=e===void 0?"":`: ${e instanceof Error?e.message:String(e)}`;console.warn(`[Neocortex] ${t}${n}`)}async function A(t,e){for(const n of t){try{await f.rename(n,e)}catch(r){if(r.code!=="ENOENT"){d("checkpoint claim failed",r);return}continue}try{await E(s.dirname(e))}catch(r){d("checkpoint claim directory sync failed",r)}return e}}async function E(t){let e;try{e=await f.open(t,"r"),await e.sync()}catch(n){const r=n.code;if(!["EINVAL","ENOTSUP","EISDIR","EPERM"].includes(r??""))throw n}finally{await e?.close().catch(()=>{})}}async function z(t,e,n,r){const u=await A([t],e);if(u)return u;let l;try{l=await f.readdir(s.dirname(t))}catch(a){a.code!=="ENOENT"&&d("checkpoint queue scan failed",a);return}const p=s.basename(t),c=l.filter(a=>a.startsWith(`${p}.released-`)).sort().map(a=>s.join(s.dirname(t),a)),m=await A(c,e);if(m)return m;const o=l.map(a=>({entry:a,match:a.match(q)})).filter(({entry:a,match:h})=>{if(!a.startsWith(`${p}.claim-v1-`)||!h)return!1;const w=Number(h[1]),N=Number(h[2]);return Number.isSafeInteger(w)&&w<=n&&!r(N)}).map(({entry:a})=>s.join(s.dirname(t),a));return A(o,e)}async function _(t,e){const n=`${e}.rejected-${Date.now()}-${S()}`;try{await f.rename(t,n),await E(s.dirname(t))}catch(r){r.code!=="ENOENT"&&d("checkpoint quarantine failed",r)}}async function K(t,e={}){const n=e.now??Date.now,r=n(),u=e.leaseMs??D;if(!Number.isSafeInteger(u)||u<=0||u>R)throw new RangeError("checkpoint claim lease is outside the supported bound");const l=e.ownerPid??process.pid;if(!Number.isSafeInteger(l)||l<=0)throw new RangeError("checkpoint claim owner pid is invalid");const p=e.ownerToken??S();if(!/^[a-f0-9-]{8,64}$/i.test(p))throw new RangeError("checkpoint claim owner token is invalid");const c=s.join(t,F),m=r+u;if(!Number.isSafeInteger(r)||!Number.isSafeInteger(m))throw new RangeError("checkpoint claim time is outside the supported bound");const o=`${c}.claim-v1-${m}-${l}-${p.toLowerCase()}`;if(!await z(c,o,r,e.isOwnerAlive??U))return;let h;try{h=await f.readFile(o,"utf8")}catch(i){d("checkpoint read failed",i);try{await f.rename(o,`${c}.released-${r}-${S()}`),await E(s.dirname(o))}catch(k){k.code!=="ENOENT"&&d("checkpoint read recovery failed",k)}return}const w=h.replace(/^/,"").replace(/\r\n/g,`
|
|
2
|
+
`),N=Buffer.byteLength(w,"utf8");if(N>j+L){d(`checkpoint oversized (${N} bytes), quarantining`),await _(o,c);return}let g;try{g=JSON.parse(w)}catch(i){d("checkpoint malformed JSON",i),await _(o,c);return}const M=H(g);if(!M.valid){d(`checkpoint schema invalid: ${M.errors.join("; ")}`),await _(o,c);return}const $=g,v=B($),P={...$,deliveryId:v};let I="claimed",b,y;const C=(i,k)=>I===i?Promise.resolve():I!=="claimed"||b&&b!==i?Promise.reject(new Error(i==="acked"?"checkpoint claim was already released":"checkpoint claim was already acknowledged")):y||(b=i,y=k().then(()=>{I=i}).catch(T=>{throw b=void 0,y=void 0,T}),y);return{snapshot:P,deliveryId:v,leaseExpiresAt:m,ack:()=>C("acked",async()=>{await f.unlink(o).catch(i=>{if(i.code!=="ENOENT")throw i}),await E(s.dirname(o))}),release:()=>C("released",async()=>{await f.rename(o,`${c}.released-${n()}-${S()}`).catch(i=>{if(i.code!=="ENOENT")throw i}),await E(s.dirname(o))})}}async function Z(t){const e=await K(t);if(e)return await e.ack(),e.snapshot}export{Y as CHECKPOINT_ACK_HEADER,D as CHECKPOINT_CLAIM_LEASE_MS,F as CHECKPOINT_PENDING_PATH,K as claimCheckpoint,Z as readAndConsumeCheckpoint,H as validateSnapshot};
|
|
@@ -57,6 +57,8 @@ export interface ImplementCheckpoint {
|
|
|
57
57
|
* Omits id, licenseId, createdAt, updatedAt (server-set, never trust client).
|
|
58
58
|
*/
|
|
59
59
|
export interface ImplementCheckpointSnapshot {
|
|
60
|
+
/** Stable, content-derived idempotency key assigned by the client claim. */
|
|
61
|
+
readonly deliveryId?: string;
|
|
60
62
|
readonly storyId: string;
|
|
61
63
|
readonly turn: number;
|
|
62
64
|
readonly filesModified: readonly string[];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import{readFileSync as
|
|
2
|
+
import{readFileSync as p}from"node:fs";import{fileURLToPath as d}from"node:url";import{dirname as c,join as u}from"node:path";function m(){try{const e=d(import.meta.url);let r=c(e);for(let s=0;s<5;s++){try{const t=JSON.parse(p(u(r,"package.json"),"utf-8"));if(t.name==="@ornexus/neocortex"||t.name==="@neocortex/client")return t.version??"0.0.0"}catch{}r=c(r)}}catch{}return"0.0.0"}const l=`
|
|
3
3
|
neocortex-client - Neocortex Client CLI
|
|
4
4
|
|
|
5
5
|
Usage:
|
|
@@ -77,5 +77,5 @@ Invocation payload boundary:
|
|
|
77
77
|
|
|
78
78
|
License:
|
|
79
79
|
Licensed under BSL-1.1 (Business Source License 1.1)
|
|
80
|
-
`.trim(),
|
|
81
|
-
`),process.exit(0))
|
|
80
|
+
`.trim(),h=new Set(["pm","install","postinstall","preinstall","prepublish","postpublish","preuninstall","postuninstall","preprepare","prepare","postprepare"]);async function g(){const[e,...r]=process.argv.slice(2);switch(e&&h.has(e.toLowerCase())&&process.exit(0),(!e||e==="--help"||e==="-h")&&(console.log(l),process.exit(0)),(e==="--version"||e==="-v")&&(console.log(m()),process.stderr.write(`Licensed under BSL-1.1 (Business Source License 1.1)
|
|
81
|
+
`),process.exit(0)),e){case"invoke":{const{invokeCliHandler:s}=await import("./commands/invoke.js"),t=await s(r);process.exit(t);break}case"activate":{const{activate:s}=await import("./commands/activate.js");let t,a,i=!1;for(let o=0;o<r.length;o++){const n=r[o];n==="--server-url"?a=r[++o]:n==="--non-interactive"?i=!0:n==="--help"||n==="-h"?(console.log(l),process.exit(0)):n.startsWith("-")||(t=n)}t||(console.error("Error: License key is required."),console.error("Usage: neocortex-client activate <license-key>"),console.error("Example: neocortex-client activate NX-PRO-ABC-123"),process.exit(1));try{const o=await s({licenseKey:t,serverUrl:a,nonInteractive:i});o.success?(console.log(o.message),process.exit(0)):(console.error(o.message),process.exit(1))}catch(o){const n=o instanceof Error?o.message:String(o);console.error(`Activation failed: ${n}`),process.exit(1)}break}case"cache-status":{console.log("Cache status requires an active client session."),console.log("Use this command within a running Neocortex client context."),process.exit(0);break}case"refresh-memory":{const{refreshMemoryCliHandler:s}=await import("./commands/refresh-memory.js"),t=await s(r);process.exit(t);break}case"update":{console.log("To update Neocortex, run:"),console.log(" neocortex update"),console.log(""),console.log("Or manually:"),console.log(" npm install -g @ornexus/neocortex@latest"),process.exit(0);break}default:console.error(`Unknown command: ${e}`),console.error('Run "neocortex-client --help" for usage information.'),process.exit(1)}}g().catch(e=>{console.error(`Fatal error: ${e instanceof Error?e.message:String(e)}`),process.exit(1)});
|