litcodex-ai 0.3.7 → 0.3.8
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.
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
// store PLAN_MISSING→3, PLAN_CORRUPT→4, WRITE_FAILED→5; unknown subcommand/usage→1; bad args→2;
|
|
10
10
|
// not-found→3. The `doctor` route delegates to the canonical M11 6-check doctor (A3 C5) and ALWAYS
|
|
11
11
|
// resolves exit 0 (a diagnostic, never a gate).
|
|
12
|
-
import { renderDoctorJson, renderDoctorText, runLoopDoctor } from "./loop-doctor.js";
|
|
12
|
+
import { hookRegisteredInstalled, renderDoctorJson, renderDoctorText, runLoopDoctor } from "./loop-doctor.js";
|
|
13
13
|
import { exitCodeForLoop, LitLoopError } from "./loop-errors.js";
|
|
14
14
|
import { handleCheckpoint, handleCreate, handleRecordEvidence, handleRun, handleStatus, hasFlag, } from "./loop-handlers.js";
|
|
15
15
|
import { resolveLoopScope } from "./state-store.js";
|
|
@@ -59,7 +59,10 @@ export async function loopCommand(argv, io, clock) {
|
|
|
59
59
|
const scope = resolveLoopScope({ argv: rest, env: process.env });
|
|
60
60
|
if (head === "doctor") {
|
|
61
61
|
// A3 C5: delegate to the canonical M11 6-check doctor; ALWAYS exit 0 (diagnostic, not gate).
|
|
62
|
-
|
|
62
|
+
// Use the INSTALL-AWARE hook probe: a real `litcodex loop doctor` runs from an arbitrary cwd
|
|
63
|
+
// (no dev-tree manifest), so the pure repoRoot-only probe falsely warns "hook not registered".
|
|
64
|
+
// hookRegisteredInstalled also checks where the installed plugin actually lives (Codex cache).
|
|
65
|
+
const report = await runLoopDoctor({ repoRoot, scope }, { hookRegistered: (r) => hookRegisteredInstalled(r) });
|
|
63
66
|
stdout.write(json ? renderDoctorJson(report) : renderDoctorText(report));
|
|
64
67
|
return 0;
|
|
65
68
|
}
|
|
@@ -12,6 +12,19 @@ export declare const TERMINAL_LEDGER_KINDS: {
|
|
|
12
12
|
* catch (→ warn). Never executes any command from the manifest.
|
|
13
13
|
*/
|
|
14
14
|
export declare function hookRegistered(repoRoot: string): Promise<boolean>;
|
|
15
|
+
/**
|
|
16
|
+
* Install-aware hook probe (production default the global CLI injects). The dev-tree manifest
|
|
17
|
+
* `<repoRoot>/plugins/litcodex/hooks/hooks.json` ONLY exists when run from the repo; a real install
|
|
18
|
+
* (`litcodex loop doctor` from any cwd) has no such file, so the pure `hookRegistered` falsely warns
|
|
19
|
+
* "not registered". This trusts the repoRoot manifest when present (dev/test), and otherwise looks
|
|
20
|
+
* where the INSTALLED plugin actually lives: the Codex plugin cache
|
|
21
|
+
* `<CODEX_HOME>/plugins/cache/.../hooks/hooks.json` (the global CLI runs the bundled lit-loop, so the
|
|
22
|
+
* plugin's aggregate manifest only exists in that cache). Pure-read, never executes a command;
|
|
23
|
+
* `opts.codexHome` is injectable so tests stay hermetic.
|
|
24
|
+
*/
|
|
25
|
+
export declare function hookRegisteredInstalled(repoRoot: string, opts?: {
|
|
26
|
+
codexHome?: string;
|
|
27
|
+
}): Promise<boolean>;
|
|
15
28
|
/** Scan the ledger tail backward for the last terminal entry; null when none usable. */
|
|
16
29
|
export declare function latestCheckpointFromLedger(entries: ReadonlyArray<Record<string, unknown>>): LoopCheckpointRef | null;
|
|
17
30
|
/**
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
//
|
|
12
12
|
// Imports flat siblings `./state-store.js`/`./state-paths.js`/`./loop-model.js`/`./loop-types.js`
|
|
13
13
|
// (A3 C9 — NEVER `../state/...`). No `node:fs` import: existence-probing is M08 `statExists` only.
|
|
14
|
-
import { readFile } from "node:fs/promises";
|
|
14
|
+
import { readdir, readFile } from "node:fs/promises";
|
|
15
|
+
import { homedir } from "node:os";
|
|
15
16
|
import { join } from "node:path";
|
|
16
17
|
import { sanitizeId } from "./loop-doctor-render.js";
|
|
17
18
|
import { summarizePlan } from "./loop-model.js";
|
|
@@ -52,6 +53,58 @@ export async function hookRegistered(repoRoot) {
|
|
|
52
53
|
const parsed = JSON.parse(raw);
|
|
53
54
|
return scanForCommand(parsed);
|
|
54
55
|
}
|
|
56
|
+
/** Read + scan a manifest at an absolute path; ENOENT/parse-error ⇒ false (never throws). */
|
|
57
|
+
async function scanManifestAt(manifestAbs) {
|
|
58
|
+
try {
|
|
59
|
+
if (!(await statExists(manifestAbs)))
|
|
60
|
+
return false;
|
|
61
|
+
return scanForCommand(JSON.parse(await readFile(manifestAbs, "utf8")));
|
|
62
|
+
}
|
|
63
|
+
catch {
|
|
64
|
+
return false;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** Walk `<cacheRoot>/<marketplace>/<plugin>/<version>/hooks/hooks.json` (bounded; a few installs). */
|
|
68
|
+
async function scanCodexPluginCache(cacheRoot) {
|
|
69
|
+
const listDirs = async (dir) => {
|
|
70
|
+
try {
|
|
71
|
+
return await readdir(dir);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
for (const mp of await listDirs(cacheRoot)) {
|
|
78
|
+
for (const plugin of await listDirs(join(cacheRoot, mp))) {
|
|
79
|
+
for (const ver of await listDirs(join(cacheRoot, mp, plugin))) {
|
|
80
|
+
if (await scanManifestAt(join(cacheRoot, mp, plugin, ver, "hooks", "hooks.json")))
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Install-aware hook probe (production default the global CLI injects). The dev-tree manifest
|
|
89
|
+
* `<repoRoot>/plugins/litcodex/hooks/hooks.json` ONLY exists when run from the repo; a real install
|
|
90
|
+
* (`litcodex loop doctor` from any cwd) has no such file, so the pure `hookRegistered` falsely warns
|
|
91
|
+
* "not registered". This trusts the repoRoot manifest when present (dev/test), and otherwise looks
|
|
92
|
+
* where the INSTALLED plugin actually lives: the Codex plugin cache
|
|
93
|
+
* `<CODEX_HOME>/plugins/cache/.../hooks/hooks.json` (the global CLI runs the bundled lit-loop, so the
|
|
94
|
+
* plugin's aggregate manifest only exists in that cache). Pure-read, never executes a command;
|
|
95
|
+
* `opts.codexHome` is injectable so tests stay hermetic.
|
|
96
|
+
*/
|
|
97
|
+
export async function hookRegisteredInstalled(repoRoot, opts) {
|
|
98
|
+
// 1. Dev tree — authoritative when present (preserves dev/test behavior and keeps the real-fs cache
|
|
99
|
+
// walk below from ever running inside the repo's own hermetic unit tests).
|
|
100
|
+
const devManifest = join(repoRoot, ...HOOK_MANIFEST_RELPATH.split("/"));
|
|
101
|
+
if (await statExists(devManifest)) {
|
|
102
|
+
return scanManifestAt(devManifest);
|
|
103
|
+
}
|
|
104
|
+
// 2. Codex plugin cache — the real install context for a global-CLI `litcodex loop doctor`.
|
|
105
|
+
const codexHome = opts?.codexHome ?? (process.env["CODEX_HOME"]?.trim() || join(homedir(), ".codex"));
|
|
106
|
+
return scanCodexPluginCache(join(codexHome, "plugins", "cache"));
|
|
107
|
+
}
|
|
55
108
|
/** Recursively scan every string value under a `command` key for ALL three fragments. */
|
|
56
109
|
function scanForCommand(node) {
|
|
57
110
|
if (Array.isArray(node)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "litcodex-ai",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.8",
|
|
4
4
|
"description": "Codex loop harness installer. Run `npx litcodex-ai install` to set up the LitCodex Codex platform: the bare `lit` hook and the durable lit-loop runtime.",
|
|
5
5
|
"keywords": ["codex", "litcodex", "lit-loop", "ai-agents", "orchestration"],
|
|
6
6
|
"author": "LitCodex Authors",
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
},
|
|
16
16
|
"files": ["bin", "dist", "model-catalog.json", "README.md", "LICENSE"],
|
|
17
17
|
"dependencies": {
|
|
18
|
-
"@litcodex/lit-loop": "0.3.
|
|
18
|
+
"@litcodex/lit-loop": "0.3.8"
|
|
19
19
|
},
|
|
20
20
|
"bundledDependencies": ["@litcodex/lit-loop"],
|
|
21
21
|
"scripts": {
|