@ctxo/cli 0.13.1 → 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 +16 -0
- 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-WB7WZKX4.js → chunk-ZL36YS2V.js} +766 -1614
- package/dist/chunk-ZL36YS2V.js.map +1 -0
- package/dist/{cli-router-DDHH4WQH.js → cli-router-AZCDNX5R.js} +980 -504
- 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 +54 -0
- package/llms.txt +11 -0
- package/package.json +13 -2
- package/dist/chunk-DT3ZH4AU.js.map +0 -1
- package/dist/chunk-WB7WZKX4.js.map +0 -1
- package/dist/cli-router-DDHH4WQH.js.map +0 -1
- /package/dist/{json-index-reader-OXTRTGE6.js.map → internal.js.map} +0 -0
package/README.md
CHANGED
|
@@ -141,6 +141,21 @@ ctxo contexts --json # the full contexts-view.json
|
|
|
141
141
|
|
|
142
142
|
Agents get the same view from `get_architectural_overlay(mode: "contexts")`, and one candidate in full with `candidate: <id>`.
|
|
143
143
|
|
|
144
|
+
## Cards that go stale
|
|
145
|
+
|
|
146
|
+
An agent changes the lending rules in code, and the card that describes them stays as it was. Name your bounded contexts in a registry, and Ctxo checks the structure, never the meaning: a context's code changed in the session without a card update or a `Domain-Unchanged` commit trailer, a rule ID that no card defines, a card link that no longer resolves, an edge that crosses a context boundary outside its contracts. The opt-in `@ctxo/domain` package adds the commands and a Claude Code guard; Ctxo never writes the cards.
|
|
147
|
+
|
|
148
|
+
```shell
|
|
149
|
+
ctxo install domain # the opt-in @ctxo/domain package
|
|
150
|
+
ctxo init --domain # registry skeleton, fold guard, skill, domain config
|
|
151
|
+
ctxo contexts-touched --since origin/main # which named contexts a branch changed, and which are resolved
|
|
152
|
+
ctxo verify-domain --since origin/main # CI gate: registry, cards, rule IDs, loader rules, boundaries
|
|
153
|
+
ctxo domain rules --write # path-scoped rules that point the agent at the card
|
|
154
|
+
ctxo domain discover # propose tracker prefixes and test conventions from the repo
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
See [Ctxo domain conventions v1](docs/features/domain-knowledge.md#ctxo-domain-conventions-v1) for the registry, the card template and the adoption ladder.
|
|
158
|
+
|
|
144
159
|
## Supported Languages
|
|
145
160
|
|
|
146
161
|
| Language | Plugin | Tier | Notes |
|
|
@@ -156,6 +171,7 @@ Install via the CLI shortcut:
|
|
|
156
171
|
```bash
|
|
157
172
|
ctxo install typescript go csharp # syntax or full tier auto-detected
|
|
158
173
|
ctxo install sql # T-SQL plugin (alias tsql)
|
|
174
|
+
ctxo install domain # opt-in domain knowledge layer (@ctxo/domain)
|
|
159
175
|
ctxo install java # installs syntax tier; adds full-tier analyzer when JRE 11+ detected
|
|
160
176
|
ctxo install java --full-tier # force full-tier analyzer install
|
|
161
177
|
ctxo install java --syntax-only # skip analyzer, syntax tier only
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
createLogger
|
|
4
|
+
} from "./chunk-5DSKZJNN.js";
|
|
5
|
+
|
|
6
|
+
// src/adapters/language/plugin-discovery.ts
|
|
7
|
+
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
8
|
+
import { createRequire } from "module";
|
|
9
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
10
|
+
import { pathToFileURL } from "url";
|
|
11
|
+
import { SUPPORTED_API_VERSIONS } from "@ctxo/plugin-api";
|
|
12
|
+
var log = createLogger("ctxo:plugin-discovery");
|
|
13
|
+
var OFFICIAL_PREFIX = "@ctxo/lang-";
|
|
14
|
+
var COMMUNITY_PREFIX = "ctxo-lang-";
|
|
15
|
+
function readManifestDeps(manifestPath) {
|
|
16
|
+
try {
|
|
17
|
+
const raw = readFileSync(manifestPath, "utf-8");
|
|
18
|
+
const manifest = JSON.parse(raw);
|
|
19
|
+
const all = {
|
|
20
|
+
...manifest.dependencies,
|
|
21
|
+
...manifest.devDependencies,
|
|
22
|
+
...manifest.peerDependencies
|
|
23
|
+
};
|
|
24
|
+
return Object.keys(all).filter(
|
|
25
|
+
(name) => name.startsWith(OFFICIAL_PREFIX) || name.startsWith(COMMUNITY_PREFIX)
|
|
26
|
+
);
|
|
27
|
+
} catch (err) {
|
|
28
|
+
log.warn(`Failed to read manifest ${manifestPath}: ${err.message}`);
|
|
29
|
+
return [];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
function isLocalSpecifier(spec) {
|
|
33
|
+
return spec.startsWith(".") || spec.startsWith("/") || isAbsolute(spec);
|
|
34
|
+
}
|
|
35
|
+
function pickEsmEntry(exportsField) {
|
|
36
|
+
if (!exportsField) return void 0;
|
|
37
|
+
if (typeof exportsField === "string") return exportsField;
|
|
38
|
+
if (typeof exportsField !== "object") return void 0;
|
|
39
|
+
const table = exportsField;
|
|
40
|
+
const dot = "." in table ? table["."] : exportsField;
|
|
41
|
+
if (typeof dot === "string") return dot;
|
|
42
|
+
if (!dot || typeof dot !== "object") return void 0;
|
|
43
|
+
for (const key of ["import", "node", "default"]) {
|
|
44
|
+
const nested = dot[key];
|
|
45
|
+
if (typeof nested === "string") return nested;
|
|
46
|
+
if (nested && typeof nested === "object") {
|
|
47
|
+
for (const inner of ["default", "import", "node"]) {
|
|
48
|
+
const v = nested[inner];
|
|
49
|
+
if (typeof v === "string") return v;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return void 0;
|
|
54
|
+
}
|
|
55
|
+
var cliFallbackRequire = createRequire(import.meta.url);
|
|
56
|
+
function resolvePluginPackageJson(specifier, projectRequire) {
|
|
57
|
+
try {
|
|
58
|
+
return projectRequire.resolve(`${specifier}/package.json`);
|
|
59
|
+
} catch (primary) {
|
|
60
|
+
try {
|
|
61
|
+
return cliFallbackRequire.resolve(`${specifier}/package.json`);
|
|
62
|
+
} catch {
|
|
63
|
+
throw primary;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
function readPackageJsonVia(specifier, req) {
|
|
68
|
+
const files = [];
|
|
69
|
+
try {
|
|
70
|
+
files.push(req.resolve(`${specifier}/package.json`));
|
|
71
|
+
} catch {
|
|
72
|
+
for (const base of req.resolve.paths(specifier) ?? []) {
|
|
73
|
+
files.push(join(base, specifier, "package.json"));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
for (const file of files) {
|
|
77
|
+
try {
|
|
78
|
+
return { pkgDir: dirname(file), raw: readFileSync(file, "utf-8") };
|
|
79
|
+
} catch {
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return void 0;
|
|
83
|
+
}
|
|
84
|
+
function resolvePackageCandidates(specifier, projectRoot, cliAnchor) {
|
|
85
|
+
const lookups = [
|
|
86
|
+
{ source: "project", req: createRequire(join(projectRoot, "package.json")) },
|
|
87
|
+
{
|
|
88
|
+
source: "cli",
|
|
89
|
+
req: cliAnchor === void 0 ? cliFallbackRequire : createRequire(join(cliAnchor, "package.json"))
|
|
90
|
+
}
|
|
91
|
+
];
|
|
92
|
+
const found = [];
|
|
93
|
+
const seen = /* @__PURE__ */ new Set();
|
|
94
|
+
for (const { source, req } of lookups) {
|
|
95
|
+
const read = readPackageJsonVia(specifier, req);
|
|
96
|
+
if (!read) continue;
|
|
97
|
+
try {
|
|
98
|
+
const real = realpathSync(read.pkgDir);
|
|
99
|
+
if (seen.has(real)) continue;
|
|
100
|
+
seen.add(real);
|
|
101
|
+
const pkg = JSON.parse(read.raw);
|
|
102
|
+
const entry = pickEsmEntry(pkg.exports) ?? pkg.module ?? pkg.main ?? "index.js";
|
|
103
|
+
found.push({
|
|
104
|
+
name: pkg.name ?? specifier,
|
|
105
|
+
version: pkg.version ?? "0.0.0",
|
|
106
|
+
pkgDir: real,
|
|
107
|
+
entryUrl: pathToFileURL(resolve(real, entry)).href,
|
|
108
|
+
source
|
|
109
|
+
});
|
|
110
|
+
} catch (err) {
|
|
111
|
+
log.warn(`Skipped ${specifier} candidate in ${read.pkgDir}: ${err.message}`);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return found;
|
|
115
|
+
}
|
|
116
|
+
function resolvePluginEntryUrl(specifier, manifestRequire) {
|
|
117
|
+
const pkgJsonPath = resolvePluginPackageJson(specifier, manifestRequire);
|
|
118
|
+
const pkgDir = dirname(pkgJsonPath);
|
|
119
|
+
const pkgJson = JSON.parse(readFileSync(pkgJsonPath, "utf-8"));
|
|
120
|
+
const entry = pickEsmEntry(pkgJson.exports) ?? pkgJson.module ?? pkgJson.main ?? "index.js";
|
|
121
|
+
return pathToFileURL(resolve(pkgDir, entry)).href;
|
|
122
|
+
}
|
|
123
|
+
async function tryLoad(specifier, baseDir, manifestRequire) {
|
|
124
|
+
try {
|
|
125
|
+
const target = isLocalSpecifier(specifier) ? pathToFileURL(resolve(baseDir, specifier)).href : resolvePluginEntryUrl(specifier, manifestRequire);
|
|
126
|
+
const mod = await import(target);
|
|
127
|
+
const candidate = mod.default ?? mod.plugin ?? mod;
|
|
128
|
+
if (!candidate || typeof candidate !== "object") {
|
|
129
|
+
return { specifier, reason: "module export is not an object" };
|
|
130
|
+
}
|
|
131
|
+
if (!SUPPORTED_API_VERSIONS.includes(candidate.apiVersion)) {
|
|
132
|
+
return {
|
|
133
|
+
specifier,
|
|
134
|
+
reason: `unsupported apiVersion ${String(candidate.apiVersion)} (supported: ${SUPPORTED_API_VERSIONS.join(", ")})`
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
if (typeof candidate.createAdapter !== "function") {
|
|
138
|
+
return { specifier, reason: "missing createAdapter()" };
|
|
139
|
+
}
|
|
140
|
+
if (typeof candidate.id !== "string" || candidate.id.length === 0) {
|
|
141
|
+
return { specifier, reason: "missing or empty id" };
|
|
142
|
+
}
|
|
143
|
+
if (!Array.isArray(candidate.extensions)) {
|
|
144
|
+
return { specifier, reason: "extensions must be an array" };
|
|
145
|
+
}
|
|
146
|
+
if (candidate.tier !== "syntax" && candidate.tier !== "full") {
|
|
147
|
+
return { specifier, reason: `invalid tier ${String(candidate.tier)}` };
|
|
148
|
+
}
|
|
149
|
+
return candidate;
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return { specifier, reason: err.message };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function discoverPlugins(options) {
|
|
155
|
+
const { manifestPath } = options;
|
|
156
|
+
if (!existsSync(manifestPath)) {
|
|
157
|
+
log.info(`Manifest not found at ${manifestPath} \u2014 no plugins discovered`);
|
|
158
|
+
return { plugins: [], failures: [] };
|
|
159
|
+
}
|
|
160
|
+
const baseDir = dirname(manifestPath);
|
|
161
|
+
const specifiers = options.explicit ?? readManifestDeps(manifestPath);
|
|
162
|
+
const manifestRequire = createRequire(pathToFileURL(manifestPath).href);
|
|
163
|
+
const plugins = [];
|
|
164
|
+
const failures = [];
|
|
165
|
+
for (const spec of specifiers) {
|
|
166
|
+
if (options.shouldSkipSpecifier?.(spec)) {
|
|
167
|
+
log.info(`Skipping plugin ${spec} (matched ignoreProjects)`);
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
const result = await tryLoad(spec, baseDir, manifestRequire);
|
|
171
|
+
if ("apiVersion" in result) {
|
|
172
|
+
plugins.push({ plugin: result, specifier: spec });
|
|
173
|
+
} else {
|
|
174
|
+
failures.push(result);
|
|
175
|
+
log.warn(`Skipped plugin ${spec}: ${result.reason}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return { plugins, failures };
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
export {
|
|
182
|
+
resolvePluginPackageJson,
|
|
183
|
+
resolvePackageCandidates,
|
|
184
|
+
discoverPlugins
|
|
185
|
+
};
|
|
186
|
+
//# sourceMappingURL=chunk-2Q76LH2H.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/adapters/language/plugin-discovery.ts"],"sourcesContent":["import { existsSync, readFileSync, realpathSync } from 'node:fs';\nimport { createRequire } from 'node:module';\nimport { dirname, isAbsolute, join, resolve } from 'node:path';\nimport { pathToFileURL } from 'node:url';\nimport type { CtxoLanguagePlugin } from '@ctxo/plugin-api';\nimport { SUPPORTED_API_VERSIONS } from '@ctxo/plugin-api';\nimport { createLogger } from '../../core/logger.js';\n\nconst log = createLogger('ctxo:plugin-discovery');\n\nconst OFFICIAL_PREFIX = '@ctxo/lang-';\nconst COMMUNITY_PREFIX = 'ctxo-lang-';\n\nexport interface DiscoverOptions {\n /** Absolute path to the package.json used for dependency scanning. */\n readonly manifestPath: string;\n /**\n * Explicit plugin list. When provided, suppresses auto-discovery and loads\n * only these packages/paths. Strings starting with '.' or '/' are treated\n * as local paths resolved against the manifest directory.\n */\n readonly explicit?: readonly string[];\n /**\n * Predicate that returns true for plugin specifiers that should be skipped\n * entirely (never imported). Used by the CLI to short-circuit loading of\n * plugins whose backing workspace was excluded via\n * `.ctxo/config.yaml#index.ignoreProjects`.\n */\n readonly shouldSkipSpecifier?: (specifier: string) => boolean;\n}\n\nexport interface DiscoveredPlugin {\n readonly plugin: CtxoLanguagePlugin;\n readonly specifier: string;\n}\n\nexport interface PluginLoadFailure {\n readonly specifier: string;\n readonly reason: string;\n}\n\nexport interface DiscoveryResult {\n readonly plugins: readonly DiscoveredPlugin[];\n readonly failures: readonly PluginLoadFailure[];\n}\n\nfunction readManifestDeps(manifestPath: string): string[] {\n try {\n const raw = readFileSync(manifestPath, 'utf-8');\n const manifest = JSON.parse(raw) as {\n dependencies?: Record<string, string>;\n devDependencies?: Record<string, string>;\n peerDependencies?: Record<string, string>;\n };\n const all = {\n ...manifest.dependencies,\n ...manifest.devDependencies,\n ...manifest.peerDependencies,\n };\n return Object.keys(all).filter(\n (name) => name.startsWith(OFFICIAL_PREFIX) || name.startsWith(COMMUNITY_PREFIX),\n );\n } catch (err) {\n log.warn(`Failed to read manifest ${manifestPath}: ${(err as Error).message}`);\n return [];\n }\n}\n\nfunction isLocalSpecifier(spec: string): boolean {\n return spec.startsWith('.') || spec.startsWith('/') || isAbsolute(spec);\n}\n\ntype ExportsEntry = string | { [condition: string]: ExportsEntry } | null | undefined;\n\nfunction pickEsmEntry(exportsField: unknown): string | undefined {\n if (!exportsField) return undefined;\n if (typeof exportsField === 'string') return exportsField;\n if (typeof exportsField !== 'object') return undefined;\n const table = exportsField as Record<string, ExportsEntry>;\n const dot = '.' in table ? table['.'] : (exportsField as ExportsEntry);\n if (typeof dot === 'string') return dot;\n if (!dot || typeof dot !== 'object') return undefined;\n for (const key of ['import', 'node', 'default'] as const) {\n const nested = (dot as Record<string, ExportsEntry>)[key];\n if (typeof nested === 'string') return nested;\n if (nested && typeof nested === 'object') {\n for (const inner of ['default', 'import', 'node'] as const) {\n const v = (nested as Record<string, ExportsEntry>)[inner];\n if (typeof v === 'string') return v;\n }\n }\n }\n return undefined;\n}\n\nconst cliFallbackRequire = createRequire(import.meta.url);\n\n/**\n * Absolute path of `${specifier}/package.json`, found the way the plugin loader\n * finds a plugin: through `projectRequire` (the consumer project) first, then\n * from the CLI's own location. The CLI fallback matters for a global install\n * run in a repository without a package.json, and for dev/test flows (where the\n * CLI source lives inside the plugin's monorepo) that resolve without a\n * populated node_modules. Throws the project lookup's error when neither finds it.\n *\n * Anchored on `${specifier}/package.json` because `require.resolve(spec)` fails\n * for ESM-only packages whose \"exports\" map has no \"require\" condition.\n */\nexport function resolvePluginPackageJson(specifier: string, projectRequire: NodeJS.Require): string {\n try {\n return projectRequire.resolve(`${specifier}/package.json`);\n } catch (primary) {\n try {\n return cliFallbackRequire.resolve(`${specifier}/package.json`);\n } catch {\n throw primary;\n }\n }\n}\n\nexport interface ResolvedPackage {\n readonly name: string;\n readonly version: string;\n readonly pkgDir: string;\n readonly entryUrl: string;\n readonly source: 'project' | 'cli';\n}\n\n/** `package.json` text of `specifier` as seen from `req`, read once; undefined when it cannot be found or read. */\nfunction readPackageJsonVia(specifier: string, req: NodeJS.Require): { pkgDir: string; raw: string } | undefined {\n const files: string[] = [];\n try {\n files.push(req.resolve(`${specifier}/package.json`));\n } catch {\n // The package may not export ./package.json: look in the lookup paths directly.\n for (const base of req.resolve.paths(specifier) ?? []) {\n files.push(join(base, specifier, 'package.json'));\n }\n }\n for (const file of files) {\n try {\n return { pkgDir: dirname(file), raw: readFileSync(file, 'utf-8') };\n } catch {\n // try the next lookup path\n }\n }\n return undefined;\n}\n\n/**\n * Every copy of `specifier` the CLI may load, in order: the project's own copy first\n * (resolved from `<projectRoot>/package.json`), then the copy next to the running CLI\n * (`cliAnchor` is a directory standing in for the CLI's location; it defaults to this module).\n * A copy that is the same real directory as an earlier one appears once. Never throws.\n */\nexport function resolvePackageCandidates(\n specifier: string,\n projectRoot: string,\n cliAnchor?: string,\n): ResolvedPackage[] {\n const lookups: Array<{ source: 'project' | 'cli'; req: NodeJS.Require }> = [\n { source: 'project', req: createRequire(join(projectRoot, 'package.json')) },\n {\n source: 'cli',\n req: cliAnchor === undefined ? cliFallbackRequire : createRequire(join(cliAnchor, 'package.json')),\n },\n ];\n const found: ResolvedPackage[] = [];\n const seen = new Set<string>();\n for (const { source, req } of lookups) {\n const read = readPackageJsonVia(specifier, req);\n if (!read) continue;\n try {\n const real = realpathSync(read.pkgDir);\n if (seen.has(real)) continue;\n seen.add(real);\n const pkg = JSON.parse(read.raw) as {\n name?: string;\n version?: string;\n main?: string;\n module?: string;\n exports?: unknown;\n };\n const entry = pickEsmEntry(pkg.exports) ?? pkg.module ?? pkg.main ?? 'index.js';\n found.push({\n name: pkg.name ?? specifier,\n version: pkg.version ?? '0.0.0',\n pkgDir: real,\n entryUrl: pathToFileURL(resolve(real, entry)).href,\n source,\n });\n } catch (err) {\n log.warn(`Skipped ${specifier} candidate in ${read.pkgDir}: ${(err as Error).message}`);\n }\n }\n return found;\n}\n\nfunction resolvePluginEntryUrl(\n specifier: string,\n manifestRequire: NodeJS.Require,\n): string {\n // Resolve bare specifiers against the consumer project's node_modules first,\n // not the CLI bundle location. Matters when `ctxo` runs via npx, where plain\n // import() would look in ~/.npm/_npx/<hash>/node_modules.\n const pkgJsonPath = resolvePluginPackageJson(specifier, manifestRequire);\n const pkgDir = dirname(pkgJsonPath);\n const pkgJson = JSON.parse(readFileSync(pkgJsonPath, 'utf-8')) as {\n main?: string;\n module?: string;\n exports?: unknown;\n };\n const entry = pickEsmEntry(pkgJson.exports) ?? pkgJson.module ?? pkgJson.main ?? 'index.js';\n return pathToFileURL(resolve(pkgDir, entry)).href;\n}\n\nasync function tryLoad(\n specifier: string,\n baseDir: string,\n manifestRequire: NodeJS.Require,\n): Promise<CtxoLanguagePlugin | PluginLoadFailure> {\n try {\n const target = isLocalSpecifier(specifier)\n ? pathToFileURL(resolve(baseDir, specifier)).href\n : resolvePluginEntryUrl(specifier, manifestRequire);\n\n const mod = (await import(target)) as {\n default?: unknown;\n plugin?: unknown;\n };\n const candidate = (mod.default ?? mod.plugin ?? mod) as Partial<CtxoLanguagePlugin>;\n\n if (!candidate || typeof candidate !== 'object') {\n return { specifier, reason: 'module export is not an object' };\n }\n if (!(SUPPORTED_API_VERSIONS as readonly unknown[]).includes(candidate.apiVersion)) {\n return {\n specifier,\n reason: `unsupported apiVersion ${String(candidate.apiVersion)} (supported: ${SUPPORTED_API_VERSIONS.join(', ')})`,\n };\n }\n if (typeof candidate.createAdapter !== 'function') {\n return { specifier, reason: 'missing createAdapter()' };\n }\n if (typeof candidate.id !== 'string' || candidate.id.length === 0) {\n return { specifier, reason: 'missing or empty id' };\n }\n if (!Array.isArray(candidate.extensions)) {\n return { specifier, reason: 'extensions must be an array' };\n }\n if (candidate.tier !== 'syntax' && candidate.tier !== 'full') {\n return { specifier, reason: `invalid tier ${String(candidate.tier)}` };\n }\n\n return candidate as CtxoLanguagePlugin;\n } catch (err) {\n return { specifier, reason: (err as Error).message };\n }\n}\n\nexport async function discoverPlugins(options: DiscoverOptions): Promise<DiscoveryResult> {\n const { manifestPath } = options;\n if (!existsSync(manifestPath)) {\n log.info(`Manifest not found at ${manifestPath} — no plugins discovered`);\n return { plugins: [], failures: [] };\n }\n\n const baseDir = dirname(manifestPath);\n const specifiers = options.explicit ?? readManifestDeps(manifestPath);\n const manifestRequire = createRequire(pathToFileURL(manifestPath).href);\n\n const plugins: DiscoveredPlugin[] = [];\n const failures: PluginLoadFailure[] = [];\n\n for (const spec of specifiers) {\n if (options.shouldSkipSpecifier?.(spec)) {\n log.info(`Skipping plugin ${spec} (matched ignoreProjects)`);\n continue;\n }\n const result = await tryLoad(spec, baseDir, manifestRequire);\n if ('apiVersion' in result) {\n plugins.push({ plugin: result, specifier: spec });\n } else {\n failures.push(result);\n log.warn(`Skipped plugin ${spec}: ${result.reason}`);\n }\n }\n\n return { plugins, failures };\n}\n"],"mappings":"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","names":[]}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/cli/cli-paths.ts
|
|
4
|
+
import { existsSync, readFileSync } from "fs";
|
|
5
|
+
import { dirname, join } from "path";
|
|
6
|
+
function findCliPackageJson() {
|
|
7
|
+
let dir = import.meta.dirname;
|
|
8
|
+
for (let i = 0; i < 10; i++) {
|
|
9
|
+
const pkg = join(dir, "package.json");
|
|
10
|
+
if (existsSync(pkg)) return pkg;
|
|
11
|
+
const parent = join(dir, "..");
|
|
12
|
+
if (parent === dir) break;
|
|
13
|
+
dir = parent;
|
|
14
|
+
}
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
function getVersion() {
|
|
18
|
+
const pkg = findCliPackageJson();
|
|
19
|
+
if (pkg === void 0) return "unknown";
|
|
20
|
+
try {
|
|
21
|
+
const json = JSON.parse(readFileSync(pkg, "utf-8"));
|
|
22
|
+
return json.version ?? "unknown";
|
|
23
|
+
} catch {
|
|
24
|
+
return "unknown";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function getCliPackageRoot() {
|
|
28
|
+
const pkg = findCliPackageJson();
|
|
29
|
+
return pkg !== void 0 ? dirname(pkg) : dirname(import.meta.dirname);
|
|
30
|
+
}
|
|
31
|
+
function locateGitRoot(startDir) {
|
|
32
|
+
let dir = startDir;
|
|
33
|
+
for (let i = 0; i < 12; i++) {
|
|
34
|
+
if (existsSync(join(dir, ".git"))) return dir;
|
|
35
|
+
const parent = dirname(dir);
|
|
36
|
+
if (parent === dir) break;
|
|
37
|
+
dir = parent;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// src/core/compare-codepoint.ts
|
|
43
|
+
function compareCodepoint(a, b) {
|
|
44
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export {
|
|
48
|
+
compareCodepoint,
|
|
49
|
+
getVersion,
|
|
50
|
+
getCliPackageRoot,
|
|
51
|
+
locateGitRoot
|
|
52
|
+
};
|
|
53
|
+
//# sourceMappingURL=chunk-72CEQEQD.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/cli/cli-paths.ts","../src/core/compare-codepoint.ts"],"sourcesContent":["import { existsSync, readFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\n\n/**\n * Light path helpers shared by the CLI router, the commands and the module host. This file imports\n * nothing but `node:` modules, so a route that must stay fast (the domain guard hook) can use it\n * without loading the router or the MCP stack.\n */\n\n/**\n * The nearest `package.json` at or above this module (capped at 10 levels): the CLI's own manifest,\n * whether the code runs bundled from `dist/` or from `src/cli/` in development.\n */\nfunction findCliPackageJson(): string | undefined {\n let dir = import.meta.dirname;\n for (let i = 0; i < 10; i++) {\n const pkg = join(dir, 'package.json');\n if (existsSync(pkg)) return pkg;\n const parent = join(dir, '..');\n if (parent === dir) break;\n dir = parent;\n }\n return undefined;\n}\n\n/** The running CLI's version from its `package.json`, or 'unknown' when it cannot be read. */\nexport function getVersion(): string {\n const pkg = findCliPackageJson();\n if (pkg === undefined) return 'unknown';\n try {\n const json = JSON.parse(readFileSync(pkg, 'utf-8'));\n return json.version ?? 'unknown';\n } catch {\n return 'unknown';\n }\n}\n\n/**\n * The directory of the running CLI's `package.json` (the package root). When no manifest is found,\n * the parent of this module's directory, which is the package root of the bundled `dist/` layout.\n */\nexport function getCliPackageRoot(): string {\n const pkg = findCliPackageJson();\n return pkg !== undefined ? dirname(pkg) : dirname(import.meta.dirname);\n}\n\n/**\n * Walk up from `startDir` (capped at 12 levels) looking for a directory that\n * contains `.git`. First hit wins. Matches how git itself resolves the working\n * tree and keeps every subcommand consistent when the user runs ctxo from a\n * subdirectory or when pnpm --filter sets cwd to a monorepo package.\n */\nexport function locateGitRoot(startDir: string): string | null {\n let dir = startDir;\n for (let i = 0; i < 12; i++) {\n if (existsSync(join(dir, '.git'))) return dir;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n return null;\n}\n","/**\n * Compares strings with `<` and `>` (UTF-16 code unit order, which Ctxo calls code point order),\n * never by locale.\n *\n * `localeCompare` depends on the machine's locale (Turkish treats dotted and dotless i specially, for\n * one), so output sorted with it can differ between teammates and CI runners. Every list that ends up\n * in a committed artifact is sorted with this comparator instead.\n */\nexport function compareCodepoint(a: string, b: string): number {\n return a < b ? -1 : a > b ? 1 : 0;\n}\n"],"mappings":";;;AAAA,SAAS,YAAY,oBAAoB;AACzC,SAAS,SAAS,YAAY;AAY9B,SAAS,qBAAyC;AAChD,MAAI,MAAM,YAAY;AACtB,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,UAAM,MAAM,KAAK,KAAK,cAAc;AACpC,QAAI,WAAW,GAAG,EAAG,QAAO;AAC5B,UAAM,SAAS,KAAK,KAAK,IAAI;AAC7B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;AAGO,SAAS,aAAqB;AACnC,QAAM,MAAM,mBAAmB;AAC/B,MAAI,QAAQ,OAAW,QAAO;AAC9B,MAAI;AACF,UAAM,OAAO,KAAK,MAAM,aAAa,KAAK,OAAO,CAAC;AAClD,WAAO,KAAK,WAAW;AAAA,EACzB,QAAQ;AACN,WAAO;AAAA,EACT;AACF;AAMO,SAAS,oBAA4B;AAC1C,QAAM,MAAM,mBAAmB;AAC/B,SAAO,QAAQ,SAAY,QAAQ,GAAG,IAAI,QAAQ,YAAY,OAAO;AACvE;AAQO,SAAS,cAAc,UAAiC;AAC7D,MAAI,MAAM;AACV,WAAS,IAAI,GAAG,IAAI,IAAI,KAAK;AAC3B,QAAI,WAAW,KAAK,KAAK,MAAM,CAAC,EAAG,QAAO;AAC1C,UAAM,SAAS,QAAQ,GAAG;AAC1B,QAAI,WAAW,IAAK;AACpB,UAAM;AAAA,EACR;AACA,SAAO;AACT;;;ACrDO,SAAS,iBAAiB,GAAW,GAAmB;AAC7D,SAAO,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI;AAClC;","names":[]}
|