add-coder 0.3.27 → 0.3.29
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 +252 -150
- package/package.json +2 -1
- package/templates/.add-coder-src-hash.json +126 -148
- package/templates/adapters/claude/hooks/doc-format-guard.ts +32 -0
- package/templates/adapters/claude/hooks/lib/claude-env.ts +7 -0
- package/templates/adapters/claude/hooks/notification.ts +24 -0
- package/templates/adapters/claude/hooks/permission-denied.ts +33 -0
- package/templates/adapters/claude/hooks/permission-gate.ts +12 -0
- package/templates/adapters/claude/hooks/post-tool-failure.ts +4 -0
- package/templates/adapters/claude/hooks/post-tool-use.ts +17 -0
- package/templates/adapters/claude/hooks/pre-compact.ts +10 -0
- package/templates/adapters/claude/hooks/pre-tool-use.ts +81 -0
- package/templates/adapters/claude/hooks/prompt-submit.ts +82 -0
- package/templates/adapters/claude/hooks/review-checklist.ts +4 -0
- package/templates/adapters/claude/hooks/session-end.ts +10 -0
- package/templates/adapters/claude/hooks/session-start.ts +24 -0
- package/templates/adapters/claude/hooks/stop-check.ts +15 -0
- package/templates/adapters/claude/hooks/stop-failure.ts +34 -0
- package/templates/adapters/claude/hooks/subagent-guard.ts +25 -0
- package/templates/adapters/claude/hooks/subagent-stop.ts +12 -0
- package/templates/adapters/claude/settings.json +13 -13
- package/templates/adapters/codex/hooks/doc-format-guard.ts +30 -0
- package/templates/adapters/codex/hooks/notification.ts +26 -0
- package/templates/adapters/codex/hooks/permission-gate.ts +15 -0
- package/templates/adapters/codex/hooks/post-tool-failure.ts +14 -0
- package/templates/adapters/codex/hooks/post-tool-use.ts +85 -0
- package/templates/adapters/codex/hooks/pre-compact.ts +20 -0
- package/templates/adapters/codex/hooks/pre-tool-use.ts +141 -0
- package/templates/adapters/codex/hooks/prompt-submit.ts +38 -0
- package/templates/adapters/codex/hooks/review-checklist.ts +5 -0
- package/templates/adapters/codex/hooks/session-end.ts +19 -0
- package/templates/adapters/codex/hooks/session-start.ts +45 -0
- package/templates/adapters/codex/hooks/stop-check.ts +59 -0
- package/templates/adapters/codex/hooks/subagent-guard.ts +22 -0
- package/templates/adapters/codex/hooks/subagent-stop.ts +20 -0
- package/templates/adapters/codex/hooks.json +5 -5
- package/templates/adapters/qoder/hooks/doc-format-guard.ts +34 -0
- package/templates/adapters/qoder/hooks/lib/context-inject.ts +61 -0
- package/templates/adapters/qoder/hooks/lib/qoder-env.ts +23 -0
- package/templates/adapters/qoder/hooks/lib/review-checklist.ts +5 -0
- package/templates/adapters/qoder/hooks/notification.ts +34 -0
- package/templates/adapters/qoder/hooks/permission-gate.ts +26 -0
- package/templates/adapters/qoder/hooks/post-tool-failure.ts +30 -0
- package/templates/adapters/qoder/hooks/post-tool-use.ts +32 -0
- package/templates/adapters/qoder/hooks/pre-compact.ts +16 -0
- package/templates/adapters/qoder/hooks/pre-tool-use.ts +112 -0
- package/templates/adapters/qoder/hooks/prompt-submit.ts +66 -0
- package/templates/adapters/qoder/hooks/review-checklist.ts +24 -0
- package/templates/adapters/qoder/hooks/session-end.ts +27 -0
- package/templates/adapters/qoder/hooks/session-start.ts +83 -0
- package/templates/adapters/qoder/hooks/stop-check.ts +69 -0
- package/templates/adapters/qoder/hooks/subagent-guard.ts +16 -0
- package/templates/adapters/qoder/hooks/subagent-stop.ts +33 -0
- package/templates/adapters/qoder/settings.json +13 -13
- package/templates/adapters/trae/hooks/doc-format-guard.ts +30 -0
- package/templates/adapters/trae/hooks/notification.ts +22 -0
- package/templates/adapters/trae/hooks/permission-gate.ts +15 -0
- package/templates/adapters/trae/hooks/post-tool-failure.ts +14 -0
- package/templates/adapters/trae/hooks/post-tool-use.ts +24 -0
- package/templates/adapters/trae/hooks/pre-compact.ts +15 -0
- package/templates/adapters/trae/hooks/pre-tool-use.ts +40 -0
- package/templates/adapters/trae/hooks/prompt-submit.ts +29 -0
- package/templates/adapters/trae/hooks/review-checklist.ts +5 -0
- package/templates/adapters/trae/hooks/session-end.ts +16 -0
- package/templates/adapters/trae/hooks/session-start.ts +15 -0
- package/templates/adapters/trae/hooks/stop-check.ts +20 -0
- package/templates/adapters/trae/hooks/subagent-guard.ts +22 -0
- package/templates/adapters/trae/hooks/subagent-stop.ts +15 -0
- package/templates/adapters/trae/hooks.json +1 -1
- package/templates/adapters/vscode/hooks/doc-format-guard.ts +27 -0
- package/templates/adapters/vscode/hooks/notification.ts +22 -0
- package/templates/adapters/vscode/hooks/permission-gate.ts +15 -0
- package/templates/adapters/vscode/hooks/post-tool-failure.ts +32 -0
- package/templates/adapters/vscode/hooks/post-tool-use.ts +25 -0
- package/templates/adapters/vscode/hooks/pre-compact.ts +15 -0
- package/templates/adapters/vscode/hooks/pre-tool-use.ts +78 -0
- package/templates/adapters/vscode/hooks/prompt-submit.ts +74 -0
- package/templates/adapters/vscode/hooks/review-checklist.ts +5 -0
- package/templates/adapters/vscode/hooks/session-end.ts +16 -0
- package/templates/adapters/vscode/hooks/session-start.ts +24 -0
- package/templates/adapters/vscode/hooks/stop-check.ts +21 -0
- package/templates/adapters/vscode/hooks/subagent-guard.ts +21 -0
- package/templates/adapters/vscode/hooks/subagent-stop.ts +15 -0
- package/templates/core/docs/ADD-governance-claude-code.md +3 -3
- package/templates/core/docs/ADD-governance-codex.md +5 -5
- package/templates/core/docs/ADD-governance-qoder-cn.md +4 -4
- package/templates/core/docs/ADD-governance-trae.md +2 -2
- package/templates/core/docs/ADD-governance-vscode-copilot.md +21 -22
- package/templates/core/governance/audit-bridge.ts +47 -0
- package/templates/core/governance/common.ts +299 -0
- package/templates/core/governance/context-inject.ts +53 -0
- package/templates/core/governance/doc-format-guard.ts +440 -0
- package/templates/core/governance/notification-router.ts +62 -0
- package/templates/core/governance/notify.ts +66 -0
- package/templates/core/governance/permission-gate-router.ts +42 -0
- package/templates/core/governance/post-tool-failure-router.ts +56 -0
- package/templates/core/governance/post-tool-router.ts +212 -0
- package/templates/core/governance/pre-compact-guard.ts +41 -0
- package/templates/core/governance/pre-tool-guard.ts +309 -0
- package/templates/core/governance/preload-templates.ts +241 -0
- package/templates/core/governance/prompt-router.ts +222 -0
- package/templates/core/governance/review-checklist-guard.ts +186 -0
- package/templates/core/governance/rules.ts +720 -0
- package/templates/core/governance/session-end.ts +88 -0
- package/templates/core/governance/session-start-guard.ts +99 -0
- package/templates/core/governance/state-detect.ts +6 -0
- package/templates/core/governance/stop-router.ts +140 -0
- package/templates/core/governance/subagent-guard-router.ts +35 -0
- package/templates/core/governance/subagent-stop-router.ts +93 -0
- package/templates/core/governance/subagent-stop.ts +75 -0
- package/templates/core/governance/vocabulary.ts +109 -0
- package/templates/core/hooks/doc-format-guard.ts +21 -0
- package/templates/core/hooks/notification.ts +11 -0
- package/templates/core/hooks/permission-gate.ts +7 -0
- package/templates/core/hooks/post-tool-failure.ts +7 -0
- package/templates/core/hooks/post-tool-use.ts +23 -0
- package/templates/core/hooks/pre-compact.ts +9 -0
- package/templates/core/hooks/pre-tool-use.ts +30 -0
- package/templates/core/hooks/prompt-submit.ts +11 -0
- package/templates/core/hooks/review-checklist.ts +4 -0
- package/templates/core/hooks/session-end.ts +10 -0
- package/templates/core/hooks/session-start.ts +9 -0
- package/templates/core/hooks/stop-check.ts +14 -0
- package/templates/core/hooks/subagent-guard.ts +8 -0
- package/templates/core/hooks/subagent-stop.ts +9 -0
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +12 -1
- package/templates/core/scripts/mcp-server/tools/plan.ts +12 -2
- package/templates/adapters/claude/hooks/doc-format-guard.sh +0 -283
- package/templates/adapters/claude/hooks/lib/common.sh +0 -220
- package/templates/adapters/claude/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/claude/hooks/lib/notify.sh +0 -36
- package/templates/adapters/claude/hooks/lib/preload-templates.sh +0 -212
- package/templates/adapters/claude/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/claude/hooks/lib/state-detect.sh +0 -13
- package/templates/adapters/claude/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/claude/hooks/lib/vocabulary.sh +0 -62
- package/templates/adapters/claude/hooks/notification.sh +0 -45
- package/templates/adapters/claude/hooks/permission-denied.sh +0 -42
- package/templates/adapters/claude/hooks/permission-gate.sh +0 -18
- package/templates/adapters/claude/hooks/post-tool-failure.sh +0 -10
- package/templates/adapters/claude/hooks/post-tool-use.sh +0 -35
- package/templates/adapters/claude/hooks/pre-compact.sh +0 -37
- package/templates/adapters/claude/hooks/pre-tool-use.sh +0 -153
- package/templates/adapters/claude/hooks/prompt-submit.sh +0 -99
- package/templates/adapters/claude/hooks/review-checklist.sh +0 -10
- package/templates/adapters/claude/hooks/session-end.sh +0 -21
- package/templates/adapters/claude/hooks/session-start.sh +0 -36
- package/templates/adapters/claude/hooks/stop-check.sh +0 -54
- package/templates/adapters/claude/hooks/stop-failure.sh +0 -44
- package/templates/adapters/claude/hooks/subagent-guard.sh +0 -36
- package/templates/adapters/claude/hooks/subagent-stop.sh +0 -20
- package/templates/adapters/codex/hooks/doc-format-guard.sh +0 -343
- package/templates/adapters/codex/hooks/lib/common.sh +0 -209
- package/templates/adapters/codex/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/codex/hooks/lib/notify.sh +0 -36
- package/templates/adapters/codex/hooks/lib/preload-templates.sh +0 -208
- package/templates/adapters/codex/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/codex/hooks/lib/state-detect.sh +0 -8
- package/templates/adapters/codex/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/codex/hooks/lib/vocabulary.sh +0 -51
- package/templates/adapters/codex/hooks/notification.sh +0 -30
- package/templates/adapters/codex/hooks/permission-gate.sh +0 -18
- package/templates/adapters/codex/hooks/post-tool-failure.sh +0 -10
- package/templates/adapters/codex/hooks/post-tool-use.sh +0 -43
- package/templates/adapters/codex/hooks/pre-compact.sh +0 -37
- package/templates/adapters/codex/hooks/pre-tool-use.sh +0 -130
- package/templates/adapters/codex/hooks/prompt-submit.sh +0 -92
- package/templates/adapters/codex/hooks/review-checklist.sh +0 -10
- package/templates/adapters/codex/hooks/session-end.sh +0 -38
- package/templates/adapters/codex/hooks/session-start.sh +0 -63
- package/templates/adapters/codex/hooks/stop-check.sh +0 -71
- package/templates/adapters/codex/hooks/subagent-guard.sh +0 -15
- package/templates/adapters/codex/hooks/subagent-stop.sh +0 -47
- package/templates/adapters/qoder/hooks/doc-format-guard.sh +0 -341
- package/templates/adapters/qoder/hooks/lib/common.sh +0 -223
- package/templates/adapters/qoder/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/qoder/hooks/lib/notify.sh +0 -36
- package/templates/adapters/qoder/hooks/lib/preload-templates.sh +0 -212
- package/templates/adapters/qoder/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/qoder/hooks/lib/state-detect.sh +0 -13
- package/templates/adapters/qoder/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/qoder/hooks/lib/vocabulary.sh +0 -62
- package/templates/adapters/qoder/hooks/notification.sh +0 -25
- package/templates/adapters/qoder/hooks/permission-gate.sh +0 -8
- package/templates/adapters/qoder/hooks/post-tool-failure.sh +0 -8
- package/templates/adapters/qoder/hooks/post-tool-use.sh +0 -121
- package/templates/adapters/qoder/hooks/pre-compact.sh +0 -37
- package/templates/adapters/qoder/hooks/pre-tool-use.sh +0 -154
- package/templates/adapters/qoder/hooks/prompt-submit.sh +0 -96
- package/templates/adapters/qoder/hooks/review-checklist.sh +0 -157
- package/templates/adapters/qoder/hooks/session-end.sh +0 -24
- package/templates/adapters/qoder/hooks/session-start.sh +0 -45
- package/templates/adapters/qoder/hooks/stop-check.sh +0 -59
- package/templates/adapters/qoder/hooks/subagent-guard.sh +0 -27
- package/templates/adapters/qoder/hooks/subagent-stop.sh +0 -26
- package/templates/adapters/trae/hooks/doc-format-guard.sh +0 -343
- package/templates/adapters/trae/hooks/lib/common.sh +0 -220
- package/templates/adapters/trae/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/trae/hooks/lib/notify.sh +0 -36
- package/templates/adapters/trae/hooks/lib/preload-templates.sh +0 -212
- package/templates/adapters/trae/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/trae/hooks/lib/state-detect.sh +0 -10
- package/templates/adapters/trae/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/trae/hooks/lib/vocabulary.sh +0 -62
- package/templates/adapters/trae/hooks/notification.sh +0 -45
- package/templates/adapters/trae/hooks/permission-gate.sh +0 -18
- package/templates/adapters/trae/hooks/post-tool-failure.sh +0 -10
- package/templates/adapters/trae/hooks/post-tool-use.sh +0 -138
- package/templates/adapters/trae/hooks/pre-compact.sh +0 -37
- package/templates/adapters/trae/hooks/pre-tool-use.sh +0 -223
- package/templates/adapters/trae/hooks/prompt-submit.sh +0 -87
- package/templates/adapters/trae/hooks/review-checklist.sh +0 -10
- package/templates/adapters/trae/hooks/session-end.sh +0 -38
- package/templates/adapters/trae/hooks/session-start.sh +0 -53
- package/templates/adapters/trae/hooks/stop-check.sh +0 -54
- package/templates/adapters/trae/hooks/subagent-guard.sh +0 -15
- package/templates/adapters/trae/hooks/subagent-stop.sh +0 -47
- package/templates/adapters/vscode/hooks/doc-format-guard.sh +0 -282
- package/templates/adapters/vscode/hooks/lib/common.sh +0 -220
- package/templates/adapters/vscode/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/vscode/hooks/lib/notify.sh +0 -36
- package/templates/adapters/vscode/hooks/lib/preload-templates.sh +0 -212
- package/templates/adapters/vscode/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/vscode/hooks/lib/state-detect.sh +0 -13
- package/templates/adapters/vscode/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/vscode/hooks/lib/vocabulary.sh +0 -62
- package/templates/adapters/vscode/hooks/notification.sh +0 -43
- package/templates/adapters/vscode/hooks/permission-gate.sh +0 -18
- package/templates/adapters/vscode/hooks/post-tool-failure.sh +0 -28
- package/templates/adapters/vscode/hooks/post-tool-use.sh +0 -35
- package/templates/adapters/vscode/hooks/pre-compact.sh +0 -37
- package/templates/adapters/vscode/hooks/pre-tool-use.sh +0 -153
- package/templates/adapters/vscode/hooks/prompt-submit.sh +0 -99
- package/templates/adapters/vscode/hooks/review-checklist.sh +0 -10
- package/templates/adapters/vscode/hooks/session-end.sh +0 -13
- package/templates/adapters/vscode/hooks/session-start.sh +0 -35
- package/templates/adapters/vscode/hooks/stop-check.sh +0 -62
- package/templates/adapters/vscode/hooks/subagent-guard.sh +0 -37
- package/templates/adapters/vscode/hooks/subagent-stop.sh +0 -12
- package/templates/core/hooks/doc-format-guard.sh +0 -343
- package/templates/core/hooks/lib/common.sh +0 -220
- package/templates/core/hooks/lib/context-inject.sh +0 -96
- package/templates/core/hooks/lib/notify.sh +0 -36
- package/templates/core/hooks/lib/preload-templates.sh +0 -212
- package/templates/core/hooks/lib/session-end.sh +0 -76
- package/templates/core/hooks/lib/state-detect.sh +0 -10
- package/templates/core/hooks/lib/subagent-stop.sh +0 -90
- package/templates/core/hooks/lib/vocabulary.sh +0 -62
- package/templates/core/hooks/notification.sh +0 -45
- package/templates/core/hooks/permission-gate.sh +0 -18
- package/templates/core/hooks/post-tool-failure.sh +0 -10
- package/templates/core/hooks/post-tool-use.sh +0 -138
- package/templates/core/hooks/pre-compact.sh +0 -37
- package/templates/core/hooks/pre-tool-use.sh +0 -223
- package/templates/core/hooks/prompt-submit.sh +0 -87
- package/templates/core/hooks/review-checklist.sh +0 -10
- package/templates/core/hooks/session-end.sh +0 -38
- package/templates/core/hooks/session-start.sh +0 -53
- package/templates/core/hooks/stop-check.sh +0 -54
- package/templates/core/hooks/subagent-guard.sh +0 -15
- package/templates/core/hooks/subagent-stop.sh +0 -47
- package/templates/shared/hooks-lib/common.sh +0 -22
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// session-end.ts — SessionEnd 治理卡位(bash 版 session-end.sh 的 TS 同语义实现)
|
|
2
|
+
// 路径: templates/core/hooks/lib/session-end.ts
|
|
3
|
+
//
|
|
4
|
+
// 治理职能(卡位 #2):
|
|
5
|
+
// ① 清理 tpl-injected 标记文件
|
|
6
|
+
// ② 会话审计结算(汇总 tool 调用统计)
|
|
7
|
+
// ③ Stop 未触发兜底: 若 Stop 未执行验收检查,补执行 checklist 快照(best-effort)
|
|
8
|
+
//
|
|
9
|
+
// 设计范式: OOP 服务类(会话收尾三职责聚合)+ common.ts 纯函数复用(标记/状态/验收)。
|
|
10
|
+
|
|
11
|
+
import { unlinkSync } from "node:fs"
|
|
12
|
+
import {
|
|
13
|
+
checkAddCompleteness,
|
|
14
|
+
clearDevAction,
|
|
15
|
+
detectActiveAdd,
|
|
16
|
+
hasDevAction,
|
|
17
|
+
localIsoSeconds,
|
|
18
|
+
projectHash,
|
|
19
|
+
} from "./common.js"
|
|
20
|
+
|
|
21
|
+
/** 纯函数:解析 state 字段(对齐 bash awk -F'::' 切分) */
|
|
22
|
+
function stateField(state: string, index: 0 | 1 | 2 | 3 | 4): string {
|
|
23
|
+
return state.split("::")[index] ?? ""
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* SessionEnd 治理服务:
|
|
28
|
+
* - cleanupFlags: ① 清理标记(tpl-injected + dev action)
|
|
29
|
+
* - settle: ② 审计结算(stderr 输出)
|
|
30
|
+
* - stopFallback: ③ Stop 未触发兜底(best-effort,不阻断)
|
|
31
|
+
*/
|
|
32
|
+
export class SessionEndGuard {
|
|
33
|
+
private readonly projectDir: string
|
|
34
|
+
private readonly tplFlag: string
|
|
35
|
+
|
|
36
|
+
constructor(projectDir?: string) {
|
|
37
|
+
this.projectDir = projectDir ?? process.env.PROJECT_DIR ?? process.cwd()
|
|
38
|
+
// 标记路径唯一算法 = common.ts projectHash(对齐 bash echo 含换行 md5)
|
|
39
|
+
this.tplFlag = `/tmp/add_tpl_${projectHash()}`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** ① 清理 tpl-injected 标记 + dev action 标记(对齐 cleanup_tpl_flag/cleanup_dev_flag) */
|
|
43
|
+
cleanupFlags(): void {
|
|
44
|
+
try {
|
|
45
|
+
unlinkSync(this.tplFlag)
|
|
46
|
+
} catch {
|
|
47
|
+
/* ignore(对齐 rm -f || true) */
|
|
48
|
+
}
|
|
49
|
+
clearDevAction()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** ② 审计结算(core 默认: stderr 文本;qoder 子类 override: stdout JSON additionalContext) */
|
|
53
|
+
protected emitSettle(): void {
|
|
54
|
+
process.stderr.write(`[ADD SessionEnd] 会话结束 — ${localIsoSeconds()}\n`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** ② 审计结算(输出到 stderr 供日志记录,对齐 bash date -Iseconds) */
|
|
58
|
+
settle(): void {
|
|
59
|
+
this.emitSettle()
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** ③ Stop 未触发兜底(对齐 stop_fallback:dev action 标记还在 → 补 checklist 快照) */
|
|
63
|
+
stopFallback(): void {
|
|
64
|
+
if (!hasDevAction()) return
|
|
65
|
+
process.stderr.write("[ADD SessionEnd] ⚠️ 检测到 dev action 标记未清除——Stop 可能未触发验收检查\n")
|
|
66
|
+
|
|
67
|
+
const state = detectActiveAdd()
|
|
68
|
+
if (state === null) return
|
|
69
|
+
|
|
70
|
+
const handoff = stateField(state, 3)
|
|
71
|
+
const addRoute = stateField(state, 4)
|
|
72
|
+
if (handoff && handoff !== "none") {
|
|
73
|
+
process.stderr.write("[ADD SessionEnd] 补执行 checklist 快照(best-effort,不阻断)\n")
|
|
74
|
+
const issues = checkAddCompleteness(handoff, addRoute !== "none" ? addRoute : "")
|
|
75
|
+
for (const issue of issues) {
|
|
76
|
+
process.stderr.write(issue + "\n")
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** 主入口:①清理 → ②结算 → ③兜底(对齐 bash main) */
|
|
82
|
+
run(): number {
|
|
83
|
+
this.cleanupFlags()
|
|
84
|
+
this.settle()
|
|
85
|
+
this.stopFallback()
|
|
86
|
+
return 0
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
// lib/session-start-guard.ts — SessionStart 上下文恢复守卫(治理逻辑层,Task 2.1 类化收敛)
|
|
2
|
+
// 治理卡位 #1: ADD状态恢复 + 模板索引注入
|
|
3
|
+
//
|
|
4
|
+
// 设计范式: OOP 守卫类(状态恢复/模板索引/代办/HITL 四职责聚合)。
|
|
5
|
+
|
|
6
|
+
import { existsSync, readdirSync, statSync } from "node:fs"
|
|
7
|
+
import { join } from "node:path"
|
|
8
|
+
import { detectActiveAdd, tryResolveMagicDir } from "./common.js"
|
|
9
|
+
import { PreloadTemplates } from "./preload-templates.js"
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* SessionStart 守卫(① 状态恢复 → ② 模板索引 → ③ 代办 → ④ HITL 待审批检测):
|
|
13
|
+
* 缺陷修复(Plan §3.2): PLANS_DIR 由 magicDir 推导(bash 版未绑定崩溃已修)
|
|
14
|
+
*/
|
|
15
|
+
export class SessionStartGuard {
|
|
16
|
+
protected readonly projectDir: string
|
|
17
|
+
protected readonly magicDir: string
|
|
18
|
+
|
|
19
|
+
constructor(projectDir: string) {
|
|
20
|
+
this.projectDir = projectDir
|
|
21
|
+
// MAGIC_DIR 解析(唯一链:注入优先 → 物理推导)
|
|
22
|
+
const inferred = tryResolveMagicDir()
|
|
23
|
+
if (inferred && !process.env.MAGIC_DIR) {
|
|
24
|
+
process.env.MAGIC_DIR = inferred
|
|
25
|
+
}
|
|
26
|
+
this.magicDir = process.env.MAGIC_DIR ?? ""
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 主入口:返回 exit code(0) */
|
|
30
|
+
run(): number {
|
|
31
|
+
// ── ① ADD 状态恢复(扩展点: core 纯文本 / qoder JSON)──
|
|
32
|
+
const state = detectActiveAdd()
|
|
33
|
+
this.emitState(state)
|
|
34
|
+
|
|
35
|
+
// ── ② 模板索引注入(扩展点: core 纯文本 / qoder JSON lines 计数)──
|
|
36
|
+
this.emitIndex()
|
|
37
|
+
|
|
38
|
+
// ── ③ §代办刷新(扩展点: adapter 可 override 关闭)──
|
|
39
|
+
this.emitTodoReminder(state)
|
|
40
|
+
|
|
41
|
+
// ── ④ §HITL 待审批检测(扩展点: adapter 可 override 关闭)──
|
|
42
|
+
this.emitHitlPending()
|
|
43
|
+
|
|
44
|
+
return 0
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
48
|
+
|
|
49
|
+
/** ① ADD 状态恢复输出(core: 纯文本块) */
|
|
50
|
+
protected emitState(state: string | null): void {
|
|
51
|
+
if (state === null) return
|
|
52
|
+
const [plan, step, rounds, handoff] = state.split("::")
|
|
53
|
+
process.stdout.write(`[ADD SessionStart] 检测到活跃 ADD Plan:
|
|
54
|
+
Plan: ${plan}
|
|
55
|
+
轮次: ${rounds}
|
|
56
|
+
当前 Step: ${step}
|
|
57
|
+
handoff: ${handoff}
|
|
58
|
+
恢复命令: query_audit_logs({ planKeyword: '${plan}' })
|
|
59
|
+
`)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** ② 模板索引输出(core: 纯文本 index) */
|
|
63
|
+
protected emitIndex(): void {
|
|
64
|
+
try {
|
|
65
|
+
process.stdout.write(new PreloadTemplates().index())
|
|
66
|
+
} catch {
|
|
67
|
+
/* 模板目录缺失等 fail-fast 场景:stderr 已在 validate 抛出,入口不阻断 */
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** ③ 代办刷新(core: 活跃 Plan 时输出提醒;claude: 无此段) */
|
|
72
|
+
protected emitTodoReminder(state: string | null): void {
|
|
73
|
+
if (state !== null) {
|
|
74
|
+
const plan = state.split("::")[0] ?? ""
|
|
75
|
+
process.stdout.write(`[代办] 检测到活跃 Plan: ${plan}。如有未加载的 IDE 代办清单,请从 tasks.md §IDE JSON 刷新 TodoWrite。
|
|
76
|
+
`)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** ④ HITL 待审批检测(core: 7 天内 .hitl.md 计数提示;claude: 无此段) */
|
|
81
|
+
protected emitHitlPending(): void {
|
|
82
|
+
const PLANS_DIR = this.magicDir ? join(this.projectDir, this.magicDir, "plans") : ""
|
|
83
|
+
if (PLANS_DIR && existsSync(PLANS_DIR)) {
|
|
84
|
+
const weekAgo = Date.now() - 7 * 86400000
|
|
85
|
+
const hitlCount = readdirSync(PLANS_DIR)
|
|
86
|
+
.filter((f) => f.endsWith(".hitl.md"))
|
|
87
|
+
.filter((f) => {
|
|
88
|
+
try {
|
|
89
|
+
return statSync(join(PLANS_DIR, f)).mtimeMs >= weekAgo
|
|
90
|
+
} catch {
|
|
91
|
+
return false
|
|
92
|
+
}
|
|
93
|
+
}).length
|
|
94
|
+
if (hitlCount > 0) {
|
|
95
|
+
process.stdout.write(`[HITL 待审批] 检测到 ${hitlCount} 个待审批 HITL 提案,请检查并处理\n`)
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
// stop-router.ts — Stop 四象限分流路由基类(治理逻辑层,Task 2.1/4.1 继承体系)
|
|
2
|
+
// 治理卡位 #7: 验收检查 + devlog + 阻断
|
|
3
|
+
// DB lifecycle 真相源(协议层):DB 不可用时 fail-closed,禁止回退 Handoff/add-route 猜测
|
|
4
|
+
//
|
|
5
|
+
// 设计范式: 模板方法基类——Q0-Q4 分流流程固化,输出形态(JSON/纯文本)由 adapter 子类 override。
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
EXIT_BLOCK,
|
|
9
|
+
checkAddCompleteness,
|
|
10
|
+
clearDevAction,
|
|
11
|
+
detectActiveAdd,
|
|
12
|
+
hasDevAction,
|
|
13
|
+
} from "./common.js"
|
|
14
|
+
import { buildStopContext } from "./context-inject.js"
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Stop 四象限分流路由(Q0-Q4,与 bash stop-check.sh 同语义):
|
|
18
|
+
* Q0: DB 不可用 → fail closed(禁止当"无 Plan")
|
|
19
|
+
* Q1: 无 ADD + 无 dev → 正常停
|
|
20
|
+
* Q2: 无 ADD + 有 dev → 严重违规,few-shot 注入 + 阻断
|
|
21
|
+
* Q3: 有 ADD + 无 dev → 注入状态
|
|
22
|
+
* Q4: 有 ADD + 有 dev → 验收检查
|
|
23
|
+
* 扩展点(protected,adapter 子类 override 输出形态):
|
|
24
|
+
* - emitQ0 / emitQ2 / emitQ3 / emitQ4Unclosed / emitQ4Pass(core: 纯文本;qoder: stdout JSON)
|
|
25
|
+
* - unclosedInterpolate(): has_add_dev_unclosed 是否插值(core: true;qoder: false 缺陷照搬)
|
|
26
|
+
*/
|
|
27
|
+
export class StopRouter {
|
|
28
|
+
/** 主路由:返回 exit code(0 放行 / 2 阻断) */
|
|
29
|
+
run(): number {
|
|
30
|
+
const state = detectActiveAdd()
|
|
31
|
+
const hasDev = hasDevAction()
|
|
32
|
+
|
|
33
|
+
// ═══════════ Q0: DB 不可用 → fail closed ═══════════
|
|
34
|
+
if (state !== null && state.startsWith("__STATUS_UNAVAILABLE__")) {
|
|
35
|
+
const reason = state.split("::")[1] ?? ""
|
|
36
|
+
return this.emitQ0(reason)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// ═══════════ Q1: 无 ADD + 无 dev → 正常停 ═══════════
|
|
40
|
+
if (state === null && !hasDev) {
|
|
41
|
+
return 0
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ═══════════ Q2: 无 ADD + 有 dev → 严重违规 ═══════════
|
|
45
|
+
if (state === null && hasDev) {
|
|
46
|
+
return this.emitQ2()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const fields = (state as string).split("::")
|
|
50
|
+
const plan = fields[0] ?? ""
|
|
51
|
+
const step = fields[1] ?? ""
|
|
52
|
+
const rounds = fields[2] ?? ""
|
|
53
|
+
const handoff = fields[3] ?? ""
|
|
54
|
+
const addRoute = fields[4] ?? ""
|
|
55
|
+
|
|
56
|
+
// ═══════════ Q3: 有 ADD + 无 dev → 注入状态 ═══════════
|
|
57
|
+
if (!hasDev) {
|
|
58
|
+
return this.emitQ3(plan, rounds, step)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ═══════════ Q4: 有 ADD + 有 dev → 验收检查(决策逻辑扩展点: core checklist / codex DB 进度)═══
|
|
62
|
+
return this.q4Check(plan, rounds, step, handoff, addRoute)
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
|
|
69
|
+
* 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total → 未完成阻断提示)
|
|
70
|
+
* 维度 2: checklist 质量(checkAddCompleteness 未闭环 → 阻断)
|
|
71
|
+
* 互补非替代——codex 原 DB 进度分流语义上提 core,core checklist 质量语义保留。
|
|
72
|
+
*/
|
|
73
|
+
protected q4Check(plan: string, rounds: string, step: string, handoff: string, addRoute: string): number {
|
|
74
|
+
void plan
|
|
75
|
+
void rounds
|
|
76
|
+
// 维度 1: DB 任务进度(step 格式 done/total,如 11/64)
|
|
77
|
+
const [donePart, totalPart] = step.split("/")
|
|
78
|
+
if (
|
|
79
|
+
/^\d+$/.test(donePart) &&
|
|
80
|
+
/^\d+$/.test(totalPart) &&
|
|
81
|
+
Number(donePart) < Number(totalPart)
|
|
82
|
+
) {
|
|
83
|
+
return this.emitQ4Unclosed(
|
|
84
|
+
`DB Plan 任务进度 ${donePart}/${totalPart},尚有未完成 Task。请继续执行当前 Plan 的未完成 Task,并为本轮改动补齐 record_dev_operation 审计。`
|
|
85
|
+
)
|
|
86
|
+
}
|
|
87
|
+
// 维度 2: checklist 质量
|
|
88
|
+
const issues = checkAddCompleteness(
|
|
89
|
+
handoff && handoff !== "none" ? handoff : "",
|
|
90
|
+
addRoute && addRoute !== "none" ? addRoute : ""
|
|
91
|
+
)
|
|
92
|
+
if (issues.length > 0) {
|
|
93
|
+
return this.emitQ4Unclosed(issues.join("\n"))
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
clearDevAction()
|
|
97
|
+
return this.emitQ4Pass()
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** Q0: DB 不可用(core: stderr + 2) */
|
|
101
|
+
protected emitQ0(reason: string): number {
|
|
102
|
+
process.stderr.write(
|
|
103
|
+
`[ADD Stop] ⛔ Plan status 暂不可用(${reason})。未回退 Handoff/add-route 猜测,请恢复数据库或 MCP resolver 后重试。\n`
|
|
104
|
+
)
|
|
105
|
+
return EXIT_BLOCK
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** Q2: 无 ADD + 有 dev(core: stderr few-shot + 2) */
|
|
109
|
+
protected emitQ2(): number {
|
|
110
|
+
process.stderr.write(buildStopContext("no_add_has_dev", "") + "\n")
|
|
111
|
+
return EXIT_BLOCK
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** Q3: 有 ADD + 无 dev(core: 纯文本状态注入 + 0) */
|
|
115
|
+
protected emitQ3(plan: string, rounds: string, step: string): number {
|
|
116
|
+
process.stdout.write(`[ADD Stop] Plan: ${plan}, 轮次: ${rounds}, Step: ${step}\n`)
|
|
117
|
+
process.stdout.write("本次无代码改动。下次继续时执行 session-init 恢复上下文。\n")
|
|
118
|
+
return 0
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Q4 验收未闭环(core: stderr + 2;unclosedInterpolate=true 插值) */
|
|
122
|
+
protected emitQ4Unclosed(info: string): number {
|
|
123
|
+
const text = this.unclosedInterpolate()
|
|
124
|
+
? buildStopContext("has_add_dev_unclosed", info)
|
|
125
|
+
: buildStopContext("has_add_dev_unclosed", "")
|
|
126
|
+
process.stderr.write(text + "\n")
|
|
127
|
+
return EXIT_BLOCK
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** Q4 验收通过(core: 纯文本 + 0) */
|
|
131
|
+
protected emitQ4Pass(): number {
|
|
132
|
+
process.stdout.write("[ADD Stop] ✅ 验收通过——checklist 全部勾选,devlog 已记录。\n")
|
|
133
|
+
return 0
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** has_add_dev_unclosed 是否插值(core: true;qoder: false 缺陷照搬) */
|
|
137
|
+
protected unclosedInterpolate(): boolean {
|
|
138
|
+
return true
|
|
139
|
+
}
|
|
140
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// subagent-guard-router.ts — SubagentStart 上下文注入路由(治理逻辑层,Task 3.1 能力对齐)
|
|
2
|
+
// 治理卡位 #10: ADD上下文注入子agent + 审计初始化
|
|
3
|
+
//
|
|
4
|
+
// 能力对齐(2026-08-14 六端实态核验):
|
|
5
|
+
// 强版(状态注入): claude/vscode/qoder——detect_active_add → 注入 Plan/Step/轮次/handoff
|
|
6
|
+
// 弱版(事件提示): core/trae/codex——仅"子代理启动请确认遵循 ADD 规范"空话
|
|
7
|
+
// → 本基类统一为状态注入语义(core 对齐强版);输出形态(JSON/纯文本)为协议差异由子类 override。
|
|
8
|
+
//
|
|
9
|
+
// 设计范式: 模板方法基类——状态获取流程固化,emit() 输出形态由子类 override。
|
|
10
|
+
|
|
11
|
+
import { detectActiveAdd } from "./common.js"
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* SubagentStart 上下文注入路由(模板方法):
|
|
15
|
+
* 流程固化(不可 override): run() = detectActiveAdd → emit(state)
|
|
16
|
+
* 扩展点(protected): emit()——core/qoder: hookSpecificOutput JSON;claude/vscode/trae/codex: 纯文本块
|
|
17
|
+
*/
|
|
18
|
+
export class SubagentGuardRouter {
|
|
19
|
+
/** 主路由:返回 exit code(0) */
|
|
20
|
+
run(): number {
|
|
21
|
+
const state = detectActiveAdd()
|
|
22
|
+
if (state === null) return 0
|
|
23
|
+
this.emit(state)
|
|
24
|
+
return 0
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/** 上下文注入输出(core 协议: hookSpecificOutput JSON,qoder 同构) */
|
|
28
|
+
protected emit(state: string): void {
|
|
29
|
+
const [plan, step, rounds, handoff] = state.split("::")
|
|
30
|
+
const ctx = `[ADD SubagentStart] Plan: ${plan}, Step: ${step}, Round: ${rounds}, handoff: ${handoff}。遵循 ADD 规范,完成后检查 checklist。`
|
|
31
|
+
process.stdout.write(
|
|
32
|
+
JSON.stringify({ hookSpecificOutput: { hookEventName: "SubagentStart", additionalContext: ctx } }) + "\n"
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// lib/subagent-stop-router.ts — SubagentStop 入口路由(core 通用协议参考实现,Task 2.2 薄壳化)
|
|
2
|
+
// 治理卡位 #11: 子agent边界校验 + 审计聚合 + 阻断
|
|
3
|
+
// 与 SubagentStopGuard(能力对齐版)的关系: 本类是 core 入口版语义(bash 逐字),
|
|
4
|
+
// SubagentStopGuard 供 claude/vscode 薄壳消费(additionalContext 文本差异)。
|
|
5
|
+
// ——两者文本契约不同(IDE 协议差异),行为等价基线各自由 golden 固化。
|
|
6
|
+
//
|
|
7
|
+
// 设计范式: OOP 路由类(边界校验 + 审计聚合单一职责)。
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
10
|
+
import { detectActiveAdd, EXIT_BLOCK, localIsoSeconds } from "./common.js"
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* SubagentStop 入口路由(core 入口版,bash subagent-stop.sh 逐字语义):
|
|
14
|
+
* ① 边界校验: 检查交付物是否超出 spec 边界(无活跃 Plan → 无法校验告警)
|
|
15
|
+
* ② 审计聚合: stderr 审计输出(对齐 bash date -Iseconds)
|
|
16
|
+
*/
|
|
17
|
+
export class SubagentStopRouter {
|
|
18
|
+
/** 对齐 bash jq -r '.agent_type // .subagent_name // "unknown"':
|
|
19
|
+
* 空输入/非法 JSON → jq 无输出 → 空串(不是 "unknown");字段缺失才回退 "unknown" */
|
|
20
|
+
private agentNameFrom(input: string): string {
|
|
21
|
+
if (input.trim() === "") return ""
|
|
22
|
+
try {
|
|
23
|
+
const parsed = JSON.parse(input) as { agent_type?: unknown; subagent_name?: unknown }
|
|
24
|
+
const v = parsed.agent_type ?? parsed.subagent_name
|
|
25
|
+
return typeof v === "string" && v !== "" ? v : "unknown"
|
|
26
|
+
} catch {
|
|
27
|
+
return ""
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
32
|
+
|
|
33
|
+
/** ① 无活跃 Plan 告警(core: stderr;qoder 子类 override: stdout JSON) */
|
|
34
|
+
protected emitNoPlanWarn(agentName: string): void {
|
|
35
|
+
process.stderr.write(`[ADD SubagentStop] ⚠️ ${agentName} 已完成,但无活跃 ADD Plan 无法校验边界\n`)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** ② 边界通过审计(core: stderr;qoder 子类 override: stdout JSON) */
|
|
39
|
+
protected emitBoundaryPass(agentName: string, planKw: string, _handoff: string): void {
|
|
40
|
+
process.stderr.write(`[ADD SubagentStop] ${agentName} 已完成 — 关联 Plan: ${planKw}\n`)
|
|
41
|
+
process.stderr.write(`[ADD SubagentStop] ${agentName} 边界校验通过 — ${localIsoSeconds()}\n`)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** 交付物越界检查开关(core: true;qoder: false——qoder 轻量提示无阻断) */
|
|
45
|
+
protected checkDeliverables(): boolean {
|
|
46
|
+
return true
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** 主路由:返回 exit code(0 放行 / 2 阻断) */
|
|
50
|
+
run(input: string): number {
|
|
51
|
+
const agentName = this.agentNameFrom(input)
|
|
52
|
+
|
|
53
|
+
// ── ① 边界校验:检查子 agent 交付物是否超出 spec 范围 ──
|
|
54
|
+
const state = detectActiveAdd()
|
|
55
|
+
if (state === null) {
|
|
56
|
+
this.emitNoPlanWarn(agentName)
|
|
57
|
+
return 0
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const planKw = state.split("::")[0] ?? ""
|
|
61
|
+
const handoff = state.split("::")[3] ?? ""
|
|
62
|
+
|
|
63
|
+
// 如果 handoff 中存在允许的文件清单,检查交付物是否越界(扩展点: qoder 关闭)
|
|
64
|
+
if (this.checkDeliverables() && handoff && handoff !== "none" && existsSync(handoff)) {
|
|
65
|
+
/** 对齐 bash jq -r '.deliverables // ""' */
|
|
66
|
+
const deliverablesRaw = (() => {
|
|
67
|
+
try {
|
|
68
|
+
const parsed = JSON.parse(input) as { deliverables?: unknown }
|
|
69
|
+
return typeof parsed.deliverables === "string" ? parsed.deliverables : ""
|
|
70
|
+
} catch {
|
|
71
|
+
return ""
|
|
72
|
+
}
|
|
73
|
+
})()
|
|
74
|
+
if (deliverablesRaw !== "") {
|
|
75
|
+
const handoffContent = readFileSync(handoff, "utf-8")
|
|
76
|
+
const violations: string[] = []
|
|
77
|
+
for (const f of deliverablesRaw.split(/\s+/)) {
|
|
78
|
+
if (f === "") continue
|
|
79
|
+
if (!handoffContent.includes(f)) violations.push(f)
|
|
80
|
+
}
|
|
81
|
+
if (violations.length > 0) {
|
|
82
|
+
process.stderr.write(`[ADD SubagentStop] ❌ ${agentName} 交付物超出 spec 边界: ${violations.join(" ")}\n`)
|
|
83
|
+
process.stderr.write("[ADD SubagentStop] 要求重做——请检查这些文件是否属于本轮 spec 范围\n")
|
|
84
|
+
return EXIT_BLOCK
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// ── ② 审计聚合(扩展点: core stderr / qoder stdout JSON)──
|
|
90
|
+
this.emitBoundaryPass(agentName, planKw, handoff)
|
|
91
|
+
return 0
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// subagent-stop.ts — SubagentStop 治理卡位(bash 版 subagent-stop.sh 的 TS 同语义实现)
|
|
2
|
+
// 路径: templates/core/hooks/lib/subagent-stop.ts
|
|
3
|
+
//
|
|
4
|
+
// 治理职能(卡位 #11):
|
|
5
|
+
// ① 子 agent 结果边界校验: 检查交付物是否在 spec 边界内(不越界、不遗漏)
|
|
6
|
+
// ② 审计聚合: 将子 agent 的 sub-traceId 审计记录合并回主 traceId
|
|
7
|
+
// ③ 阻断能力: 子 agent 结果不符合 spec → exit 2 要求重做
|
|
8
|
+
//
|
|
9
|
+
// 设计范式: OOP 服务类(边界校验单一职责聚合)+ 纯函数解析(state 字段切分)。
|
|
10
|
+
|
|
11
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
12
|
+
import { detectActiveAdd, EXIT_BLOCK, jsonGet, localIsoSeconds, readHookInput } from "./common.js"
|
|
13
|
+
|
|
14
|
+
/** 纯函数:解析 state 字段(对齐 bash awk -F'::' 切分) */
|
|
15
|
+
function stateField(state: string, index: 0 | 1 | 2 | 3 | 4): string {
|
|
16
|
+
return state.split("::")[index] ?? ""
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SubagentStop 治理服务:
|
|
21
|
+
* - checkBoundary: 交付物边界校验(无活跃 Plan → 仅告警不阻断)
|
|
22
|
+
* - main: stdin 解析 → agent_type 提取 → 边界校验 → 审计聚合
|
|
23
|
+
*/
|
|
24
|
+
export class SubagentStopGuard {
|
|
25
|
+
/**
|
|
26
|
+
* 边界校验: 检查子 agent 交付物是否超出 spec 定义的文件范围。
|
|
27
|
+
* 返回 0=通过,1=越界(要求重做)。
|
|
28
|
+
*/
|
|
29
|
+
checkBoundary(subagentName: string, deliverables: readonly string[]): number {
|
|
30
|
+
const state = detectActiveAdd()
|
|
31
|
+
|
|
32
|
+
if (state === null) {
|
|
33
|
+
// 无活跃 Plan —— 无 spec 可参考,仅告警不阻断
|
|
34
|
+
process.stderr.write(`[ADD SubagentStop] ⚠️ 无活跃 ADD Plan,无法校验 ${subagentName} 的交付物边界\n`)
|
|
35
|
+
return 0
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const planKw = stateField(state, 0)
|
|
39
|
+
const handoff = stateField(state, 3)
|
|
40
|
+
|
|
41
|
+
process.stderr.write(`[ADD SubagentStop] ${subagentName} 已完成,交付物: ${deliverables.join(" ")}\n`)
|
|
42
|
+
process.stderr.write(`[ADD SubagentStop] 关联 Plan: ${planKw} | handoff: ${handoff}\n`)
|
|
43
|
+
|
|
44
|
+
// best-effort: 如果 handoff 中列出了允许的文件范围,检查交付物是否在其中
|
|
45
|
+
if (handoff && existsSync(handoff) && deliverables.length > 0) {
|
|
46
|
+
const handoffContent = readFileSync(handoff, "utf-8")
|
|
47
|
+
const violations = deliverables.filter((f) => !handoffContent.includes(f))
|
|
48
|
+
if (violations.length > 0) {
|
|
49
|
+
process.stderr.write(`[ADD SubagentStop] ❌ ${subagentName} 交付物超出 spec 边界: ${violations.join(" ")}\n`)
|
|
50
|
+
process.stderr.write("[ADD SubagentStop] 请检查这些文件是否属于本轮 spec 范围,或更新 handoff 文件清单\n")
|
|
51
|
+
return 1
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return 0
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** 主入口(对齐 bash main) */
|
|
58
|
+
run(subagentName: string, stdin: string): number {
|
|
59
|
+
// 尝试从 stdin JSON 提取交付物信息(对齐 bash grep/sed 提取 agent_type)
|
|
60
|
+
const agentType = jsonGet(stdin, "agent_type")
|
|
61
|
+
const effectiveName = agentType ? `${subagentName}(${agentType})` : subagentName
|
|
62
|
+
const deliverables: string[] = []
|
|
63
|
+
|
|
64
|
+
// ① 边界校验
|
|
65
|
+
if (this.checkBoundary(effectiveName, deliverables) !== 0) {
|
|
66
|
+
process.stderr.write("[ADD SubagentStop] 要求重做——交付物超出 spec 边界\n")
|
|
67
|
+
return EXIT_BLOCK
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// ② 审计聚合(输出到 stderr 供日志记录,对齐 bash date -Iseconds)
|
|
71
|
+
process.stderr.write(`[ADD SubagentStop] ${effectiveName} 边界校验通过 — ${localIsoSeconds()}\n`)
|
|
72
|
+
|
|
73
|
+
return 0
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
// vocabulary.ts — 从 vocabulary markdown 表格加载触发词(bash 版 vocabulary.sh 的 TS 同语义实现)
|
|
2
|
+
// 单一数据源: {MAGIC_DIR}/vocabulary/add-governance-vocabulary.md §类别 A-F 表格
|
|
3
|
+
// 协议层契约(Review P1 #5): 仅当前 magicDir scope;magicDir 解析统一委托 common.ts
|
|
4
|
+
// (tryResolveMagicDir——唯一解析链的柔和变体,禁止第二套探测,禁止跨端扫描与 .add fallback)
|
|
5
|
+
//
|
|
6
|
+
// 设计范式: 函数式——纯函数流式变换(read → inRange 过滤 → 表格解析 → 结构化条目),
|
|
7
|
+
// 内部结构化类型 TriggerEntry(泛型 parse 收敛字段切分),外部序列化对齐 bash "prio::regex::action" 契约。
|
|
8
|
+
|
|
9
|
+
import { existsSync, readFileSync } from "node:fs"
|
|
10
|
+
import { join } from "node:path"
|
|
11
|
+
import { tryResolveMagicDir } from "./common.js"
|
|
12
|
+
|
|
13
|
+
/** 结构化触发词条目(内部类型;输出契约由 serializeTrigger 序列化) */
|
|
14
|
+
export interface TriggerEntry {
|
|
15
|
+
/** P0/P1/P2 优先级 */
|
|
16
|
+
readonly priority: string
|
|
17
|
+
/** 触发词正则(已去反引号、' / ' → '|'、trim) */
|
|
18
|
+
readonly regex: string
|
|
19
|
+
/** 响应文本(trim) */
|
|
20
|
+
readonly action: string
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** 输出契约序列化: 优先级::触发词正则::响应文本(:: 避免与触发词内的 | 冲突) */
|
|
24
|
+
export function serializeTrigger(e: TriggerEntry): string {
|
|
25
|
+
return `${e.priority}::${e.regex}::${e.action}`
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** 纯函数:vocabulary 文件路径(magicDir 未注入且无法推导 → "",调用方静默返回空) */
|
|
29
|
+
function vocabularyFile(): string {
|
|
30
|
+
const magicDir = tryResolveMagicDir()
|
|
31
|
+
if (!magicDir) return ""
|
|
32
|
+
return join(
|
|
33
|
+
process.env.PROJECT_DIR || process.cwd(),
|
|
34
|
+
magicDir,
|
|
35
|
+
"vocabulary",
|
|
36
|
+
"add-governance-vocabulary.md"
|
|
37
|
+
)
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* 泛型表格行解析器:把 IFS='|' 切分的行映射为结构化结果。
|
|
42
|
+
* 字段语义(对齐 bash while IFS='|' read -r _ prio raw_trigger action):
|
|
43
|
+
* cells[1]=prio、cells[2]=raw_trigger、cells[3]=action(xargs 等价 trim)
|
|
44
|
+
*/
|
|
45
|
+
function mapTableRow<T>(line: string, map: (prio: string, rawTrigger: string, action: string) => T | null): T | null {
|
|
46
|
+
const cells = line.split("|")
|
|
47
|
+
const prio = cells[1]?.trim() ?? ""
|
|
48
|
+
const rawTrigger = cells[2] ?? ""
|
|
49
|
+
const action = cells[3]?.trim() ?? ""
|
|
50
|
+
// trigger 归一化: 去反引号、' * / *' → '|'、trim(对齐 bash sed 链)
|
|
51
|
+
const trigger = rawTrigger.replace(/`/g, "").replace(/ *\/ */g, "|").trim()
|
|
52
|
+
if (!trigger) return null
|
|
53
|
+
return map(prio, trigger, action)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* 加载触发词(对齐 bash load_triggers 的 sed 范围/grep 行过滤/while 解析):
|
|
58
|
+
* - 范围: ^## 类别 A: 文档类型 至 ^## 类别 [G-Z]
|
|
59
|
+
* - 行过滤: ^| (P0|P1|P2)
|
|
60
|
+
* 文件缺失/路径不可判定 → 空数组(与 bash return 1 等价,静默不阻断)
|
|
61
|
+
*/
|
|
62
|
+
export function loadTriggers(): TriggerEntry[] {
|
|
63
|
+
const file = vocabularyFile()
|
|
64
|
+
if (!file || !existsSync(file)) return []
|
|
65
|
+
const lines = readFileSync(file, "utf-8").split("\n")
|
|
66
|
+
const out: TriggerEntry[] = []
|
|
67
|
+
let inRange = false
|
|
68
|
+
for (const line of lines) {
|
|
69
|
+
if (/^## 类别 A: 文档类型/.test(line)) inRange = true
|
|
70
|
+
if (/^## 类别 [G-Z]/.test(line)) inRange = false
|
|
71
|
+
if (!inRange) continue
|
|
72
|
+
if (!/^\| (P0|P1|P2) /.test(line)) continue
|
|
73
|
+
const entry = mapTableRow(line, (priority, regex, action) => ({ priority, regex, action }))
|
|
74
|
+
if (entry) out.push(entry)
|
|
75
|
+
}
|
|
76
|
+
return out
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** 开发关键词检测行判定(Layer 2/3 分流特征:超长 regex 含 修.?bug|fix.?bug) */
|
|
80
|
+
function isDevKeywordLine(regex: string): boolean {
|
|
81
|
+
return /修\.?bug|fix\.?bug/i.test(regex)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 匹配触发词(对齐 bash match_trigger):
|
|
86
|
+
* - 跳过开发关键词检测行
|
|
87
|
+
* - 不区分大小写正则匹配 prompt → "[ADD 触发] ${regex} → ${action}"
|
|
88
|
+
* - 非法正则静默跳过(与 bash grep -E 行为对齐)
|
|
89
|
+
*/
|
|
90
|
+
export function matchTrigger(prompt: string): string[] {
|
|
91
|
+
return loadTriggers().flatMap(({ regex, action }) => {
|
|
92
|
+
if (isDevKeywordLine(regex)) return []
|
|
93
|
+
try {
|
|
94
|
+
return new RegExp(regex, "i").test(prompt) ? [`[ADD 触发] ${regex} → ${action}`] : []
|
|
95
|
+
} catch {
|
|
96
|
+
return []
|
|
97
|
+
}
|
|
98
|
+
})
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* 加载开发关键词(对齐 bash load_dev_keywords):
|
|
103
|
+
* 只取包含"开发|改功能"的开发关键词检测行,输出其 regex
|
|
104
|
+
*/
|
|
105
|
+
export function loadDevKeywords(): string[] {
|
|
106
|
+
return loadTriggers()
|
|
107
|
+
.filter((e) => serializeTrigger(e).includes("开发|改功能"))
|
|
108
|
+
.map((e) => e.regex)
|
|
109
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// doc-format-guard.ts — doc-format-guard 入口薄壳(Task 2.2 薄壳化)
|
|
2
|
+
// 治理逻辑: lib/doc-format-guard.ts(DocFormatGuard 服务类,规则消费 hook-doc-format-rules.toml)
|
|
3
|
+
// 入口职责: 仅解析 + 进程语义(<50 行),治理 0 复制。
|
|
4
|
+
|
|
5
|
+
import { readFileSync } from "node:fs"
|
|
6
|
+
import { DocFormatGuard } from "../governance/doc-format-guard.js"
|
|
7
|
+
|
|
8
|
+
// ── 主入口(仅产物被 node 直调时执行;被 import 时保持纯库) ──
|
|
9
|
+
if (import.meta.url === new URL(`file://${process.argv[1] ?? ""}`).href) {
|
|
10
|
+
const PROJECT_DIR = process.env.PROJECT_DIR || process.cwd()
|
|
11
|
+
const MAGIC_DIR = process.env.MAGIC_DIR || ".qoder"
|
|
12
|
+
|
|
13
|
+
const guard = new DocFormatGuard(PROJECT_DIR, MAGIC_DIR)
|
|
14
|
+
let raw = ""
|
|
15
|
+
try {
|
|
16
|
+
raw = readFileSync(0, "utf-8")
|
|
17
|
+
} catch {
|
|
18
|
+
raw = ""
|
|
19
|
+
}
|
|
20
|
+
process.exitCode = guard.run(raw)
|
|
21
|
+
}
|