@danielblomma/cortex-mcp 2.4.1 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/README.md +5 -0
  3. package/bin/cli/arguments.mjs +60 -0
  4. package/bin/cli/context-passthrough.mjs +129 -0
  5. package/bin/cli/daemon.mjs +157 -0
  6. package/bin/cli/enterprise.mjs +516 -0
  7. package/bin/cli/help.mjs +88 -0
  8. package/bin/cli/hooks.mjs +199 -0
  9. package/bin/cli/mcp-command.mjs +87 -0
  10. package/bin/cli/paths.mjs +14 -0
  11. package/bin/cli/process.mjs +49 -0
  12. package/bin/cli/project-commands.mjs +237 -0
  13. package/bin/cli/project-runtime.mjs +34 -0
  14. package/bin/cli/query-command.mjs +18 -0
  15. package/bin/cli/router.mjs +66 -0
  16. package/bin/cli/run-command.mjs +44 -0
  17. package/bin/cli/scaffold-ownership.mjs +936 -0
  18. package/bin/cli/scaffold.mjs +687 -0
  19. package/bin/cli/stage-command.mjs +9 -0
  20. package/bin/cli/telemetry-command.mjs +18 -0
  21. package/bin/cli/trusted-runtime.mjs +18 -0
  22. package/bin/cortex.mjs +6 -1834
  23. package/mcp-registry-submission.json +1 -1
  24. package/package.json +3 -2
  25. package/scaffold/ownership/baseline-v2.4.1.json +22 -0
  26. package/scaffold/ownership/current.json +4 -0
  27. package/scaffold/ownership/v1.json +456 -0
  28. package/scaffold/scripts/ingest-parsers.mjs +1 -387
  29. package/scaffold/scripts/ingest-worker.mjs +4 -1
  30. package/scaffold/scripts/ingest.mjs +5 -3899
  31. package/scaffold/scripts/lib/ingest/arguments.mjs +78 -0
  32. package/scaffold/scripts/lib/ingest/chunks.mjs +212 -0
  33. package/scaffold/scripts/lib/ingest/config.mjs +120 -0
  34. package/scaffold/scripts/lib/ingest/constants.mjs +222 -0
  35. package/scaffold/scripts/lib/ingest/files.mjs +387 -0
  36. package/scaffold/scripts/lib/ingest/incremental-state.mjs +131 -0
  37. package/scaffold/scripts/lib/ingest/io.mjs +78 -0
  38. package/scaffold/scripts/lib/ingest/main.mjs +76 -0
  39. package/scaffold/scripts/lib/ingest/parser-composition.mjs +140 -0
  40. package/scaffold/scripts/lib/ingest/parser-registry.mjs +387 -0
  41. package/scaffold/scripts/lib/ingest/pipeline-stages.mjs +1625 -0
  42. package/scaffold/scripts/lib/ingest/projects.mjs +272 -0
  43. package/scaffold/scripts/lib/ingest/relations.mjs +860 -0
  44. package/scaffold/scripts/lib/ingest/runtime-paths.mjs +11 -0
  45. package/scaffold/scripts/lib/ingest/workers.mjs +264 -0
package/CHANGELOG.md CHANGED
@@ -1,5 +1,30 @@
1
1
  # Changelog
2
2
 
3
+ ## 2.4.2 — 2026-07-30
4
+
5
+ ### Changed
6
+
7
+ - Split the Cortex CLI into cohesive command modules behind the existing
8
+ executable, preserving command names, arguments, streams, exit behavior,
9
+ JSON envelopes, Enterprise trust resolution, and `.context/mcp`
10
+ compatibility.
11
+ - Established `scaffold/scripts/lib/ingest/` as the single canonical ingest
12
+ implementation for development and packaged entrypoints. Deterministic
13
+ output, worker fallback, trace checkpoints, and bounded result retention
14
+ remain unchanged.
15
+ - Added versioned scaffold ownership and installed fingerprints. Forced
16
+ upgrades remove only unmodified obsolete Cortex-managed files, reject
17
+ modified or unsafe collisions, and preserve unknown files, configuration,
18
+ rules, ontology changes, Enterprise secrets, and agent instructions.
19
+ - Extended release synchronization to keep the shipped MCP registry package
20
+ and Node runtime requirement aligned with `package.json`.
21
+
22
+ ### Upgrade
23
+
24
+ - Upgrade the npm package, then run `cortex init --force`, `cortex bootstrap`,
25
+ and `cortex update`. Projects created before ownership state existed are
26
+ migrated only from hash-verified Cortex 2.4.1 scaffold files.
27
+
3
28
  ## 2.4.1 — 2026-07-28
4
29
 
5
30
  ### Security
package/README.md CHANGED
@@ -108,6 +108,11 @@ per-user daemon; `cortex bootstrap` performs the verified restart.
108
108
 
109
109
  Version-specific notes (see [CHANGELOG.md](CHANGELOG.md) for details):
110
110
 
111
+ - **2.4.2**: `cortex init --force` now uses versioned ownership metadata to
112
+ remove only unmodified obsolete Cortex-managed files. Unknown files and
113
+ protected configuration, rules, ontology, Enterprise, and agent-instruction
114
+ content remain user-owned; modified obsolete files and unsafe collisions
115
+ fail the upgrade instead of being overwritten or deleted.
111
116
  - **2.4.1**: enterprise onboarding no longer accepts API keys as positional
112
117
  arguments. Pipe the key to
113
118
  `sudo cortex enterprise install --api-key-stdin`. Enterprise endpoints must
@@ -0,0 +1,60 @@
1
+ import path from "node:path";
2
+
3
+ export function parseInitArgs(args) {
4
+ let target = process.cwd();
5
+ let force = false;
6
+ let bootstrap = false;
7
+ let connect = false;
8
+ let watch = true;
9
+
10
+ for (const arg of args) {
11
+ if (arg === "--force") {
12
+ force = true;
13
+ continue;
14
+ }
15
+ if (arg === "--bootstrap") {
16
+ bootstrap = true;
17
+ continue;
18
+ }
19
+ if (arg === "--connect") {
20
+ connect = true;
21
+ continue;
22
+ }
23
+ if (arg === "--no-connect") {
24
+ connect = false;
25
+ continue;
26
+ }
27
+ if (arg === "--watch") {
28
+ watch = true;
29
+ continue;
30
+ }
31
+ if (arg === "--no-watch") {
32
+ watch = false;
33
+ continue;
34
+ }
35
+ if (arg.startsWith("-")) {
36
+ throw new Error(`Unknown init option: ${arg}`);
37
+ }
38
+ target = path.resolve(arg);
39
+ }
40
+
41
+ return { target, force, bootstrap, connect, watch };
42
+ }
43
+
44
+ export function parseConnectArgs(args) {
45
+ let target = process.cwd();
46
+ let skipBuild = false;
47
+
48
+ for (const arg of args) {
49
+ if (arg === "--skip-build") {
50
+ skipBuild = true;
51
+ continue;
52
+ }
53
+ if (arg.startsWith("-")) {
54
+ throw new Error(`Unknown connect option: ${arg}`);
55
+ }
56
+ target = path.resolve(arg);
57
+ }
58
+
59
+ return { target, skipBuild };
60
+ }
@@ -0,0 +1,129 @@
1
+ import fs from "node:fs";
2
+ import path from "node:path";
3
+ import { restartDaemonAfterRuntimeUpgrade } from "./daemon.mjs";
4
+ import { CONTEXT_SCRIPTS_REL } from "./paths.mjs";
5
+ import { runCommand } from "./process.mjs";
6
+ import {
7
+ ensureScaffoldExists,
8
+ hardenEnterpriseConfigPermissions,
9
+ initializeScaffold,
10
+ installAssistantHelpers,
11
+ isScaffoldOutOfDate,
12
+ isTruthyEnv,
13
+ maybeInstallGitHooks,
14
+ } from "./scaffold.mjs";
15
+
16
+ export const PASSTHROUGH_COMMANDS = new Set([
17
+ "bootstrap",
18
+ "update",
19
+ "status",
20
+ "ingest",
21
+ "embed",
22
+ "graph-load",
23
+ "dashboard",
24
+ "watch",
25
+ "refresh",
26
+ "memory-compile",
27
+ "memory-lint",
28
+ "doctor",
29
+ ]);
30
+
31
+ const INDEX_MUTATING_COMMANDS = new Set([
32
+ "bootstrap",
33
+ "update",
34
+ "refresh",
35
+ "ingest",
36
+ "embed",
37
+ "graph-load",
38
+ ]);
39
+
40
+ function invalidateSessionStatusCache(cwd) {
41
+ try {
42
+ fs.rmSync(
43
+ path.join(cwd, ".context", "cache", "session-status.json"),
44
+ { force: true },
45
+ );
46
+ } catch {
47
+ // Best effort: a stale cache only delays the status refresh.
48
+ }
49
+ }
50
+
51
+ export async function runContextCommand(cwd, contextArgs) {
52
+ const contextScript = path.join(cwd, CONTEXT_SCRIPTS_REL, "context.sh");
53
+ if (!fs.existsSync(contextScript)) {
54
+ throw new Error(`Missing ${contextScript}. Run 'cortex init' first.`);
55
+ }
56
+ try {
57
+ await runCommand("bash", [contextScript, ...contextArgs], cwd);
58
+ } finally {
59
+ if (INDEX_MUTATING_COMMANDS.has(contextArgs[0])) {
60
+ invalidateSessionStatusCache(cwd);
61
+ }
62
+ }
63
+ }
64
+
65
+ async function confirmPrompt(message) {
66
+ const { createInterface } = await import("node:readline/promises");
67
+ const rl = createInterface({
68
+ input: process.stdin,
69
+ output: process.stderr,
70
+ });
71
+ try {
72
+ const answer = (await rl.question(message)).trim().toLowerCase();
73
+ return answer === "y" || answer === "yes";
74
+ } finally {
75
+ rl.close();
76
+ }
77
+ }
78
+
79
+ export async function maybeMigrateScaffold(targetDir, command) {
80
+ if (!isScaffoldOutOfDate(targetDir)) return;
81
+
82
+ const autoYes = isTruthyEnv(process.env.CORTEX_AUTO_MIGRATE);
83
+ const interactive = Boolean(process.stdin.isTTY && process.stderr.isTTY);
84
+
85
+ console.error(
86
+ `[cortex] scaffold in ${targetDir} is out of date ` +
87
+ `(missing .context/scripts/doctor.sh, context runtime package.json, doctor subcommand in context.sh, ` +
88
+ `or carries a legacy mcp/ directory at the project root).`,
89
+ );
90
+
91
+ let proceed = autoYes;
92
+ if (!autoYes) {
93
+ if (!interactive) {
94
+ throw new Error(
95
+ `Cortex CLI ${process.env.CORTEX_CLI_VERSION ?? ""} needs an updated scaffold to run '${command}'. ` +
96
+ `Run 'cortex init --bootstrap' to upgrade, or re-run with CORTEX_AUTO_MIGRATE=true.`,
97
+ );
98
+ }
99
+ proceed = await confirmPrompt(
100
+ "[cortex] Upgrade scaffold now (runs 'cortex init --bootstrap')? [y/N] ",
101
+ );
102
+ }
103
+
104
+ if (!proceed) {
105
+ throw new Error(
106
+ "Scaffold upgrade declined. Run 'cortex init --bootstrap' manually to continue.",
107
+ );
108
+ }
109
+
110
+ console.error(`[cortex] migrating scaffold in ${targetDir}`);
111
+ ensureScaffoldExists();
112
+ initializeScaffold(targetDir, true);
113
+ installAssistantHelpers(targetDir);
114
+ await maybeInstallGitHooks(targetDir);
115
+ await runContextCommand(targetDir, ["bootstrap"]);
116
+ console.error(`[cortex] scaffold upgraded; continuing with '${command}'`);
117
+ }
118
+
119
+ export async function runPassthroughCommand(command, rest) {
120
+ const cwd = process.cwd();
121
+ await maybeMigrateScaffold(cwd, command);
122
+ if (command === "bootstrap") {
123
+ hardenEnterpriseConfigPermissions(cwd);
124
+ }
125
+ await runContextCommand(cwd, [command, ...rest]);
126
+ if (command === "bootstrap") {
127
+ await restartDaemonAfterRuntimeUpgrade(cwd);
128
+ }
129
+ }
@@ -0,0 +1,157 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { spawn } from "node:child_process";
5
+ import {
6
+ callDaemon,
7
+ probeVerifiedDaemon,
8
+ stopVerifiedDaemon,
9
+ } from "../daemon-control.mjs";
10
+ import { resolveDaemonEntry } from "./project-runtime.mjs";
11
+
12
+ function daemonDirPath() {
13
+ return path.join(process.env.HOME || os.homedir(), ".cortex");
14
+ }
15
+
16
+ function daemonPidFilePath() {
17
+ return path.join(daemonDirPath(), "daemon.pid");
18
+ }
19
+
20
+ function pidFileExists() {
21
+ return fs.existsSync(daemonPidFilePath());
22
+ }
23
+
24
+ function readPid() {
25
+ try {
26
+ const raw = fs.readFileSync(daemonPidFilePath(), "utf8").trim();
27
+ const pid = Number.parseInt(raw, 10);
28
+ return Number.isFinite(pid) && pid > 0 ? pid : null;
29
+ } catch {
30
+ return null;
31
+ }
32
+ }
33
+
34
+ function isPidAlive(pid) {
35
+ if (!pid) return false;
36
+ try {
37
+ process.kill(pid, 0);
38
+ return true;
39
+ } catch (err) {
40
+ if (err && typeof err === "object" && err.code === "EPERM") {
41
+ return true;
42
+ }
43
+ return false;
44
+ }
45
+ }
46
+
47
+ async function daemonControlDeps() {
48
+ return {
49
+ readPid,
50
+ isPidAlive,
51
+ call: callDaemon,
52
+ };
53
+ }
54
+
55
+ async function waitForVerifiedDaemon(deps, timeoutMs = 5_000) {
56
+ const deadline = Date.now() + timeoutMs;
57
+ while (Date.now() < deadline) {
58
+ const probe = await probeVerifiedDaemon(deps, 250);
59
+ if (probe.verified) return probe;
60
+ await new Promise((resolve) => setTimeout(resolve, 50));
61
+ }
62
+ return probeVerifiedDaemon(deps, 250);
63
+ }
64
+
65
+ export async function runDaemonCommand(args, options = {}) {
66
+ const sub = args[0] || "status";
67
+ const projectRoot = options.projectRoot || process.cwd();
68
+ const deps = await daemonControlDeps();
69
+ if (sub === "start") {
70
+ const existing = await probeVerifiedDaemon(deps);
71
+ if (existing.verified) {
72
+ console.log("Daemon already running.");
73
+ return;
74
+ }
75
+ if (existing.running) {
76
+ throw new Error(
77
+ `Refusing to replace unverified live pid ${existing.pid} (${existing.reason}).`,
78
+ );
79
+ }
80
+ const daemonDir = daemonDirPath();
81
+ fs.mkdirSync(daemonDir, { recursive: true });
82
+ const entry = resolveDaemonEntry(projectRoot);
83
+ if (!fs.existsSync(entry)) {
84
+ throw new Error(`Daemon entry not found: ${entry}. Build cortex first.`);
85
+ }
86
+ const logFd = fs.openSync(path.join(daemonDir, "daemon.log"), "a");
87
+ const child = spawn(process.execPath, [entry], {
88
+ detached: true,
89
+ stdio: ["ignore", logFd, logFd],
90
+ cwd: projectRoot,
91
+ env: { ...process.env, CORTEX_PROJECT_ROOT: projectRoot },
92
+ });
93
+ child.unref();
94
+ fs.closeSync(logFd);
95
+ const started = await waitForVerifiedDaemon(deps);
96
+ if (!started.verified) {
97
+ throw new Error(
98
+ `Daemon did not complete its verified socket handshake (${started.reason}).`,
99
+ );
100
+ }
101
+ console.log(
102
+ `Daemon started (pid=${started.pid}). Log: ${path.join(daemonDir, "daemon.log")}`,
103
+ );
104
+ return;
105
+ }
106
+ if (sub === "stop") {
107
+ const result = await stopVerifiedDaemon(deps);
108
+ if (!result.stopped) {
109
+ console.log("Daemon not running.");
110
+ return;
111
+ }
112
+ console.log(`Daemon stopped (verified pid=${result.pid}).`);
113
+ return;
114
+ }
115
+ if (sub === "restart") {
116
+ await stopVerifiedDaemon(deps);
117
+ await runDaemonCommand(["start"], { projectRoot });
118
+ return;
119
+ }
120
+ if (sub === "status") {
121
+ const probe = await probeVerifiedDaemon(deps);
122
+ if (probe.verified) {
123
+ console.log(`Daemon running (verified pid=${probe.pid})`);
124
+ } else if (probe.running) {
125
+ console.log(
126
+ `Daemon state unsafe: live pid=${probe.pid}, identity not verified (${probe.reason}).`,
127
+ );
128
+ } else {
129
+ console.log("Daemon not running.");
130
+ if (pidFileExists()) {
131
+ console.log(`(stale pid file at ${daemonPidFilePath()})`);
132
+ }
133
+ }
134
+ return;
135
+ }
136
+ throw new Error(
137
+ `Unknown daemon subcommand: ${sub}. Try start|stop|restart|status`,
138
+ );
139
+ }
140
+
141
+ export async function restartDaemonAfterRuntimeUpgrade(projectRoot) {
142
+ const daemonEntry = resolveDaemonEntry(projectRoot);
143
+ if (!fs.existsSync(daemonEntry)) return;
144
+ const deps = await daemonControlDeps();
145
+ const probe = await probeVerifiedDaemon(deps);
146
+ if (!probe.running) return;
147
+ if (!probe.verified) {
148
+ throw new Error(
149
+ `Runtime upgraded, but live pid ${probe.pid} could not be verified. ` +
150
+ "Refusing to signal it; stop it manually after confirming ownership.",
151
+ );
152
+ }
153
+ console.log(
154
+ `[cortex] restarting verified daemon pid=${probe.pid} to activate the upgraded runtime`,
155
+ );
156
+ await runDaemonCommand(["restart"], { projectRoot });
157
+ }