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
|
@@ -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(
|
|
@@ -388,10 +388,6 @@ var protocol = {
|
|
|
388
388
|
".trae",
|
|
389
389
|
".codex"
|
|
390
390
|
]
|
|
391
|
-
},
|
|
392
|
-
"stop": {
|
|
393
|
-
"max_prompt_per_context": 3,
|
|
394
|
-
"window_minutes": 30
|
|
395
391
|
}
|
|
396
392
|
};
|
|
397
393
|
|
|
@@ -500,6 +496,156 @@ function writeHookEvent(hook, decision, cmd, reason, plan = "unknown", status =
|
|
|
500
496
|
appendFileSync(file, line);
|
|
501
497
|
}
|
|
502
498
|
|
|
499
|
+
// templates/core/validation/schema-validator.ts
|
|
500
|
+
function countOccurrences(haystack, needle) {
|
|
501
|
+
if (!needle) return 0;
|
|
502
|
+
const h = normalizeWidth(haystack);
|
|
503
|
+
const n = normalizeWidth(needle);
|
|
504
|
+
let count = 0;
|
|
505
|
+
let idx = h.indexOf(n);
|
|
506
|
+
while (idx !== -1) {
|
|
507
|
+
count++;
|
|
508
|
+
idx = h.indexOf(n, idx + n.length);
|
|
509
|
+
}
|
|
510
|
+
return count;
|
|
511
|
+
}
|
|
512
|
+
var FULLWIDTH_START = 65281;
|
|
513
|
+
var FULLWIDTH_END = 65374;
|
|
514
|
+
var FULLWIDTH_TO_HALF = 65248;
|
|
515
|
+
var WIDTH_FOLD_EXTRA = { "\u3000": " " };
|
|
516
|
+
function normalizeWidth(text) {
|
|
517
|
+
let out = "";
|
|
518
|
+
for (const ch of text) {
|
|
519
|
+
const code = ch.codePointAt(0);
|
|
520
|
+
if (code >= FULLWIDTH_START && code <= FULLWIDTH_END) {
|
|
521
|
+
out += String.fromCharCode(code - FULLWIDTH_TO_HALF);
|
|
522
|
+
} else {
|
|
523
|
+
out += WIDTH_FOLD_EXTRA[ch] ?? ch;
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
return out;
|
|
527
|
+
}
|
|
528
|
+
function stripFencedBlocks(content) {
|
|
529
|
+
return content.replace(/```[\s\S]*?```/g, "\n");
|
|
530
|
+
}
|
|
531
|
+
function structText(content, groupColumn) {
|
|
532
|
+
let text = (content.match(/^#{2,}\s.*$/gm) ?? []).join("\n");
|
|
533
|
+
const col = typeof groupColumn === "number" ? groupColumn : Number(groupColumn);
|
|
534
|
+
if (Number.isFinite(col) && col > 0) {
|
|
535
|
+
const cells = content.split("\n").map((line) => {
|
|
536
|
+
const cells2 = line.split("|");
|
|
537
|
+
return cells2.length > col ? (cells2[col + 1] ?? "").trim() : "";
|
|
538
|
+
}).filter(Boolean);
|
|
539
|
+
text += "\n" + cells.join("\n");
|
|
540
|
+
}
|
|
541
|
+
return text;
|
|
542
|
+
}
|
|
543
|
+
function validateAnchors(content, schema, templateContent) {
|
|
544
|
+
const issues = [];
|
|
545
|
+
const nContent = normalizeWidth(content);
|
|
546
|
+
for (const section of schema.sections) {
|
|
547
|
+
if (!section.anchor) continue;
|
|
548
|
+
const refAnchor = normalizeWidth(section.anchor);
|
|
549
|
+
const refLine = templateContent.split("\n").find((l) => normalizeWidth(l).includes(refAnchor));
|
|
550
|
+
if (!refLine) continue;
|
|
551
|
+
const tokens = [
|
|
552
|
+
...new Set(
|
|
553
|
+
normalizeWidth(refLine).replace(/[#*`|(){]/g, " ").split(/\s+/).filter((t) => t !== "" && !t.includes("{"))
|
|
554
|
+
)
|
|
555
|
+
];
|
|
556
|
+
if (tokens.length === 0) continue;
|
|
557
|
+
let scope = nContent;
|
|
558
|
+
if (section.within) {
|
|
559
|
+
const startIdx = nContent.indexOf(normalizeWidth(section.within));
|
|
560
|
+
if (startIdx < 0) continue;
|
|
561
|
+
const endIdx = nContent.indexOf("\n## ", startIdx + 1);
|
|
562
|
+
scope = nContent.slice(startIdx, endIdx === -1 ? void 0 : endIdx);
|
|
563
|
+
}
|
|
564
|
+
const missTokens = tokens.filter((tok) => !scope.includes(tok));
|
|
565
|
+
if (missTokens.length > 0) {
|
|
566
|
+
issues.push({
|
|
567
|
+
code: "ANCHOR_MISS",
|
|
568
|
+
detail: `\u7F3A\u951A\u70B9(${section.id}): ${missTokens.join(" ")}`,
|
|
569
|
+
expected: section.anchor
|
|
570
|
+
});
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
return issues;
|
|
574
|
+
}
|
|
575
|
+
function inferRoundHeading(schema) {
|
|
576
|
+
const round = schema.sections.find((s) => s.heading?.includes("<\u7B2CN\u8F6E>"));
|
|
577
|
+
return round?.heading ?? null;
|
|
578
|
+
}
|
|
579
|
+
function countRounds(content, roundHeading) {
|
|
580
|
+
const heading = normalizeWidth(roundHeading);
|
|
581
|
+
if (!heading.includes("<\u7B2CN\u8F6E>")) return 0;
|
|
582
|
+
const pattern = heading.replace(/[.*+?^${}()|[\]\\]/g, "\\$&").replace("<\u7B2CN\u8F6E>", "\u7B2C\\s*\\d+\\s*\u8F6E");
|
|
583
|
+
const re = new RegExp(`^${pattern}`, "gm");
|
|
584
|
+
return (normalizeWidth(content).match(re) ?? []).length;
|
|
585
|
+
}
|
|
586
|
+
function validateAgainstSchema(content, schema, opts = {}) {
|
|
587
|
+
const issues = [];
|
|
588
|
+
const proseOnly = stripFencedBlocks(content);
|
|
589
|
+
const contentNormalized = normalizeWidth(content);
|
|
590
|
+
const roundHeading = opts.roundHeading ?? inferRoundHeading(schema);
|
|
591
|
+
for (const section of schema.sections) {
|
|
592
|
+
if (!section.heading) continue;
|
|
593
|
+
const isRoundSection = roundHeading !== null && section.heading === roundHeading;
|
|
594
|
+
if (isRoundSection) {
|
|
595
|
+
const need = opts.expectRounds ?? 1;
|
|
596
|
+
const found = countRounds(content, roundHeading);
|
|
597
|
+
if (found < need) {
|
|
598
|
+
issues.push({
|
|
599
|
+
code: "ROUND_COUNT_SHORT",
|
|
600
|
+
detail: `\u8F6E\u6B21\u7AE0\u8282\u4E0D\u8DB3\uFF1A\u671F\u671B ${need} \u8F6E\uFF0C\u5B9E\u9645 ${found} \u8F6E`,
|
|
601
|
+
expected: roundHeading
|
|
602
|
+
});
|
|
603
|
+
}
|
|
604
|
+
for (const sub of section.subsections ?? []) {
|
|
605
|
+
if (countOccurrences(content, sub.heading) < need) {
|
|
606
|
+
issues.push({
|
|
607
|
+
code: "MISSING_SUBSECTION",
|
|
608
|
+
detail: `\u8F6E\u6B21\u5B50\u7AE0\u8282\u7F3A\u5931\u6216\u4E0D\u8DB3\uFF08\u9700\u8981 ${need} \u4EFD\uFF09\uFF1A${sub.heading}`,
|
|
609
|
+
expected: sub.heading
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
if (section.required && countOccurrences(content, section.heading) === 0) {
|
|
616
|
+
issues.push({ code: "MISSING_SECTION", detail: `\u7F3A\u5C11\u5FC5\u9700\u7AE0\u8282\uFF1A${section.heading}`, expected: section.heading });
|
|
617
|
+
}
|
|
618
|
+
for (const sub of section.subsections ?? []) {
|
|
619
|
+
const need = isRoundSection ? opts.expectRounds ?? 1 : 1;
|
|
620
|
+
if (countOccurrences(content, sub.heading) < need) {
|
|
621
|
+
issues.push({
|
|
622
|
+
code: "MISSING_SUBSECTION",
|
|
623
|
+
detail: `\u5B50\u7AE0\u8282\u7F3A\u5931\u6216\u4E0D\u8DB3\uFF08\u9700\u8981 ${need} \u4EFD\uFF09\uFF1A${sub.heading}`,
|
|
624
|
+
expected: sub.heading
|
|
625
|
+
});
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
for (const ph of schema.placeholders ?? []) {
|
|
630
|
+
if (!ph) continue;
|
|
631
|
+
const nPh = normalizeWidth(ph);
|
|
632
|
+
const idx = contentNormalized.indexOf(nPh);
|
|
633
|
+
if (idx === -1) continue;
|
|
634
|
+
const hit = nPh === ph ? ph : `${content.slice(idx, idx + nPh.length)}\uFF08\u5BBD\u5EA6\u7B49\u4EF7\u4E8E ${ph}\uFF09`;
|
|
635
|
+
issues.push({ code: "PLACEHOLDER_LEFT", detail: `\u5360\u4F4D\u7B26\u672A\u66FF\u6362\uFF1A${hit}`, expected: ph });
|
|
636
|
+
}
|
|
637
|
+
const struct = normalizeWidth(structText(content, schema.groupColumn));
|
|
638
|
+
for (const term of schema.forbidden_terms ?? []) {
|
|
639
|
+
if (term && struct.includes(normalizeWidth(term))) {
|
|
640
|
+
issues.push({ code: "FORBIDDEN_TERM", detail: `\u7ED3\u6784\u4F4D\u7981\u8BCD\uFF1A${term}\uFF08\u4EC5\u6807\u9898\u884C\u4E0E\u6307\u5B9A\u5217\u5224\u5B9A\uFF09`, expected: term });
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
if (opts.templateContent) {
|
|
644
|
+
issues.push(...validateAnchors(content, schema, opts.templateContent));
|
|
645
|
+
}
|
|
646
|
+
return issues;
|
|
647
|
+
}
|
|
648
|
+
|
|
503
649
|
// templates/core/governance/doc-format-guard.ts
|
|
504
650
|
function extractContent(input) {
|
|
505
651
|
const ti = input.tool_input;
|
|
@@ -595,90 +741,28 @@ var DocFormatGuard = class {
|
|
|
595
741
|
}
|
|
596
742
|
/** 章节/锚定/占位符/禁词校验,返回 struct 统计 */
|
|
597
743
|
runSchemaChecks(schema, templateName, content, isSearchReplace) {
|
|
598
|
-
const
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
const
|
|
603
|
-
const
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
if (tokens.length === 0) {
|
|
621
|
-
applied--;
|
|
622
|
-
continue;
|
|
623
|
-
}
|
|
624
|
-
let scope = content;
|
|
625
|
-
if (section.within) {
|
|
626
|
-
if (content.includes(section.within)) {
|
|
627
|
-
const startIdx = content.indexOf(section.within);
|
|
628
|
-
const endIdx = content.indexOf("\n## ", startIdx + 1);
|
|
629
|
-
scope = content.slice(startIdx, endIdx === -1 ? void 0 : endIdx);
|
|
630
|
-
} else {
|
|
631
|
-
process.stderr.write(`[doc-format-guard] within_miss: schema ${section.id} \u7684 within '${section.within}' \u5728\u6587\u6863\u4E2D\u672A\u5B9A\u4F4D\uFF0C\u8DF3\u8FC7\u8BE5\u89C4\u5219
|
|
632
|
-
`);
|
|
633
|
-
applied--;
|
|
634
|
-
continue;
|
|
635
|
-
}
|
|
636
|
-
}
|
|
637
|
-
const missTokens = tokens.filter((tok) => !scope.includes(tok));
|
|
638
|
-
if (missTokens.length > 0) {
|
|
639
|
-
this.issues.push(` \u7F3A\u951A\u70B9(${section.id}): ${missTokens.join(" ")}`);
|
|
640
|
-
missed++;
|
|
641
|
-
anchorHit = false;
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
for (const heading of requiredHeadings) {
|
|
645
|
-
applied++;
|
|
646
|
-
if (!content.includes(heading)) {
|
|
647
|
-
this.issues.push(` \u7F3A\u7AE0\u8282: ${heading}`);
|
|
648
|
-
missed++;
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
for (const sub of subs) {
|
|
652
|
-
applied++;
|
|
653
|
-
if (!content.includes(sub)) {
|
|
654
|
-
this.issues.push(` \u7F3A\u5B50\u7AE0\u8282: ${sub}`);
|
|
655
|
-
missed++;
|
|
656
|
-
}
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
for (const ph of placeholders) {
|
|
660
|
-
if (content.includes(ph)) {
|
|
661
|
-
this.issues.push(` \u672A\u66FF\u6362\u5360\u4F4D\u7B26: ${ph}`);
|
|
662
|
-
missed++;
|
|
663
|
-
}
|
|
664
|
-
}
|
|
665
|
-
let structText = (content.match(/^#{2,}\s.*$/gm) ?? []).join("\n");
|
|
666
|
-
const col = typeof schema.groupColumn === "number" ? schema.groupColumn : Number(schema.groupColumn);
|
|
667
|
-
if (!Number.isNaN(col) && col > 0) {
|
|
668
|
-
const colLines = content.split("\n").map((l) => {
|
|
669
|
-
const cells = l.split("|");
|
|
670
|
-
return cells.length > col ? (cells[col + 1] ?? "").trim() : "";
|
|
671
|
-
}).filter(Boolean);
|
|
672
|
-
structText += "\n" + colLines.join("\n");
|
|
673
|
-
}
|
|
674
|
-
for (const term of terms) {
|
|
675
|
-
applied++;
|
|
676
|
-
if (structText.includes(term)) {
|
|
677
|
-
this.issues.push(` \u7ED3\u6784\u4F4D\u7981\u8BCD: ${term}`);
|
|
678
|
-
missed++;
|
|
679
|
-
}
|
|
680
|
-
}
|
|
681
|
-
return { applied, missed, anchorHit };
|
|
744
|
+
const templatePath = join3(this.projectDir, this.magicDir, "templates", templateName);
|
|
745
|
+
const templateContent = existsSync3(templatePath) ? readFileSync2(templatePath, "utf-8") : "";
|
|
746
|
+
const all = validateAgainstSchema(content, schema, { templateContent });
|
|
747
|
+
const relevant = isSearchReplace ? all.filter((i) => i.code === "PLACEHOLDER_LEFT" || i.code === "FORBIDDEN_TERM") : all;
|
|
748
|
+
for (const i of relevant) this.issues.push(` ${i.detail}`);
|
|
749
|
+
const missed = relevant.length;
|
|
750
|
+
const declared = this.countDeclaredRules(schema, isSearchReplace);
|
|
751
|
+
return {
|
|
752
|
+
applied: Math.max(missed, declared),
|
|
753
|
+
missed,
|
|
754
|
+
anchorHit: !relevant.some((i) => i.code === "ANCHOR_MISS")
|
|
755
|
+
};
|
|
756
|
+
}
|
|
757
|
+
/** 已声明规则数(用于 struct_score 分母;口径与原内联实现一致:按规则条数计) */
|
|
758
|
+
countDeclaredRules(schema, isSearchReplace) {
|
|
759
|
+
const placeholders = (schema.placeholders ?? []).length;
|
|
760
|
+
const terms = (schema.forbidden_terms ?? []).length;
|
|
761
|
+
if (isSearchReplace) return placeholders + terms;
|
|
762
|
+
const anchors = schema.sections.filter((s) => s.anchor).length;
|
|
763
|
+
const required = schema.sections.filter((s) => s.required === true && s.heading).length;
|
|
764
|
+
const subs = schema.sections.flatMap((s) => s.subsections ?? []).filter((s) => s.heading).length;
|
|
765
|
+
return anchors + required + subs + placeholders + terms;
|
|
682
766
|
}
|
|
683
767
|
/** 算法化规则校验(真源: [doc.anti_cheat] + HITL 表非空 + handoff 冲突)
|
|
684
768
|
* protected: adapter 子类可 override(如 claude 版无算法规则段) */
|
|
@@ -147,10 +147,6 @@ var protocol = {
|
|
|
147
147
|
".trae",
|
|
148
148
|
".codex"
|
|
149
149
|
]
|
|
150
|
-
},
|
|
151
|
-
"stop": {
|
|
152
|
-
"max_prompt_per_context": 3,
|
|
153
|
-
"window_minutes": 30
|
|
154
150
|
}
|
|
155
151
|
};
|
|
156
152
|
|
|
@@ -281,7 +277,7 @@ function projectHash() {
|
|
|
281
277
|
var DEV_FLAG = `/tmp/add_dev_${projectHash()}`;
|
|
282
278
|
|
|
283
279
|
// templates/core/governance/post-tool-router.ts
|
|
284
|
-
import { existsSync as existsSync3, readFileSync as readFileSync2, readdirSync, writeFileSync as writeFileSync2 } from "node:fs";
|
|
280
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync2, readFileSync as readFileSync2, readdirSync, appendFileSync as appendFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
285
281
|
import { join as join3, basename as basename2, dirname as dirname2 } from "node:path";
|
|
286
282
|
|
|
287
283
|
// templates/core/governance/notify.ts
|
|
@@ -346,6 +342,37 @@ var AuditBridge = class {
|
|
|
346
342
|
}
|
|
347
343
|
};
|
|
348
344
|
|
|
345
|
+
// templates/core/scripts/mcp-server/shared/memory/switches.ts
|
|
346
|
+
function recallMode(env = process.env) {
|
|
347
|
+
const v = (env.ADD_MEMORY_RECALL_MODE ?? "shadow").toLowerCase();
|
|
348
|
+
return v === "off" || v === "inject" ? v : "shadow";
|
|
349
|
+
}
|
|
350
|
+
function evidenceEnabled(env = process.env) {
|
|
351
|
+
return (env.ADD_MEMORY_EVIDENCE ?? "on").toLowerCase() !== "off";
|
|
352
|
+
}
|
|
353
|
+
var MEMORY_DIR_NAME = "memory";
|
|
354
|
+
var EVIDENCE_QUEUE_FILE = "evidence-queue.jsonl";
|
|
355
|
+
var L1_SNAPSHOT_TTL_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
356
|
+
|
|
357
|
+
// templates/core/scripts/mcp-server/shared/memory/jobs/evidence-collector.ts
|
|
358
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
359
|
+
function classifyEvidenceSource(filePath) {
|
|
360
|
+
const p = filePath.replace(/\\/g, "/");
|
|
361
|
+
if (/(^|\/)plans\/[^/]*-plan-v\d+\.md$/.test(p)) return "PLAN";
|
|
362
|
+
if (/(^|\/)plans\/[^/]*-add-route-v\d+\.md$/.test(p)) return "PLAN";
|
|
363
|
+
if (/(^|\/)specs\/.+\.md$/.test(p)) return "SPEC";
|
|
364
|
+
if (/handoff[^/]*\.md$/i.test(p)) return "HANDOFF";
|
|
365
|
+
if (/(^|\/)reviews\/.+\.md$/.test(p)) return "DEV_OPERATION";
|
|
366
|
+
return null;
|
|
367
|
+
}
|
|
368
|
+
function buildEvidenceEvent(filePath, excerpt, occurredAt = /* @__PURE__ */ new Date()) {
|
|
369
|
+
const sourceType = classifyEvidenceSource(filePath);
|
|
370
|
+
if (!sourceType) throw new Error(`\u975E\u767D\u540D\u5355\u8DEF\u5F84: ${filePath}`);
|
|
371
|
+
const sourceRef = filePath;
|
|
372
|
+
const dedupKey = createHash2("sha256").update(`${sourceType}|${sourceRef}|${excerpt}`, "utf8").digest("hex").slice(0, 32);
|
|
373
|
+
return { dedupKey, sourceType, sourceRef, excerpt: excerpt.slice(0, 500), occurredAt: occurredAt.toISOString() };
|
|
374
|
+
}
|
|
375
|
+
|
|
349
376
|
// templates/core/governance/post-tool-router.ts
|
|
350
377
|
function extractOutputText(raw) {
|
|
351
378
|
const m1 = raw.match(/"output"\s*:\s*"([^"]*)"/);
|
|
@@ -476,6 +503,7 @@ var PostToolRouter = class {
|
|
|
476
503
|
this.postDocSections(filePath);
|
|
477
504
|
this.emitLine(this.emitAuditReminder(filePath));
|
|
478
505
|
this.emitWriteEvent(filePath);
|
|
506
|
+
this.emitMemoryEvidence(filePath);
|
|
479
507
|
} else if (toolName2 === "Bash") {
|
|
480
508
|
this.emitLine(this.emitBashDone());
|
|
481
509
|
} else {
|
|
@@ -503,6 +531,32 @@ var PostToolRouter = class {
|
|
|
503
531
|
emitWriteEvent(filePath) {
|
|
504
532
|
this.auditBridge.emit(filePath);
|
|
505
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* §2g Memory 采证入队(Spec §10 PostToolUse 白名单,扩展点):
|
|
536
|
+
* 白名单路径(plans/specs/reviews/handoff)→ 追加 evidence-queue.jsonl(幂等 dedupKey),
|
|
537
|
+
* 由 consolidation job 异步落库。ADD_MEMORY_EVIDENCE=off 或 RECALL_MODE=off 时关闭。
|
|
538
|
+
* fail-open:任何异常不阻断文件写入主路径(Plan §9.3)。
|
|
539
|
+
*/
|
|
540
|
+
emitMemoryEvidence(filePath) {
|
|
541
|
+
try {
|
|
542
|
+
if (!evidenceEnabled() || recallMode() === "off") return;
|
|
543
|
+
if (!classifyEvidenceSource(filePath)) return;
|
|
544
|
+
if (!this.magicDir) return;
|
|
545
|
+
let excerpt = "";
|
|
546
|
+
try {
|
|
547
|
+
excerpt = readFileSync2(filePath, "utf-8").slice(0, 500);
|
|
548
|
+
} catch {
|
|
549
|
+
excerpt = basename2(filePath);
|
|
550
|
+
}
|
|
551
|
+
const ev = buildEvidenceEvent(filePath, excerpt);
|
|
552
|
+
const dir = join3(this.projectDir, this.magicDir, MEMORY_DIR_NAME);
|
|
553
|
+
mkdirSync2(dir, { recursive: true });
|
|
554
|
+
appendFileSync2(join3(dir, EVIDENCE_QUEUE_FILE), JSON.stringify(ev) + "\n", "utf-8");
|
|
555
|
+
this.emitLine(`[ADD PostToolUse] \u{1F9E0} \u8BB0\u5FC6\u91C7\u8BC1\u5165\u961F: ${filePath}\uFF08consolidation \u65F6\u843D\u5E93\uFF09
|
|
556
|
+
`);
|
|
557
|
+
} catch {
|
|
558
|
+
}
|
|
559
|
+
}
|
|
506
560
|
/** §3 Bash 增强(core: lint/tsc;codex 子类: lint/typecheck/test) */
|
|
507
561
|
emitBashDone() {
|
|
508
562
|
return "[ADD PostToolUse] \u547D\u4EE4\u6267\u884C\u5B8C\u6210\u3002\u5982\u6709 lint/tsc \u9519\u8BEF\u8BF7\u4FEE\u590D\u3002\n";
|