Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .claude-plugin/marketplace.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
"name": "llmdoc",
"source": "./",
"description": "Persistent engineering context powered by the llmdoc CLI and progressive MDX knowledge retrieval",
"version": "3.5.2"
"version": "3.5.3"
}
]
}
2 changes: 1 addition & 1 deletion .claude-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "llmdoc",
"description": "Persistent engineering context powered by the llmdoc CLI and progressive MDX knowledge retrieval",
"version": "3.5.2",
"version": "3.5.3",
"author": {
"name": "DJJ & Danniel"
}
Expand Down
2 changes: 1 addition & 1 deletion .codex-plugin/plugin.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "llmdoc",
"version": "3.5.2",
"version": "3.5.3",
"description": "Persistent engineering context powered by the llmdoc CLI and progressive MDX knowledge retrieval",
"skills": "./.agents/skills/"
}
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,10 @@ counts, and Git state.
`context --files` evaluates every input independently and reports
`unmappedFiles`; a non-empty impacted set never hides unmapped siblings.

Plugin lifecycle hooks invoke the same scoped CLI through an npm package alias.
This prevents a same-name local or `file:` dependency without a built bin from
shadowing the hook runtime; normal interactive commands keep the shorter form.

```bash
# Map the knowledge surface
npx -y @tokenroll/llmdoc tree --docs
Expand Down
3 changes: 3 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,9 @@ flowchart LR
`context --files` 会逐个判断输入并报告 `unmappedFiles`;非空的 impacted
结果不会再掩盖同批查询中未映射的其他路径。

插件生命周期 hook 会通过 npm package alias 调用同一个 scoped CLI,避免宿主仓库中
同名但尚未构建 bin 的本地或 `file:` 依赖遮蔽 hook runtime;日常交互命令仍使用上面的短写法。

```bash
# 展开知识地图
npx -y @tokenroll/llmdoc tree --docs
Expand Down
4 changes: 2 additions & 2 deletions cli/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tokenroll/llmdoc",
"version": "3.5.2",
"version": "3.5.3",
"description": "V3 runtime CLI for llmdoc repositories",
"homepage": "https://github.com/TokenRollAI/llmdoc",
"repository": {
Expand Down
3 changes: 2 additions & 1 deletion docs/v3-design/05-packaging.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ llmdoc-repository/ # 根 = 标准 Claude Code plugin
│ └── upgrade/SKILL.md # disable-model-invocation: true,仅显式调用
├── agents/ # investigator.md / reflector.md / recorder.md(角色契约)
├── hooks/
│ └── hooks.json # SessionStart/Stop/PreCompact → npx @tokenroll/llmdoc hook *
│ └── hooks.json # SessionStart/Stop/PreCompact → npm alias → scoped llmdoc hook *
├── cli/ # npm 包 @tokenroll/llmdoc,暴露 bin llmdoc(Runtime 实体)
│ ├── src/
│ ├── schemas/ # front matter / meta.json 的 JSON Schema(事实源)
Expand Down Expand Up @@ -53,6 +53,7 @@ llmdoc-repository/ # 根 = 标准 Claude Code plugin

- upgrade 入口禁止隐式调用,正文惰性加载;
- hook 只注入短状态信号,失败不阻塞开发;
- hook 的 `npx --package` 使用 npm alias 指向 `@tokenroll/llmdoc`,避免宿主仓库中同名但缺失 bin 的 local/file dependency 抢先满足解析;
- 插件不复制 CLI 已承担的机械逻辑。

## 3. CI
Expand Down
8 changes: 4 additions & 4 deletions hooks/hooks.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
{
"description": "V3 llmdoc hooks delegated to the scoped llmdoc CLI. Missing packages are installed non-interactively; SessionStart may return plain text, while Stop and PreCompact return JSON on success.",
"description": "V3 llmdoc hooks delegated to the scoped llmdoc CLI through an npm alias that cannot be shadowed by a same-name local dependency. Missing packages are installed non-interactively; SessionStart may return plain text, while Stop and PreCompact return JSON on success.",
"hooks": {
"SessionStart": [
{
"hooks": [
{
"type": "command",
"command": "npx -y @tokenroll/llmdoc hook session-start",
"command": "npx -y --package=@tokenroll/llmdoc-hook-runtime@npm:@tokenroll/llmdoc -- llmdoc hook session-start",
"additionalContextLimit": 1200
}
]
Expand All @@ -17,7 +17,7 @@
"hooks": [
{
"type": "command",
"command": "npx -y @tokenroll/llmdoc hook stop"
"command": "npx -y --package=@tokenroll/llmdoc-hook-runtime@npm:@tokenroll/llmdoc -- llmdoc hook stop"
}
]
}
Expand All @@ -27,7 +27,7 @@
"hooks": [
{
"type": "command",
"command": "npx -y @tokenroll/llmdoc hook compact"
"command": "npx -y --package=@tokenroll/llmdoc-hook-runtime@npm:@tokenroll/llmdoc -- llmdoc hook compact"
}
]
}
Expand Down
10 changes: 5 additions & 5 deletions llmdoc/meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,19 @@
},
"documents": {
"architecture.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
"validatedRevision": "255d8672b5c4de14149324c15a017392b0865488"
},
"cli-runtime/state-and-validation.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
"validatedRevision": "255d8672b5c4de14149324c15a017392b0865488"
},
"cli-runtime/retrieval-and-mutation.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
},
"plugin-packaging/claude-and-codex.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
"validatedRevision": "255d8672b5c4de14149324c15a017392b0865488"
},
"plugin-packaging/development-and-release.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
"validatedRevision": "255d8672b5c4de14149324c15a017392b0865488"
},
"workflows/init-and-update.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
Expand All @@ -27,7 +27,7 @@
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
},
"website/product-and-deployment.mdx": {
"validatedRevision": "2b114c1e18652ea1ef337848f5837a67157bf057"
"validatedRevision": "255d8672b5c4de14149324c15a017392b0865488"
}
},
"convergence": {
Expand Down
12 changes: 7 additions & 5 deletions llmdoc/plugin-packaging/claude-and-codex.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: Claude canonical surface、Codex 生成面、共同 CLI runtime 与跨宿主一致性不变量
description: Claude canonical surface、Codex 生成面,以及防同名依赖遮蔽的 lifecycle hook runtime 启动边界
kind: architecture
code:
paths:
Expand All @@ -22,16 +22,18 @@ code:

# Claude 与 Codex 表面

## 真相源与共同 runtime
## 真相源与 runtime 启动边界

Claude 根插件是手工维护的 canonical surface:skills 定义 Retrieval/Reflection/Stable Knowledge Gate 与显式工作流,三个 agent 定义调查、候选捕获和正式写入边界。Codex 的 skills、agents、manifest 与 marketplace 是转换产物,不得成为独立设计源。

两种宿主都把确定性行为收敛到外部 `@tokenroll/llmdoc` CLI。统一调用形式是 `npx -y @tokenroll/llmdoc <cmd>`:不向消费仓库写依赖;需要复现时在包名中固定版本;不得退回会解析错误包名或交互挂起的裸调用。
两种宿主都把确定性行为收敛到外部 `@tokenroll/llmdoc` CLI。普通交互命令使用 `npx -y @tokenroll/llmdoc <cmd>`:不向消费仓库写依赖;需要复现时在包名中固定版本;不得退回会解析错误包名或交互挂起的裸调用。

生命周期 hook 属于不同的启动边界:宿主从消费仓库的 cwd 调用 npm,而该仓库可能暴露同名的 local/`file:` 依赖。npm 会让这个依赖先满足普通 package identity;即使其 bin 尚未构建或不是应运行的发布 runtime,进程也不会自动回退到 registry CLI。hook 因此必须通过一个不同 identity 的 scoped npm alias 请求 `@tokenroll/llmdoc`,再调用它暴露的 `llmdoc hook *` bin。alias 只隔离 package 解析,不改变共同 runtime、参数或输出契约,也不应用于日常交互命令。

## 生成与一致性不变量

转换必须在临时副本生成后替换式同步,因为生成器不保证清除陈旧输出。Codex 角色文本不手工维护;宿主特有 front matter/TOML 可以不同,实际 skill 与 agent 指令正文必须与 Claude canonical 一致。被 skill 按需加载的 reference 同样属于可执行 prompt surface:Codex 镜像必须包含相同正文,相关 skill/agent 也必须保留可发现的加载路径。

`scripts/check-codex-surface.mjs` 在 CI 中机械校验版本/marketplace 身份、五个 skill 与三个 agent 的正文一致性、按需 reference 的正文 parity 与加载路径、Reflection Gate 约束和 hook 调用。它还分别遍历 `skills/` 与 `.agents/skills/`,用 `gray-matter` 解析每份 `SKILL.md` 的 YAML front matter,并要求 `name`、`description` 是非空字符串;这是两侧各自的语法门槛,不替代跨宿主正文 parity。人工 checklist 只补宿主 UI policy、知识拓扑质量与信任模型等无法从正文等价判断的部分。
`scripts/check-codex-surface.mjs` 在 CI 中机械校验版本/marketplace 身份、五个 skill 与三个 agent 的正文一致性、按需 reference 的正文 parity 与加载路径、Reflection Gate 约束和三种 lifecycle hook 的 alias 调用。它还分别遍历 `skills/` 与 `.agents/skills/`,用 `gray-matter` 解析每份 `SKILL.md` 的 YAML front matter,并要求 `name`、`description` 是非空字符串;这是两侧各自的语法门槛,不替代跨宿主正文 parity。人工 checklist 只补宿主 UI policy、知识拓扑质量与信任模型等无法从正文等价判断的部分。

hooks 位于共享插件根,一份配置供两种宿主使用;它们保持 fail-open、只读,且由安装宿主按自己的信任模型启用
hooks 位于共享插件根,一份配置供两种宿主使用;它们保持只读,且由安装宿主按自己的信任模型启用。CLI 成功启动后 hook 逻辑保持 fail-open,但进程启动前的 npm 解析或 registry 失败不受这层保护;保留 alias 启动边界是避免本地同名依赖把错误暴露在宿主生命周期中的必要条件
7 changes: 6 additions & 1 deletion llmdoc/plugin-packaging/development-and-release.mdx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
---
description: 开发前分支同步、消费边界、验证与发布表面的同步不变量
description: 开发前分支同步、消费边界、生命周期 hook 发布验证与发布表面的同步不变量
kind: reference
code:
paths:
Expand All @@ -8,6 +8,7 @@ code:
- .github/workflows/ci.yml
- .github/workflows/codex-plugin-scanner.yml
- .github/workflows/release.yml
- scripts/check-prompt-budget.mjs
- README.md
- README.zh-CN.md
- docs/agent-integration.md
Expand All @@ -29,6 +30,10 @@ CLI 命令或输出语义变化必须在同一 change-set 同步:canonical ski

`scripts/check-codex-surface.mjs` 负责版本、身份与生成正文 parity;CI 还运行 lint、typecheck、test、build、prompt budget、dogfood validate 和消费者安装 smoke。自动化通过后仍需审查语义是否在所有公开表面一致,特别是宿主 UI policy 与授权边界。

## Lifecycle hook 发布验证

hook 的发布边界包含宿主 cwd 中的 npm package 解析、发布包的 bin 链接和 CLI 启动,而不只是 `hook` 命令进入后的业务逻辑。发布验证必须从 `hooks/hooks.json` 读取并执行真实 lifecycle 命令,在可见同名 local/`file:` 依赖的消费方环境中确认 alias 仍选择发布 runtime,并验证 SessionStart 的文本与 Stop/PreCompact 的 JSON 契约。静态命令前缀检查、直接运行仓库 `dist` 或只调用内部 hook handler 都不能替代这项验证,因为它们绕过了发生遮蔽的启动层。

## 外部宿主指引复核不变量

公开的外部宿主安装或启用指导在发布前必须依据宿主当时的官方用户文档复核。仓库中存在或本地可调用的命令,不足以单独成为主快速入门路径的依据;可选、恢复性或其他条件性步骤必须保留其条件语义。具体命令、UI 文案与步骤序列属于易变事实,不在稳定知识中固定。
6 changes: 3 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@tokenroll/llmdoc-repository",
"version": "3.5.2",
"version": "3.5.3",
"private": true,
"description": "Development workspace for the llmdoc V3 CLI and plugin surfaces",
"scripts": {
Expand Down
21 changes: 15 additions & 6 deletions scripts/check-codex-surface.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -221,16 +221,25 @@ for (const name of ["investigator", "reflector", "recorder"]) {
}
}

// 6) hooks.json:合法 JSON 且所有命令使用 scoped 包名 + 非交互安装确认
// 6) hooks.json:合法 JSON;npm alias 强制从 scoped registry package 解析 runtime,
// 避免消费仓库中同名但缺少 bin 的本地/file dependency 遮蔽 hook CLI。
const hooks = readJson("hooks/hooks.json");
if (hooks) {
const commands = JSON.stringify(hooks).match(/"command":"([^"]+)"/g) ?? [];
for (const raw of commands) {
const command = raw.slice(11, -1);
if (!command.startsWith("npx -y @tokenroll/llmdoc")) {
errors.push(`hooks.json: 命令必须以 'npx -y @tokenroll/llmdoc' 开头: ${command}`);
const runtime = "npx -y --package=@tokenroll/llmdoc-hook-runtime@npm:@tokenroll/llmdoc -- llmdoc hook";
const expectedCommands = new Set([
`${runtime} session-start`,
`${runtime} stop`,
`${runtime} compact`
]);
const commands = (JSON.stringify(hooks).match(/"command":"([^"]+)"/g) ?? []).map((raw) => raw.slice(11, -1));
for (const command of commands) {
if (!expectedCommands.delete(command)) {
errors.push(`hooks.json: 非法或重复 hook runtime 命令: ${command}`);
}
}
for (const missing of expectedCommands) {
errors.push(`hooks.json: 缺少防本地遮蔽的 hook runtime 命令: ${missing}`);
}
}

if (errors.length > 0) {
Expand Down
45 changes: 45 additions & 0 deletions scripts/check-prompt-budget.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,51 @@ for (const hookCase of hookCases) {
}
}

// The direct dist checks above prove current hook semantics. Execute the actual
// plugin commands too: npm package resolution is a separate launcher boundary,
// and a same-name local/file dependency can otherwise shadow the scoped CLI.
const hookConfig = JSON.parse(fs.readFileSync(path.join(root, "hooks/hooks.json"), "utf8"));
const launcherCases = [
{ event: "SessionStart", stdin: JSON.stringify({ source: "startup" }), json: false },
{ event: "Stop", stdin: "{}", json: true },
{ event: "PreCompact", stdin: "{}", json: true }
];

for (const launcherCase of launcherCases) {
const configured = hookConfig.hooks?.[launcherCase.event]?.flatMap((group) => group.hooks ?? []) ?? [];
if (configured.length !== 1 || typeof configured[0]?.command !== "string") {
failures.push(`hook launcher ${launcherCase.event}: expected exactly one command`);
continue;
}
const result = spawnSync(configured[0].command, {
cwd: root,
encoding: "utf8",
input: launcherCase.stdin,
shell: true,
timeout: 120_000
});
if (result.error) {
failures.push(`hook launcher ${launcherCase.event}: ${result.error.message}`);
continue;
}
if (result.status !== 0) {
failures.push(
`hook launcher ${launcherCase.event}: exited ${result.status}: ${(result.stderr ?? "").trim()}`
);
continue;
}
const output = result.stdout.trim();
if (launcherCase.json) {
try {
JSON.parse(output);
} catch {
failures.push(`hook launcher ${launcherCase.event}: output is not valid JSON`);
continue;
}
}
process.stdout.write(`hook launcher ${launcherCase.event}: ok\n`);
}

if (failures.length > 0) {
process.stderr.write(`${failures.join("\n")}\n`);
process.exit(1);
Expand Down
2 changes: 1 addition & 1 deletion tests/parity-checklist.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Claude Code 根插件是唯一手工维护的准源。Codex 表面由 ACPlugin
- [ ] 两个平台暴露 `investigator`、受限 `reflector` 与 `recorder` 三个角色契约;Reflector 只能写 `.llmdoc-tmp/reflections/pending/`。
- [ ] `init/update/prune/upgrade` 的宿主专属 front matter / UI policy 保持正确;五个 skill 和三个 agent 的正文一致性已由脚本机械校验。
- [ ] `upgrade` 在两个平台都保持仅显式调用(Claude 侧 `disable-model-invocation: true`;Codex 侧 `policy.allow_implicit_invocation: false`),未被 operating skill 或 hook 隐式触发。
- [ ] Claude 的 `SessionStart`、`Stop`、`PreCompact` 都通过 `npx -y @tokenroll/llmdoc` 调用 scoped CLI;Codex 保留仓库根 `hooks/hooks.json`,并按官方信任模型启用。
- [ ] Claude 的 `SessionStart`、`Stop`、`PreCompact` 都通过 npm alias `@tokenroll/llmdoc-hook-runtime@npm:@tokenroll/llmdoc` 调用 scoped CLI,避免消费仓库的同名本地依赖遮蔽 runtime;Codex 保留仓库根 `hooks/hooks.json`,并按官方信任模型启用。
- [ ] hooks fail-open、永不写 `llmdoc/`;SessionStart 不超过 200 token,Stop/PreCompact 成功时输出合法 JSON;pending 反思候选能在无代码 delta 时触发 update 提醒。
- [ ] 生成目录中没有 V2 `worker`、tracked reflection/memory 树、startup pack、watermark 或旧命令残留;恢复后的 Reflector 不保存 transcript。
- [ ] `.agents/skills/upgrade/agents/openai.yaml` 设置 `policy.allow_implicit_invocation: false`,确保 upgrade 只能显式调用。
Expand Down
Loading