add-coder 0.3.34 → 0.3.35
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/dist/index.js +21 -3
- package/package.json +3 -3
- package/templates/.add-coder-src-hash.json +144 -90
- package/templates/adapters/claude/hooks/doc-format-guard.mjs +172 -88
- package/templates/adapters/claude/hooks/notification.mjs +0 -4
- package/templates/adapters/claude/hooks/permission-denied.mjs +0 -4
- package/templates/adapters/claude/hooks/permission-gate.mjs +0 -4
- package/templates/adapters/claude/hooks/post-tool-use.mjs +59 -5
- package/templates/adapters/claude/hooks/pre-compact.mjs +0 -4
- package/templates/adapters/claude/hooks/pre-tool-use.mjs +0 -4
- package/templates/adapters/claude/hooks/prompt-submit.mjs +72 -4
- package/templates/adapters/claude/hooks/session-end.mjs +0 -4
- package/templates/adapters/claude/hooks/session-start.mjs +65 -5
- package/templates/adapters/claude/hooks/stop-check.mjs +6 -87
- package/templates/adapters/claude/hooks/stop-failure.mjs +0 -4
- package/templates/adapters/claude/hooks/subagent-guard.mjs +0 -4
- package/templates/adapters/claude/hooks/subagent-stop.mjs +0 -4
- package/templates/adapters/codex/hooks/doc-format-guard.mjs +172 -88
- package/templates/adapters/codex/hooks/notification.mjs +0 -4
- package/templates/adapters/codex/hooks/permission-gate.mjs +0 -4
- package/templates/adapters/codex/hooks/post-tool-failure.mjs +0 -4
- package/templates/adapters/codex/hooks/post-tool-use.mjs +59 -5
- package/templates/adapters/codex/hooks/pre-compact.mjs +0 -4
- package/templates/adapters/codex/hooks/pre-tool-use.mjs +0 -4
- package/templates/adapters/codex/hooks/prompt-submit.mjs +72 -4
- package/templates/adapters/codex/hooks/session-end.mjs +0 -4
- package/templates/adapters/codex/hooks/session-start.mjs +65 -5
- package/templates/adapters/codex/hooks/stop-check.mjs +6 -87
- package/templates/adapters/codex/hooks/subagent-guard.mjs +0 -4
- package/templates/adapters/codex/hooks/subagent-stop.mjs +0 -4
- package/templates/adapters/qoder/hooks/doc-format-guard.mjs +172 -88
- package/templates/adapters/qoder/hooks/notification.mjs +0 -4
- package/templates/adapters/qoder/hooks/permission-gate.mjs +0 -4
- package/templates/adapters/qoder/hooks/post-tool-failure.mjs +0 -4
- package/templates/adapters/qoder/hooks/post-tool-use.mjs +59 -5
- package/templates/adapters/qoder/hooks/pre-compact.mjs +0 -4
- package/templates/adapters/qoder/hooks/pre-tool-use.mjs +0 -4
- package/templates/adapters/qoder/hooks/prompt-submit.mjs +72 -4
- package/templates/adapters/qoder/hooks/review-checklist.mjs +0 -4
- package/templates/adapters/qoder/hooks/session-end.mjs +0 -4
- package/templates/adapters/qoder/hooks/session-start.mjs +67 -5
- package/templates/adapters/qoder/hooks/stop-check.mjs +6 -87
- package/templates/adapters/qoder/hooks/subagent-guard.mjs +0 -4
- package/templates/adapters/qoder/hooks/subagent-stop.mjs +0 -4
- package/templates/adapters/trae/hooks/doc-format-guard.mjs +172 -88
- package/templates/adapters/trae/hooks/notification.mjs +0 -4
- package/templates/adapters/trae/hooks/permission-gate.mjs +0 -4
- package/templates/adapters/trae/hooks/post-tool-failure.mjs +0 -4
- package/templates/adapters/trae/hooks/post-tool-use.mjs +59 -5
- package/templates/adapters/trae/hooks/pre-compact.mjs +0 -4
- package/templates/adapters/trae/hooks/pre-tool-use.mjs +0 -4
- package/templates/adapters/trae/hooks/prompt-submit.mjs +72 -4
- package/templates/adapters/trae/hooks/session-end.mjs +0 -4
- package/templates/adapters/trae/hooks/session-start.mjs +65 -5
- package/templates/adapters/trae/hooks/stop-check.mjs +6 -87
- package/templates/adapters/trae/hooks/subagent-guard.mjs +0 -4
- package/templates/adapters/trae/hooks/subagent-stop.mjs +0 -4
- package/templates/adapters/vscode/hooks/doc-format-guard.mjs +172 -88
- package/templates/adapters/vscode/hooks/notification.mjs +0 -4
- package/templates/adapters/vscode/hooks/permission-gate.mjs +0 -4
- package/templates/adapters/vscode/hooks/post-tool-failure.mjs +0 -4
- package/templates/adapters/vscode/hooks/post-tool-use.mjs +59 -5
- package/templates/adapters/vscode/hooks/pre-compact.mjs +0 -4
- package/templates/adapters/vscode/hooks/pre-tool-use.mjs +0 -4
- package/templates/adapters/vscode/hooks/prompt-submit.mjs +72 -4
- package/templates/adapters/vscode/hooks/session-end.mjs +0 -4
- package/templates/adapters/vscode/hooks/session-start.mjs +65 -5
- package/templates/adapters/vscode/hooks/stop-check.mjs +6 -87
- package/templates/adapters/vscode/hooks/subagent-guard.mjs +0 -4
- package/templates/adapters/vscode/hooks/subagent-stop.mjs +0 -4
- package/templates/core/governance/doc-format-guard.ts +29 -112
- package/templates/core/governance/post-tool-router.ts +33 -1
- package/templates/core/governance/prompt-router.ts +47 -0
- package/templates/core/governance/rules.ts +0 -4
- package/templates/core/governance/session-start-guard.ts +48 -1
- package/templates/core/governance/stop-router.ts +7 -101
- package/templates/core/prisma/add.prisma +203 -0
- package/templates/core/scripts/db-ensure.sh +61 -2
- package/templates/core/scripts/mcp-server/shared/db-types.ts +119 -0
- package/templates/core/scripts/mcp-server/shared/hitl-create-policy.ts +27 -0
- package/templates/core/scripts/mcp-server/shared/hitl-proposal-content.ts +110 -0
- package/templates/core/scripts/mcp-server/shared/hitl-widget-instance.ts +85 -0
- package/templates/core/scripts/mcp-server/shared/memory/calibration/batch-fit.ts +250 -0
- package/templates/core/scripts/mcp-server/shared/memory/calibration/feedback-stats.ts +101 -0
- package/templates/core/scripts/mcp-server/shared/memory/calibration/unit-state.ts +224 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/conflicts.ts +59 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/dedup.ts +45 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/errors.ts +33 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/handoff-digest.ts +92 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/metric-candidate.ts +79 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/scope.ts +94 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/secrets.ts +50 -0
- package/templates/core/scripts/mcp-server/shared/memory/domain/state-machine.ts +90 -0
- package/templates/core/scripts/mcp-server/shared/memory/embedding/index.ts +117 -0
- package/templates/core/scripts/mcp-server/shared/memory/embedding/local-onnx.ts +105 -0
- package/templates/core/scripts/mcp-server/shared/memory/embedding/openai-compatible.ts +87 -0
- package/templates/core/scripts/mcp-server/shared/memory/jobs/consolidation.ts +226 -0
- package/templates/core/scripts/mcp-server/shared/memory/jobs/evidence-collector.ts +153 -0
- package/templates/core/scripts/mcp-server/shared/memory/jobs/snapshot.ts +114 -0
- package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-recall.ts +134 -0
- package/templates/core/scripts/mcp-server/shared/memory/metrics/gate-writer.ts +217 -0
- package/templates/core/scripts/mcp-server/shared/memory/metrics/stage-words.ts +69 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts +89 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/pg.ts +139 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite-fts5.sql +29 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fts/sqlite.ts +106 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/fusion.ts +43 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/pipeline.ts +285 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/query-terms.ts +31 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/recall-writer.ts +87 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/reranker.ts +116 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/types.ts +52 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/pgvector.ts +143 -0
- package/templates/core/scripts/mcp-server/shared/memory/retrieval/vector/sqlite-vec.ts +118 -0
- package/templates/core/scripts/mcp-server/shared/memory/switches.ts +39 -0
- package/templates/core/scripts/mcp-server/shared/review-files.ts +22 -0
- package/templates/core/scripts/mcp-server/shared/runtime-freshness.ts +235 -0
- package/templates/core/scripts/mcp-server/tools/gateway/check_dps.ts +41 -3
- package/templates/core/scripts/mcp-server/tools/gateway/check_rahs.ts +40 -1
- package/templates/core/scripts/mcp-server/tools/gateway/check_spec_sync.ts +2 -2
- package/templates/core/scripts/mcp-server/tools/hitl.ts +108 -42
- package/templates/core/scripts/mcp-server/tools/index.ts +7 -1
- package/templates/core/scripts/mcp-server/tools/memory-compat.ts +258 -0
- package/templates/core/scripts/mcp-server/tools/memory.ts +654 -0
- package/templates/core/scripts/mcp-server/tools/plan.ts +8 -3
- package/templates/core/scripts/mcp-server/tools/review.ts +10 -7
- package/templates/core/scripts/mcp-server.ts +36 -0
- package/templates/core/templates/checklist-template.md +13 -0
- package/templates/core/templates/review-implementation-template.md +24 -0
- package/templates/core/templates/review-template.md +16 -0
- package/templates/core/validation/index.ts +136 -0
- package/templates/core/validation/policy.ts +91 -0
- package/templates/core/validation/registry.ts +61 -0
- package/templates/core/validation/schema-validator.ts +277 -0
- package/templates/core/validation/validators/add-route.ts +32 -0
- package/templates/core/validation/validators/checklist.ts +48 -0
- package/templates/core/validation/validators/handoff.ts +46 -0
- package/templates/core/validation/validators/hitl.ts +22 -0
- package/templates/core/validation/validators/index.ts +52 -0
- package/templates/core/validation/validators/plan.ts +20 -0
- package/templates/core/validation/validators/report.ts +16 -0
- package/templates/core/validation/validators/review.ts +30 -0
- package/templates/core/validation/validators/spec.ts +25 -0
- package/templates/core/validation/validators/tasks.ts +40 -0
- package/templates/core/validation/validators/types.ts +32 -0
- package/templates/core/vocabulary/add-governance-vocabulary.md +18 -0
- package/templates/core/scripts/mcp-server/shared/dps-spec-ref.ts +0 -17
|
@@ -145,10 +145,6 @@ var protocol = {
|
|
|
145
145
|
".trae",
|
|
146
146
|
".codex"
|
|
147
147
|
]
|
|
148
|
-
},
|
|
149
|
-
"stop": {
|
|
150
|
-
"max_prompt_per_context": 3,
|
|
151
|
-
"window_minutes": 30
|
|
152
148
|
}
|
|
153
149
|
};
|
|
154
150
|
|
|
@@ -635,7 +631,45 @@ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status =
|
|
|
635
631
|
appendFileSync(file, line);
|
|
636
632
|
}
|
|
637
633
|
|
|
634
|
+
// templates/core/scripts/mcp-server/shared/memory/switches.ts
|
|
635
|
+
function recallMode(env = process.env) {
|
|
636
|
+
const v = (env.ADD_MEMORY_RECALL_MODE ?? "shadow").toLowerCase();
|
|
637
|
+
return v === "off" || v === "inject" ? v : "shadow";
|
|
638
|
+
}
|
|
639
|
+
var L1_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
640
|
+
|
|
641
|
+
// templates/core/scripts/mcp-server/shared/memory/metrics/stage-words.ts
|
|
642
|
+
var STAGE_LABEL = {
|
|
643
|
+
"plan-start": "Plan \u8D77\u8349",
|
|
644
|
+
"spec-start": "Spec \u8D77\u8349",
|
|
645
|
+
dps: "DPS \u95E8\u7981",
|
|
646
|
+
rahs: "RAHS \u95E8\u7981",
|
|
647
|
+
handoff: "Handoff \u4EA4\u63A5"
|
|
648
|
+
};
|
|
649
|
+
var STAGE_PATTERNS = [
|
|
650
|
+
{ stage: "handoff", re: /handoff|交接(手册|文档|说明)?|交接给/i },
|
|
651
|
+
{ stage: "rahs", re: /rahs|注意力漂移|执行健康度/i },
|
|
652
|
+
{ stage: "dps", re: /dps|文档质量闸门|质量闸门|门禁(评分|检查)?/i },
|
|
653
|
+
{ stage: "spec-start", re: /(生成|写|新建|起草|补)\s*(spec|规格|三元组)|specs?\s*三元组|WHEN-?THEN/i },
|
|
654
|
+
{ stage: "plan-start", re: /(生成|写|新建|起草|补)\s*(plan|计划|方案)|plan\s*阶段|规划阶段/i }
|
|
655
|
+
];
|
|
656
|
+
function detectRecallStage(prompt) {
|
|
657
|
+
if (!prompt) return null;
|
|
658
|
+
for (const { stage, re } of STAGE_PATTERNS) {
|
|
659
|
+
if (re.test(prompt)) return stage;
|
|
660
|
+
}
|
|
661
|
+
return null;
|
|
662
|
+
}
|
|
663
|
+
function buildStageRecallHint(stage, mode) {
|
|
664
|
+
if (mode === "off") return null;
|
|
665
|
+
const label = STAGE_LABEL[stage];
|
|
666
|
+
const tail = mode === "shadow" ? "\uFF08\u5F53\u524D shadow \u6A21\u5F0F\uFF1AHook \u4E0D\u6CE8\u5165\u4E0A\u4E0B\u6587\uFF0C\u53EC\u56DE\u7ED3\u679C\u9700\u663E\u5F0F\u6D88\u8D39\uFF09" : "\uFF08\u5F53\u524D inject \u6A21\u5F0F\uFF1A\u8C03\u7528\u540E\u4E0A\u4E0B\u6587\u5C06\u88AB\u6CE8\u5165\uFF09";
|
|
667
|
+
return `[Memory] \u68C0\u6D4B\u5230${label}\u9636\u6BB5 \u2192 \u5EFA\u8BAE\u8C03\u7528 recall_memory({ stage: "${stage}", query: <\u672C\u9636\u6BB5\u610F\u56FE>, planKeyword: <Plan \u5173\u952E\u8BCD> }) \u83B7\u53D6\u53D7\u6CBB\u7406\u7684\u5386\u53F2\u4E0A\u4E0B\u6587\uFF08\u542B\u6765\u6E90\u4E0E\u8BC4\u5206\uFF09\u3002${tail}
|
|
668
|
+
`;
|
|
669
|
+
}
|
|
670
|
+
|
|
638
671
|
// templates/core/governance/prompt-router.ts
|
|
672
|
+
var MEMORY_RECALL_HINT_RE = /之前|上次|还记得|记得吗|为什么当时|类似问题|历史决策|以前的方案|曾经怎么/i;
|
|
639
673
|
var PromptRouter = class {
|
|
640
674
|
magicDir;
|
|
641
675
|
constructor(magicDir) {
|
|
@@ -698,6 +732,38 @@ var PromptRouter = class {
|
|
|
698
732
|
/** 前置注入(core: 无;qoder: 无条件 "ADD workflow active" JSON) */
|
|
699
733
|
preamble(_input) {
|
|
700
734
|
}
|
|
735
|
+
/**
|
|
736
|
+
* 显式记忆回忆提示(Spec §10 / Plan §9.1 补充入口):
|
|
737
|
+
* 命中回忆词且 ADD_MEMORY_RECALL_MODE != off → 提示调用 recall_memory。
|
|
738
|
+
* 不做自动召回(同步 Hook ≤200ms 无 DB);shadow 模式下提示语明示"仅显式调用"。
|
|
739
|
+
*/
|
|
740
|
+
maybeMemoryHint(prompt) {
|
|
741
|
+
try {
|
|
742
|
+
if (recallMode() === "off") return;
|
|
743
|
+
if (!MEMORY_RECALL_HINT_RE.test(prompt)) return;
|
|
744
|
+
const mode = recallMode();
|
|
745
|
+
const suffix = mode === "shadow" ? "\uFF08\u5F53\u524D shadow \u6A21\u5F0F\uFF1A\u53EC\u56DE\u5C06\u843D\u5BA1\u8BA1\uFF0CHook \u4E0D\u81EA\u52A8\u6CE8\u5165\uFF09" : "";
|
|
746
|
+
process.stdout.write(
|
|
747
|
+
`[Memory] \u68C0\u6D4B\u5230\u5386\u53F2\u56DE\u5FC6\u610F\u56FE${suffix}\u3002\u53EF\u8C03\u7528 recall_memory({ query: <\u4F60\u7684\u95EE\u9898>, stage: "prompt" }) \u83B7\u53D6\u53D7\u6CBB\u7406\u7684\u8BB0\u5FC6\u4E0A\u4E0B\u6587\uFF08\u542B\u6765\u6E90\u4E0E\u8BC4\u5206\uFF09\u3002
|
|
748
|
+
`
|
|
749
|
+
);
|
|
750
|
+
} catch {
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* 阶段确定性召回提示(Spec §2 §DeterministicRecall,Plan §3.1 方案 B1 的 Hook 侧):
|
|
755
|
+
* 命中 ADD 阶段词 → 提示到工具侧做确定性召回(plan-start/spec-start/dps/rahs/handoff)。
|
|
756
|
+
* Hook 同步路径无 DB:只输出提示文本,不召回、不写审计;任何异常 fail-open。
|
|
757
|
+
*/
|
|
758
|
+
maybeStageHint(prompt) {
|
|
759
|
+
try {
|
|
760
|
+
const stage = detectRecallStage(prompt);
|
|
761
|
+
if (!stage) return;
|
|
762
|
+
const hint = buildStageRecallHint(stage, recallMode());
|
|
763
|
+
if (hint) process.stdout.write(hint);
|
|
764
|
+
} catch {
|
|
765
|
+
}
|
|
766
|
+
}
|
|
701
767
|
/** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
|
|
702
768
|
layer3Json() {
|
|
703
769
|
return false;
|
|
@@ -729,6 +795,8 @@ var PromptRouter = class {
|
|
|
729
795
|
for (const m of matched) process.stdout.write(m + "\n");
|
|
730
796
|
return 0;
|
|
731
797
|
}
|
|
798
|
+
this.maybeMemoryHint(prompt);
|
|
799
|
+
this.maybeStageHint(prompt);
|
|
732
800
|
const devKw = loadDevKeywords();
|
|
733
801
|
if (devKw.length === 0) return 0;
|
|
734
802
|
if (!this.devKwMatched(prompt, devKw)) return 0;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// templates/core/governance/session-start-guard.ts
|
|
2
|
-
import { existsSync as existsSync3, readdirSync, statSync } from "node:fs";
|
|
2
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
3
3
|
import { join as join3 } from "node:path";
|
|
4
4
|
|
|
5
5
|
// templates/core/governance/common.ts
|
|
@@ -121,10 +121,6 @@ var protocol = {
|
|
|
121
121
|
".trae",
|
|
122
122
|
".codex"
|
|
123
123
|
]
|
|
124
|
-
},
|
|
125
|
-
"stop": {
|
|
126
|
-
"max_prompt_per_context": 3,
|
|
127
|
-
"window_minutes": 30
|
|
128
124
|
}
|
|
129
125
|
};
|
|
130
126
|
|
|
@@ -482,6 +478,30 @@ var PreloadTemplates = class _PreloadTemplates {
|
|
|
482
478
|
}
|
|
483
479
|
};
|
|
484
480
|
|
|
481
|
+
// templates/core/scripts/mcp-server/shared/memory/switches.ts
|
|
482
|
+
function recallMode(env = process.env) {
|
|
483
|
+
const v = (env.ADD_MEMORY_RECALL_MODE ?? "shadow").toLowerCase();
|
|
484
|
+
return v === "off" || v === "inject" ? v : "shadow";
|
|
485
|
+
}
|
|
486
|
+
function memoryMaxTokens(env = process.env) {
|
|
487
|
+
const n = Number(env.ADD_MEMORY_MAX_TOKENS ?? 600);
|
|
488
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;
|
|
489
|
+
}
|
|
490
|
+
var MEMORY_DIR_NAME = "memory";
|
|
491
|
+
var L1_SNAPSHOT_FILE = "l1-context.md";
|
|
492
|
+
var L1_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
493
|
+
|
|
494
|
+
// templates/core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts
|
|
495
|
+
function estimateTokens(text) {
|
|
496
|
+
let cjk = 0;
|
|
497
|
+
let other = 0;
|
|
498
|
+
for (const ch of text) {
|
|
499
|
+
if (/[-ヿ㐀-䶿一-鿿豈--]/.test(ch)) cjk++;
|
|
500
|
+
else other++;
|
|
501
|
+
}
|
|
502
|
+
return cjk + Math.ceil(other / 4);
|
|
503
|
+
}
|
|
504
|
+
|
|
485
505
|
// templates/core/governance/session-start-guard.ts
|
|
486
506
|
var SessionStartGuard = class {
|
|
487
507
|
projectDir;
|
|
@@ -501,6 +521,7 @@ var SessionStartGuard = class {
|
|
|
501
521
|
this.emitIndex();
|
|
502
522
|
this.emitTodoReminder(state);
|
|
503
523
|
this.emitHitlPending();
|
|
524
|
+
this.emitMemoryL1();
|
|
504
525
|
return 0;
|
|
505
526
|
}
|
|
506
527
|
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
@@ -549,6 +570,45 @@ var SessionStartGuard = class {
|
|
|
549
570
|
}
|
|
550
571
|
}
|
|
551
572
|
}
|
|
573
|
+
/**
|
|
574
|
+
* ⑤ Memory L1 快照注入(ADD_MEMORY_RECALL_MODE 三态):
|
|
575
|
+
* off → 不输出
|
|
576
|
+
* shadow → 仅提示快照存在(召回可执行并落审计,但不注入上下文)
|
|
577
|
+
* inject → 读预计算快照注入(新鲜度 ≤7 天,token 预算截断,带来源边界标签)
|
|
578
|
+
* 同步 Hook ≤200ms 约束:只读文件,任何异常 fail-open 静默跳过。
|
|
579
|
+
*/
|
|
580
|
+
emitMemoryL1() {
|
|
581
|
+
try {
|
|
582
|
+
const mode = recallMode();
|
|
583
|
+
if (mode === "off" || !this.magicDir) return;
|
|
584
|
+
const file = join3(this.projectDir, this.magicDir, MEMORY_DIR_NAME, L1_SNAPSHOT_FILE);
|
|
585
|
+
if (!existsSync3(file)) return;
|
|
586
|
+
if (mode === "shadow") {
|
|
587
|
+
process.stdout.write(`[Memory] L1 \u5FEB\u7167\u5DF2\u751F\u6210\uFF08shadow \u6A21\u5F0F\u672A\u6CE8\u5165\uFF09\u3002\u9700\u8981\u65F6\u8C03\u7528 recall_memory \u663E\u5F0F\u53EC\u56DE: ${file}
|
|
588
|
+
`);
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
if (Date.now() - statSync(file).mtimeMs > L1_SNAPSHOT_TTL_MS) return;
|
|
592
|
+
const text = readFileSync3(file, "utf-8");
|
|
593
|
+
const budget = memoryMaxTokens();
|
|
594
|
+
if (estimateTokens(text) <= budget) {
|
|
595
|
+
process.stdout.write(text);
|
|
596
|
+
return;
|
|
597
|
+
}
|
|
598
|
+
const lines = text.split("\n");
|
|
599
|
+
const kept = [];
|
|
600
|
+
let used = 0;
|
|
601
|
+
for (const line of lines) {
|
|
602
|
+
const t = estimateTokens(line);
|
|
603
|
+
if (used + t > budget) break;
|
|
604
|
+
kept.push(line);
|
|
605
|
+
used += t;
|
|
606
|
+
}
|
|
607
|
+
kept.push(`[Memory L1] \u26A0\uFE0F \u8D85\u51FA\u9884\u7B97 ${budget} tokens\uFF0C\u5DF2\u622A\u65AD\uFF08\u5B8C\u6574\u5FEB\u7167: ${file}\uFF09`);
|
|
608
|
+
process.stdout.write(kept.join("\n") + "\n");
|
|
609
|
+
} catch {
|
|
610
|
+
}
|
|
611
|
+
}
|
|
552
612
|
};
|
|
553
613
|
|
|
554
614
|
// templates/adapters/vscode/hooks/session-start.ts
|
|
@@ -192,10 +192,6 @@ var protocol = {
|
|
|
192
192
|
".trae",
|
|
193
193
|
".codex"
|
|
194
194
|
]
|
|
195
|
-
},
|
|
196
|
-
"stop": {
|
|
197
|
-
"max_prompt_per_context": 3,
|
|
198
|
-
"window_minutes": 30
|
|
199
195
|
}
|
|
200
196
|
};
|
|
201
197
|
|
|
@@ -406,10 +402,6 @@ function buildStopContext(quadrant, info) {
|
|
|
406
402
|
}
|
|
407
403
|
|
|
408
404
|
// templates/core/governance/stop-router.ts
|
|
409
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
410
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
411
|
-
import { tmpdir } from "node:os";
|
|
412
|
-
import { join as join2 } from "node:path";
|
|
413
405
|
var StopRouter = class {
|
|
414
406
|
/** 主路由:返回 exit code(0 放行 / 2 阻断) */
|
|
415
407
|
run() {
|
|
@@ -438,20 +430,14 @@ var StopRouter = class {
|
|
|
438
430
|
}
|
|
439
431
|
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
440
432
|
/**
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
*/
|
|
433
|
+
* Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
|
|
434
|
+
* 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total → 未完成阻断提示)
|
|
435
|
+
* 维度 2: checklist 质量(checkAddCompleteness 未闭环 → 阻断)
|
|
436
|
+
* 互补非替代——codex 原 DB 进度分流语义上提 core,core checklist 质量语义保留。
|
|
437
|
+
*/
|
|
447
438
|
q4Check(plan, rounds, step, handoff, addRoute) {
|
|
439
|
+
void plan;
|
|
448
440
|
void rounds;
|
|
449
|
-
const limited = this.stopPromptLimited(plan);
|
|
450
|
-
if (limited) {
|
|
451
|
-
process.stderr.write(`[ADD Stop] \u5DF2\u8FBE\u672C Plan \u5F39\u6846\u4E0A\u9650(${this.stopPromptMax()})\uFF0C\u672C\u6B21\u653E\u884C\u3002\u8BF7\u5728\u540E\u7EED\u5B9E\u65BD\u4E2D\u5B8C\u6210\u9A8C\u6536\u95ED\u73AF\uFF08devlog + handoff\uFF09\u540E\u505C\u6B62\u3002
|
|
452
|
-
`);
|
|
453
|
-
return 0;
|
|
454
|
-
}
|
|
455
441
|
const [donePart, totalPart] = step.split("/");
|
|
456
442
|
if (/^\d+$/.test(donePart) && /^\d+$/.test(totalPart) && Number(donePart) < Number(totalPart)) {
|
|
457
443
|
return this.emitQ4Unclosed(
|
|
@@ -468,73 +454,6 @@ var StopRouter = class {
|
|
|
468
454
|
clearDevAction();
|
|
469
455
|
return this.emitQ4Pass();
|
|
470
456
|
}
|
|
471
|
-
// ── 弹框频控(临时方案,规则真源: hook-protocol-rules.toml [protocol.stop])──
|
|
472
|
-
// 计数隔离: 文件级 = PROJECT_DIR md5(跨项目不互扰);key 级 = planName(同项目跨 Plan 不互扰)
|
|
473
|
-
// 故障降级: 哨兵读/写异常 → fail-open 放行(不阻塞主流程)
|
|
474
|
-
/** 哨兵记录:同一项目同一 Plan 的弹框计数 */
|
|
475
|
-
stopPromptMax() {
|
|
476
|
-
const cfg = protocol.stop;
|
|
477
|
-
return cfg?.max_prompt_per_context ?? 3;
|
|
478
|
-
}
|
|
479
|
-
/** 哨兵窗口(毫秒) */
|
|
480
|
-
stopPromptWindowMs() {
|
|
481
|
-
const cfg = protocol.stop;
|
|
482
|
-
return (cfg?.window_minutes ?? 30) * 6e4;
|
|
483
|
-
}
|
|
484
|
-
/** 哨兵文件路径(项目隔离:PROJECT_DIR md5) */
|
|
485
|
-
stopSentinelPath() {
|
|
486
|
-
const projectDir = process.env.PROJECT_DIR || process.env.QODER_PROJECT_DIR || process.env.QODERCN_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
487
|
-
const md5 = createHash2("md5").update(projectDir).digest("hex").slice(0, 8);
|
|
488
|
-
return join2(tmpdir(), `add_stop_${md5}.json`);
|
|
489
|
-
}
|
|
490
|
-
/** 写哨兵(失败静默,fail-open) */
|
|
491
|
-
writeStopPromptSentinel(data) {
|
|
492
|
-
try {
|
|
493
|
-
writeFileSync2(this.stopSentinelPath(), JSON.stringify(data), "utf-8");
|
|
494
|
-
} catch {
|
|
495
|
-
}
|
|
496
|
-
}
|
|
497
|
-
/**
|
|
498
|
-
* 频控判定:返回 true = 已达上限应放行(不弹框)
|
|
499
|
-
* 生成态缺失 protocol.stop(TOML 未同步)→ 跳过频控保持既有行为(生成链幂等校验兜底)
|
|
500
|
-
*/
|
|
501
|
-
stopPromptLimited(plan) {
|
|
502
|
-
try {
|
|
503
|
-
const stopCfg = protocol.stop;
|
|
504
|
-
if (!stopCfg) return false;
|
|
505
|
-
const max = this.stopPromptMax();
|
|
506
|
-
const windowMs = this.stopPromptWindowMs();
|
|
507
|
-
const path3 = this.stopSentinelPath();
|
|
508
|
-
let data = {};
|
|
509
|
-
if (existsSync2(path3)) {
|
|
510
|
-
try {
|
|
511
|
-
data = JSON.parse(readFileSync2(path3, "utf-8")) ?? {};
|
|
512
|
-
} catch {
|
|
513
|
-
return true;
|
|
514
|
-
}
|
|
515
|
-
}
|
|
516
|
-
const now = Date.now();
|
|
517
|
-
const entry = data[plan];
|
|
518
|
-
if (!entry) {
|
|
519
|
-
data[plan] = { count: 1, lastPromptAt: now };
|
|
520
|
-
this.writeStopPromptSentinel(data);
|
|
521
|
-
return false;
|
|
522
|
-
}
|
|
523
|
-
if (now - entry.lastPromptAt > windowMs) {
|
|
524
|
-
data[plan] = { count: 1, lastPromptAt: now };
|
|
525
|
-
this.writeStopPromptSentinel(data);
|
|
526
|
-
return false;
|
|
527
|
-
}
|
|
528
|
-
if (entry.count >= max) {
|
|
529
|
-
return true;
|
|
530
|
-
}
|
|
531
|
-
data[plan] = { count: entry.count + 1, lastPromptAt: now };
|
|
532
|
-
this.writeStopPromptSentinel(data);
|
|
533
|
-
return false;
|
|
534
|
-
} catch {
|
|
535
|
-
return true;
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
457
|
/** Q0: DB 不可用(core: stderr + 2) */
|
|
539
458
|
emitQ0(reason) {
|
|
540
459
|
process.stderr.write(
|
|
@@ -11,22 +11,8 @@ import { spawnSync } from "node:child_process"
|
|
|
11
11
|
import { detectActiveAdd, localIsoSeconds } from "./common.js"
|
|
12
12
|
import { writeHookEvent } from "./notify.js"
|
|
13
13
|
import { doc } from "./rules.js"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
id?: string
|
|
17
|
-
heading?: string
|
|
18
|
-
required?: boolean
|
|
19
|
-
anchor?: string
|
|
20
|
-
within?: string
|
|
21
|
-
subsections?: Array<{ heading?: string }>
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface SchemaFile {
|
|
25
|
-
sections: SchemaSection[]
|
|
26
|
-
placeholders?: string[]
|
|
27
|
-
forbidden_terms?: string[]
|
|
28
|
-
groupColumn?: number | string
|
|
29
|
-
}
|
|
14
|
+
// 校验逻辑真源:core 校验层(Plan core-validation-lifecycle 轮 3——守卫不再内联实现)
|
|
15
|
+
import { validateAgainstSchema, type SchemaFile } from "../validation/schema-validator.js"
|
|
30
16
|
|
|
31
17
|
/** token 规则条目(rules.doc.token_rules 结构) */
|
|
32
18
|
interface TokenRule {
|
|
@@ -161,104 +147,35 @@ export class DocFormatGuard {
|
|
|
161
147
|
|
|
162
148
|
/** 章节/锚定/占位符/禁词校验,返回 struct 统计 */
|
|
163
149
|
private runSchemaChecks(schema: SchemaFile, templateName: string, content: string, isSearchReplace: boolean): { applied: number; missed: number; anchorHit: boolean } {
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
const
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
? readFileSync(join(templatesDir, templateName), "utf-8")
|
|
182
|
-
: ""
|
|
183
|
-
const refLine = templateContent.split("\n").find((l) => l.includes(section.anchor as string))
|
|
184
|
-
if (!refLine) {
|
|
185
|
-
process.stderr.write(`[doc-format-guard] anchor_miss: schema ${section.id} 声明的 anchor '${section.anchor}' 在 ${templateName} 中未定位,跳过该规则(冒烟巡检兑底)\n`)
|
|
186
|
-
applied--
|
|
187
|
-
continue
|
|
188
|
-
}
|
|
189
|
-
const tokens = [...new Set(refLine.replace(/[#*`|(){]/g, " ").split(/\s+/).filter((t) => t !== "" && !t.includes("{")))]
|
|
190
|
-
if (tokens.length === 0) {
|
|
191
|
-
applied--
|
|
192
|
-
continue
|
|
193
|
-
}
|
|
194
|
-
let scope = content
|
|
195
|
-
if (section.within) {
|
|
196
|
-
if (content.includes(section.within)) {
|
|
197
|
-
const startIdx = content.indexOf(section.within)
|
|
198
|
-
const endIdx = content.indexOf("\n## ", startIdx + 1)
|
|
199
|
-
scope = content.slice(startIdx, endIdx === -1 ? undefined : endIdx)
|
|
200
|
-
} else {
|
|
201
|
-
process.stderr.write(`[doc-format-guard] within_miss: schema ${section.id} 的 within '${section.within}' 在文档中未定位,跳过该规则\n`)
|
|
202
|
-
applied--
|
|
203
|
-
continue
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
const missTokens = tokens.filter((tok) => !scope.includes(tok))
|
|
207
|
-
if (missTokens.length > 0) {
|
|
208
|
-
this.issues.push(` 缺锚点(${section.id}): ${missTokens.join(" ")}`)
|
|
209
|
-
missed++
|
|
210
|
-
anchorHit = false
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// 必选章节
|
|
215
|
-
for (const heading of requiredHeadings) {
|
|
216
|
-
applied++
|
|
217
|
-
if (!content.includes(heading)) {
|
|
218
|
-
this.issues.push(` 缺章节: ${heading}`)
|
|
219
|
-
missed++
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
// 子章节
|
|
223
|
-
for (const sub of subs) {
|
|
224
|
-
applied++
|
|
225
|
-
if (!content.includes(sub)) {
|
|
226
|
-
this.issues.push(` 缺子章节: ${sub}`)
|
|
227
|
-
missed++
|
|
228
|
-
}
|
|
229
|
-
}
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// 占位符
|
|
233
|
-
for (const ph of placeholders) {
|
|
234
|
-
if (content.includes(ph)) {
|
|
235
|
-
this.issues.push(` 未替换占位符: ${ph}`)
|
|
236
|
-
missed++
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
// 结构位禁词(标题行 + groupColumn)
|
|
241
|
-
let structText = (content.match(/^#{2,}\s.*$/gm) ?? []).join("\n")
|
|
242
|
-
const col = typeof schema.groupColumn === "number" ? schema.groupColumn : Number(schema.groupColumn)
|
|
243
|
-
if (!Number.isNaN(col) && col > 0) {
|
|
244
|
-
const colLines = content
|
|
245
|
-
.split("\n")
|
|
246
|
-
.map((l) => {
|
|
247
|
-
const cells = l.split("|")
|
|
248
|
-
return cells.length > col ? (cells[col + 1] ?? "").trim() : ""
|
|
249
|
-
})
|
|
250
|
-
.filter(Boolean)
|
|
251
|
-
structText += "\n" + colLines.join("\n")
|
|
252
|
-
}
|
|
253
|
-
for (const term of terms) {
|
|
254
|
-
applied++
|
|
255
|
-
if (structText.includes(term)) {
|
|
256
|
-
this.issues.push(` 结构位禁词: ${term}`)
|
|
257
|
-
missed++
|
|
258
|
-
}
|
|
150
|
+
// ★ 校验真源 = core 校验层(Plan core-validation-lifecycle 轮 3:删除内联实现)
|
|
151
|
+
// SearchReplace 只传 patch → 标题/锚定类规则不适用,仅保留占位符与结构位禁词
|
|
152
|
+
const templatePath = join(this.projectDir, this.magicDir, "templates", templateName)
|
|
153
|
+
const templateContent = existsSync(templatePath) ? readFileSync(templatePath, "utf-8") : ""
|
|
154
|
+
const all = validateAgainstSchema(content, schema, { templateContent })
|
|
155
|
+
const relevant = isSearchReplace
|
|
156
|
+
? all.filter((i) => i.code === "PLACEHOLDER_LEFT" || i.code === "FORBIDDEN_TERM")
|
|
157
|
+
: all
|
|
158
|
+
|
|
159
|
+
for (const i of relevant) this.issues.push(` ${i.detail}`)
|
|
160
|
+
|
|
161
|
+
const missed = relevant.length
|
|
162
|
+
const declared = this.countDeclaredRules(schema, isSearchReplace)
|
|
163
|
+
return {
|
|
164
|
+
applied: Math.max(missed, declared),
|
|
165
|
+
missed,
|
|
166
|
+
anchorHit: !relevant.some((i) => i.code === "ANCHOR_MISS"),
|
|
259
167
|
}
|
|
168
|
+
}
|
|
260
169
|
|
|
261
|
-
|
|
170
|
+
/** 已声明规则数(用于 struct_score 分母;口径与原内联实现一致:按规则条数计) */
|
|
171
|
+
private countDeclaredRules(schema: SchemaFile, isSearchReplace: boolean): number {
|
|
172
|
+
const placeholders = (schema.placeholders ?? []).length
|
|
173
|
+
const terms = (schema.forbidden_terms ?? []).length
|
|
174
|
+
if (isSearchReplace) return placeholders + terms
|
|
175
|
+
const anchors = schema.sections.filter((s) => s.anchor).length
|
|
176
|
+
const required = schema.sections.filter((s) => s.required === true && s.heading).length
|
|
177
|
+
const subs = schema.sections.flatMap((s) => s.subsections ?? []).filter((s) => s.heading).length
|
|
178
|
+
return anchors + required + subs + placeholders + terms
|
|
262
179
|
}
|
|
263
180
|
|
|
264
181
|
/** 算法化规则校验(真源: [doc.anti_cheat] + HITL 表非空 + handoff 冲突)
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
//
|
|
4
4
|
// 设计范式: OOP 路由类(§1 DPS 哨兵自动化 / §2 Edit·Write 守卫 / §3 Bash 增强)。
|
|
5
5
|
|
|
6
|
-
import { existsSync, readFileSync, readdirSync, writeFileSync } from "node:fs"
|
|
6
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, appendFileSync, writeFileSync } from "node:fs"
|
|
7
7
|
import { join, basename, dirname } from "node:path"
|
|
8
8
|
import { jsonGet } from "./common.js"
|
|
9
9
|
import { AuditBridge } from "./audit-bridge.js"
|
|
10
|
+
import { evidenceEnabled, EVIDENCE_QUEUE_FILE, MEMORY_DIR_NAME, recallMode } from "../scripts/mcp-server/shared/memory/switches.js"
|
|
11
|
+
import { buildEvidenceEvent, classifyEvidenceSource } from "../scripts/mcp-server/shared/memory/jobs/evidence-collector.js"
|
|
10
12
|
|
|
11
13
|
/** 纯函数:从 input 中提取 output/content 拼接文本(对齐 bash grep -o 组合) */
|
|
12
14
|
function extractOutputText(raw: string): string {
|
|
@@ -165,6 +167,9 @@ export class PostToolRouter {
|
|
|
165
167
|
|
|
166
168
|
// §2f: 审计桥接(Task 8.1,ADD-7 自动化)——文件写入事件 → jsonl → MCP 常驻消费落库
|
|
167
169
|
this.emitWriteEvent(filePath)
|
|
170
|
+
|
|
171
|
+
// §2g: Memory 采证入队(白名单 → evidence-queue.jsonl → consolidation 异步落库)
|
|
172
|
+
this.emitMemoryEvidence(filePath)
|
|
168
173
|
} else if (toolName === "Bash") {
|
|
169
174
|
// ═══════════════ §3: Bash matcher: 结果增强(扩展点: codex 文本)═══════════════
|
|
170
175
|
this.emitLine(this.emitBashDone())
|
|
@@ -200,6 +205,33 @@ export class PostToolRouter {
|
|
|
200
205
|
this.auditBridge.emit(filePath)
|
|
201
206
|
}
|
|
202
207
|
|
|
208
|
+
/**
|
|
209
|
+
* §2g Memory 采证入队(Spec §10 PostToolUse 白名单,扩展点):
|
|
210
|
+
* 白名单路径(plans/specs/reviews/handoff)→ 追加 evidence-queue.jsonl(幂等 dedupKey),
|
|
211
|
+
* 由 consolidation job 异步落库。ADD_MEMORY_EVIDENCE=off 或 RECALL_MODE=off 时关闭。
|
|
212
|
+
* fail-open:任何异常不阻断文件写入主路径(Plan §9.3)。
|
|
213
|
+
*/
|
|
214
|
+
protected emitMemoryEvidence(filePath: string): void {
|
|
215
|
+
try {
|
|
216
|
+
if (!evidenceEnabled() || recallMode() === "off") return
|
|
217
|
+
if (!classifyEvidenceSource(filePath)) return
|
|
218
|
+
if (!this.magicDir) return
|
|
219
|
+
let excerpt = ""
|
|
220
|
+
try {
|
|
221
|
+
excerpt = readFileSync(filePath, "utf-8").slice(0, 500)
|
|
222
|
+
} catch {
|
|
223
|
+
excerpt = basename(filePath)
|
|
224
|
+
}
|
|
225
|
+
const ev = buildEvidenceEvent(filePath, excerpt)
|
|
226
|
+
const dir = join(this.projectDir, this.magicDir, MEMORY_DIR_NAME)
|
|
227
|
+
mkdirSync(dir, { recursive: true })
|
|
228
|
+
appendFileSync(join(dir, EVIDENCE_QUEUE_FILE), JSON.stringify(ev) + "\n", "utf-8")
|
|
229
|
+
this.emitLine(`[ADD PostToolUse] 🧠 记忆采证入队: ${filePath}(consolidation 时落库)\n`)
|
|
230
|
+
} catch {
|
|
231
|
+
/* fail-open */
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
203
235
|
/** §3 Bash 增强(core: lint/tsc;codex 子类: lint/typecheck/test) */
|
|
204
236
|
protected emitBashDone(): string {
|
|
205
237
|
return "[ADD PostToolUse] 命令执行完成。如有 lint/tsc 错误请修复。\n"
|
|
@@ -17,6 +17,14 @@ import {
|
|
|
17
17
|
import { loadDevKeywords, matchTrigger } from "./vocabulary.js"
|
|
18
18
|
import { writeHookEvent } from "./notify.js"
|
|
19
19
|
import { event } from "./rules.js"
|
|
20
|
+
import { recallMode } from "../scripts/mcp-server/shared/memory/switches.js"
|
|
21
|
+
import {
|
|
22
|
+
buildStageRecallHint,
|
|
23
|
+
detectRecallStage,
|
|
24
|
+
} from "../scripts/mcp-server/shared/memory/metrics/stage-words.js"
|
|
25
|
+
|
|
26
|
+
/** 显式记忆回忆意图触发词(Plan §9.1:对话触发词仅作补充入口;vocabulary 文档类别 G 同步维护) */
|
|
27
|
+
const MEMORY_RECALL_HINT_RE = /之前|上次|还记得|记得吗|为什么当时|类似问题|历史决策|以前的方案|曾经怎么/i
|
|
20
28
|
|
|
21
29
|
/**
|
|
22
30
|
* UserPromptSubmit 触发词路由基类(模板方法):
|
|
@@ -108,6 +116,41 @@ export class PromptRouter {
|
|
|
108
116
|
// core 协议: 无前置注入
|
|
109
117
|
}
|
|
110
118
|
|
|
119
|
+
/**
|
|
120
|
+
* 显式记忆回忆提示(Spec §10 / Plan §9.1 补充入口):
|
|
121
|
+
* 命中回忆词且 ADD_MEMORY_RECALL_MODE != off → 提示调用 recall_memory。
|
|
122
|
+
* 不做自动召回(同步 Hook ≤200ms 无 DB);shadow 模式下提示语明示"仅显式调用"。
|
|
123
|
+
*/
|
|
124
|
+
protected maybeMemoryHint(prompt: string): void {
|
|
125
|
+
try {
|
|
126
|
+
if (recallMode() === "off") return
|
|
127
|
+
if (!MEMORY_RECALL_HINT_RE.test(prompt)) return
|
|
128
|
+
const mode = recallMode()
|
|
129
|
+
const suffix = mode === "shadow" ? "(当前 shadow 模式:召回将落审计,Hook 不自动注入)" : ""
|
|
130
|
+
process.stdout.write(
|
|
131
|
+
`[Memory] 检测到历史回忆意图${suffix}。可调用 recall_memory({ query: <你的问题>, stage: "prompt" }) 获取受治理的记忆上下文(含来源与评分)。\n`
|
|
132
|
+
)
|
|
133
|
+
} catch {
|
|
134
|
+
/* fail-open */
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 阶段确定性召回提示(Spec §2 §DeterministicRecall,Plan §3.1 方案 B1 的 Hook 侧):
|
|
140
|
+
* 命中 ADD 阶段词 → 提示到工具侧做确定性召回(plan-start/spec-start/dps/rahs/handoff)。
|
|
141
|
+
* Hook 同步路径无 DB:只输出提示文本,不召回、不写审计;任何异常 fail-open。
|
|
142
|
+
*/
|
|
143
|
+
protected maybeStageHint(prompt: string): void {
|
|
144
|
+
try {
|
|
145
|
+
const stage = detectRecallStage(prompt)
|
|
146
|
+
if (!stage) return
|
|
147
|
+
const hint = buildStageRecallHint(stage, recallMode())
|
|
148
|
+
if (hint) process.stdout.write(hint)
|
|
149
|
+
} catch {
|
|
150
|
+
/* fail-open */
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
111
154
|
/** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
|
|
112
155
|
protected layer3Json(): boolean {
|
|
113
156
|
return false
|
|
@@ -148,6 +191,10 @@ export class PromptRouter {
|
|
|
148
191
|
return 0
|
|
149
192
|
}
|
|
150
193
|
|
|
194
|
+
// ─── Layer 1.5: 显式记忆回忆意图(补充入口;不替代确定性召回,见 Plan §9.1)───
|
|
195
|
+
this.maybeMemoryHint(prompt)
|
|
196
|
+
this.maybeStageHint(prompt)
|
|
197
|
+
|
|
151
198
|
// ─── 开发关键词检测(动态加载) ───
|
|
152
199
|
const devKw = loadDevKeywords()
|
|
153
200
|
if (devKw.length === 0) return 0
|