@akasha-book/gate 0.0.0-stage → 1.2.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 CHANGED
@@ -1,3 +1,21 @@
1
- # Temporary Holding Version
1
+ # @akasha-book/gate
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ > 阿卡夏之书(Akasha Book)· 门控插件
4
+
5
+ 阿卡夏之书门控 —— 把「可机检的纪律」做成硬约束(投影律:把不可检的德行,投影成可检的工序)。
6
+
7
+ - ① `tools.guard`:拒绝 `edit` / `write` / `apply_patch` 对数据目录的直写;拒绝 `pwsh` / `bash` 命令里对数据目录的写操作(Set-Content / Add-Content / Out-File / Remove-Item / Move-Item / Copy-Item / New-Item / `del` / 重定向 等)。数据必须经校验写入:`akasha` CLI / `mcp__akasha__*`。
8
+ - **已知边界**:门控防「顺手直改」,不防刻意绕过(例如 `node -e` 脚本内写文件)——那是安全边界的活,不属于本层承诺。
9
+ - ② `systemPrompt.section`(name `akasha:protocol`,order 700):**渲染时读库**——`text` 传函数,宿主在组装时逐次求值,读 `canon-akasha-usage` 的**当前版本**(`currentRecords` 取链尾);**改用法 = `revise canon-akasha-usage`,下一次组装即生效**(无需重启/改码);读库失败回退本文件内置兜底(渲染永不抛)。
10
+ - ③ 观测线(写 `logs/hooks.jsonl`):`gate-armed`(含 `usageSource: library|fallback`)/ `gate-denied` / `prompt-change`(只跟结构变更)/ `gate-usage-fallback` / `gate-usage-recovered`。
11
+ - 自测:`node selftest.mjs`(断言 20+,桩 ctx + 系统临时目录 + 桩库;不需要宿主)。
12
+ - 配置:`akashaDir` / `dataDir` / `log` / `sectionOrder` 均可在插件配置中覆盖;默认 `~/.akasha`。
13
+
14
+ ## 打包 / 安装(DSH 宿主)
15
+
16
+ ```bash
17
+ npm pack
18
+ # 然后:plugin_manager install_bundle <tgz 绝对路径>(新 bundle 热生效;替换已装包需重启宿主)
19
+ ```
20
+
21
+ 依赖:DSH 宿主(`tools` / `systemPrompt` 服务);核心库 `../../core/lib.mjs`(包内自带,或指向你的 akasha 目录)。
@@ -0,0 +1,6 @@
1
+ - insert:
2
+ - id: akasha-gate
3
+ name: '@akasha-book/gate'
4
+ config:
5
+ # 全部路径可按需覆盖;默认 ~/.akasha(见 index.js 的 DEFAULTS)
6
+ sectionOrder: 700
package/index.js ADDED
@@ -0,0 +1,132 @@
1
+ // 阿卡夏之书门控 v1.2 —— 三件可机检的事(设计稿 §10 投影律):
2
+ // ① tools.guard:拦下对 akasha\data\ 的直接文件写入与命令写操作(数据必须经校验路径)
3
+ // ② systemPrompt.section:把「用法 + 来源态约定」注入系统提示(akasha:protocol)——
4
+ // **渲染时读库当前版本 canon-akasha-usage**(库自己说话:改用法 = revise 记录,提示自动跟上);
5
+ // 读库失败回退到本文件内置的同文兜底(FALLBACK_SECTION),渲染永不抛。
6
+ // ③ 观测线:gate-armed 记录 usageSource(library / fallback)。
7
+ // 自身异常一律放行/静默:门控自崩不能拖垮宿主。
8
+ import { appendFileSync, mkdirSync } from 'node:fs';
9
+ import { createRequire } from 'node:module';
10
+ import { homedir } from 'node:os';
11
+ import { dirname, join } from 'node:path';
12
+
13
+ export const inject = ['tools', 'systemPrompt'];
14
+
15
+ // 默认值:家目录下的 .akasha(可用 config.akashaDir / dataDir / log 覆盖)。
16
+ const DEFAULT_AKASHA_DIR = join(homedir(), '.akasha');
17
+ const DEFAULT_DATA_DIR = join(DEFAULT_AKASHA_DIR, 'data');
18
+ const DEFAULT_LOG = join(DEFAULT_AKASHA_DIR, 'logs', 'hooks.jsonl');
19
+ const WRITE_TOOLS = new Set(['edit', 'write', 'apply_patch']);
20
+ const SHELL_TOOLS = new Set(['pwsh', 'bash']);
21
+ // 2026-10-07 复查:重定向判定排除 JS 箭头(`=>`)与 `2>&1`——`(?<![=\-])` 挡 `=>`/`->`,`(?![&=])` 挡 `>&`;
22
+ const WRITE_IDIOM = /(>>|(?<![=\-])>(?![&=])\s*[^\s|]|Set-Content|Add-Content|Out-File|Remove-Item|Move-Item|Copy-Item|New-Item|Set-Item|tee\b|sed -i|drop\b|delete\b|\bdel\b)/i;
23
+
24
+ // 读库失败时的兜底文本(与 canon-akasha-usage 初始版本同文;改兜底 = 升级本插件)。
25
+ const FALLBACK_SECTION = [
26
+ '## 阿卡夏之书(外置大脑 · v0)',
27
+ '- 事实性断言尽量带来源态:学过 / 接触过 / 记得·库内 / 搜到;未标注按「学过」(未核验)处理。',
28
+ '- 排序钉子:无据的确定 ≪ 有据的不确定;编造比承认不知道更糟。',
29
+ '- 开工先 akasha_kit(起床包:睡眠 + 待办 + 审计 + 库况);相关主题先 akasha_brief;查库 akasha_lookup;对位 akasha_cross;基石 akasha_frontier_due;自查 akasha_audit。',
30
+ '- 更正走修订链:akasha_revise / frontier recheck(追加不改原文,currentRecords 取当前版本)。',
31
+ '- 数据只经校验写入(akasha CLI / MCP 工具);直接改 akasha\\data\\*.jsonl 会被门控拒绝。',
32
+ '- 本段文本随库更新:改用法 = revise canon-akasha-usage(当前版本即注入内容)。'
33
+ ].join('\n');
34
+
35
+ export function apply(ctx, config = {}) {
36
+ const logPath = typeof config.log === 'string' && config.log.trim() !== '' ? config.log : DEFAULT_LOG;
37
+ const dataDir = (typeof config.dataDir === 'string' && config.dataDir.trim() !== '' ? config.dataDir : DEFAULT_DATA_DIR)
38
+ .replace(/\//g, '\\').replace(/\\+$/, '');
39
+ const akashaDir = (typeof config.akashaDir === 'string' && config.akashaDir.trim() !== '' ? config.akashaDir : DEFAULT_AKASHA_DIR)
40
+ .replace(/\//g, '\\').replace(/\\+$/, '');
41
+ const sectionOrder = Number.isFinite(config.sectionOrder) ? config.sectionOrder : 700;
42
+
43
+ let require_ = null;
44
+ try { require_ = createRequire(import.meta.url); } catch { /* 拿不到 require 就只用兜底 */ }
45
+
46
+ // 库驱动的降级/恢复只在**切换时**落一行观测线(防刷屏;静默降级必须有痕——2026-10-07 类扫补)。
47
+ let lastUsageSource = null;
48
+ const settleUsage = (source) => {
49
+ if (source !== lastUsageSource && lastUsageSource !== null) {
50
+ write({ kind: source === 'fallback' ? 'gate-usage-fallback' : 'gate-usage-recovered' });
51
+ }
52
+ lastUsageSource = source;
53
+ return source;
54
+ };
55
+ // 渲染时读库:canon-akasha-usage 的当前版本即注入文本;任何异常 → 兜底,绝不抛。
56
+ const usageText = () => {
57
+ try {
58
+ if (!require_) { settleUsage('fallback'); return FALLBACK_SECTION; }
59
+ const lib = require_(join(akashaDir, 'lib.mjs'));
60
+ const recs = lib.currentRecords(lib.loadStore('canon').records);
61
+ const hit = recs.filter((r) => r.id === 'canon-akasha-usage' || r.id.startsWith('canon-akasha-usage-r')).pop();
62
+ const text = hit && typeof hit.claim === 'string' ? hit.claim.trim() : '';
63
+ if (text) { settleUsage('library'); return text; }
64
+ settleUsage('fallback');
65
+ return FALLBACK_SECTION;
66
+ } catch {
67
+ settleUsage('fallback');
68
+ return FALLBACK_SECTION;
69
+ }
70
+ };
71
+
72
+ let ready = false;
73
+ try {
74
+ mkdirSync(dirname(logPath), { recursive: true });
75
+ ready = true;
76
+ } catch { /* 静默:写不了日志不代表门控不能工作 */ }
77
+
78
+ const write = (record) => {
79
+ if (!ready) return;
80
+ try {
81
+ appendFileSync(logPath, JSON.stringify({ ts: new Date().toISOString(), ...record }) + '\n', 'utf8');
82
+ } catch { /* 静默 */ }
83
+ };
84
+
85
+ const norm = (p) => String(p ?? '').replace(/\//g, '\\').toLowerCase();
86
+ const dataNeedle = () => norm(dataDir).replace(/\\+$/, '') + '\\';
87
+ // 文件路径参数:按整体前缀判定
88
+ const isDataPath = (p) => {
89
+ const s = norm(p);
90
+ const d = norm(dataDir);
91
+ return s === d || s.startsWith(dataNeedle());
92
+ };
93
+ // 命令文本:路径出现在任意位置都算(命令不等于路径)
94
+ const mentionsDataDir = (text) => norm(text).includes(dataNeedle());
95
+
96
+ // ① 数据目录写入守卫(同步、与注册顺序无关)。
97
+ ctx.tools.guard((exec) => {
98
+ try {
99
+ const name = String(exec?.name ?? '');
100
+ const args = exec?.arguments ?? {};
101
+ if (WRITE_TOOLS.has(name)) {
102
+ const target = String(args.file_path ?? args.path ?? '');
103
+ if (target && isDataPath(target)) {
104
+ write({ kind: 'gate-denied', tool: name, target });
105
+ return `阿卡夏门控:拒绝 ${name} 直接写 ${target}——数据必须经校验写入(akasha CLI / mcp__akasha__*),直改 JSONL 会污染 append-only 库。确需绕过请先停用 @akasha-book/gate。`;
106
+ }
107
+ } else if (SHELL_TOOLS.has(name)) {
108
+ const command = String(args.command ?? args.script ?? '');
109
+ if (command && mentionsDataDir(command) && WRITE_IDIOM.test(command)) {
110
+ write({ kind: 'gate-denied', tool: name, target: 'akasha\\data(命令)' });
111
+ return `阿卡夏门控:拒绝 ${name} 命令里对 akasha\\data 的写操作——请改走 akasha CLI / mcp__akasha__*。确需绕过请先停用 @akasha-book/gate。`;
112
+ }
113
+ }
114
+ } catch { /* 守卫异常 → 放行 */ }
115
+ return undefined;
116
+ });
117
+
118
+ // ② 系统提示注入:用法 + 来源态约定(order 700,落在 TEAM_POLICY 600 与 PTC_ONLY 800 之间)。
119
+ // text 传函数 —— system-prompt 在组装时逐次求值(dsh-system-prompt renderPrompt: typeof text === 'function' ? text(ctx) : text),
120
+ // 因此库里的 canon-akasha-usage 一旦 revise,下一次组装即生效(无需重启/重载)。
121
+ ctx.systemPrompt.section({
122
+ name: 'akasha:protocol',
123
+ order: sectionOrder,
124
+ text: usageText
125
+ });
126
+
127
+ // 提示变更的观测线(**只跟结构变更**——section / context 的注册与销毁;动态文本漂移不触发,2026-10-07 实测)。
128
+ ctx.on('system-prompt/change', () => write({ kind: 'prompt-change' }));
129
+
130
+ usageText(); // 播种 usageSource(首调不落切换线)
131
+ write({ kind: 'gate-armed', pid: process.pid, dataDir, sectionOrder, akashaDir, usageSource: lastUsageSource });
132
+ }
package/package.json CHANGED
@@ -1,6 +1,14 @@
1
1
  {
2
2
  "name": "@akasha-book/gate",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "1.2.0",
4
+ "license": "MIT",
5
+ "type": "module",
6
+ "description": "阿卡夏之书门控 v0:数据目录写入守卫(只许经校验写入)+ 系统提示注入来源态约定(akasha:protocol)",
7
+ "exports": { ".": "./index.js" },
8
+ "files": ["index.js", "cordis.patch.yml", "README.md"],
9
+ "dsh": {
10
+ "bundle": {
11
+ "patch": "./cordis.patch.yml"
12
+ }
13
+ }
14
+ }