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
|
@@ -144,10 +144,6 @@ var protocol = {
|
|
|
144
144
|
".trae",
|
|
145
145
|
".codex"
|
|
146
146
|
]
|
|
147
|
-
},
|
|
148
|
-
"stop": {
|
|
149
|
-
"max_prompt_per_context": 3,
|
|
150
|
-
"window_minutes": 30
|
|
151
147
|
}
|
|
152
148
|
};
|
|
153
149
|
|
|
@@ -278,7 +274,7 @@ function projectHash() {
|
|
|
278
274
|
var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
|
|
279
275
|
|
|
280
276
|
// templates/core/governance/post-tool-router.ts
|
|
281
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
277
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, appendFileSync as appendFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
282
278
|
import { join as join3, basename as basename2, dirname as dirname2 } from "node:path";
|
|
283
279
|
|
|
284
280
|
// templates/core/governance/notify.ts
|
|
@@ -343,6 +339,37 @@ var AuditBridge = class {
|
|
|
343
339
|
}
|
|
344
340
|
};
|
|
345
341
|
|
|
342
|
+
// templates/core/scripts/mcp-server/shared/memory/switches.ts
|
|
343
|
+
function recallMode(env = process.env) {
|
|
344
|
+
const v = (env.ADD_MEMORY_RECALL_MODE ?? "shadow").toLowerCase();
|
|
345
|
+
return v === "off" || v === "inject" ? v : "shadow";
|
|
346
|
+
}
|
|
347
|
+
function evidenceEnabled(env = process.env) {
|
|
348
|
+
return (env.ADD_MEMORY_EVIDENCE ?? "on").toLowerCase() !== "off";
|
|
349
|
+
}
|
|
350
|
+
var MEMORY_DIR_NAME = "memory";
|
|
351
|
+
var EVIDENCE_QUEUE_FILE = "evidence-queue.jsonl";
|
|
352
|
+
var L1_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
353
|
+
|
|
354
|
+
// templates/core/scripts/mcp-server/shared/memory/jobs/evidence-collector.ts
|
|
355
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
356
|
+
function classifyEvidenceSource(filePath) {
|
|
357
|
+
const p = filePath.replace(/\\/g, "/");
|
|
358
|
+
if (/(^|\/)plans\/[^/]*-plan-v\d+\.md$/.test(p)) return "PLAN";
|
|
359
|
+
if (/(^|\/)plans\/[^/]*-add-route-v\d+\.md$/.test(p)) return "PLAN";
|
|
360
|
+
if (/(^|\/)specs\/.+\.md$/.test(p)) return "SPEC";
|
|
361
|
+
if (/handoff[^/]*\.md$/i.test(p)) return "HANDOFF";
|
|
362
|
+
if (/(^|\/)reviews\/.+\.md$/.test(p)) return "DEV_OPERATION";
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
function buildEvidenceEvent(filePath, excerpt, occurredAt = /* @__PURE__ */ new Date()) {
|
|
366
|
+
const sourceType = classifyEvidenceSource(filePath);
|
|
367
|
+
if (!sourceType) throw new Error(`\u975E\u767D\u540D\u5355\u8DEF\u5F84: ${filePath}`);
|
|
368
|
+
const sourceRef = filePath;
|
|
369
|
+
const dedupKey = createHash2("sha256").update(`${sourceType}|${sourceRef}|${excerpt}`, "utf8").digest("hex").slice(0, 32);
|
|
370
|
+
return { dedupKey, sourceType, sourceRef, excerpt: excerpt.slice(0, 500), occurredAt: occurredAt.toISOString() };
|
|
371
|
+
}
|
|
372
|
+
|
|
346
373
|
// templates/core/governance/post-tool-router.ts
|
|
347
374
|
function extractOutputText(raw) {
|
|
348
375
|
const m1 = raw.match(/"output"\s*:\s*"([^"]*)"/);
|
|
@@ -473,6 +500,7 @@ var PostToolRouter = class {
|
|
|
473
500
|
this.postDocSections(filePath);
|
|
474
501
|
this.emitLine(this.emitAuditReminder(filePath));
|
|
475
502
|
this.emitWriteEvent(filePath);
|
|
503
|
+
this.emitMemoryEvidence(filePath);
|
|
476
504
|
} else if (toolName2 === "Bash") {
|
|
477
505
|
this.emitLine(this.emitBashDone());
|
|
478
506
|
} else {
|
|
@@ -500,6 +528,32 @@ var PostToolRouter = class {
|
|
|
500
528
|
emitWriteEvent(filePath) {
|
|
501
529
|
this.auditBridge.emit(filePath);
|
|
502
530
|
}
|
|
531
|
+
/**
|
|
532
|
+
* §2g Memory 采证入队(Spec §10 PostToolUse 白名单,扩展点):
|
|
533
|
+
* 白名单路径(plans/specs/reviews/handoff)→ 追加 evidence-queue.jsonl(幂等 dedupKey),
|
|
534
|
+
* 由 consolidation job 异步落库。ADD_MEMORY_EVIDENCE=off 或 RECALL_MODE=off 时关闭。
|
|
535
|
+
* fail-open:任何异常不阻断文件写入主路径(Plan §9.3)。
|
|
536
|
+
*/
|
|
537
|
+
emitMemoryEvidence(filePath) {
|
|
538
|
+
try {
|
|
539
|
+
if (!evidenceEnabled() || recallMode() === "off") return;
|
|
540
|
+
if (!classifyEvidenceSource(filePath)) return;
|
|
541
|
+
if (!this.magicDir) return;
|
|
542
|
+
let excerpt = "";
|
|
543
|
+
try {
|
|
544
|
+
excerpt = readFileSync2(filePath, "utf-8").slice(0, 500);
|
|
545
|
+
} catch {
|
|
546
|
+
excerpt = basename2(filePath);
|
|
547
|
+
}
|
|
548
|
+
const ev = buildEvidenceEvent(filePath, excerpt);
|
|
549
|
+
const dir = join3(this.projectDir, this.magicDir, MEMORY_DIR_NAME);
|
|
550
|
+
mkdirSync2(dir, { recursive: true });
|
|
551
|
+
appendFileSync2(join3(dir, EVIDENCE_QUEUE_FILE), JSON.stringify(ev) + "\n", "utf-8");
|
|
552
|
+
this.emitLine(`[ADD PostToolUse] \u{1F9E0} \u8BB0\u5FC6\u91C7\u8BC1\u5165\u961F: ${filePath}\uFF08consolidation \u65F6\u843D\u5E93\uFF09
|
|
553
|
+
`);
|
|
554
|
+
} catch {
|
|
555
|
+
}
|
|
556
|
+
}
|
|
503
557
|
/** §3 Bash 增强(core: lint/tsc;codex 子类: lint/typecheck/test) */
|
|
504
558
|
emitBashDone() {
|
|
505
559
|
return "[ADD PostToolUse] \u547D\u4EE4\u6267\u884C\u5B8C\u6210\u3002\u5982\u6709 lint/tsc \u9519\u8BEF\u8BF7\u4FEE\u590D\u3002\n";
|
|
@@ -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
|
|
|
@@ -447,7 +443,45 @@ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status =
|
|
|
447
443
|
appendFileSync(file, line);
|
|
448
444
|
}
|
|
449
445
|
|
|
446
|
+
// templates/core/scripts/mcp-server/shared/memory/switches.ts
|
|
447
|
+
function recallMode(env = process.env) {
|
|
448
|
+
const v = (env.ADD_MEMORY_RECALL_MODE ?? "shadow").toLowerCase();
|
|
449
|
+
return v === "off" || v === "inject" ? v : "shadow";
|
|
450
|
+
}
|
|
451
|
+
var L1_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
452
|
+
|
|
453
|
+
// templates/core/scripts/mcp-server/shared/memory/metrics/stage-words.ts
|
|
454
|
+
var STAGE_LABEL = {
|
|
455
|
+
"plan-start": "Plan \u8D77\u8349",
|
|
456
|
+
"spec-start": "Spec \u8D77\u8349",
|
|
457
|
+
dps: "DPS \u95E8\u7981",
|
|
458
|
+
rahs: "RAHS \u95E8\u7981",
|
|
459
|
+
handoff: "Handoff \u4EA4\u63A5"
|
|
460
|
+
};
|
|
461
|
+
var STAGE_PATTERNS = [
|
|
462
|
+
{ stage: "handoff", re: /handoff|交接(手册|文档|说明)?|交接给/i },
|
|
463
|
+
{ stage: "rahs", re: /rahs|注意力漂移|执行健康度/i },
|
|
464
|
+
{ stage: "dps", re: /dps|文档质量闸门|质量闸门|门禁(评分|检查)?/i },
|
|
465
|
+
{ stage: "spec-start", re: /(生成|写|新建|起草|补)\s*(spec|规格|三元组)|specs?\s*三元组|WHEN-?THEN/i },
|
|
466
|
+
{ stage: "plan-start", re: /(生成|写|新建|起草|补)\s*(plan|计划|方案)|plan\s*阶段|规划阶段/i }
|
|
467
|
+
];
|
|
468
|
+
function detectRecallStage(prompt) {
|
|
469
|
+
if (!prompt) return null;
|
|
470
|
+
for (const { stage, re } of STAGE_PATTERNS) {
|
|
471
|
+
if (re.test(prompt)) return stage;
|
|
472
|
+
}
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
function buildStageRecallHint(stage, mode) {
|
|
476
|
+
if (mode === "off") return null;
|
|
477
|
+
const label = STAGE_LABEL[stage];
|
|
478
|
+
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";
|
|
479
|
+
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}
|
|
480
|
+
`;
|
|
481
|
+
}
|
|
482
|
+
|
|
450
483
|
// templates/core/governance/prompt-router.ts
|
|
484
|
+
var MEMORY_RECALL_HINT_RE = /之前|上次|还记得|记得吗|为什么当时|类似问题|历史决策|以前的方案|曾经怎么/i;
|
|
451
485
|
var PromptRouter = class {
|
|
452
486
|
magicDir;
|
|
453
487
|
constructor(magicDir) {
|
|
@@ -510,6 +544,38 @@ var PromptRouter = class {
|
|
|
510
544
|
/** 前置注入(core: 无;qoder: 无条件 "ADD workflow active" JSON) */
|
|
511
545
|
preamble(_input) {
|
|
512
546
|
}
|
|
547
|
+
/**
|
|
548
|
+
* 显式记忆回忆提示(Spec §10 / Plan §9.1 补充入口):
|
|
549
|
+
* 命中回忆词且 ADD_MEMORY_RECALL_MODE != off → 提示调用 recall_memory。
|
|
550
|
+
* 不做自动召回(同步 Hook ≤200ms 无 DB);shadow 模式下提示语明示"仅显式调用"。
|
|
551
|
+
*/
|
|
552
|
+
maybeMemoryHint(prompt) {
|
|
553
|
+
try {
|
|
554
|
+
if (recallMode() === "off") return;
|
|
555
|
+
if (!MEMORY_RECALL_HINT_RE.test(prompt)) return;
|
|
556
|
+
const mode = recallMode();
|
|
557
|
+
const suffix = mode === "shadow" ? "\uFF08\u5F53\u524D shadow \u6A21\u5F0F\uFF1A\u53EC\u56DE\u5C06\u843D\u5BA1\u8BA1\uFF0CHook \u4E0D\u81EA\u52A8\u6CE8\u5165\uFF09" : "";
|
|
558
|
+
process.stdout.write(
|
|
559
|
+
`[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
|
|
560
|
+
`
|
|
561
|
+
);
|
|
562
|
+
} catch {
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
/**
|
|
566
|
+
* 阶段确定性召回提示(Spec §2 §DeterministicRecall,Plan §3.1 方案 B1 的 Hook 侧):
|
|
567
|
+
* 命中 ADD 阶段词 → 提示到工具侧做确定性召回(plan-start/spec-start/dps/rahs/handoff)。
|
|
568
|
+
* Hook 同步路径无 DB:只输出提示文本,不召回、不写审计;任何异常 fail-open。
|
|
569
|
+
*/
|
|
570
|
+
maybeStageHint(prompt) {
|
|
571
|
+
try {
|
|
572
|
+
const stage = detectRecallStage(prompt);
|
|
573
|
+
if (!stage) return;
|
|
574
|
+
const hint = buildStageRecallHint(stage, recallMode());
|
|
575
|
+
if (hint) process.stdout.write(hint);
|
|
576
|
+
} catch {
|
|
577
|
+
}
|
|
578
|
+
}
|
|
513
579
|
/** Layer 3 输出形态(core: 纯文本逐行;qoder: hookSpecificOutput JSON 包) */
|
|
514
580
|
layer3Json() {
|
|
515
581
|
return false;
|
|
@@ -541,6 +607,8 @@ var PromptRouter = class {
|
|
|
541
607
|
for (const m of matched) process.stdout.write(m + "\n");
|
|
542
608
|
return 0;
|
|
543
609
|
}
|
|
610
|
+
this.maybeMemoryHint(prompt);
|
|
611
|
+
this.maybeStageHint(prompt);
|
|
544
612
|
const devKw = loadDevKeywords();
|
|
545
613
|
if (devKw.length === 0) return 0;
|
|
546
614
|
if (!this.devKwMatched(prompt, devKw)) return 0;
|
|
@@ -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
|
|
|
@@ -483,8 +479,34 @@ var PreloadTemplates = class _PreloadTemplates {
|
|
|
483
479
|
};
|
|
484
480
|
|
|
485
481
|
// templates/core/governance/session-start-guard.ts
|
|
486
|
-
import { existsSync as existsSync3, readdirSync, statSync } from "node:fs";
|
|
482
|
+
import { existsSync as existsSync3, readdirSync, readFileSync as readFileSync3, statSync } from "node:fs";
|
|
487
483
|
import { join as join3 } from "node:path";
|
|
484
|
+
|
|
485
|
+
// templates/core/scripts/mcp-server/shared/memory/switches.ts
|
|
486
|
+
function recallMode(env = process.env) {
|
|
487
|
+
const v = (env.ADD_MEMORY_RECALL_MODE ?? "shadow").toLowerCase();
|
|
488
|
+
return v === "off" || v === "inject" ? v : "shadow";
|
|
489
|
+
}
|
|
490
|
+
function memoryMaxTokens(env = process.env) {
|
|
491
|
+
const n = Number(env.ADD_MEMORY_MAX_TOKENS ?? 600);
|
|
492
|
+
return Number.isFinite(n) && n > 0 ? Math.floor(n) : 600;
|
|
493
|
+
}
|
|
494
|
+
var MEMORY_DIR_NAME = "memory";
|
|
495
|
+
var L1_SNAPSHOT_FILE = "l1-context.md";
|
|
496
|
+
var L1_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
497
|
+
|
|
498
|
+
// templates/core/scripts/mcp-server/shared/memory/retrieval/context-builder.ts
|
|
499
|
+
function estimateTokens(text) {
|
|
500
|
+
let cjk = 0;
|
|
501
|
+
let other = 0;
|
|
502
|
+
for (const ch of text) {
|
|
503
|
+
if (/[-ヿ㐀-䶿一-鿿豈--]/.test(ch)) cjk++;
|
|
504
|
+
else other++;
|
|
505
|
+
}
|
|
506
|
+
return cjk + Math.ceil(other / 4);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// templates/core/governance/session-start-guard.ts
|
|
488
510
|
var SessionStartGuard = class {
|
|
489
511
|
projectDir;
|
|
490
512
|
magicDir;
|
|
@@ -503,6 +525,7 @@ var SessionStartGuard = class {
|
|
|
503
525
|
this.emitIndex();
|
|
504
526
|
this.emitTodoReminder(state);
|
|
505
527
|
this.emitHitlPending();
|
|
528
|
+
this.emitMemoryL1();
|
|
506
529
|
return 0;
|
|
507
530
|
}
|
|
508
531
|
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
@@ -551,6 +574,45 @@ var SessionStartGuard = class {
|
|
|
551
574
|
}
|
|
552
575
|
}
|
|
553
576
|
}
|
|
577
|
+
/**
|
|
578
|
+
* ⑤ Memory L1 快照注入(ADD_MEMORY_RECALL_MODE 三态):
|
|
579
|
+
* off → 不输出
|
|
580
|
+
* shadow → 仅提示快照存在(召回可执行并落审计,但不注入上下文)
|
|
581
|
+
* inject → 读预计算快照注入(新鲜度 ≤7 天,token 预算截断,带来源边界标签)
|
|
582
|
+
* 同步 Hook ≤200ms 约束:只读文件,任何异常 fail-open 静默跳过。
|
|
583
|
+
*/
|
|
584
|
+
emitMemoryL1() {
|
|
585
|
+
try {
|
|
586
|
+
const mode = recallMode();
|
|
587
|
+
if (mode === "off" || !this.magicDir) return;
|
|
588
|
+
const file = join3(this.projectDir, this.magicDir, MEMORY_DIR_NAME, L1_SNAPSHOT_FILE);
|
|
589
|
+
if (!existsSync3(file)) return;
|
|
590
|
+
if (mode === "shadow") {
|
|
591
|
+
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}
|
|
592
|
+
`);
|
|
593
|
+
return;
|
|
594
|
+
}
|
|
595
|
+
if (Date.now() - statSync(file).mtimeMs > L1_SNAPSHOT_TTL_MS) return;
|
|
596
|
+
const text = readFileSync3(file, "utf-8");
|
|
597
|
+
const budget = memoryMaxTokens();
|
|
598
|
+
if (estimateTokens(text) <= budget) {
|
|
599
|
+
process.stdout.write(text);
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
const lines = text.split("\n");
|
|
603
|
+
const kept = [];
|
|
604
|
+
let used = 0;
|
|
605
|
+
for (const line of lines) {
|
|
606
|
+
const t = estimateTokens(line);
|
|
607
|
+
if (used + t > budget) break;
|
|
608
|
+
kept.push(line);
|
|
609
|
+
used += t;
|
|
610
|
+
}
|
|
611
|
+
kept.push(`[Memory L1] \u26A0\uFE0F \u8D85\u51FA\u9884\u7B97 ${budget} tokens\uFF0C\u5DF2\u622A\u65AD\uFF08\u5B8C\u6574\u5FEB\u7167: ${file}\uFF09`);
|
|
612
|
+
process.stdout.write(kept.join("\n") + "\n");
|
|
613
|
+
} catch {
|
|
614
|
+
}
|
|
615
|
+
}
|
|
554
616
|
};
|
|
555
617
|
|
|
556
618
|
// templates/adapters/qoder/hooks/lib/qoder-env.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
|
|
|
@@ -410,10 +406,6 @@ function buildStopContext(quadrant, info) {
|
|
|
410
406
|
}
|
|
411
407
|
|
|
412
408
|
// templates/core/governance/stop-router.ts
|
|
413
|
-
import { createHash as createHash2 } from "node:crypto";
|
|
414
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
415
|
-
import { tmpdir } from "node:os";
|
|
416
|
-
import { join as join2 } from "node:path";
|
|
417
409
|
var StopRouter = class {
|
|
418
410
|
/** 主路由:返回 exit code(0 放行 / 2 阻断) */
|
|
419
411
|
run() {
|
|
@@ -442,20 +434,14 @@ var StopRouter = class {
|
|
|
442
434
|
}
|
|
443
435
|
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
444
436
|
/**
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
*/
|
|
437
|
+
* Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
|
|
438
|
+
* 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total → 未完成阻断提示)
|
|
439
|
+
* 维度 2: checklist 质量(checkAddCompleteness 未闭环 → 阻断)
|
|
440
|
+
* 互补非替代——codex 原 DB 进度分流语义上提 core,core checklist 质量语义保留。
|
|
441
|
+
*/
|
|
451
442
|
q4Check(plan, rounds, step, handoff, addRoute) {
|
|
443
|
+
void plan;
|
|
452
444
|
void rounds;
|
|
453
|
-
const limited = this.stopPromptLimited(plan);
|
|
454
|
-
if (limited) {
|
|
455
|
-
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
|
|
456
|
-
`);
|
|
457
|
-
return 0;
|
|
458
|
-
}
|
|
459
445
|
const [donePart, totalPart] = step.split("/");
|
|
460
446
|
if (/^\d+$/.test(donePart) && /^\d+$/.test(totalPart) && Number(donePart) < Number(totalPart)) {
|
|
461
447
|
return this.emitQ4Unclosed(
|
|
@@ -472,73 +458,6 @@ var StopRouter = class {
|
|
|
472
458
|
clearDevAction();
|
|
473
459
|
return this.emitQ4Pass();
|
|
474
460
|
}
|
|
475
|
-
// ── 弹框频控(临时方案,规则真源: hook-protocol-rules.toml [protocol.stop])──
|
|
476
|
-
// 计数隔离: 文件级 = PROJECT_DIR md5(跨项目不互扰);key 级 = planName(同项目跨 Plan 不互扰)
|
|
477
|
-
// 故障降级: 哨兵读/写异常 → fail-open 放行(不阻塞主流程)
|
|
478
|
-
/** 哨兵记录:同一项目同一 Plan 的弹框计数 */
|
|
479
|
-
stopPromptMax() {
|
|
480
|
-
const cfg = protocol.stop;
|
|
481
|
-
return cfg?.max_prompt_per_context ?? 3;
|
|
482
|
-
}
|
|
483
|
-
/** 哨兵窗口(毫秒) */
|
|
484
|
-
stopPromptWindowMs() {
|
|
485
|
-
const cfg = protocol.stop;
|
|
486
|
-
return (cfg?.window_minutes ?? 30) * 6e4;
|
|
487
|
-
}
|
|
488
|
-
/** 哨兵文件路径(项目隔离:PROJECT_DIR md5) */
|
|
489
|
-
stopSentinelPath() {
|
|
490
|
-
const projectDir = process.env.PROJECT_DIR || process.env.QODER_PROJECT_DIR || process.env.QODERCN_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
491
|
-
const md5 = createHash2("md5").update(projectDir).digest("hex").slice(0, 8);
|
|
492
|
-
return join2(tmpdir(), `add_stop_${md5}.json`);
|
|
493
|
-
}
|
|
494
|
-
/** 写哨兵(失败静默,fail-open) */
|
|
495
|
-
writeStopPromptSentinel(data) {
|
|
496
|
-
try {
|
|
497
|
-
writeFileSync2(this.stopSentinelPath(), JSON.stringify(data), "utf-8");
|
|
498
|
-
} catch {
|
|
499
|
-
}
|
|
500
|
-
}
|
|
501
|
-
/**
|
|
502
|
-
* 频控判定:返回 true = 已达上限应放行(不弹框)
|
|
503
|
-
* 生成态缺失 protocol.stop(TOML 未同步)→ 跳过频控保持既有行为(生成链幂等校验兜底)
|
|
504
|
-
*/
|
|
505
|
-
stopPromptLimited(plan) {
|
|
506
|
-
try {
|
|
507
|
-
const stopCfg = protocol.stop;
|
|
508
|
-
if (!stopCfg) return false;
|
|
509
|
-
const max = this.stopPromptMax();
|
|
510
|
-
const windowMs = this.stopPromptWindowMs();
|
|
511
|
-
const path3 = this.stopSentinelPath();
|
|
512
|
-
let data = {};
|
|
513
|
-
if (existsSync2(path3)) {
|
|
514
|
-
try {
|
|
515
|
-
data = JSON.parse(readFileSync2(path3, "utf-8")) ?? {};
|
|
516
|
-
} catch {
|
|
517
|
-
return true;
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
const now = Date.now();
|
|
521
|
-
const entry = data[plan];
|
|
522
|
-
if (!entry) {
|
|
523
|
-
data[plan] = { count: 1, lastPromptAt: now };
|
|
524
|
-
this.writeStopPromptSentinel(data);
|
|
525
|
-
return false;
|
|
526
|
-
}
|
|
527
|
-
if (now - entry.lastPromptAt > windowMs) {
|
|
528
|
-
data[plan] = { count: 1, lastPromptAt: now };
|
|
529
|
-
this.writeStopPromptSentinel(data);
|
|
530
|
-
return false;
|
|
531
|
-
}
|
|
532
|
-
if (entry.count >= max) {
|
|
533
|
-
return true;
|
|
534
|
-
}
|
|
535
|
-
data[plan] = { count: entry.count + 1, lastPromptAt: now };
|
|
536
|
-
this.writeStopPromptSentinel(data);
|
|
537
|
-
return false;
|
|
538
|
-
} catch {
|
|
539
|
-
return true;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
461
|
/** Q0: DB 不可用(core: stderr + 2) */
|
|
543
462
|
emitQ0(reason) {
|
|
544
463
|
process.stderr.write(
|