@luckychen1993/pm-mcp 0.1.3 → 0.1.5
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/LICENSE +21 -21
- package/README.md +23 -8
- package/dist/agents-md.js +51 -0
- package/dist/audit-tools.js +8 -7
- package/dist/audit.js +10 -5
- package/dist/budget.js +36 -3
- package/dist/idempotency.js +201 -67
- package/dist/index-store.js +163 -94
- package/dist/index.js +35 -17
- package/dist/init.js +3 -0
- package/dist/license.js +7 -4
- package/dist/paths.js +35 -0
- package/dist/project-tools.js +23 -2
- package/dist/search.js +3 -3
- package/dist/security.js +77 -9
- package/dist/setup.js +129 -9
- package/dist/store.js +175 -58
- package/dist/tool-base.js +66 -7
- package/dist/usage-log.js +278 -0
- package/dist/watcher-coordinator.js +182 -0
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026 pm-mcp contributors
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pm-mcp contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -25,13 +25,15 @@ AI 编码项目的典型失控(本工具逐一给出机制):
|
|
|
25
25
|
| 调试过程随会话消失 | `log_debug` 调试知识账(症状/根因/修法/验证) |
|
|
26
26
|
| 长任务崩溃、上下文丢失 | 任务步骤清单 + `checkpoint` 断点存档,新会话从恢复点续做 |
|
|
27
27
|
| 团队协作与知识传递 | Git 项目中共享 `.pm/` + 会话署名 + `onboard` 入职简报工作流 |
|
|
28
|
-
| 多 Agent 重复读写 |
|
|
28
|
+
| 多 Agent 重复读写 | 实际重叠的同参读跨进程合并;写工具支持业务幂等键、瞬时自动去重与故障不确定态 fail-closed |
|
|
29
29
|
| token 猛烧 | 三段式定位 + 读类工具输出硬预算 + 文件用途索引 |
|
|
30
30
|
| 法律知识产权 | `audit_license` 依赖许可证/copyleft 冲突 + 来源登记(provenance) |
|
|
31
31
|
| 测试投机(删测试、skip 凑绿) | 禁用/蒸发/空测试检测 + 功能测试背书占比 + done 必附验证 |
|
|
32
32
|
|
|
33
33
|
## 一个命令自动添加到 AI 编程助手
|
|
34
34
|
|
|
35
|
+
本页安装示例固定使用 **0.1.5**,包含安全扫描、项目命名和字面量搜索修复。0.1.4 不支持 `setup --project`;验证这些能力时请确认实际运行版本为 0.1.5 或更新版本。
|
|
36
|
+
|
|
35
37
|
要求:Node.js ≥ 22.18。Windows、macOS 和 Linux 使用同一条命令:
|
|
36
38
|
|
|
37
39
|
```bash
|
|
@@ -46,6 +48,16 @@ npx -y @luckychen1993/pm-mcp@latest setup --dry-run
|
|
|
46
48
|
|
|
47
49
|
非标准安装路径无法自动发现时,可在同一命令末尾指定 `--client codex|claude|zcode|cursor|vscode`;`--client print` 输出通用 MCP JSON。首次启动由 `npx` 下载并在本机运行,pm-mcp 本身不需要 API Key,也不会调用远程模型。
|
|
48
50
|
|
|
51
|
+
### 新项目一条命令上手(Codex 必用)
|
|
52
|
+
|
|
53
|
+
Codex 桌面版启动 MCP 服务时不带工作区路径,逐项目钉根才能用。在项目目录里跑一条命令,自动完成「注册钉定条目 + 初始化 .pm + 写 AGENTS.md 工作规矩」:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
npx -y @luckychen1993/pm-mcp@0.1.5 setup --client codex --project
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
跑完完全重启 Codex,新会话即可用(工具名前缀 `pm-mcp-<目录名>-<路径摘要>`)。不同中文目录和同名目录独立注册;重复执行会核对项目路径,匹配的旧版条目仍可复用。
|
|
60
|
+
|
|
49
61
|
### 客户端官方命令(自动检测失败时备用)
|
|
50
62
|
|
|
51
63
|
#### Codex / ChatGPT 桌面版 / Codex IDE 扩展
|
|
@@ -53,7 +65,7 @@ npx -y @luckychen1993/pm-mcp@latest setup --dry-run
|
|
|
53
65
|
Codex 的本地客户端共享同一份 MCP 配置。复制执行:
|
|
54
66
|
|
|
55
67
|
```bash
|
|
56
|
-
codex mcp add pm-mcp -- npx -y @luckychen1993/pm-mcp@0.1.
|
|
68
|
+
codex mcp add pm-mcp -- npx -y @luckychen1993/pm-mcp@0.1.5
|
|
57
69
|
```
|
|
58
70
|
|
|
59
71
|
重启客户端后可用 `codex mcp list` 检查。该命令遵循 [OpenAI 官方 MCP CLI 格式](https://learn.chatgpt.com/docs/extend/mcp?surface=cli)。
|
|
@@ -61,7 +73,7 @@ codex mcp add pm-mcp -- npx -y @luckychen1993/pm-mcp@0.1.3
|
|
|
61
73
|
#### Claude Code
|
|
62
74
|
|
|
63
75
|
```bash
|
|
64
|
-
claude mcp add pm-mcp --scope user -- npx -y @luckychen1993/pm-mcp@0.1.
|
|
76
|
+
claude mcp add pm-mcp --scope user -- npx -y @luckychen1993/pm-mcp@0.1.5
|
|
65
77
|
```
|
|
66
78
|
|
|
67
79
|
#### ZCode、Cursor、VS Code(Windows 备用脚本)
|
|
@@ -69,7 +81,7 @@ claude mcp add pm-mcp --scope user -- npx -y @luckychen1993/pm-mcp@0.1.3
|
|
|
69
81
|
将 `<client>` 替换为 `zcode`、`cursor` 或 `vscode`,整行复制到 PowerShell:
|
|
70
82
|
|
|
71
83
|
```powershell
|
|
72
|
-
$u='https://raw.githubusercontent.com/happy520ai/pm-mcp/v0.1.
|
|
84
|
+
$u='https://raw.githubusercontent.com/happy520ai/pm-mcp/v0.1.5/install.ps1'; $f=Join-Path $env:TEMP 'install-pm-mcp.ps1'; Invoke-WebRequest $u -OutFile $f; & $f -Client <client>
|
|
73
85
|
```
|
|
74
86
|
|
|
75
87
|
脚本只写对应客户端的 MCP 配置;已有 JSON 配置会先生成带时间戳的备份。建议执行前先打开 [`install.ps1`](install.ps1) 审阅。ZCode 默认写入 `$ZCODE_HOME/cli/config.json` 或 `~/.zcode/cli/config.json`,Cursor 默认写入 `~/.cursor/mcp.json`;也可用 `-ConfigPath` 指定路径。
|
|
@@ -81,7 +93,7 @@ $u='https://raw.githubusercontent.com/happy520ai/pm-mcp/v0.1.3/install.ps1'; $f=
|
|
|
81
93
|
"mcpServers": {
|
|
82
94
|
"pm-mcp": {
|
|
83
95
|
"command": "npx",
|
|
84
|
-
"args": ["-y", "@luckychen1993/pm-mcp@0.1.
|
|
96
|
+
"args": ["-y", "@luckychen1993/pm-mcp@0.1.5"],
|
|
85
97
|
"env": {}
|
|
86
98
|
}
|
|
87
99
|
}
|
|
@@ -131,7 +143,7 @@ snapshot_codebase + audit_structure 对账;audit_security 安全体检;audit
|
|
|
131
143
|
|
|
132
144
|
也可以直接用内置 prompts:`start-session` / `end-session` / `onboard` / `architecture-review` / `acceptance-review`;其中 `onboard` 会引导客户端读取状态并生成新人/AI 入职简报。
|
|
133
145
|
|
|
134
|
-
## 工具清单(
|
|
146
|
+
## 工具清单(48 个)
|
|
135
147
|
|
|
136
148
|
| 域 | 工具 | 说明 |
|
|
137
149
|
|---|---|---|
|
|
@@ -146,6 +158,7 @@ snapshot_codebase + audit_structure 对账;audit_security 安全体检;audit
|
|
|
146
158
|
| 审计 | `snapshot_codebase` / `audit_structure` | 结构快照;完整性/增长/漂移/债务/churn/复杂度/索引/足迹/测试/Git 十节对账 |
|
|
147
159
|
| 安全 | `audit_security` / `list_findings` / `resolve_finding` | 密钥+危险模式+新增/通配依赖提示;扫描命中原文不落盘,处置 note 拒绝内置密钥形态 |
|
|
148
160
|
| 法律 | `audit_license` | 依赖许可证清单、copyleft 冲突、GPL 头检测、LICENSE 检查、来源登记 |
|
|
161
|
+
| 可观测 | `get_usage_log` / `get_runtime_log` | 使用日志(工具调用统计、错误率、耗时、输出与折叠省下的 token 估算——验证省不省 token)与运行日志(server/watcher/工具报错);只记计量不记参数内容 |
|
|
149
162
|
| 注册表 | `list_projects` | 本机所有被管理项目 |
|
|
150
163
|
| 治理模型 | `init_governance` / `get_governance` / `upsert_module` / `upsert_interface` / `upsert_repository` / `set_governance_policies` | 结构化模块根、owner、语言、公开接口、允许/禁止依赖、跨仓版本约束与强制策略 |
|
|
151
164
|
| 语义治理 | `discover_languages` / `audit_governance` / `impact_analysis` / `list_semantic_evidence` / `save_semantic_evidence` | 编译器/Tree-sitter AST 关系、hash-bound 原生分析器/运行时证据、循环/越界/unresolved/覆盖率与变更反向闭包 |
|
|
@@ -156,6 +169,8 @@ Resources:`pm://dashboard`、`pm://roadmap`、`pm://tasks`、`pm://changelog`
|
|
|
156
169
|
|
|
157
170
|
所有写工具都接受可选 `idempotency_key`。同一业务的多个 Agent 应传同一个稳定键(例如 `T-123:add-login-task`):同键同参数只执行一次并复用首次结果,同键不同参数直接拒绝。即使没有显式键,完全相同的并发写也会在短窗口内自动合并;完全相同的并行读会等待首个执行者并复用结果。
|
|
158
171
|
|
|
172
|
+
并发保证有明确边界:正常运行的同键写只落一次,follower 最多等待 3 秒复用首次结果;仍未完成时以 MCP error 返回 pending,不把占位冒充成功。若进程在业务写入后、幂等完成记录前被强杀,pm-mcp 会把该键标为 `uncertain` 并禁止自动重放。它提供的是 **at-most-once + 不确定时 fail-closed**,不是跨多个 JSON 文件的事务型 exactly-once;核对目标账本后才能决定是否换新键。v0.1.3 的标准全局幂等记录会在首次复用时迁移到项目运行态,旧文件保留以便回滚。读合并只覆盖业务处理实际重叠的在途请求,完成结果不会继续缓存,避免外部代码刚变化却返回旧内容。每个项目由 SQLite 生命周期选举一个 watcher leader,其余进程低频待命;leader 被强杀后由一个 standby 接管。结构、安全、许可证与快照等权威审计强制重读内容,不把 `fs.watch` 或 mtime/size 缓存的最终一致性冒充强一致性。
|
|
173
|
+
|
|
159
174
|
## 数据模型(被管理项目内的 `.pm/`;JSON/Markdown 账本可 git diff,`index.db` 是忽略并可重建的缓存)
|
|
160
175
|
|
|
161
176
|
```
|
|
@@ -178,7 +193,7 @@ PROJECT.md # 仓库根,自动生成的仪表盘(勿手改)
|
|
|
178
193
|
changelog.md # 自动生成
|
|
179
194
|
```
|
|
180
195
|
|
|
181
|
-
全局注册表:`~/.pm-mcp/registry.json
|
|
196
|
+
全局注册表:`~/.pm-mcp/registry.json`。跨进程读合并、写幂等、恢复锁与 watcher 选举运行态位于被管理项目的 `.pm/.runtime/`,由 `.pm/.gitignore` 排除;因此路径别名、不同客户端或不同 `PM_MCP_HOME` 仍共享同一协调域,但运行态不会进入 Git。
|
|
182
197
|
|
|
183
198
|
## 跨文件、跨模块、跨语言治理
|
|
184
199
|
|
|
@@ -310,7 +325,7 @@ npm run gate # 完整本地门禁
|
|
|
310
325
|
| 单元 | budget/store/roadmap/health/security/license/audit/search/dashboard | 各账本与机制的正确性(含伪造密钥/危险模式 fixture、已识别命中原文不落盘断言) |
|
|
311
326
|
| 治理 | governance-model/language-adapters/semantic-graph/semantic-evidence/polyglot-ast/portfolio/governance-audit | 模块 schema、八语言 AST、symbol-bound call、原生/runtime证据、私有接口、循环、影响闭包、semver 与跨仓 fail-closed |
|
|
312
327
|
| 标准化验收 | acceptance-evaluator/acceptance-tools/acceptance-gate/quality-evidence | 九特性/五阶段、冻结指标与断言、双向追踪、风险接受、源码/证据/报告 SHA-256、伪造 PASS 失败 |
|
|
313
|
-
| 集成 | integration/governance-mcp/quality-gate | 真实拉起 stdio server 全链路:
|
|
328
|
+
| 集成 | integration/governance-mcp/quality-gate | 真实拉起 stdio server 全链路:48 工具、7 资源、5 提示词;真实执行安全 argv 质量命令 |
|
|
314
329
|
| 场景 | scenario.test.ts | **对 dist 产物**跑三段会话生命周期:中文+空格路径、CRLF、断点跨进程恢复、故障注入(删测试/加 skip/删入口文件/新增依赖/伪造密钥/GPL 头)、三种根解析方式(cwd / PM_ROOT / --root)各用一遍 |
|
|
315
330
|
| 完整性 + 真实仓库 | integrity.test.ts / realrepo.test.ts | 状态不变式(全账本 schema、派生逐字节一致、原子性、损坏恢复、500 任务规模折叠、并发双进程零丢失);并以本仓库真实 `.pm` 为被测对象(漂移为零、PROJECT.md 同步、done 纪律、全仓拷贝审计不误报) |
|
|
316
331
|
| 钻空(红队) | exploit.test.ts | 每个反制机制配一个「先钻、再断言钻不进」的测试(见上表) |
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* AGENTS.md 工作规矩:init_project 时自动写入/合并("项目开启即有规矩")。
|
|
5
|
+
* - 文件不存在 → 创建
|
|
6
|
+
* - 已存在且无标记段 → 末尾追加(绝不覆盖你的内容)
|
|
7
|
+
* - 已存在且有标记段 → 原样保留(幂等;模板升级时用新版本号标记替换)
|
|
8
|
+
*/
|
|
9
|
+
const BEGIN = "<!-- pm-mcp:agents-md:v1:begin -->";
|
|
10
|
+
const END = "<!-- pm-mcp:agents-md:v1:end -->";
|
|
11
|
+
const SECTION = `${BEGIN}
|
|
12
|
+
# 项目管理规矩(pm-mcp 自动维护段落,可整体删除但不建议)
|
|
13
|
+
|
|
14
|
+
本项目由 pm-mcp 管理,状态以 .pm/ 目录为准,禁止凭记忆陈述项目状态。
|
|
15
|
+
|
|
16
|
+
## 开工(每次会话第一件事)
|
|
17
|
+
1. get_status:了解阶段、里程碑、进行中任务与其 checkpoint 下一步
|
|
18
|
+
2. 不清楚的模块先 search_code 定位到 file:line 再读文件,不要全量读代码
|
|
19
|
+
3. 陈述本次计划:做哪个任务、动哪些文件、验收标准
|
|
20
|
+
|
|
21
|
+
## 做事过程
|
|
22
|
+
- 长任务:add_task 时给 steps;上下文快满或中断前 checkpoint 存断点
|
|
23
|
+
- 走捷径:立刻 add_task(type=debt) 登记债务,不许无声欠债
|
|
24
|
+
- 修 bug:完成后 log_debug 记录症状/根因/修法/验证
|
|
25
|
+
- 陈述「某功能在某文件」之前,先 search_code 求证
|
|
26
|
+
|
|
27
|
+
## 收工(缺一不可)
|
|
28
|
+
1. 完成的任务 update_task 置 done + result_note;feature/fix 补 verification
|
|
29
|
+
2. 未完成任务 checkpoint(进展 + 下一步具体动作)
|
|
30
|
+
3. 落地的功能 register_feature(入口文件 + 测试文件)
|
|
31
|
+
4. log_session 如实记录改动文件清单
|
|
32
|
+
|
|
33
|
+
## 每个里程碑节点
|
|
34
|
+
snapshot_codebase + audit_structure 对账;audit_security 安全体检;audit_license 许可证审计
|
|
35
|
+
${END}`;
|
|
36
|
+
/** 幂等 upsert:返回发生了什么(skipped = 调用方显式关闭) */
|
|
37
|
+
export function upsertAgentsMd(root, enabled = true) {
|
|
38
|
+
if (!enabled)
|
|
39
|
+
return "skipped";
|
|
40
|
+
const file = path.join(root, "AGENTS.md");
|
|
41
|
+
if (!fs.existsSync(file)) {
|
|
42
|
+
fs.writeFileSync(file, SECTION + "\n", "utf8");
|
|
43
|
+
return "created";
|
|
44
|
+
}
|
|
45
|
+
const content = fs.readFileSync(file, "utf8");
|
|
46
|
+
if (content.includes(BEGIN))
|
|
47
|
+
return "unchanged";
|
|
48
|
+
const separator = content.endsWith("\n") ? "" : "\n";
|
|
49
|
+
fs.writeFileSync(file, content + separator + "\n" + SECTION + "\n", "utf8");
|
|
50
|
+
return "appended";
|
|
51
|
+
}
|
package/dist/audit-tools.js
CHANGED
|
@@ -5,25 +5,26 @@ import { refreshDerived } from "./dashboard.js";
|
|
|
5
5
|
import { auditLicense } from "./license.js";
|
|
6
6
|
import { isInitialized, requireInitialized } from "./paths.js";
|
|
7
7
|
import { listRegistry } from "./registry.js";
|
|
8
|
-
import { auditSecurity, listFindings, resolveFinding } from "./security.js";
|
|
8
|
+
import { auditSecurity, listFindings, prepareSecurityAudit, resolveFinding } from "./security.js";
|
|
9
9
|
import { budgetLines, toolR, toolW } from "./tool-base.js";
|
|
10
|
+
import { walkRefresh } from "./index-store.js";
|
|
10
11
|
export function registerAuditTools(server, root) {
|
|
11
12
|
toolW(server, root, "snapshot_codebase", "给代码结构拍快照(文件数/行数/目录分布/测试数/skip 标记/依赖清单)。之后 audit_structure 会与上次快照 diff。建议每个会话或每个里程碑结束时拍一次。", {}, () => {
|
|
12
13
|
requireInitialized(root);
|
|
13
14
|
const { snapshot, summary } = snapshotCodebase(root);
|
|
14
15
|
refreshDerived(root);
|
|
15
16
|
return [...summary, `下次 audit_structure 将以此为基线(${snapshot.file})。`].join("\n");
|
|
16
|
-
});
|
|
17
|
+
}, () => { requireInitialized(root); walkRefresh(root, { forceContent: true }); });
|
|
17
18
|
toolR(server, root, "audit_structure", "八项结构对账(定期做):①增长与新增依赖 ②漂移对账(功能↔文件,防幻觉)③债务与重构配额 ④churn 热点 ⑤复杂度预算 ⑥索引覆盖率 ⑦足迹/产出 ⑧测试健康(禁用/蒸发/空测试/背书占比)。", {}, () => {
|
|
18
19
|
requireInitialized(root);
|
|
19
|
-
return auditStructure(root, budgetLines(root));
|
|
20
|
+
return auditStructure(root, budgetLines(root), true);
|
|
20
21
|
});
|
|
21
|
-
toolW(server, root, "audit_security", "安全体检(只扫描本地、不联网,结果写入安全台账):密钥泄露 / 危险模式(eval、SQL 拼接、禁用证书校验等)/ 依赖风险。修复后重扫自动关闭;接受风险必须留理由。", {}, () => {
|
|
22
|
+
toolW(server, root, "audit_security", "安全体检(只扫描本地、不联网,结果写入安全台账):密钥泄露 / 危险模式(eval、SQL 拼接、禁用证书校验等)/ 依赖风险。修复后重扫自动关闭;接受风险必须留理由。", {}, (_args, prepared) => {
|
|
22
23
|
requireInitialized(root);
|
|
23
|
-
const report = auditSecurity(root);
|
|
24
|
+
const report = auditSecurity(root, prepared);
|
|
24
25
|
refreshDerived(root);
|
|
25
26
|
return report.text.join("\n");
|
|
26
|
-
});
|
|
27
|
+
}, () => { requireInitialized(root); return prepareSecurityAudit(root); });
|
|
27
28
|
toolR(server, root, "list_findings", "查看安全台账(可按状态过滤)。", { status: z.enum(["open", "fixed", "accepted"]).optional() }, (args) => {
|
|
28
29
|
requireInitialized(root);
|
|
29
30
|
const list = listFindings(root, args.status);
|
|
@@ -44,7 +45,7 @@ export function registerAuditTools(server, root) {
|
|
|
44
45
|
});
|
|
45
46
|
toolR(server, root, "audit_license", "许可证审计(法律账):依赖许可证清单与 copyleft 冲突、源码中的 GPL 家族许可证头、LICENSE 文件检查、来源登记清单。离线启发式,不替代法务。", {}, () => {
|
|
46
47
|
requireInitialized(root);
|
|
47
|
-
return auditLicense(root, budgetLines(root));
|
|
48
|
+
return auditLicense(root, budgetLines(root), true);
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
51
|
export function registerRegistryTools(server, root) {
|
package/dist/audit.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadFeatures, loadFileNotes, loadProject, loadRoadmap, loadSessions, lo
|
|
|
5
5
|
import { decisionsDir, pmPath, snapshotsDir } from "./paths.js";
|
|
6
6
|
import { normSep, foldLines } from "./budget.js";
|
|
7
7
|
import { readDirectDeps } from "./scan.js";
|
|
8
|
-
import { aggregates, ensureFresh, getIndex, getMeta } from "./index-store.js";
|
|
8
|
+
import { aggregates, ensureFresh, freshness, getIndex, getMeta, walkRefresh } from "./index-store.js";
|
|
9
9
|
import { quotaWarnings, milestoneStats } from "./roadmap.js";
|
|
10
10
|
import { blastRadiusWarning, churnStats, debtAging, footprint } from "./health.js";
|
|
11
11
|
import { now } from "./types.js";
|
|
@@ -21,8 +21,11 @@ export function annotatedCount(root, db) {
|
|
|
21
21
|
}
|
|
22
22
|
return n;
|
|
23
23
|
}
|
|
24
|
-
export function snapshotCodebase(root) {
|
|
25
|
-
|
|
24
|
+
export function snapshotCodebase(root, forceFresh = false, indexPrepared = false) {
|
|
25
|
+
if (forceFresh)
|
|
26
|
+
walkRefresh(root, { forceContent: true });
|
|
27
|
+
else if (!indexPrepared)
|
|
28
|
+
ensureFresh(root);
|
|
26
29
|
const db = getIndex(root);
|
|
27
30
|
const agg = aggregates(db);
|
|
28
31
|
const annotated = annotatedCount(root, db);
|
|
@@ -165,13 +168,15 @@ export function gitAudit(root) {
|
|
|
165
168
|
return out;
|
|
166
169
|
}
|
|
167
170
|
/** 完整结构对账:九项报告 */
|
|
168
|
-
export function auditStructure(root, maxLines = 150) {
|
|
171
|
+
export function auditStructure(root, maxLines = 150, forceFresh = false, indexPrepared = false) {
|
|
169
172
|
const project = loadProject(root);
|
|
170
173
|
const { features } = loadFeatures(root);
|
|
171
174
|
const { tasks } = loadTasks(root);
|
|
172
175
|
const { milestones } = loadRoadmap(root);
|
|
173
176
|
const { sessions } = loadSessions(root);
|
|
174
|
-
const fresh =
|
|
177
|
+
const fresh = forceFresh
|
|
178
|
+
? { used: "walk", freshness: (walkRefresh(root, { forceContent: true }), freshness(root)) }
|
|
179
|
+
: indexPrepared ? { used: "walk", freshness: freshness(root) } : ensureFresh(root);
|
|
175
180
|
const db = getIndex(root);
|
|
176
181
|
const agg = aggregates(db);
|
|
177
182
|
const deps = readDirectDeps(root).deps;
|
package/dist/budget.js
CHANGED
|
@@ -1,4 +1,32 @@
|
|
|
1
1
|
import { relative as pathRelative } from "node:path";
|
|
2
|
+
import { estimateTokens } from "./usage-log.js";
|
|
3
|
+
/**
|
|
4
|
+
* token 经济:读类工具输出硬预算。
|
|
5
|
+
* 超出预算自动折叠为 "Top N + 另有 M 项已折叠",引导模型用过滤参数缩小范围。
|
|
6
|
+
* 折叠/截断省下的字符与 token 会记账,供使用日志统计「省了多少 token」。
|
|
7
|
+
*/
|
|
8
|
+
let foldSavedChars = 0;
|
|
9
|
+
let foldSavedTokens = 0;
|
|
10
|
+
function noteFoldSavings(full, out) {
|
|
11
|
+
const savedChars = full.length - out.length;
|
|
12
|
+
if (savedChars <= 0)
|
|
13
|
+
return;
|
|
14
|
+
foldSavedChars += savedChars;
|
|
15
|
+
foldSavedTokens += Math.max(0, estimateTokens(full) - estimateTokens(out));
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* 取走自上次调用以来折叠/截断省下的字符与 token(估算)。
|
|
19
|
+
* 单进程内计数:并发异步调用之间的归因可能漂移,但总量精确(每次折叠恰好记一次)。
|
|
20
|
+
* 工具包装器在业务函数返回后调用,把省量归到本次工具调用。
|
|
21
|
+
*/
|
|
22
|
+
export function drainFoldSavings() {
|
|
23
|
+
if (foldSavedChars <= 0)
|
|
24
|
+
return null;
|
|
25
|
+
const out = { chars: foldSavedChars, tokens: foldSavedTokens };
|
|
26
|
+
foldSavedChars = 0;
|
|
27
|
+
foldSavedTokens = 0;
|
|
28
|
+
return out;
|
|
29
|
+
}
|
|
2
30
|
/** 限制字符串单行长度,超长截断(防止一行巨长文件吃 token) */
|
|
3
31
|
export function capLine(line, maxChars = 300) {
|
|
4
32
|
if (line.length <= maxChars)
|
|
@@ -12,8 +40,11 @@ export function capLine(line, maxChars = 300) {
|
|
|
12
40
|
export function foldLines(lines, opts) {
|
|
13
41
|
const { maxLines, hint } = opts;
|
|
14
42
|
const capped = lines.map((l) => capLine(l));
|
|
15
|
-
if (capped.length <= maxLines)
|
|
16
|
-
|
|
43
|
+
if (capped.length <= maxLines) {
|
|
44
|
+
const out = capped.join("\n");
|
|
45
|
+
noteFoldSavings(lines.join("\n"), out);
|
|
46
|
+
return out;
|
|
47
|
+
}
|
|
17
48
|
const keep = Math.max(1, Math.floor(maxLines * 0.8));
|
|
18
49
|
const tail = Math.max(0, maxLines - keep - 1);
|
|
19
50
|
const hidden = capped.length - keep - tail;
|
|
@@ -21,7 +52,9 @@ export function foldLines(lines, opts) {
|
|
|
21
52
|
parts.push(`…(另有 ${hidden} 项已折叠${hint ? "," + hint : ""},请用过滤参数缩小范围)`);
|
|
22
53
|
if (tail > 0)
|
|
23
54
|
parts.push(...capped.slice(-tail));
|
|
24
|
-
|
|
55
|
+
const out = parts.join("\n");
|
|
56
|
+
noteFoldSavings(lines.join("\n"), out);
|
|
57
|
+
return out;
|
|
25
58
|
}
|
|
26
59
|
/** 对整段文本按行折叠 */
|
|
27
60
|
export function foldText(text, maxLines, hint) {
|