@ctxo/cli 0.13.0 → 0.14.0
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/README.md +20 -4
- package/dist/chunk-2Q76LH2H.js +186 -0
- package/dist/chunk-2Q76LH2H.js.map +1 -0
- package/dist/chunk-72CEQEQD.js +53 -0
- package/dist/chunk-72CEQEQD.js.map +1 -0
- package/dist/chunk-7HSA5JVP.js +1049 -0
- package/dist/chunk-7HSA5JVP.js.map +1 -0
- package/dist/chunk-G4BMKCKG.js +76 -0
- package/dist/chunk-G4BMKCKG.js.map +1 -0
- package/dist/chunk-L2CAUER6.js +508 -0
- package/dist/chunk-L2CAUER6.js.map +1 -0
- package/dist/chunk-LAX6VVBU.js +98 -0
- package/dist/chunk-LAX6VVBU.js.map +1 -0
- package/dist/chunk-SRQZDGFZ.js +128 -0
- package/dist/chunk-SRQZDGFZ.js.map +1 -0
- package/dist/{chunk-DT3ZH4AU.js → chunk-X4TVFJ4C.js} +6 -1
- package/dist/chunk-X4TVFJ4C.js.map +1 -0
- package/dist/{chunk-BYX3UFBJ.js → chunk-ZL36YS2V.js} +785 -1624
- package/dist/chunk-ZL36YS2V.js.map +1 -0
- package/dist/{cli-router-RR4CM62C.js → cli-router-AZCDNX5R.js} +982 -506
- package/dist/cli-router-AZCDNX5R.js.map +1 -0
- package/dist/index.js +13 -2874
- package/dist/index.js.map +1 -1
- package/dist/internal.d.ts +747 -0
- package/dist/internal.js +13 -0
- package/dist/{json-index-reader-OXTRTGE6.js → json-index-reader-DBW32TCN.js} +6 -4
- package/dist/json-index-reader-DBW32TCN.js.map +1 -0
- package/dist/mcp-server-6YG2QE6E.js +2604 -0
- package/dist/mcp-server-6YG2QE6E.js.map +1 -0
- package/dist/module-host-KNMKS62R.js +13 -0
- package/dist/module-host-KNMKS62R.js.map +1 -0
- package/dist/module-loader-B3LYF3W7.js +14 -0
- package/dist/module-loader-B3LYF3W7.js.map +1 -0
- package/dist/plugin-discovery-XRXI3IJF.js +13 -0
- package/dist/plugin-discovery-XRXI3IJF.js.map +1 -0
- package/llms-full.txt +105 -1
- package/llms.txt +21 -1
- package/package.json +15 -4
- package/dist/chunk-BYX3UFBJ.js.map +0 -1
- package/dist/chunk-DT3ZH4AU.js.map +0 -1
- package/dist/cli-router-RR4CM62C.js.map +0 -1
- /package/dist/{json-index-reader-OXTRTGE6.js.map → internal.js.map} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
resolvePackageCandidates
|
|
4
|
+
} from "./chunk-2Q76LH2H.js";
|
|
5
|
+
import {
|
|
6
|
+
createLogger
|
|
7
|
+
} from "./chunk-5DSKZJNN.js";
|
|
8
|
+
|
|
9
|
+
// src/adapters/modules/module-loader.ts
|
|
10
|
+
var log = createLogger("ctxo:modules");
|
|
11
|
+
var DOMAIN_MODULE_PACKAGE = "@ctxo/domain";
|
|
12
|
+
function sameMinor(a, b) {
|
|
13
|
+
const pa = /^(\d+)\.(\d+)/.exec(a);
|
|
14
|
+
const pb = /^(\d+)\.(\d+)/.exec(b);
|
|
15
|
+
if (!pa || !pb) return false;
|
|
16
|
+
return Number(pa[1]) === Number(pb[1]) && Number(pa[2]) === Number(pb[2]);
|
|
17
|
+
}
|
|
18
|
+
var memo = /* @__PURE__ */ new Map();
|
|
19
|
+
async function loadCandidate(candidate, host) {
|
|
20
|
+
const base = { name: DOMAIN_MODULE_PACKAGE, version: candidate.version, source: candidate.source };
|
|
21
|
+
const failed = (reason) => {
|
|
22
|
+
log.warn(`${DOMAIN_MODULE_PACKAGE} failed to load: ${reason}; domain features are off.`);
|
|
23
|
+
return { kind: "stop", report: { ...base, status: "load-failed", reason } };
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
const mod = await import(candidate.entryUrl);
|
|
27
|
+
if (typeof mod.register !== "function") return failed("missing register export");
|
|
28
|
+
const registration = await mod.register(host);
|
|
29
|
+
if (registration === void 0) {
|
|
30
|
+
return { kind: "stop", report: { ...base, status: "inactive" } };
|
|
31
|
+
}
|
|
32
|
+
if (registration === null || typeof registration !== "object" || registration.id !== "domain") {
|
|
33
|
+
return failed("register returned a registration with an unexpected id");
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
kind: "active",
|
|
37
|
+
loaded: { name: DOMAIN_MODULE_PACKAGE, version: candidate.version, registration },
|
|
38
|
+
report: { ...base, status: "active" }
|
|
39
|
+
};
|
|
40
|
+
} catch (err) {
|
|
41
|
+
return failed(err.message);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
async function resolveModules(host, cliAnchor) {
|
|
45
|
+
const candidates = resolvePackageCandidates(DOMAIN_MODULE_PACKAGE, host.projectRoot, cliAnchor);
|
|
46
|
+
if (candidates.length === 0) {
|
|
47
|
+
return { modules: [], reports: [{ name: DOMAIN_MODULE_PACKAGE, status: "not-installed" }] };
|
|
48
|
+
}
|
|
49
|
+
let firstMismatch;
|
|
50
|
+
for (const candidate of candidates) {
|
|
51
|
+
if (!sameMinor(candidate.version, host.cliVersion)) {
|
|
52
|
+
firstMismatch ??= candidate;
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
const outcome = await loadCandidate(candidate, host);
|
|
56
|
+
if (outcome.kind === "active") {
|
|
57
|
+
return { modules: [outcome.loaded], reports: [outcome.report] };
|
|
58
|
+
}
|
|
59
|
+
return { modules: [], reports: [outcome.report] };
|
|
60
|
+
}
|
|
61
|
+
const mismatch = firstMismatch;
|
|
62
|
+
log.warn(
|
|
63
|
+
`${DOMAIN_MODULE_PACKAGE} ${mismatch.version} does not match @ctxo/cli ${host.cliVersion}; domain features are off. Install ${DOMAIN_MODULE_PACKAGE}@${host.cliVersion} next to the CLI you run.`
|
|
64
|
+
);
|
|
65
|
+
return {
|
|
66
|
+
modules: [],
|
|
67
|
+
reports: [
|
|
68
|
+
{
|
|
69
|
+
name: DOMAIN_MODULE_PACKAGE,
|
|
70
|
+
status: "version-mismatch",
|
|
71
|
+
version: mismatch.version,
|
|
72
|
+
source: mismatch.source,
|
|
73
|
+
reason: `cli ${host.cliVersion}`
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
function loadCtxoModules(host, options = {}) {
|
|
79
|
+
const key = `${host.projectRoot}|${host.mode}|${options.cliAnchor ?? ""}`;
|
|
80
|
+
if (!options.refresh) {
|
|
81
|
+
const cached = memo.get(key);
|
|
82
|
+
if (cached) return cached;
|
|
83
|
+
}
|
|
84
|
+
const pending = resolveModules(host, options.cliAnchor).catch((err) => {
|
|
85
|
+
const reason = err.message;
|
|
86
|
+
log.warn(`${DOMAIN_MODULE_PACKAGE} failed to load: ${reason}; domain features are off.`);
|
|
87
|
+
return { modules: [], reports: [{ name: DOMAIN_MODULE_PACKAGE, status: "load-failed", reason }] };
|
|
88
|
+
});
|
|
89
|
+
memo.set(key, pending);
|
|
90
|
+
return pending;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export {
|
|
94
|
+
DOMAIN_MODULE_PACKAGE,
|
|
95
|
+
sameMinor,
|
|
96
|
+
loadCtxoModules
|
|
97
|
+
};
|
|
98
|
+
//# sourceMappingURL=chunk-LAX6VVBU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/modules/module-loader.ts"],"sourcesContent":["import type { CtxoHost, CtxoModuleRegister, CtxoModuleRegistration, LoadedModule } from '../../ports/i-ctxo-module.js';\nimport { createLogger } from '../../core/logger.js';\nimport { resolvePackageCandidates, type ResolvedPackage } from '../language/plugin-discovery.js';\n\nconst log = createLogger('ctxo:modules');\n\nexport const DOMAIN_MODULE_PACKAGE = '@ctxo/domain';\n\nexport type ModuleStatus = 'active' | 'inactive' | 'version-mismatch' | 'load-failed' | 'not-installed';\n\nexport interface ModuleReport {\n readonly name: string;\n readonly status: ModuleStatus;\n readonly version?: string;\n readonly source?: 'project' | 'cli';\n readonly reason?: string;\n}\n\nexport type { LoadedModule };\n\nexport interface ModuleLoadResult {\n readonly modules: readonly LoadedModule[];\n readonly reports: readonly ModuleReport[];\n}\n\nexport interface LoadCtxoModulesOptions {\n /** Resolve again instead of returning the memoized result. */\n readonly refresh?: boolean;\n /** A directory standing in for the CLI's location (tests); defaults to the running CLI. */\n readonly cliAnchor?: string;\n}\n\n/**\n * Whether two versions share a numeric major and minor. A module runs only against the CLI minor it was\n * built for (ADR-018 Decision 3). `@ctxo/domain` carries its own copy of this rule, on purpose: it cannot\n * import the CLI at runtime.\n */\nexport function sameMinor(a: string, b: string): boolean {\n const pa = /^(\\d+)\\.(\\d+)/.exec(a);\n const pb = /^(\\d+)\\.(\\d+)/.exec(b);\n if (!pa || !pb) return false;\n return Number(pa[1]) === Number(pb[1]) && Number(pa[2]) === Number(pb[2]);\n}\n\nconst memo = new Map<string, Promise<ModuleLoadResult>>();\n\ntype Outcome =\n | { readonly kind: 'active'; readonly loaded: LoadedModule; readonly report: ModuleReport }\n | { readonly kind: 'stop'; readonly report: ModuleReport };\n\nasync function loadCandidate(candidate: ResolvedPackage, host: CtxoHost): Promise<Outcome> {\n const base = { name: DOMAIN_MODULE_PACKAGE, version: candidate.version, source: candidate.source } as const;\n const failed = (reason: string): Outcome => {\n log.warn(`${DOMAIN_MODULE_PACKAGE} failed to load: ${reason}; domain features are off.`);\n return { kind: 'stop', report: { ...base, status: 'load-failed', reason } };\n };\n try {\n const mod = (await import(candidate.entryUrl)) as { register?: unknown };\n if (typeof mod.register !== 'function') return failed('missing register export');\n const registration: CtxoModuleRegistration | undefined = await (mod.register as CtxoModuleRegister)(host);\n if (registration === undefined) {\n return { kind: 'stop', report: { ...base, status: 'inactive' } };\n }\n if (registration === null || typeof registration !== 'object' || registration.id !== 'domain') {\n return failed('register returned a registration with an unexpected id');\n }\n return {\n kind: 'active',\n loaded: { name: DOMAIN_MODULE_PACKAGE, version: candidate.version, registration },\n report: { ...base, status: 'active' },\n };\n } catch (err) {\n return failed((err as Error).message);\n }\n}\n\nasync function resolveModules(host: CtxoHost, cliAnchor: string | undefined): Promise<ModuleLoadResult> {\n const candidates = resolvePackageCandidates(DOMAIN_MODULE_PACKAGE, host.projectRoot, cliAnchor);\n if (candidates.length === 0) {\n return { modules: [], reports: [{ name: DOMAIN_MODULE_PACKAGE, status: 'not-installed' }] };\n }\n let firstMismatch: ResolvedPackage | undefined;\n for (const candidate of candidates) {\n if (!sameMinor(candidate.version, host.cliVersion)) {\n firstMismatch ??= candidate;\n continue;\n }\n const outcome = await loadCandidate(candidate, host);\n if (outcome.kind === 'active') {\n return { modules: [outcome.loaded], reports: [outcome.report] };\n }\n return { modules: [], reports: [outcome.report] };\n }\n const mismatch = firstMismatch!;\n log.warn(\n `${DOMAIN_MODULE_PACKAGE} ${mismatch.version} does not match @ctxo/cli ${host.cliVersion}; domain features are off. ` +\n `Install ${DOMAIN_MODULE_PACKAGE}@${host.cliVersion} next to the CLI you run.`,\n );\n return {\n modules: [],\n reports: [\n {\n name: DOMAIN_MODULE_PACKAGE,\n status: 'version-mismatch',\n version: mismatch.version,\n source: mismatch.source,\n reason: `cli ${host.cliVersion}`,\n },\n ],\n };\n}\n\n/**\n * Finds `@ctxo/domain` (the project's copy, then the copy next to the CLI), checks that its major.minor\n * equals the CLI's and calls its `register(host)`. Never throws: every failure is a report plus a warning.\n * Memoized per project root and mode; `refresh` resolves again (after an install).\n */\nexport function loadCtxoModules(host: CtxoHost, options: LoadCtxoModulesOptions = {}): Promise<ModuleLoadResult> {\n const key = `${host.projectRoot}|${host.mode}|${options.cliAnchor ?? ''}`;\n if (!options.refresh) {\n const cached = memo.get(key);\n if (cached) return cached;\n }\n const pending = resolveModules(host, options.cliAnchor).catch((err: unknown): ModuleLoadResult => {\n const reason = (err as Error).message;\n log.warn(`${DOMAIN_MODULE_PACKAGE} failed to load: ${reason}; domain features are off.`);\n return { modules: [], reports: [{ name: DOMAIN_MODULE_PACKAGE, status: 'load-failed', reason }] };\n });\n memo.set(key, pending);\n return pending;\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
compareCodepoint,
|
|
4
|
+
locateGitRoot
|
|
5
|
+
} from "./chunk-72CEQEQD.js";
|
|
6
|
+
import {
|
|
7
|
+
createLogger
|
|
8
|
+
} from "./chunk-5DSKZJNN.js";
|
|
9
|
+
|
|
10
|
+
// src/cli/module-commands.ts
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
var log = createLogger("ctxo:modules");
|
|
13
|
+
var MODULE_COMMAND_PACKAGES = Object.freeze({
|
|
14
|
+
"verify-domain": "@ctxo/domain",
|
|
15
|
+
"contexts-touched": "@ctxo/domain",
|
|
16
|
+
domain: "@ctxo/domain",
|
|
17
|
+
"domain-guard-hook": "@ctxo/domain"
|
|
18
|
+
});
|
|
19
|
+
var FAST_ROUTE_COMMANDS = /* @__PURE__ */ new Set(["domain-guard-hook"]);
|
|
20
|
+
var CORE_COMMANDS = /* @__PURE__ */ new Set([
|
|
21
|
+
"index",
|
|
22
|
+
"sync",
|
|
23
|
+
"status",
|
|
24
|
+
"verify-index",
|
|
25
|
+
"init",
|
|
26
|
+
"watch",
|
|
27
|
+
"stats",
|
|
28
|
+
"doctor",
|
|
29
|
+
"visualize",
|
|
30
|
+
"version",
|
|
31
|
+
"install",
|
|
32
|
+
"update",
|
|
33
|
+
"blast-radius",
|
|
34
|
+
"contexts",
|
|
35
|
+
"gate",
|
|
36
|
+
"gate-hook"
|
|
37
|
+
]);
|
|
38
|
+
function isKnownModuleCommand(command) {
|
|
39
|
+
return Object.hasOwn(MODULE_COMMAND_PACKAGES, command);
|
|
40
|
+
}
|
|
41
|
+
function collectModuleCommands(modules, coreCommands) {
|
|
42
|
+
const commands = /* @__PURE__ */ new Map();
|
|
43
|
+
for (const module of modules) {
|
|
44
|
+
const register = module.registration.registerCommands;
|
|
45
|
+
if (typeof register !== "function") continue;
|
|
46
|
+
try {
|
|
47
|
+
register.call(module.registration, {
|
|
48
|
+
registerCommand: (command) => {
|
|
49
|
+
if (coreCommands.has(command.name)) {
|
|
50
|
+
log.warn(`${module.name} command "${command.name}" has the name of a core command and was ignored`);
|
|
51
|
+
} else if (commands.has(command.name)) {
|
|
52
|
+
log.warn(`${module.name} command "${command.name}" is already registered and was ignored`);
|
|
53
|
+
} else {
|
|
54
|
+
commands.set(command.name, command);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
} catch (err) {
|
|
59
|
+
log.warn(`${module.name} could not register its commands: ${err.message}`);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return commands;
|
|
63
|
+
}
|
|
64
|
+
async function readAllStdin() {
|
|
65
|
+
if (process.stdin.isTTY) return "";
|
|
66
|
+
const chunks = [];
|
|
67
|
+
for await (const chunk of process.stdin) {
|
|
68
|
+
chunks.push(typeof chunk === "string" ? Buffer.from(chunk, "utf-8") : chunk);
|
|
69
|
+
}
|
|
70
|
+
return Buffer.concat(chunks).toString("utf-8");
|
|
71
|
+
}
|
|
72
|
+
async function runModuleCommand(command, args, options) {
|
|
73
|
+
const found = collectModuleCommands(options.modules, CORE_COMMANDS).get(command);
|
|
74
|
+
if (found === void 0) return void 0;
|
|
75
|
+
if (args.some((arg) => arg === "--help" || arg === "-h")) {
|
|
76
|
+
console.error((found.usage ?? found.help).join("\n"));
|
|
77
|
+
return 0;
|
|
78
|
+
}
|
|
79
|
+
const ctx = {
|
|
80
|
+
projectRoot: options.projectRoot,
|
|
81
|
+
ctxoRoot: join(options.projectRoot, ".ctxo"),
|
|
82
|
+
readStdin: readAllStdin
|
|
83
|
+
};
|
|
84
|
+
return found.run(args, ctx);
|
|
85
|
+
}
|
|
86
|
+
function moduleHelpLines(modules) {
|
|
87
|
+
if (modules.length === 0) return [' Domain commands need @ctxo/domain: run "ctxo install domain"'];
|
|
88
|
+
const visible = [...collectModuleCommands(modules, CORE_COMMANDS).values()].filter((command) => command.hidden !== true).sort((a, b) => compareCodepoint(a.name, b.name));
|
|
89
|
+
const lines = visible.flatMap((command) => command.help.map((line) => ` ${line}`));
|
|
90
|
+
return lines.length === 0 ? [] : ["Domain commands (@ctxo/domain):", ...lines];
|
|
91
|
+
}
|
|
92
|
+
async function loadModulesFor(projectRoot, mode, options = {}) {
|
|
93
|
+
try {
|
|
94
|
+
const [{ DOMAIN_MODULE_PACKAGE, loadCtxoModules }, { resolvePackageCandidates }] = await Promise.all([
|
|
95
|
+
import("./module-loader-B3LYF3W7.js"),
|
|
96
|
+
import("./plugin-discovery-XRXI3IJF.js")
|
|
97
|
+
]);
|
|
98
|
+
if (resolvePackageCandidates(DOMAIN_MODULE_PACKAGE, projectRoot, options.cliAnchor).length === 0) return [];
|
|
99
|
+
const { createModuleHost } = await import("./module-host-KNMKS62R.js");
|
|
100
|
+
const { modules } = await loadCtxoModules(createModuleHost({ projectRoot, mode }), { cliAnchor: options.cliAnchor });
|
|
101
|
+
return modules;
|
|
102
|
+
} catch (err) {
|
|
103
|
+
log.warn(`Modules could not be loaded: ${err.message}`);
|
|
104
|
+
return [];
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
async function runFastRoute(args, cwd, options = {}) {
|
|
108
|
+
const command = args[0] ?? "";
|
|
109
|
+
try {
|
|
110
|
+
const projectRoot = locateGitRoot(cwd) ?? cwd;
|
|
111
|
+
const modules = await loadModulesFor(projectRoot, "hook", { cliAnchor: options.cliAnchor });
|
|
112
|
+
const code = await runModuleCommand(command, args.slice(1), { projectRoot, modules });
|
|
113
|
+
return typeof code === "number" && Number.isInteger(code) ? code : 0;
|
|
114
|
+
} catch (err) {
|
|
115
|
+
console.error(`[ctxo] ${command} failed, allowing: ${err.message}`);
|
|
116
|
+
return 0;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export {
|
|
121
|
+
FAST_ROUTE_COMMANDS,
|
|
122
|
+
isKnownModuleCommand,
|
|
123
|
+
runModuleCommand,
|
|
124
|
+
moduleHelpLines,
|
|
125
|
+
loadModulesFor,
|
|
126
|
+
runFastRoute
|
|
127
|
+
};
|
|
128
|
+
//# sourceMappingURL=chunk-SRQZDGFZ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/module-commands.ts"],"sourcesContent":["import { join } from 'node:path';\nimport type { LoadedModule } from '../adapters/modules/module-loader.js';\nimport type { CtxoHost, ModuleCommand, ModuleCommandContext } from '../ports/i-ctxo-module.js';\nimport { compareCodepoint } from '../core/compare-codepoint.js';\nimport { createLogger } from '../core/logger.js';\nimport { locateGitRoot } from './cli-paths.js';\n\n/**\n * Commands that opt-in modules contribute (ADR-018), and the fast route for hook commands.\n *\n * This file is loaded on every run (the entry imports `FAST_ROUTE_COMMANDS`), so its static imports stay\n * light: the module host and the module loader are imported on demand, and neither the CLI router nor the\n * MCP stack is ever imported here.\n */\n\nconst log = createLogger('ctxo:modules');\n\n/**\n * The module commands the CLI knows by name, and the package that provides each. Without the package a\n * known name prints an install hint and exits 1 (the hook command exits 0 silently), so CI never passes\n * on a command that did not run.\n */\nexport const MODULE_COMMAND_PACKAGES: Readonly<Record<string, string>> = Object.freeze({\n 'verify-domain': '@ctxo/domain',\n 'contexts-touched': '@ctxo/domain',\n domain: '@ctxo/domain',\n 'domain-guard-hook': '@ctxo/domain',\n});\n\n/** Commands the entry runs without the CLI router or the MCP stack (hook commands with a time budget). */\nexport const FAST_ROUTE_COMMANDS: ReadonlySet<string> = new Set(['domain-guard-hook']);\n\n/** The commands the CLI router handles itself. A module command with one of these names is dropped. */\nexport const CORE_COMMANDS: ReadonlySet<string> = new Set([\n 'index',\n 'sync',\n 'status',\n 'verify-index',\n 'init',\n 'watch',\n 'stats',\n 'doctor',\n 'visualize',\n 'version',\n 'install',\n 'update',\n 'blast-radius',\n 'contexts',\n 'gate',\n 'gate-hook',\n]);\n\n/** True when `command` is one of the module command names the CLI knows (own keys only). */\nexport function isKnownModuleCommand(command: string): boolean {\n return Object.hasOwn(MODULE_COMMAND_PACKAGES, command);\n}\n\n/**\n * The commands the loaded modules register, by name. A command named like a core command, or like a\n * command an earlier module registered, is dropped with a warning. A module whose `registerCommands`\n * throws contributes nothing and the others still count.\n */\nexport function collectModuleCommands(\n modules: readonly LoadedModule[],\n coreCommands: ReadonlySet<string>,\n): Map<string, ModuleCommand> {\n const commands = new Map<string, ModuleCommand>();\n for (const module of modules) {\n const register = module.registration.registerCommands;\n if (typeof register !== 'function') continue;\n try {\n register.call(module.registration, {\n registerCommand: (command: ModuleCommand) => {\n if (coreCommands.has(command.name)) {\n log.warn(`${module.name} command \"${command.name}\" has the name of a core command and was ignored`);\n } else if (commands.has(command.name)) {\n log.warn(`${module.name} command \"${command.name}\" is already registered and was ignored`);\n } else {\n commands.set(command.name, command);\n }\n },\n });\n } catch (err) {\n log.warn(`${module.name} could not register its commands: ${(err as Error).message}`);\n }\n }\n return commands;\n}\n\n/** All of stdin as UTF-8 text; empty on a terminal, where nothing is piped in. */\nasync function readAllStdin(): Promise<string> {\n if (process.stdin.isTTY) return '';\n const chunks: Buffer[] = [];\n for await (const chunk of process.stdin) {\n chunks.push(typeof chunk === 'string' ? Buffer.from(chunk, 'utf-8') : (chunk as Buffer));\n }\n return Buffer.concat(chunks).toString('utf-8');\n}\n\n/**\n * Runs the module command `command` with `args` (the words after the command name) and resolves to its\n * exit code, or to undefined when no loaded module registers it. `--help` or `-h` prints the command's\n * full usage (`usage`, else `help`) and runs nothing. A throwing command rejects; the caller decides the exit code.\n */\nexport async function runModuleCommand(\n command: string,\n args: readonly string[],\n options: { readonly projectRoot: string; readonly modules: readonly LoadedModule[] },\n): Promise<number | undefined> {\n const found = collectModuleCommands(options.modules, CORE_COMMANDS).get(command);\n if (found === undefined) return undefined;\n if (args.some((arg) => arg === '--help' || arg === '-h')) {\n console.error((found.usage ?? found.help).join('\\n'));\n return 0;\n }\n const ctx: ModuleCommandContext = {\n projectRoot: options.projectRoot,\n ctxoRoot: join(options.projectRoot, '.ctxo'),\n readStdin: readAllStdin,\n };\n return found.run(args, ctx);\n}\n\n/**\n * The block `ctxo --help` ends with: the visible module commands' usage lines under a heading, sorted by\n * command name, or one install hint line when no module is loaded.\n */\nexport function moduleHelpLines(modules: readonly LoadedModule[]): string[] {\n if (modules.length === 0) return [' Domain commands need @ctxo/domain: run \"ctxo install domain\"'];\n const visible = [...collectModuleCommands(modules, CORE_COMMANDS).values()]\n .filter((command) => command.hidden !== true)\n .sort((a, b) => compareCodepoint(a.name, b.name));\n const lines = visible.flatMap((command) => command.help.map((line) => ` ${line}`));\n return lines.length === 0 ? [] : ['Domain commands (@ctxo/domain):', ...lines];\n}\n\n/**\n * The loaded modules for `projectRoot` in `mode`, or none when loading fails. The loader is imported here,\n * on demand, and the module host (with the zod and graph code it pulls in) only when a copy of the module\n * exists, so a run without `@ctxo/domain` (the post-commit `ctxo index --commit HEAD`, `--help`, `doctor`,\n * the hook route) never loads it.\n */\nexport async function loadModulesFor(\n projectRoot: string,\n mode: CtxoHost['mode'],\n options: { readonly cliAnchor?: string } = {},\n): Promise<readonly LoadedModule[]> {\n try {\n const [{ DOMAIN_MODULE_PACKAGE, loadCtxoModules }, { resolvePackageCandidates }] = await Promise.all([\n import('../adapters/modules/module-loader.js'),\n import('../adapters/language/plugin-discovery.js'),\n ]);\n if (resolvePackageCandidates(DOMAIN_MODULE_PACKAGE, projectRoot, options.cliAnchor).length === 0) return [];\n const { createModuleHost } = await import('./module-host.js');\n const { modules } = await loadCtxoModules(createModuleHost({ projectRoot, mode }), { cliAnchor: options.cliAnchor });\n return modules;\n } catch (err) {\n log.warn(`Modules could not be loaded: ${(err as Error).message}`);\n return [];\n }\n}\n\nexport interface FastRouteOptions {\n /** A directory standing in for the CLI's location when modules are looked up (tests); defaults to the running CLI. */\n readonly cliAnchor?: string;\n}\n\n/**\n * The fast route for hook commands (`FAST_ROUTE_COMMANDS`): finds the project root, loads the modules in\n * hook mode and runs the command, without the CLI router or the MCP stack. Fails open: resolves to 0 when\n * the module or the command is missing, or on any error (reported on stderr, never on stdout).\n */\nexport async function runFastRoute(args: readonly string[], cwd: string, options: FastRouteOptions = {}): Promise<number> {\n const command = args[0] ?? '';\n try {\n const projectRoot = locateGitRoot(cwd) ?? cwd;\n const modules = await loadModulesFor(projectRoot, 'hook', { cliAnchor: options.cliAnchor });\n const code = await runModuleCommand(command, args.slice(1), { projectRoot, modules });\n return typeof code === 'number' && Number.isInteger(code) ? code : 0;\n } catch (err) {\n console.error(`[ctxo] ${command} failed, allowing: ${(err as Error).message}`);\n return 0;\n }\n}\n"],"mappings":"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","names":[]}
|
|
@@ -130,6 +130,9 @@ var INDEX_ARTIFACT_FILES = [
|
|
|
130
130
|
"contexts-view.json"
|
|
131
131
|
];
|
|
132
132
|
var COMMUNITY_HISTORY_DIR = "communities.history";
|
|
133
|
+
function isMarkedArtifact(data) {
|
|
134
|
+
return data !== null && typeof data === "object" && !Array.isArray(data) && typeof data["ctxoArtifact"] === "string";
|
|
135
|
+
}
|
|
133
136
|
var JsonIndexReader = class {
|
|
134
137
|
indexDir;
|
|
135
138
|
constructor(ctxoRoot) {
|
|
@@ -161,6 +164,7 @@ var JsonIndexReader = class {
|
|
|
161
164
|
try {
|
|
162
165
|
const raw = readFileSync(absolutePath, "utf-8");
|
|
163
166
|
const data = JSON.parse(raw);
|
|
167
|
+
if (isMarkedArtifact(data)) return void 0;
|
|
164
168
|
const result = FileIndexSchema.safeParse(data);
|
|
165
169
|
if (result.success) {
|
|
166
170
|
return result.data;
|
|
@@ -259,6 +263,7 @@ export {
|
|
|
259
263
|
CoChangeMatrixSchema,
|
|
260
264
|
INDEX_ARTIFACT_FILES,
|
|
261
265
|
COMMUNITY_HISTORY_DIR,
|
|
266
|
+
isMarkedArtifact,
|
|
262
267
|
JsonIndexReader
|
|
263
268
|
};
|
|
264
|
-
//# sourceMappingURL=chunk-
|
|
269
|
+
//# sourceMappingURL=chunk-X4TVFJ4C.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/storage/json-index-reader.ts","../src/core/types.ts"],"sourcesContent":["import { readFileSync, readdirSync, existsSync, realpathSync } from 'node:fs';\nimport { join, relative } from 'node:path';\nimport {\n FileIndexSchema,\n SymbolNodeSchema,\n GraphEdgeSchema,\n CommitIntentSchema,\n AntiPatternSchema,\n CodeFactSchema,\n type FileIndex,\n} from '../../core/types.js';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:json-reader');\n\n/** JSON files next to the index entries that are products of Ctxo, not entries: the reader skips them. */\nexport const INDEX_ARTIFACT_FILES: readonly string[] = [\n 'co-changes.json',\n 'communities.json',\n 'drift-events.json',\n 'boundary-violations.json',\n 'contexts-view.json',\n];\n\n/** The folder of archived community snapshots inside the index folder: not entries either. */\nexport const COMMUNITY_HISTORY_DIR = 'communities.history';\n\n/**\n * True for a JSON value that is a module artifact rather than an index entry: an object with a string\n * `ctxoArtifact` marker (the `@ctxo/domain` artifacts under `index/domain/`, ADR-018). The marker, not the\n * folder, decides, so a source folder named `domain/` keeps its entries.\n */\nexport function isMarkedArtifact(data: unknown): boolean {\n return (\n data !== null &&\n typeof data === 'object' &&\n !Array.isArray(data) &&\n typeof (data as Record<string, unknown>)['ctxoArtifact'] === 'string'\n );\n}\n\n/** One index entry and the JSON file on disk it was read from. */\nexport interface IndexEntry {\n readonly path: string;\n readonly index: FileIndex;\n}\n\nexport class JsonIndexReader {\n private readonly indexDir: string;\n\n constructor(ctxoRoot: string) {\n this.indexDir = join(ctxoRoot, 'index');\n }\n\n readAll(): FileIndex[] {\n return this.readEntries().map((entry) => entry.index);\n }\n\n /**\n * Every valid index entry together with the path of the JSON file it came from. A caller that wants to\n * delete an entry must use this path: the `file` field inside the JSON says which source file the entry\n * is for, but a corrupt or crafted file can claim any name, such as an artifact's.\n */\n readEntries(): IndexEntry[] {\n if (!existsSync(this.indexDir)) {\n return [];\n }\n\n const jsonFiles = this.collectJsonFiles(this.indexDir);\n const results: IndexEntry[] = [];\n\n for (const filePath of jsonFiles) {\n const parsed = this.readSingle(filePath);\n if (parsed) {\n results.push({ path: filePath, index: parsed });\n }\n }\n\n return results;\n }\n\n readSingle(absolutePath: string): FileIndex | undefined {\n try {\n const raw = readFileSync(absolutePath, 'utf-8');\n const data: unknown = JSON.parse(raw);\n // A module artifact is not an entry and not a broken one: skip it without a word.\n if (isMarkedArtifact(data)) return undefined;\n const result = FileIndexSchema.safeParse(data);\n\n if (result.success) {\n return result.data;\n }\n\n // Strict parse failed — attempt resilient recovery: keep valid items, drop invalid ones.\n const rel = relative(this.indexDir, absolutePath);\n const raw_data = data as Record<string, unknown>;\n\n // Must have at least a valid file name and lastModified to be recoverable.\n if (typeof raw_data['file'] !== 'string' || !raw_data['file'] ||\n typeof raw_data['lastModified'] !== 'number') {\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n const rawSymbols = Array.isArray(raw_data['symbols']) ? raw_data['symbols'] : [];\n const rawEdges = Array.isArray(raw_data['edges']) ? raw_data['edges'] : [];\n const rawIntent = Array.isArray(raw_data['intent']) ? raw_data['intent'] : [];\n const rawAntiPatterns = Array.isArray(raw_data['antiPatterns']) ? raw_data['antiPatterns'] : [];\n\n // A symbol that fails only because of its annotations is kept without them: annotations are\n // optional metadata and must not cost the symbol.\n let droppedAnnotations = 0;\n const validSymbols: unknown[] = [];\n for (const sym of rawSymbols) {\n if (SymbolNodeSchema.safeParse(sym).success) {\n validSymbols.push(sym);\n continue;\n }\n if (sym !== null && typeof sym === 'object' && 'annotations' in sym) {\n const { annotations: _dropped, ...withoutAnnotations } = sym as Record<string, unknown>;\n if (SymbolNodeSchema.safeParse(withoutAnnotations).success) {\n validSymbols.push(withoutAnnotations);\n droppedAnnotations++;\n }\n }\n }\n // A facts value that is present but not an array is treated as absent: facts are optional metadata\n // and must not cost the file. It counts as one dropped fact list in the warning.\n const factsPresent = raw_data['facts'] !== undefined;\n const rawFacts = Array.isArray(raw_data['facts']) ? raw_data['facts'] : undefined;\n const validFacts = rawFacts?.filter((f) => CodeFactSchema.safeParse(f).success);\n const validEdges = rawEdges.filter((e) => GraphEdgeSchema.safeParse(e).success);\n const validIntent = rawIntent.filter((i) => CommitIntentSchema.safeParse(i).success);\n const validAntiPatterns = rawAntiPatterns.filter((a) => AntiPatternSchema.safeParse(a).success);\n\n const droppedSymbols = rawSymbols.length - validSymbols.length;\n const droppedFacts = rawFacts && validFacts\n ? rawFacts.length - validFacts.length\n : factsPresent ? 1 : 0;\n const droppedEdges = rawEdges.length - validEdges.length;\n const droppedIntent = rawIntent.length - validIntent.length;\n const droppedAntiPatterns = rawAntiPatterns.length - validAntiPatterns.length;\n\n if (droppedSymbols > 0 || droppedEdges > 0 || droppedIntent > 0 || droppedAntiPatterns > 0 ||\n droppedAnnotations > 0 || droppedFacts > 0) {\n log.warn(\n `${rel}: dropped ${droppedSymbols} invalid symbol(s), ${droppedEdges} invalid edge(s), ${droppedIntent} invalid intent(s), ${droppedAntiPatterns} invalid antiPattern(s), ${droppedAnnotations} invalid annotation list(s), ${droppedFacts} invalid fact(s); kept ${validSymbols.length} symbol(s), ${validEdges.length} edge(s), ${validIntent.length} intent(s), ${validAntiPatterns.length} antiPattern(s)`,\n );\n }\n\n // Re-parse with the filtered arrays to get a fully-typed FileIndex.\n const recovered = FileIndexSchema.safeParse({\n ...raw_data,\n symbols: validSymbols,\n edges: validEdges,\n intent: validIntent,\n antiPatterns: validAntiPatterns,\n facts: validFacts,\n });\n\n if (!recovered.success) {\n console.error(\n `[ctxo:json-reader] Invalid schema in ${rel}: ${result.error.message}`,\n );\n return undefined;\n }\n\n return recovered.data;\n } catch (err) {\n const rel = relative(this.indexDir, absolutePath);\n console.error(\n `[ctxo:json-reader] Failed to read ${rel}: ${(err as Error).message}`,\n );\n return undefined;\n }\n }\n\n private collectJsonFiles(dir: string, visited: Set<string> = new Set()): string[] {\n const realDir = realpathSync(dir);\n if (visited.has(realDir)) return []; // Guard against symlink loops\n visited.add(realDir);\n\n const files: string[] = [];\n\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (entry.isSymbolicLink()) continue; // Skip symlinks entirely\n\n const fullPath = join(dir, entry.name);\n if (entry.isDirectory()) {\n // Skip non-FileIndex directories under .ctxo/index/\n if (entry.name === COMMUNITY_HISTORY_DIR) continue;\n files.push(...this.collectJsonFiles(fullPath, visited));\n } else if (entry.name.endsWith('.json') && !INDEX_ARTIFACT_FILES.includes(entry.name)) {\n files.push(fullPath);\n }\n }\n\n return files;\n }\n}\n","import { z } from 'zod';\nimport { SYMBOL_KINDS, EDGE_KINDS, FACT_KINDS } from '@ctxo/plugin-api';\n\n// ── Symbol Kinds ────────────────────────────────────────────────\n\nexport { SYMBOL_KINDS };\n\nexport const SymbolKindSchema = z.enum(SYMBOL_KINDS);\nexport type SymbolKind = z.infer<typeof SymbolKindSchema>;\n\n// ── Edge Kinds ──────────────────────────────────────────────────\n\nexport { EDGE_KINDS };\n\nexport const EdgeKindSchema = z.enum(EDGE_KINDS);\nexport type EdgeKind = z.infer<typeof EdgeKindSchema>;\n\n// ── Detail Levels ───────────────────────────────────────────────\n\nexport const DETAIL_LEVELS = [1, 2, 3, 4] as const;\n\nexport const DetailLevelSchema = z.union([\n z.literal(1),\n z.literal(2),\n z.literal(3),\n z.literal(4),\n]);\nexport type DetailLevel = z.infer<typeof DetailLevelSchema>;\n\n// ── Symbol ID ───────────────────────────────────────────────────\n\n/**\n * Format: \"<relativeFile>::<name>::<kind>\"\n * Example: \"src/foo.ts::myFn::function\"\n */\nexport const SymbolIdSchema = z\n .string()\n .min(1)\n .refine(\n (value) => {\n const parts = value.split('::');\n if (parts.length !== 3) return false;\n const [file, name, kind] = parts;\n if (!file || !name || !kind) return false;\n return SymbolKindSchema.safeParse(kind).success;\n },\n { message: 'Symbol ID must match format \"<file>::<name>::<kind>\"' },\n );\nexport type SymbolId = z.infer<typeof SymbolIdSchema>;\n\n// ── Edge Target ─────────────────────────────────────────────────\n\n/**\n * An edge target: either a full 3-part symbol ID (\"<file>::<name>::<kind>\")\n * or a 2-part name-reference (\"<name>::<kind>\") for cross-file / unresolved\n * targets. The graph builder resolves name-references by name at query time.\n */\nexport const EdgeTargetSchema = z.string().refine(\n (s) => {\n const parts = s.split('::');\n if (parts.length !== 3 && parts.length !== 2) return false;\n if (parts.some((p) => p.length === 0)) return false;\n const kind = parts[parts.length - 1];\n return (SYMBOL_KINDS as readonly string[]).includes(kind as string);\n },\n { message: 'edge target must be <file>::<name>::<kind> or <name>::<kind>' },\n);\nexport type EdgeTarget = z.infer<typeof EdgeTargetSchema>;\n\n// ── Symbol Annotation ───────────────────────────────────────────\n\n/** Host-language attribute or decorator on a symbol (protocol v2). Shape only; plugins enforce caps. */\nexport const SymbolAnnotationSchema = z.object({\n name: z.string().min(1),\n args: z.array(z.string()).readonly().optional(),\n named: z.record(z.string(), z.string()).readonly().optional(),\n});\nexport type SymbolAnnotation = z.infer<typeof SymbolAnnotationSchema>;\n\n// ── Symbol Node ─────────────────────────────────────────────────\n\nexport const SymbolNodeSchema = z\n .object({\n symbolId: SymbolIdSchema,\n name: z.string().min(1),\n kind: SymbolKindSchema,\n startLine: z.number().int().nonnegative(),\n endLine: z.number().int().nonnegative(),\n startOffset: z.number().int().nonnegative().optional(),\n endOffset: z.number().int().nonnegative().optional(),\n annotations: z.array(SymbolAnnotationSchema).readonly().optional(),\n })\n .refine((node) => node.endLine >= node.startLine, {\n message: 'endLine must be >= startLine',\n });\nexport type SymbolNode = z.infer<typeof SymbolNodeSchema>;\n\n// ── Graph Edge ──────────────────────────────────────────────────\n\nexport const GraphEdgeSchema = z.object({\n from: SymbolIdSchema, // strict 3-part: from is always a local, resolved symbol\n to: EdgeTargetSchema, // 3-part OR 2-part name-ref for cross-file/unresolved targets\n kind: EdgeKindSchema,\n typeOnly: z.boolean().optional(),\n});\nexport type GraphEdge = z.infer<typeof GraphEdgeSchema>;\n\n// ── Commit Intent ───────────────────────────────────────────────\n\nexport const CommitIntentSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n kind: z.literal('commit'),\n});\nexport type CommitIntent = z.infer<typeof CommitIntentSchema>;\n\n// ── Anti-Pattern ────────────────────────────────────────────────\n\nexport const AntiPatternSchema = z.object({\n hash: z.string().min(1),\n message: z.string(),\n date: z.string().min(1),\n});\nexport type AntiPattern = z.infer<typeof AntiPatternSchema>;\n\n// ── Code Fact ───────────────────────────────────────────────────\n\nexport const FactKindSchema = z.enum(FACT_KINDS);\nexport type FactKind = z.infer<typeof FactKindSchema>;\n\n/** A fact extracted from a symbol body (protocol v2). The line is 0-based, like a symbol's start line. */\nexport const CodeFactSchema = z.object({\n kind: FactKindSchema,\n symbolId: SymbolIdSchema,\n line: z.number().int().nonnegative(),\n text: z.string(),\n detail: z.record(z.string(), z.string()).readonly().optional(),\n});\nexport type CodeFact = z.infer<typeof CodeFactSchema>;\n\n// ── File Index ──────────────────────────────────────────────────\n\nexport const ComplexityMetricsSchema = z.object({\n symbolId: z.string().min(1),\n cyclomatic: z.number().nonnegative(),\n});\n\nexport const FileIndexSchema = z.object({\n file: z.string().min(1),\n lastModified: z.number().nonnegative(),\n contentHash: z.string().optional(),\n symbols: z.array(SymbolNodeSchema),\n edges: z.array(GraphEdgeSchema),\n complexity: z.array(ComplexityMetricsSchema).optional(),\n intent: z.array(CommitIntentSchema),\n antiPatterns: z.array(AntiPatternSchema),\n facts: z.array(CodeFactSchema).readonly().optional(),\n});\nexport type FileIndex = z.infer<typeof FileIndexSchema>;\n\n// ── Logic-Slice Result ──────────────────────────────────────────\n\nexport interface LogicSliceResult {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n}\n\n// ── Formatted Slice ─────────────────────────────────────────────\n\nexport interface TruncationInfo {\n readonly truncated: true;\n readonly reason: 'token_budget_exceeded';\n}\n\nexport interface FormattedSlice {\n readonly root: SymbolNode;\n readonly dependencies: readonly SymbolNode[];\n readonly edges: readonly GraphEdge[];\n readonly level: DetailLevel;\n readonly levelDescription?: string;\n readonly truncation?: TruncationInfo;\n}\n\n// ── Complexity & Churn ──────────────────────────────────────────\n\nexport interface ComplexityMetrics {\n readonly symbolId: string;\n readonly cyclomatic: number;\n}\n\nexport interface ChurnData {\n readonly filePath: string;\n readonly commitCount: number;\n}\n\nexport const SCORE_BANDS = ['low', 'medium', 'high'] as const;\nexport type ScoreBand = (typeof SCORE_BANDS)[number];\n\nexport interface ChangeIntelligenceScore {\n readonly symbolId: string;\n readonly complexity: number;\n readonly churn: number;\n readonly composite: number;\n readonly band: ScoreBand;\n}\n\n// ── Why-Context Result ──────────────────────────────────────────\n\nexport interface WhyContextResult {\n readonly commitHistory: readonly CommitIntent[];\n readonly antiPatternWarnings: readonly AntiPattern[];\n readonly changeIntelligence?: ChangeIntelligenceScore;\n}\n\n// ── Co-Change Analysis ─────────────────────────────────────────\n\nexport interface CoChangeEntry {\n readonly file1: string;\n readonly file2: string;\n readonly sharedCommits: number;\n readonly frequency: number;\n}\n\n/** The window a co-change matrix was built from and the filters that shaped it. */\nexport interface CoChangeWindowMeta {\n /** Configured commit limit: the last N non-merge commits from HEAD, or 'all'. */\n readonly maxCommits: number | 'all';\n /** Configured duration limit such as \"12m\", anchored to HEAD's committer date. */\n readonly since?: string;\n /** Short SHA of HEAD when the matrix was built. */\n readonly head: string;\n /** Commits inside the window. */\n readonly commitsScanned: number;\n /** Window commits that fed the counts. */\n readonly commitsUsed: number;\n /** Window commits skipped for touching more than `maxFilesPerCommit` included files. */\n readonly massSkipped: number;\n readonly maxFilesPerCommit: number;\n readonly minShared: number;\n}\n\nexport interface CoChangeMatrix {\n readonly version: 1;\n readonly timestamp: number;\n /** Absent in matrices written before the window was recorded. */\n readonly window?: CoChangeWindowMeta;\n readonly entries: readonly CoChangeEntry[];\n}\n\nconst CoChangeEntrySchema = z.object({\n file1: z.string().min(1),\n file2: z.string().min(1),\n sharedCommits: z.number().int().min(1),\n frequency: z.number().min(0).max(1),\n});\n\nconst CoChangeWindowMetaSchema = z.object({\n maxCommits: z.union([z.number().int().min(1), z.literal('all')]),\n since: z.string().optional(),\n head: z.string().min(1),\n commitsScanned: z.number().int().min(0),\n commitsUsed: z.number().int().min(0),\n massSkipped: z.number().int().min(0),\n maxFilesPerCommit: z.number().int().min(1),\n minShared: z.number().int().min(1),\n});\n\n/**\n * Validates `co-changes.json`. `entries` is required and `window` optional, so files written before\n * the window was recorded still load. Unknown keys are dropped rather than rejected, so a file from\n * a newer Ctxo stays readable.\n */\nexport const CoChangeMatrixSchema: z.ZodType<CoChangeMatrix> = z.object({\n version: z.literal(1),\n timestamp: z.number(),\n window: CoChangeWindowMetaSchema.optional(),\n entries: z.array(CoChangeEntrySchema),\n});\n\n// ── Commit Record (from git adapter) ────────────────────────────\n\nexport interface CommitRecord {\n readonly hash: string;\n readonly message: string;\n readonly date: string;\n readonly author: string;\n}\n\n// ── Change Log (from git adapter) ───────────────────────────────\n\n/** git name-status letters Ctxo keeps; A added, M modified, D deleted, R renamed, C copied, T type changed. */\nexport type FileChangeStatus = 'A' | 'M' | 'D' | 'R' | 'C' | 'T';\n\nexport interface FileChange {\n readonly status: FileChangeStatus;\n /** Path after the change: repo-relative, forward slashes. */\n readonly path: string;\n /** Path before the change; set only for renames (R) and copies (C). */\n readonly oldPath?: string;\n}\n\n/** One non-merge commit and the files it changed. */\nexport interface CommitChangeSet {\n /** Full commit SHA (git %H). */\n readonly hash: string;\n /** Subject line (git %s). */\n readonly message: string;\n /** Author date, strict ISO 8601 (git %aI). */\n readonly date: string;\n /** Author name (git %an). */\n readonly author: string;\n /** Committer date in unix seconds (git %ct). */\n readonly committedAt: number;\n readonly changes: readonly FileChange[];\n}\n\nexport interface ChangeLog {\n /** Short SHA of HEAD (git %h), which may itself be a merge commit. */\n readonly head: string;\n /** Committer date of HEAD in unix seconds (git %ct). */\n readonly headCommittedAt: number;\n /** Non-merge commits reachable from HEAD, newest first. */\n readonly commits: readonly CommitChangeSet[];\n}\n\n/** A revision range: commits reachable from `head` and from none of `exclude`. */\nexport interface GitRange {\n readonly head: string;\n readonly exclude: readonly string[];\n}\n\n/** One commit of a range log. */\nexport interface RangeCommit {\n /** Full commit SHA (git %H). */\n readonly hash: string;\n readonly subject: string;\n /** More than one parent. */\n readonly isMerge: boolean;\n /** Values of the requested trailer key, in message order, trimmed, no empties. */\n readonly trailers: readonly string[];\n /** Files the commit changed; empty for merge commits. */\n readonly changes: readonly FileChange[];\n}\n\n/** Branch facts of a repository; every field but `branches` is absent when git cannot say. */\nexport interface GitRefs {\n /** Undefined on a detached HEAD. */\n readonly currentBranch?: string;\n /** For example 'origin/feat'. */\n readonly upstream?: string;\n /** For example 'origin/main'. */\n readonly originHead?: string;\n /** Local and remote-tracking short names without the remote HEAD aliases, in codepoint order. */\n readonly branches: readonly string[];\n /** Local branches only (refs/heads), in codepoint order. */\n readonly localBranches?: readonly string[];\n /** Remote-tracking branches as `<remote>/<name>` (refs/remotes, no HEAD aliases), in codepoint order. */\n readonly remoteBranches?: readonly string[];\n}\n\n// ── Blame Line (from git adapter) ───────────────────────────────\n\nexport interface BlameLine {\n readonly hash: string;\n readonly lineNumber: number;\n readonly author: string;\n readonly date: string;\n}\n\n// ── Search Types ───────────────────────────────────────────────\n\nexport interface SearchResult {\n symbolId: string;\n name: string;\n kind: string;\n filePath: string;\n /** BM25 relevance score (higher = more relevant) */\n relevanceScore: number;\n /** PageRank importance score (0-1) */\n importanceScore: number;\n /** Final combined score after all boosts */\n combinedScore: number;\n}\n\nexport interface SearchMetrics {\n porterHits: number;\n trigramHits: number;\n phase2Activated: boolean;\n fuzzyApplied: boolean;\n latencyMs: number;\n}\n\nexport interface FuzzyCorrection {\n originalQuery: string;\n correctedQuery: string;\n corrections: Array<{\n original: string;\n corrected: string;\n distance: number;\n }>;\n}\n\nexport interface SearchResponse {\n query: string;\n results: SearchResult[];\n metrics: SearchMetrics;\n fuzzyCorrection?: FuzzyCorrection;\n}\n\n// ── Community Detection ────────────────────────────────────────\n\nexport interface CommunityEntry {\n readonly symbolId: string;\n readonly communityId: number;\n readonly communityLabel: string;\n}\n\nexport interface GodNode {\n readonly symbolId: string;\n readonly bridgedCommunities: readonly number[];\n readonly centralityScore: number;\n}\n\nexport const EDGE_QUALITY = ['full', 'syntax-only', 'mixed'] as const;\nexport type EdgeQuality = (typeof EDGE_QUALITY)[number];\n\nexport interface CommunitySnapshot {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly modularity: number;\n readonly communities: readonly CommunityEntry[];\n readonly godNodes: readonly GodNode[];\n readonly edgeQuality: EdgeQuality;\n readonly crossClusterEdges: number;\n}\n\nexport interface DriftEvent {\n readonly symbolId: string;\n readonly movedFrom: { readonly id: number; readonly label: string };\n readonly movedTo: { readonly id: number; readonly label: string };\n readonly firstSeenInNewCluster: string;\n}\n\nexport interface BoundaryViolation {\n readonly from: {\n readonly symbolId: string;\n readonly communityId: number;\n readonly label: string;\n };\n readonly to: {\n readonly symbolId: string;\n readonly communityId: number;\n readonly label: string;\n };\n readonly edgeKind: EdgeKind;\n readonly historicalEdgesBetweenClusters: number;\n readonly severity: 'high' | 'medium';\n}\n\n// ── Persisted architectural artifacts ──────────────────────────\n// Written by `ctxo index` next to communities.json so the static\n// HTML visualizer can render Architecture views without re-running\n// detectors in the browser.\n\nexport type ConfidenceLevel = 'high' | 'medium' | 'low';\n\nexport interface DriftEventsArtifact {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly confidence: ConfidenceLevel;\n /** The number of history entries passed to the detector (kept for compatibility; confidence is built on `distinctCommits`). */\n readonly snapshotsAvailable: number;\n /** Commits other than `commitSha` that the history covers; `confidence` is built on this. */\n readonly distinctCommits: number;\n readonly hint?: string;\n readonly events: readonly DriftEvent[];\n}\n\nexport interface BoundaryViolationsArtifact {\n readonly version: 1;\n readonly computedAt: string;\n readonly commitSha: string;\n readonly confidence: ConfidenceLevel;\n /** The number of history entries passed to the detector (kept for compatibility; confidence is built on `distinctCommits`). */\n readonly snapshotsAvailable: number;\n /** Commits other than `commitSha` that the history covers; `confidence` is built on this. */\n readonly distinctCommits: number;\n readonly hint?: string;\n readonly violations: readonly BoundaryViolation[];\n}\n"],"mappings":"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","names":[]}
|