add-coder 0.3.26 → 0.3.28
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 +282 -155
- package/dist/index.js +481 -209
- package/package.json +15 -4
- package/templates/.add-coder-src-hash.json +186 -185
- 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/config.toml.example +12 -0
- 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 +67 -1
- 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 +4 -4
- package/templates/core/docs/ADD-governance-codex.md +6 -6
- package/templates/core/docs/ADD-governance-qoder-cn.md +5 -5
- package/templates/core/docs/ADD-governance-trae.md +3 -3
- package/templates/core/docs/ADD-governance-vscode-copilot.md +22 -23
- 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/prisma/add.prisma +140 -82
- package/templates/core/rules/project_rules.md +27 -24
- package/templates/core/scripts/db-ensure.sh +8 -0
- package/templates/core/scripts/gen-plan-index.sh +4 -15
- package/templates/core/scripts/mcp-server/notifications/hitl.ts +9 -1
- package/templates/core/scripts/mcp-server/notifications/hook.ts +25 -9
- package/templates/core/scripts/mcp-server/resources/hitl-approval-widget.ts +34 -0
- package/templates/core/scripts/mcp-server/resources/hook-events-report.ts +14 -2
- package/templates/core/scripts/mcp-server/resources/index.ts +2 -0
- package/templates/core/scripts/mcp-server/sampling/review.ts +25 -10
- package/templates/core/scripts/mcp-server/shared/db-types.ts +33 -8
- package/templates/core/scripts/mcp-server/shared/dps-scoring.strategy.ts +1 -1
- package/templates/core/scripts/mcp-server/shared/env.ts +28 -8
- package/templates/core/scripts/mcp-server/shared/fs.ts +3 -0
- package/templates/core/scripts/mcp-server/shared/hitl-interaction.strategy.ts +2 -3
- package/templates/core/scripts/mcp-server/shared/hitl-lifecycle-mutation.ts +129 -0
- package/templates/core/scripts/mcp-server/shared/hitl-proposal-mutation.ts +98 -0
- package/templates/core/scripts/mcp-server/shared/hitl-ui.ts +3 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle-events.ts +62 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle-mutation.ts +78 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle-subscriber.ts +145 -0
- package/templates/core/scripts/mcp-server/shared/plan-lifecycle.ts +153 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-events.ts +56 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-mutation.ts +143 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-store.ts +47 -0
- package/templates/core/scripts/mcp-server/shared/plan-round-subscriber.ts +99 -0
- package/templates/core/scripts/mcp-server/shared/plan-status-store.ts +37 -0
- package/templates/core/scripts/mcp-server/shared/plan-tracking-mutation.ts +71 -0
- package/templates/core/scripts/mcp-server/shared/prisma.ts +12 -6
- package/templates/core/scripts/mcp-server/shared/project-root-strategy.ts +1 -2
- package/templates/core/scripts/mcp-server/shared/quant-recheck.strategy.ts +16 -0
- package/templates/core/scripts/mcp-server/shared/redact.ts +28 -0
- package/templates/core/scripts/mcp-server/shared/response.ts +3 -2
- package/templates/core/scripts/mcp-server/shared/runtime-context.ts +77 -0
- package/templates/core/scripts/mcp-server/shared/schema-topology.ts +159 -0
- package/templates/core/scripts/mcp-server/tools/audit.ts +49 -7
- package/templates/core/scripts/mcp-server/tools/context.ts +117 -121
- package/templates/core/scripts/mcp-server/tools/contract.ts +34 -9
- package/templates/core/scripts/mcp-server/tools/gateway/check_add_route_status.ts +50 -3
- package/templates/core/scripts/mcp-server/tools/gateway/check_doc_similarity.ts +218 -0
- package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +22 -7
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +86 -4
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +19 -6
- package/templates/core/scripts/mcp-server/tools/gateway/index.ts +2 -0
- package/templates/core/scripts/mcp-server/tools/hitl.ts +160 -75
- package/templates/core/scripts/mcp-server/tools/hook-event-report.ts +9 -2
- package/templates/core/scripts/mcp-server/tools/index.ts +69 -3
- package/templates/core/scripts/mcp-server/tools/plan.ts +152 -17
- package/templates/core/scripts/mcp-server/tools/review.ts +44 -15
- package/templates/core/scripts/mcp-server.ts +60 -1
- package/templates/core/scripts/plan-status-bridge.ts +26 -0
- package/templates/core/skills/add-paradigm/SKILL.md +2 -2
- package/templates/core/templates/add-route-template.schema.json +24 -56
- package/templates/core/templates/collab-contract-template.md +1 -1
- package/templates/core/templates/handoff-multi-round-template.schema.json +48 -0
- package/templates/core/templates/handoff-single-round-template.schema.json +92 -0
- package/templates/core/templates/handoff-single-round.schema.json +1 -1
- package/templates/core/templates/hitl-approval-widget.html +222 -37
- package/templates/core/templates/spec-template.schema.json +9 -46
- package/templates/core/templates/standard-plan-template.schema.json +1 -1
- package/templates/core/templates/tasks-template.schema.json +13 -54
- package/templates/adapters/claude/hooks/doc-format-guard.sh +0 -202
- package/templates/adapters/claude/hooks/lib/common.sh +0 -261
- package/templates/adapters/claude/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/claude/hooks/lib/notify.sh +0 -34
- package/templates/adapters/claude/hooks/lib/preload-templates.sh +0 -187
- package/templates/adapters/claude/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/claude/hooks/lib/state-detect.sh +0 -117
- package/templates/adapters/claude/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/claude/hooks/lib/vocabulary.sh +0 -56
- 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 -45
- 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 -261
- package/templates/adapters/codex/hooks/lib/common.sh +0 -261
- package/templates/adapters/codex/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/codex/hooks/lib/notify.sh +0 -34
- package/templates/adapters/codex/hooks/lib/preload-templates.sh +0 -187
- package/templates/adapters/codex/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/codex/hooks/lib/state-detect.sh +0 -117
- package/templates/adapters/codex/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/codex/hooks/lib/vocabulary.sh +0 -56
- package/templates/adapters/codex/hooks/notification.sh +0 -45
- 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 -138
- package/templates/adapters/codex/hooks/pre-compact.sh +0 -37
- package/templates/adapters/codex/hooks/pre-tool-use.sh +0 -217
- package/templates/adapters/codex/hooks/prompt-submit.sh +0 -87
- 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 -53
- package/templates/adapters/codex/hooks/stop-check.sh +0 -45
- 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 -260
- package/templates/adapters/qoder/hooks/lib/common.sh +0 -261
- package/templates/adapters/qoder/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/qoder/hooks/lib/notify.sh +0 -34
- package/templates/adapters/qoder/hooks/lib/preload-templates.sh +0 -187
- package/templates/adapters/qoder/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/qoder/hooks/lib/state-detect.sh +0 -117
- package/templates/adapters/qoder/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/qoder/hooks/lib/vocabulary.sh +0 -56
- 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 -48
- 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 -261
- package/templates/adapters/trae/hooks/lib/common.sh +0 -261
- package/templates/adapters/trae/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/trae/hooks/lib/notify.sh +0 -34
- package/templates/adapters/trae/hooks/lib/preload-templates.sh +0 -187
- package/templates/adapters/trae/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/trae/hooks/lib/state-detect.sh +0 -117
- package/templates/adapters/trae/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/trae/hooks/lib/vocabulary.sh +0 -56
- 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 -217
- 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 -45
- 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 -201
- package/templates/adapters/vscode/hooks/lib/common.sh +0 -261
- package/templates/adapters/vscode/hooks/lib/context-inject.sh +0 -96
- package/templates/adapters/vscode/hooks/lib/notify.sh +0 -34
- package/templates/adapters/vscode/hooks/lib/preload-templates.sh +0 -187
- package/templates/adapters/vscode/hooks/lib/session-end.sh +0 -76
- package/templates/adapters/vscode/hooks/lib/state-detect.sh +0 -117
- package/templates/adapters/vscode/hooks/lib/subagent-stop.sh +0 -90
- package/templates/adapters/vscode/hooks/lib/vocabulary.sh +0 -56
- package/templates/adapters/vscode/hooks/notification.sh +0 -45
- 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 -53
- 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 -261
- package/templates/core/hooks/lib/common.sh +0 -261
- package/templates/core/hooks/lib/context-inject.sh +0 -96
- package/templates/core/hooks/lib/notify.sh +0 -34
- package/templates/core/hooks/lib/preload-templates.sh +0 -187
- package/templates/core/hooks/lib/session-end.sh +0 -76
- package/templates/core/hooks/lib/state-detect.sh +0 -117
- package/templates/core/hooks/lib/subagent-stop.sh +0 -90
- package/templates/core/hooks/lib/vocabulary.sh +0 -56
- 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 -217
- 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 -45
- 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,143 @@
|
|
|
1
|
+
import { createHash } from "crypto"
|
|
2
|
+
import type { RuntimeContextKey } from "./runtime-context.js"
|
|
3
|
+
import {
|
|
4
|
+
createPlanRoundChangedEnvelope,
|
|
5
|
+
publishPlanRoundChanged,
|
|
6
|
+
} from "./plan-round-events.js"
|
|
7
|
+
import type { PrismaNotifyTransaction } from "./plan-lifecycle-events.js"
|
|
8
|
+
|
|
9
|
+
export interface PlanRoundPlanRow {
|
|
10
|
+
id: string
|
|
11
|
+
projectKey: string
|
|
12
|
+
adapterKey: string
|
|
13
|
+
planName: string
|
|
14
|
+
lifecycle: string
|
|
15
|
+
revision: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PlanRoundOperationRow {
|
|
19
|
+
id: string
|
|
20
|
+
projectKey: string
|
|
21
|
+
producerAdapterKey: string
|
|
22
|
+
contextId: string
|
|
23
|
+
toolName: string
|
|
24
|
+
operationKey: string
|
|
25
|
+
planKeyword: string
|
|
26
|
+
action: string
|
|
27
|
+
targetType: string
|
|
28
|
+
targetId: string
|
|
29
|
+
beforeState: unknown
|
|
30
|
+
afterState: unknown
|
|
31
|
+
reason: string | null
|
|
32
|
+
createdAt: Date
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface PlanRoundTransaction extends PrismaNotifyTransaction {
|
|
36
|
+
planRecord: {
|
|
37
|
+
findFirst(args: { where: Record<string, unknown> }): Promise<PlanRoundPlanRow | null>
|
|
38
|
+
}
|
|
39
|
+
addUser: {
|
|
40
|
+
findUnique(args: { where: Record<string, unknown>; select: Record<string, unknown> }): Promise<{ id: string } | null>
|
|
41
|
+
create(args: { data: Record<string, unknown> }): Promise<{ id: string }>
|
|
42
|
+
}
|
|
43
|
+
devOperation: {
|
|
44
|
+
upsert(args: { where: Record<string, unknown>; create: Record<string, unknown>; update: Record<string, unknown> }): Promise<PlanRoundOperationRow>
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface PlanRoundDatabase {
|
|
49
|
+
$transaction<T>(work: (tx: PlanRoundTransaction) => Promise<T>): Promise<T>
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface ClosePlanRoundInput {
|
|
53
|
+
context: RuntimeContextKey
|
|
54
|
+
planName: string
|
|
55
|
+
round: number
|
|
56
|
+
beforeState: Record<string, unknown> | unknown[]
|
|
57
|
+
afterState: Record<string, unknown> | unknown[]
|
|
58
|
+
reason?: string
|
|
59
|
+
operationKey?: string
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export interface ClosePlanRoundResult {
|
|
63
|
+
plan: PlanRoundPlanRow
|
|
64
|
+
operation: PlanRoundOperationRow
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export function defaultPlanRoundOperationKey(input: {
|
|
68
|
+
context: RuntimeContextKey
|
|
69
|
+
planId: string
|
|
70
|
+
round: number
|
|
71
|
+
}): string {
|
|
72
|
+
return createHash("sha256")
|
|
73
|
+
.update(`${input.context.contextId}:${input.planId}:ROUND_CLOSED:${input.round}`)
|
|
74
|
+
.digest("hex")
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export async function closePlanRoundAndPublish(
|
|
78
|
+
database: PlanRoundDatabase,
|
|
79
|
+
input: ClosePlanRoundInput,
|
|
80
|
+
): Promise<ClosePlanRoundResult> {
|
|
81
|
+
if (!Number.isInteger(input.round) || input.round < 1) throw new Error("round 必须是大于 0 的整数")
|
|
82
|
+
|
|
83
|
+
return database.$transaction(async (tx) => {
|
|
84
|
+
const plan = await tx.planRecord.findFirst({
|
|
85
|
+
where: {
|
|
86
|
+
projectKey: input.context.projectKey,
|
|
87
|
+
adapterKey: input.context.adapterKey,
|
|
88
|
+
planName: input.planName,
|
|
89
|
+
},
|
|
90
|
+
})
|
|
91
|
+
if (!plan) throw new Error(`当前 RuntimeContextKey 下不存在 Plan: ${input.planName}`)
|
|
92
|
+
if (plan.projectKey !== input.context.projectKey || plan.adapterKey !== input.context.adapterKey) {
|
|
93
|
+
throw new Error("拒绝关闭 scope 外 PlanRound")
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let user = await tx.addUser.findUnique({ where: { username: "ai-assistant" }, select: { id: true } })
|
|
97
|
+
if (!user) {
|
|
98
|
+
user = await tx.addUser.create({
|
|
99
|
+
data: { id: "ai-assistant", username: "ai-assistant", email: "ai-assistant@internal" },
|
|
100
|
+
})
|
|
101
|
+
}
|
|
102
|
+
const baseOperationKey = defaultPlanRoundOperationKey({
|
|
103
|
+
context: input.context,
|
|
104
|
+
planId: plan.id,
|
|
105
|
+
round: input.round,
|
|
106
|
+
})
|
|
107
|
+
const operationKey = input.operationKey?.trim()
|
|
108
|
+
? createHash("sha256").update(`${baseOperationKey}:${input.operationKey.trim()}`).digest("hex")
|
|
109
|
+
: baseOperationKey
|
|
110
|
+
const operation = await tx.devOperation.upsert({
|
|
111
|
+
where: {
|
|
112
|
+
projectKey_producerAdapterKey_toolName_operationKey: {
|
|
113
|
+
projectKey: input.context.projectKey,
|
|
114
|
+
producerAdapterKey: input.context.adapterKey,
|
|
115
|
+
toolName: "plan_round_close",
|
|
116
|
+
operationKey,
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
create: {
|
|
120
|
+
userId: user.id,
|
|
121
|
+
projectKey: input.context.projectKey,
|
|
122
|
+
producerAdapterKey: input.context.adapterKey,
|
|
123
|
+
contextId: input.context.contextId,
|
|
124
|
+
toolName: "plan_round_close",
|
|
125
|
+
operationKey,
|
|
126
|
+
planKeyword: input.planName,
|
|
127
|
+
action: "ROUND_CLOSED",
|
|
128
|
+
targetType: "PLAN_ROUND",
|
|
129
|
+
targetId: `${input.planName}::round${input.round}`,
|
|
130
|
+
beforeState: input.beforeState,
|
|
131
|
+
afterState: input.afterState,
|
|
132
|
+
reason: input.reason ?? null,
|
|
133
|
+
},
|
|
134
|
+
update: {},
|
|
135
|
+
})
|
|
136
|
+
|
|
137
|
+
await publishPlanRoundChanged(tx, createPlanRoundChangedEnvelope({
|
|
138
|
+
context: input.context,
|
|
139
|
+
planId: plan.id,
|
|
140
|
+
}))
|
|
141
|
+
return { plan, operation }
|
|
142
|
+
})
|
|
143
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { RuntimeContextKey } from "./runtime-context.js"
|
|
2
|
+
import type { PlanRoundOperationRow } from "./plan-round-mutation.js"
|
|
3
|
+
|
|
4
|
+
export interface PlanRoundReadClient {
|
|
5
|
+
planRecord: {
|
|
6
|
+
findFirst(args: { where: Record<string, unknown>; orderBy: Record<string, unknown> }): Promise<{ id: string; planName: string } | null>
|
|
7
|
+
}
|
|
8
|
+
devOperation: {
|
|
9
|
+
findMany(args: { where: Record<string, unknown>; orderBy: Record<string, unknown> }): Promise<PlanRoundOperationRow[]>
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface PlanRoundSnapshot {
|
|
14
|
+
context: RuntimeContextKey
|
|
15
|
+
planId: string | null
|
|
16
|
+
planName: string | null
|
|
17
|
+
rounds: PlanRoundOperationRow[]
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export async function queryPlanRounds(
|
|
21
|
+
client: PlanRoundReadClient,
|
|
22
|
+
input: { context: RuntimeContextKey; planName?: string; planId?: string; round?: number },
|
|
23
|
+
): Promise<PlanRoundSnapshot> {
|
|
24
|
+
const explicitPlan = Boolean(input.planName || input.planId)
|
|
25
|
+
const plan = await client.planRecord.findFirst({
|
|
26
|
+
where: {
|
|
27
|
+
projectKey: input.context.projectKey,
|
|
28
|
+
adapterKey: input.context.adapterKey,
|
|
29
|
+
...(input.planName ? { planName: input.planName } : {}),
|
|
30
|
+
...(input.planId ? { id: input.planId } : {}),
|
|
31
|
+
...(!explicitPlan ? { lifecycle: { in: ["ACTIVE", "BLOCKED"] } } : {}),
|
|
32
|
+
},
|
|
33
|
+
orderBy: { updatedAt: "desc" },
|
|
34
|
+
})
|
|
35
|
+
if (!plan) return { context: input.context, planId: null, planName: null, rounds: [] }
|
|
36
|
+
|
|
37
|
+
const where: Record<string, unknown> = {
|
|
38
|
+
projectKey: input.context.projectKey,
|
|
39
|
+
producerAdapterKey: input.context.adapterKey,
|
|
40
|
+
toolName: "plan_round_close",
|
|
41
|
+
action: "ROUND_CLOSED",
|
|
42
|
+
planKeyword: plan.planName,
|
|
43
|
+
}
|
|
44
|
+
if (input.round !== undefined) where.targetId = `${plan.planName}::round${input.round}`
|
|
45
|
+
const rounds = await client.devOperation.findMany({ where, orderBy: { createdAt: "asc" } })
|
|
46
|
+
return { context: input.context, planId: plan.id, planName: plan.planName, rounds }
|
|
47
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import type { RuntimeContextKey } from "./runtime-context.js"
|
|
2
|
+
import type { LifecycleListenClient, LifecycleListenClientFactory, PgNotification } from "./plan-lifecycle-subscriber.js"
|
|
3
|
+
import type { PlanRoundSnapshot } from "./plan-round-store.js"
|
|
4
|
+
import {
|
|
5
|
+
isPlanRoundEventForContext,
|
|
6
|
+
parsePlanRoundChangedEnvelope,
|
|
7
|
+
PLAN_ROUND_CHANNEL,
|
|
8
|
+
type PlanRoundChangedEnvelope,
|
|
9
|
+
} from "./plan-round-events.js"
|
|
10
|
+
|
|
11
|
+
export interface PlanRoundSubscriberOptions {
|
|
12
|
+
context: RuntimeContextKey
|
|
13
|
+
clientFactory: LifecycleListenClientFactory
|
|
14
|
+
queryRounds(input: { context: RuntimeContextKey; planId?: string; reason: "initial" | "notification" | "reconnect" }): Promise<PlanRoundSnapshot>
|
|
15
|
+
onSnapshot(snapshot: PlanRoundSnapshot, envelope?: PlanRoundChangedEnvelope): void | Promise<void>
|
|
16
|
+
onError?(error: Error): void
|
|
17
|
+
reconnectDelayMs?: number
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class PlanRoundSubscriber {
|
|
21
|
+
readonly #options: PlanRoundSubscriberOptions
|
|
22
|
+
#client: LifecycleListenClient | null = null
|
|
23
|
+
#stopping = false
|
|
24
|
+
#startedOnce = false
|
|
25
|
+
#reconnectTimer: ReturnType<typeof setTimeout> | null = null
|
|
26
|
+
#refreshQueue: Promise<void> = Promise.resolve()
|
|
27
|
+
|
|
28
|
+
constructor(options: PlanRoundSubscriberOptions) { this.#options = options }
|
|
29
|
+
|
|
30
|
+
async start(): Promise<void> {
|
|
31
|
+
if (this.#client) return
|
|
32
|
+
this.#stopping = false
|
|
33
|
+
try { await this.#connectAndHydrate("initial") } catch (error) { this.#scheduleReconnect(); throw error }
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
async stop(): Promise<void> {
|
|
37
|
+
this.#stopping = true
|
|
38
|
+
if (this.#reconnectTimer) clearTimeout(this.#reconnectTimer)
|
|
39
|
+
this.#reconnectTimer = null
|
|
40
|
+
const client = this.#client
|
|
41
|
+
this.#client = null
|
|
42
|
+
if (!client) return
|
|
43
|
+
client.removeAllListeners()
|
|
44
|
+
try { await client.query(`UNLISTEN ${PLAN_ROUND_CHANNEL}`) } catch { /* connection may already be gone */ }
|
|
45
|
+
await client.end().catch(() => undefined)
|
|
46
|
+
await this.#refreshQueue
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async #connectAndHydrate(reason: "initial" | "reconnect"): Promise<void> {
|
|
50
|
+
const client = await this.#options.clientFactory()
|
|
51
|
+
this.#client = client
|
|
52
|
+
client.on("notification", (message: PgNotification) => this.#handleNotification(message))
|
|
53
|
+
client.on("error", (error: Error) => this.#handleDisconnect(client, error))
|
|
54
|
+
client.on("end", () => this.#handleDisconnect(client, new Error("PostgreSQL PlanRound LISTEN session ended")))
|
|
55
|
+
try {
|
|
56
|
+
await client.connect()
|
|
57
|
+
await client.query(`LISTEN ${PLAN_ROUND_CHANNEL}`)
|
|
58
|
+
await this.#refresh(undefined, reason)
|
|
59
|
+
this.#startedOnce = true
|
|
60
|
+
} catch (error) {
|
|
61
|
+
if (this.#client === client) this.#client = null
|
|
62
|
+
client.removeAllListeners()
|
|
63
|
+
await client.end().catch(() => undefined)
|
|
64
|
+
const normalized = error instanceof Error ? error : new Error(String(error))
|
|
65
|
+
this.#options.onError?.(normalized)
|
|
66
|
+
throw normalized
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
#handleNotification(message: PgNotification): void {
|
|
71
|
+
if (this.#stopping || message.channel !== PLAN_ROUND_CHANNEL) return
|
|
72
|
+
const envelope = parsePlanRoundChangedEnvelope(message.payload)
|
|
73
|
+
if (!envelope || !isPlanRoundEventForContext(envelope, this.#options.context)) return
|
|
74
|
+
this.#refreshQueue = this.#refreshQueue
|
|
75
|
+
.then(() => this.#refresh(envelope, "notification"))
|
|
76
|
+
.catch((error: unknown) => this.#options.onError?.(error instanceof Error ? error : new Error(String(error))))
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
#handleDisconnect(client: LifecycleListenClient, error: Error): void {
|
|
80
|
+
if (this.#stopping || this.#client !== client) return
|
|
81
|
+
this.#client = null
|
|
82
|
+
client.removeAllListeners()
|
|
83
|
+
this.#options.onError?.(error)
|
|
84
|
+
this.#scheduleReconnect()
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
#scheduleReconnect(): void {
|
|
88
|
+
if (this.#stopping || this.#reconnectTimer) return
|
|
89
|
+
this.#reconnectTimer = setTimeout(() => {
|
|
90
|
+
this.#reconnectTimer = null
|
|
91
|
+
this.#connectAndHydrate(this.#startedOnce ? "reconnect" : "initial").catch(() => this.#scheduleReconnect())
|
|
92
|
+
}, this.#options.reconnectDelayMs ?? 1000)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async #refresh(envelope: PlanRoundChangedEnvelope | undefined, reason: "initial" | "notification" | "reconnect"): Promise<void> {
|
|
96
|
+
const snapshot = await this.#options.queryRounds({ context: this.#options.context, planId: envelope?.planId, reason })
|
|
97
|
+
await this.#options.onSnapshot(snapshot, envelope)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { HitlRow, PlanRow } from "./db-types.js"
|
|
2
|
+
import { HitlRowSchema, PlanRowSchema, validatedDelegate } from "./db-types.js"
|
|
3
|
+
import type { PlanStatusStore } from "./plan-lifecycle.js"
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 数据库是 lifecycle 真相源。所有查询都强制携带 RuntimeContextKey,禁止同名
|
|
7
|
+
* Plan 在 adapter 间串线;LISTEN/NOTIFY 只负责唤醒本 store 重新查询。
|
|
8
|
+
*/
|
|
9
|
+
export function createPrismaPlanStatusStore(client: Record<string, unknown>): PlanStatusStore {
|
|
10
|
+
const plans = validatedDelegate<PlanRow>(client.planRecord, PlanRowSchema, "PlanRecord")
|
|
11
|
+
const hitls = validatedDelegate<HitlRow>(client.hitlRecord, HitlRowSchema, "HitlRecord")
|
|
12
|
+
|
|
13
|
+
return {
|
|
14
|
+
async findPlan({ context, planName, planId, activeOnly }) {
|
|
15
|
+
const where: Record<string, unknown> = {
|
|
16
|
+
projectKey: context.projectKey,
|
|
17
|
+
adapterKey: context.adapterKey,
|
|
18
|
+
}
|
|
19
|
+
if (planName) where.planName = planName
|
|
20
|
+
if (planId) where.id = planId
|
|
21
|
+
if (activeOnly) where.lifecycle = { in: ["ACTIVE", "BLOCKED"] }
|
|
22
|
+
return plans.findFirst({ where, orderBy: { updatedAt: "desc" } })
|
|
23
|
+
},
|
|
24
|
+
async findLatestPlanApproval({ context, planName }) {
|
|
25
|
+
const record = await hitls.findFirst({
|
|
26
|
+
where: {
|
|
27
|
+
projectKey: context.projectKey,
|
|
28
|
+
adapterKey: context.adapterKey,
|
|
29
|
+
planName,
|
|
30
|
+
type: "PLAN",
|
|
31
|
+
},
|
|
32
|
+
orderBy: { round: "desc" },
|
|
33
|
+
})
|
|
34
|
+
return record?.status ?? null
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import type { RuntimeContextKey } from "./runtime-context.js"
|
|
2
|
+
import {
|
|
3
|
+
createPlanLifecycleChangedEnvelope,
|
|
4
|
+
publishPlanLifecycleChanged,
|
|
5
|
+
type PrismaNotifyTransaction,
|
|
6
|
+
} from "./plan-lifecycle-events.js"
|
|
7
|
+
import type { PlanLifecycleStatus } from "./plan-lifecycle.js"
|
|
8
|
+
|
|
9
|
+
export interface TrackedPlanRow {
|
|
10
|
+
id: string
|
|
11
|
+
projectKey: string
|
|
12
|
+
adapterKey: string
|
|
13
|
+
planName: string
|
|
14
|
+
lifecycle: PlanLifecycleStatus
|
|
15
|
+
revision: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface PlanTrackingTransaction extends PrismaNotifyTransaction {
|
|
19
|
+
planRecord: {
|
|
20
|
+
findFirst(args: { where: Record<string, unknown> }): Promise<TrackedPlanRow | null>
|
|
21
|
+
create(args: { data: Record<string, unknown> }): Promise<TrackedPlanRow>
|
|
22
|
+
update(args: { where: Record<string, unknown>; data: Record<string, unknown> }): Promise<TrackedPlanRow>
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface PlanTrackingDatabase {
|
|
27
|
+
$transaction<T>(work: (tx: PlanTrackingTransaction) => Promise<T>): Promise<T>
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export async function trackPlanAndPublish(
|
|
31
|
+
database: PlanTrackingDatabase,
|
|
32
|
+
input: {
|
|
33
|
+
context: RuntimeContextKey
|
|
34
|
+
planName: string
|
|
35
|
+
planPath: string
|
|
36
|
+
projection: Record<string, unknown>
|
|
37
|
+
},
|
|
38
|
+
): Promise<{ created: boolean; plan: TrackedPlanRow }> {
|
|
39
|
+
return database.$transaction(async (tx) => {
|
|
40
|
+
const scope = {
|
|
41
|
+
projectKey: input.context.projectKey,
|
|
42
|
+
adapterKey: input.context.adapterKey,
|
|
43
|
+
planName: input.planName,
|
|
44
|
+
}
|
|
45
|
+
const existing = await tx.planRecord.findFirst({ where: scope })
|
|
46
|
+
const plan = existing
|
|
47
|
+
? await tx.planRecord.update({
|
|
48
|
+
where: { id: existing.id, revision: existing.revision },
|
|
49
|
+
data: { ...input.projection, planPath: input.planPath, revision: { increment: 1 } },
|
|
50
|
+
})
|
|
51
|
+
: await tx.planRecord.create({
|
|
52
|
+
data: {
|
|
53
|
+
...scope,
|
|
54
|
+
...input.projection,
|
|
55
|
+
planPath: input.planPath,
|
|
56
|
+
lifecycle: "DRAFT",
|
|
57
|
+
revision: 0,
|
|
58
|
+
},
|
|
59
|
+
})
|
|
60
|
+
const expectedRevision = existing ? existing.revision + 1 : 0
|
|
61
|
+
if (plan.revision !== expectedRevision) {
|
|
62
|
+
throw new Error(`Plan track revision 未按预期变化: expected=${expectedRevision}, actual=${plan.revision}`)
|
|
63
|
+
}
|
|
64
|
+
await publishPlanLifecycleChanged(tx, createPlanLifecycleChangedEnvelope({
|
|
65
|
+
context: input.context,
|
|
66
|
+
planId: plan.id,
|
|
67
|
+
revision: plan.revision,
|
|
68
|
+
}))
|
|
69
|
+
return { created: existing === null, plan }
|
|
70
|
+
})
|
|
71
|
+
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import { DATABASE_URL, PROJECT_ROOT } from "./env.js"
|
|
2
|
-
import { join, dirname
|
|
1
|
+
import { DATABASE_URL, PROJECT_ROOT, MAGIC_DIR } from "./env.js"
|
|
2
|
+
import { join, dirname } from "path"
|
|
3
3
|
import { existsSync } from "fs"
|
|
4
|
-
import { fileURLToPath } from "url"
|
|
5
4
|
import { createRequire } from "module"
|
|
5
|
+
import { findUpSync } from "find-up"
|
|
6
6
|
|
|
7
7
|
const require = createRequire(import.meta.url)
|
|
8
8
|
|
|
@@ -11,14 +11,20 @@ const require = createRequire(import.meta.url)
|
|
|
11
11
|
// 双库分离项目(add.prisma=postgres + 业务 schema=mysql)需显式设置环境变量
|
|
12
12
|
// PRISMA_CLIENT_DIR=add-prisma,否则会加载业务库 client 导致 provider mismatch
|
|
13
13
|
const clientDir = process.env.PRISMA_CLIENT_DIR || "prisma"
|
|
14
|
+
// 锚点查找兜底(find-up 包):magicDir 向上查找项目根,替代手算层级(轮次 3)
|
|
15
|
+
const anchorRoot = (() => {
|
|
16
|
+
const hit = findUpSync(MAGIC_DIR, { cwd: import.meta.dirname, type: "directory" });
|
|
17
|
+
return hit ? dirname(hit) : null;
|
|
18
|
+
})();
|
|
14
19
|
const candidates = [
|
|
15
20
|
join(PROJECT_ROOT, `src/generated/${clientDir}/client.ts`),
|
|
16
21
|
join(PROJECT_ROOT, `src/generated/${clientDir}/client.js`),
|
|
17
22
|
join(process.cwd(), `src/generated/${clientDir}/client.ts`),
|
|
18
23
|
join(process.cwd(), `src/generated/${clientDir}/client.js`),
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
24
|
+
...(anchorRoot ? [
|
|
25
|
+
join(anchorRoot, `src/generated/${clientDir}/client.ts`),
|
|
26
|
+
join(anchorRoot, `src/generated/${clientDir}/client.js`),
|
|
27
|
+
] : []),
|
|
22
28
|
]
|
|
23
29
|
// 双库分离项目提示:检测到 add-prisma 客户端但未显式选择时提醒用户自行决策
|
|
24
30
|
if (clientDir === "prisma" && existsSync(join(PROJECT_ROOT, "src/generated/add-prisma/client.ts"))) {
|
|
@@ -4,8 +4,7 @@
|
|
|
4
4
|
// >>> CAIJUE GENERATED START >>>
|
|
5
5
|
export const PROJECT_ROOT_PRIORITY = ["env_var", "dirname_fallback", "cwd_fallback"] as const;
|
|
6
6
|
export type ProjectRootTier = (typeof PROJECT_ROOT_PRIORITY)[number];
|
|
7
|
-
// <<< CAIJUE GENERATED END
|
|
8
|
-
// >>> USER CODE >>>
|
|
7
|
+
// <<< CAIJUE GENERATED END <<<// >>> USER CODE >>>
|
|
9
8
|
import { resolve, dirname } from "path"
|
|
10
9
|
import { existsSync } from "fs"
|
|
11
10
|
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ⚠️ 由 caijuehub/transcribe.ts 自动生成,不要手动编辑!
|
|
2
|
+
// 改 *-rules.toml 后重新运行: add-coder generate
|
|
3
|
+
|
|
4
|
+
// >>> CAIJUE GENERATED START >>>
|
|
5
|
+
export const QUANT_RECHECK_CONFIG = {
|
|
6
|
+
ANCHOR_WEIGHT: 3,
|
|
7
|
+
IDF_MIN_DOCS: 5,
|
|
8
|
+
RRF_K: 10,
|
|
9
|
+
MIN_ABS_SIMILARITY: 0.25,
|
|
10
|
+
RRF_MIN_CANDIDATES: 2,
|
|
11
|
+
EWMA_LAMBDA: 0.3,
|
|
12
|
+
EWMA_K: 1.5,
|
|
13
|
+
EWMA_COLD_START: 10,
|
|
14
|
+
EWMA_BASELINE: 0.16,
|
|
15
|
+
} as const;
|
|
16
|
+
// <<< CAIJUE GENERATED END <<<
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* @Author : xiaomingming wujixmm@gmail.com
|
|
3
|
+
* @Date : 2026-08-10 18:41:34
|
|
4
|
+
* @LastEditors : xiaomingming wujixmm@gmail.com
|
|
5
|
+
* @LastEditTime : 2026-08-10 18:41:35
|
|
6
|
+
* @FilePath : /farm-agent/home/xmm/ai/add-coder/templates/core/scripts/mcp-server/shared/redact.ts
|
|
7
|
+
* @Description :
|
|
8
|
+
*/
|
|
9
|
+
// 敏感信息脱敏(进程层并发契约 v2:日志/错误不泄露 DATABASE_URL/密码)
|
|
10
|
+
// 统一入口:response.ts 的 textResponse/errorResponse 内部调用,一处封装全覆盖所有工具
|
|
11
|
+
|
|
12
|
+
const CONNECTION_URL_RE =
|
|
13
|
+
/(postgres(?:ql)?|mysql|redis|mongodb(?:\+srv)?):\/\/[^\s"'`]+/gi
|
|
14
|
+
|
|
15
|
+
export function redactUrl(url: string): string {
|
|
16
|
+
// postgres://user:pass@host:port/db → postgres://user:****@host:port/db
|
|
17
|
+
return url.replace(/(:\/\/[^:/\s]+:)([^@/\s]+)(@)/, "$1****$3")
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function redact(text: string): string {
|
|
21
|
+
return text.replace(CONNECTION_URL_RE, (m) => {
|
|
22
|
+
try {
|
|
23
|
+
return redactUrl(m)
|
|
24
|
+
} catch {
|
|
25
|
+
return m
|
|
26
|
+
}
|
|
27
|
+
})
|
|
28
|
+
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ToolResponse } from "../types.js"
|
|
2
|
+
import { redact } from "./redact.js"
|
|
2
3
|
|
|
3
4
|
export function textResponse(text: string): { content: ToolResponse } {
|
|
4
|
-
return { content: [{ type: "text", text }] }
|
|
5
|
+
return { content: [{ type: "text", text: redact(text) }] }
|
|
5
6
|
}
|
|
6
7
|
|
|
7
8
|
export function errorResponse(message: string): { content: ToolResponse; isError: boolean } {
|
|
8
|
-
return { content: [{ type: "text", text: message }], isError: true }
|
|
9
|
+
return { content: [{ type: "text", text: redact(message) }], isError: true }
|
|
9
10
|
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { createHash } from "crypto"
|
|
2
|
+
import { realpathSync } from "fs"
|
|
3
|
+
import { isAbsolute, relative, resolve } from "path"
|
|
4
|
+
|
|
5
|
+
export const ADAPTER_MAGIC_DIR = Object.freeze({
|
|
6
|
+
add: ".add",
|
|
7
|
+
claude: ".claude",
|
|
8
|
+
codex: ".codex",
|
|
9
|
+
qoder: ".qoder",
|
|
10
|
+
trae: ".trae",
|
|
11
|
+
vscode: ".vscode",
|
|
12
|
+
} as const)
|
|
13
|
+
|
|
14
|
+
export type AdapterKey = keyof typeof ADAPTER_MAGIC_DIR
|
|
15
|
+
export type AdapterMagicDir = (typeof ADAPTER_MAGIC_DIR)[AdapterKey]
|
|
16
|
+
|
|
17
|
+
export interface RuntimeContextKey {
|
|
18
|
+
projectKey: string
|
|
19
|
+
adapterKey: AdapterKey
|
|
20
|
+
projectRoot: string
|
|
21
|
+
magicDir: AdapterMagicDir
|
|
22
|
+
contextId: string
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const MAGIC_DIR_ADAPTER = new Map<AdapterMagicDir, AdapterKey>(
|
|
26
|
+
Object.entries(ADAPTER_MAGIC_DIR).map(([adapterKey, magicDir]) => [magicDir, adapterKey as AdapterKey]),
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
export function adapterKeyFromMagicDir(magicDir: string): AdapterKey {
|
|
30
|
+
const adapterKey = MAGIC_DIR_ADAPTER.get(magicDir as AdapterMagicDir)
|
|
31
|
+
if (!adapterKey) {
|
|
32
|
+
throw new Error(`未知 ADD adapter magicDir: ${magicDir}`)
|
|
33
|
+
}
|
|
34
|
+
return adapterKey
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export function canonicalProjectRoot(projectRoot: string): string {
|
|
38
|
+
if (!projectRoot.trim()) throw new Error("PROJECT_ROOT 不能为空")
|
|
39
|
+
return realpathSync.native(resolve(projectRoot))
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function projectKeyFromRoot(projectRoot: string): string {
|
|
43
|
+
const canonicalRoot = canonicalProjectRoot(projectRoot)
|
|
44
|
+
return createHash("sha256").update(`add-project\0${canonicalRoot}`).digest("hex")
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function createRuntimeContext(projectRoot: string, magicDir: string): Readonly<RuntimeContextKey> {
|
|
48
|
+
const adapterKey = adapterKeyFromMagicDir(magicDir)
|
|
49
|
+
const canonicalRoot = canonicalProjectRoot(projectRoot)
|
|
50
|
+
const projectKey = projectKeyFromRoot(canonicalRoot)
|
|
51
|
+
return Object.freeze({
|
|
52
|
+
projectKey,
|
|
53
|
+
adapterKey,
|
|
54
|
+
projectRoot: canonicalRoot,
|
|
55
|
+
magicDir: ADAPTER_MAGIC_DIR[adapterKey],
|
|
56
|
+
contextId: `${projectKey}:${adapterKey}`,
|
|
57
|
+
})
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function runtimeScopeRoot(context: RuntimeContextKey): string {
|
|
61
|
+
return resolve(context.projectRoot, context.magicDir)
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export function isPathInRuntimeScope(context: RuntimeContextKey, candidatePath: string): boolean {
|
|
65
|
+
const scopeRoot = runtimeScopeRoot(context)
|
|
66
|
+
const absoluteCandidate = isAbsolute(candidatePath) ? resolve(candidatePath) : resolve(context.projectRoot, candidatePath)
|
|
67
|
+
const scopedRelative = relative(scopeRoot, absoluteCandidate)
|
|
68
|
+
return scopedRelative === "" || (!scopedRelative.startsWith("..") && !isAbsolute(scopedRelative))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export function assertPathInRuntimeScope(context: RuntimeContextKey, candidatePath: string): string {
|
|
72
|
+
const absoluteCandidate = isAbsolute(candidatePath) ? resolve(candidatePath) : resolve(context.projectRoot, candidatePath)
|
|
73
|
+
if (!isPathInRuntimeScope(context, absoluteCandidate)) {
|
|
74
|
+
throw new Error(`路径越出当前 ADD adapter scope: ${absoluteCandidate}(scope=${runtimeScopeRoot(context)})`)
|
|
75
|
+
}
|
|
76
|
+
return absoluteCandidate
|
|
77
|
+
}
|