add-coder 0.3.33 → 0.3.34
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/package.json +1 -1
- package/templates/.add-coder-src-hash.json +70 -70
- package/templates/adapters/claude/hooks/doc-format-guard.mjs +4 -0
- package/templates/adapters/claude/hooks/notification.mjs +4 -0
- package/templates/adapters/claude/hooks/permission-denied.mjs +4 -0
- package/templates/adapters/claude/hooks/permission-gate.mjs +4 -0
- package/templates/adapters/claude/hooks/post-tool-use.mjs +4 -0
- package/templates/adapters/claude/hooks/pre-compact.mjs +4 -0
- package/templates/adapters/claude/hooks/pre-tool-use.mjs +4 -0
- package/templates/adapters/claude/hooks/prompt-submit.mjs +4 -0
- package/templates/adapters/claude/hooks/session-end.mjs +4 -0
- package/templates/adapters/claude/hooks/session-start.mjs +4 -0
- package/templates/adapters/claude/hooks/stop-check.mjs +87 -6
- package/templates/adapters/claude/hooks/stop-failure.mjs +4 -0
- package/templates/adapters/claude/hooks/subagent-guard.mjs +4 -0
- package/templates/adapters/claude/hooks/subagent-stop.mjs +4 -0
- package/templates/adapters/codex/hooks/doc-format-guard.mjs +4 -0
- package/templates/adapters/codex/hooks/notification.mjs +4 -0
- package/templates/adapters/codex/hooks/permission-gate.mjs +4 -0
- package/templates/adapters/codex/hooks/post-tool-failure.mjs +4 -0
- package/templates/adapters/codex/hooks/post-tool-use.mjs +4 -0
- package/templates/adapters/codex/hooks/pre-compact.mjs +4 -0
- package/templates/adapters/codex/hooks/pre-tool-use.mjs +4 -0
- package/templates/adapters/codex/hooks/prompt-submit.mjs +4 -0
- package/templates/adapters/codex/hooks/session-end.mjs +4 -0
- package/templates/adapters/codex/hooks/session-start.mjs +4 -0
- package/templates/adapters/codex/hooks/stop-check.mjs +87 -6
- package/templates/adapters/codex/hooks/subagent-guard.mjs +4 -0
- package/templates/adapters/codex/hooks/subagent-stop.mjs +4 -0
- package/templates/adapters/qoder/hooks/doc-format-guard.mjs +4 -0
- package/templates/adapters/qoder/hooks/notification.mjs +4 -0
- package/templates/adapters/qoder/hooks/permission-gate.mjs +4 -0
- package/templates/adapters/qoder/hooks/post-tool-failure.mjs +4 -0
- package/templates/adapters/qoder/hooks/post-tool-use.mjs +4 -0
- package/templates/adapters/qoder/hooks/pre-compact.mjs +4 -0
- package/templates/adapters/qoder/hooks/pre-tool-use.mjs +4 -0
- package/templates/adapters/qoder/hooks/prompt-submit.mjs +4 -0
- package/templates/adapters/qoder/hooks/review-checklist.mjs +4 -0
- package/templates/adapters/qoder/hooks/session-end.mjs +4 -0
- package/templates/adapters/qoder/hooks/session-start.mjs +4 -0
- package/templates/adapters/qoder/hooks/stop-check.mjs +87 -6
- package/templates/adapters/qoder/hooks/subagent-guard.mjs +4 -0
- package/templates/adapters/qoder/hooks/subagent-stop.mjs +4 -0
- package/templates/adapters/trae/hooks/doc-format-guard.mjs +4 -0
- package/templates/adapters/trae/hooks/notification.mjs +4 -0
- package/templates/adapters/trae/hooks/permission-gate.mjs +4 -0
- package/templates/adapters/trae/hooks/post-tool-failure.mjs +4 -0
- package/templates/adapters/trae/hooks/post-tool-use.mjs +4 -0
- package/templates/adapters/trae/hooks/pre-compact.mjs +4 -0
- package/templates/adapters/trae/hooks/pre-tool-use.mjs +4 -0
- package/templates/adapters/trae/hooks/prompt-submit.mjs +4 -0
- package/templates/adapters/trae/hooks/session-end.mjs +4 -0
- package/templates/adapters/trae/hooks/session-start.mjs +4 -0
- package/templates/adapters/trae/hooks/stop-check.mjs +87 -6
- package/templates/adapters/trae/hooks/subagent-guard.mjs +4 -0
- package/templates/adapters/trae/hooks/subagent-stop.mjs +4 -0
- package/templates/adapters/vscode/hooks/doc-format-guard.mjs +4 -0
- package/templates/adapters/vscode/hooks/notification.mjs +4 -0
- package/templates/adapters/vscode/hooks/permission-gate.mjs +4 -0
- package/templates/adapters/vscode/hooks/post-tool-failure.mjs +4 -0
- package/templates/adapters/vscode/hooks/post-tool-use.mjs +4 -0
- package/templates/adapters/vscode/hooks/pre-compact.mjs +4 -0
- package/templates/adapters/vscode/hooks/pre-tool-use.mjs +4 -0
- package/templates/adapters/vscode/hooks/prompt-submit.mjs +4 -0
- package/templates/adapters/vscode/hooks/session-end.mjs +4 -0
- package/templates/adapters/vscode/hooks/session-start.mjs +4 -0
- package/templates/adapters/vscode/hooks/stop-check.mjs +87 -6
- package/templates/adapters/vscode/hooks/subagent-guard.mjs +4 -0
- package/templates/adapters/vscode/hooks/subagent-stop.mjs +4 -0
- package/templates/core/governance/rules.ts +4 -0
- package/templates/core/governance/stop-router.ts +101 -7
|
@@ -195,6 +195,10 @@ var protocol = {
|
|
|
195
195
|
".trae",
|
|
196
196
|
".codex"
|
|
197
197
|
]
|
|
198
|
+
},
|
|
199
|
+
"stop": {
|
|
200
|
+
"max_prompt_per_context": 3,
|
|
201
|
+
"window_minutes": 30
|
|
198
202
|
}
|
|
199
203
|
};
|
|
200
204
|
|
|
@@ -409,6 +413,10 @@ function buildStopContext(quadrant, info) {
|
|
|
409
413
|
}
|
|
410
414
|
|
|
411
415
|
// templates/core/governance/stop-router.ts
|
|
416
|
+
import { createHash as createHash2 } from "node:crypto";
|
|
417
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
418
|
+
import { tmpdir } from "node:os";
|
|
419
|
+
import { join as join2 } from "node:path";
|
|
412
420
|
var StopRouter = class {
|
|
413
421
|
/** 主路由:返回 exit code(0 放行 / 2 阻断) */
|
|
414
422
|
run() {
|
|
@@ -437,14 +445,20 @@ var StopRouter = class {
|
|
|
437
445
|
}
|
|
438
446
|
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
439
447
|
/**
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
448
|
+
* Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
|
|
449
|
+
* 维度 0(新增,临时方案): 弹框频控——同项目同 Plan 窗口内达上限 → 降级放行
|
|
450
|
+
* 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total → 未完成阻断提示)
|
|
451
|
+
* 维度 2: checklist 质量(checkAddCompleteness 未闭环 → 阻断)
|
|
452
|
+
* 互补非替代——codex 原 DB 进度分流语义上提 core,core checklist 质量语义保留。
|
|
453
|
+
*/
|
|
445
454
|
q4Check(plan, rounds, step, handoff, addRoute) {
|
|
446
|
-
void plan;
|
|
447
455
|
void rounds;
|
|
456
|
+
const limited = this.stopPromptLimited(plan);
|
|
457
|
+
if (limited) {
|
|
458
|
+
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
|
|
459
|
+
`);
|
|
460
|
+
return 0;
|
|
461
|
+
}
|
|
448
462
|
const [donePart, totalPart] = step.split("/");
|
|
449
463
|
if (/^\d+$/.test(donePart) && /^\d+$/.test(totalPart) && Number(donePart) < Number(totalPart)) {
|
|
450
464
|
return this.emitQ4Unclosed(
|
|
@@ -461,6 +475,73 @@ var StopRouter = class {
|
|
|
461
475
|
clearDevAction();
|
|
462
476
|
return this.emitQ4Pass();
|
|
463
477
|
}
|
|
478
|
+
// ── 弹框频控(临时方案,规则真源: hook-protocol-rules.toml [protocol.stop])──
|
|
479
|
+
// 计数隔离: 文件级 = PROJECT_DIR md5(跨项目不互扰);key 级 = planName(同项目跨 Plan 不互扰)
|
|
480
|
+
// 故障降级: 哨兵读/写异常 → fail-open 放行(不阻塞主流程)
|
|
481
|
+
/** 哨兵记录:同一项目同一 Plan 的弹框计数 */
|
|
482
|
+
stopPromptMax() {
|
|
483
|
+
const cfg = protocol.stop;
|
|
484
|
+
return cfg?.max_prompt_per_context ?? 3;
|
|
485
|
+
}
|
|
486
|
+
/** 哨兵窗口(毫秒) */
|
|
487
|
+
stopPromptWindowMs() {
|
|
488
|
+
const cfg = protocol.stop;
|
|
489
|
+
return (cfg?.window_minutes ?? 30) * 6e4;
|
|
490
|
+
}
|
|
491
|
+
/** 哨兵文件路径(项目隔离:PROJECT_DIR md5) */
|
|
492
|
+
stopSentinelPath() {
|
|
493
|
+
const projectDir = process.env.PROJECT_DIR || process.env.QODER_PROJECT_DIR || process.env.QODERCN_PROJECT_DIR || process.env.CLAUDE_PROJECT_DIR || process.cwd();
|
|
494
|
+
const md5 = createHash2("md5").update(projectDir).digest("hex").slice(0, 8);
|
|
495
|
+
return join2(tmpdir(), `add_stop_${md5}.json`);
|
|
496
|
+
}
|
|
497
|
+
/** 写哨兵(失败静默,fail-open) */
|
|
498
|
+
writeStopPromptSentinel(data) {
|
|
499
|
+
try {
|
|
500
|
+
writeFileSync2(this.stopSentinelPath(), JSON.stringify(data), "utf-8");
|
|
501
|
+
} catch {
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* 频控判定:返回 true = 已达上限应放行(不弹框)
|
|
506
|
+
* 生成态缺失 protocol.stop(TOML 未同步)→ 跳过频控保持既有行为(生成链幂等校验兜底)
|
|
507
|
+
*/
|
|
508
|
+
stopPromptLimited(plan) {
|
|
509
|
+
try {
|
|
510
|
+
const stopCfg = protocol.stop;
|
|
511
|
+
if (!stopCfg) return false;
|
|
512
|
+
const max = this.stopPromptMax();
|
|
513
|
+
const windowMs = this.stopPromptWindowMs();
|
|
514
|
+
const path3 = this.stopSentinelPath();
|
|
515
|
+
let data = {};
|
|
516
|
+
if (existsSync2(path3)) {
|
|
517
|
+
try {
|
|
518
|
+
data = JSON.parse(readFileSync2(path3, "utf-8")) ?? {};
|
|
519
|
+
} catch {
|
|
520
|
+
return true;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const now = Date.now();
|
|
524
|
+
const entry = data[plan];
|
|
525
|
+
if (!entry) {
|
|
526
|
+
data[plan] = { count: 1, lastPromptAt: now };
|
|
527
|
+
this.writeStopPromptSentinel(data);
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
if (now - entry.lastPromptAt > windowMs) {
|
|
531
|
+
data[plan] = { count: 1, lastPromptAt: now };
|
|
532
|
+
this.writeStopPromptSentinel(data);
|
|
533
|
+
return false;
|
|
534
|
+
}
|
|
535
|
+
if (entry.count >= max) {
|
|
536
|
+
return true;
|
|
537
|
+
}
|
|
538
|
+
data[plan] = { count: entry.count + 1, lastPromptAt: now };
|
|
539
|
+
this.writeStopPromptSentinel(data);
|
|
540
|
+
return false;
|
|
541
|
+
} catch {
|
|
542
|
+
return true;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
464
545
|
/** Q0: DB 不可用(core: stderr + 2) */
|
|
465
546
|
emitQ0(reason) {
|
|
466
547
|
process.stderr.write(
|
|
@@ -192,6 +192,10 @@ var protocol = {
|
|
|
192
192
|
".trae",
|
|
193
193
|
".codex"
|
|
194
194
|
]
|
|
195
|
+
},
|
|
196
|
+
"stop": {
|
|
197
|
+
"max_prompt_per_context": 3,
|
|
198
|
+
"window_minutes": 30
|
|
195
199
|
}
|
|
196
200
|
};
|
|
197
201
|
|
|
@@ -406,6 +410,10 @@ function buildStopContext(quadrant, info) {
|
|
|
406
410
|
}
|
|
407
411
|
|
|
408
412
|
// 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";
|
|
409
417
|
var StopRouter = class {
|
|
410
418
|
/** 主路由:返回 exit code(0 放行 / 2 阻断) */
|
|
411
419
|
run() {
|
|
@@ -434,14 +442,20 @@ var StopRouter = class {
|
|
|
434
442
|
}
|
|
435
443
|
// ─────────────────────────── 扩展点 ───────────────────────────
|
|
436
444
|
/**
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
445
|
+
* Q4 验收决策(双维度组合——2026-08-14 Task 9.4.4④ 上提,回流: I2):
|
|
446
|
+
* 维度 0(新增,临时方案): 弹框频控——同项目同 Plan 窗口内达上限 → 降级放行
|
|
447
|
+
* 维度 1(前置): DB 任务进度(step = done/total,数值且 done<total → 未完成阻断提示)
|
|
448
|
+
* 维度 2: checklist 质量(checkAddCompleteness 未闭环 → 阻断)
|
|
449
|
+
* 互补非替代——codex 原 DB 进度分流语义上提 core,core checklist 质量语义保留。
|
|
450
|
+
*/
|
|
442
451
|
q4Check(plan, rounds, step, handoff, addRoute) {
|
|
443
|
-
void plan;
|
|
444
452
|
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
|
+
}
|
|
445
459
|
const [donePart, totalPart] = step.split("/");
|
|
446
460
|
if (/^\d+$/.test(donePart) && /^\d+$/.test(totalPart) && Number(donePart) < Number(totalPart)) {
|
|
447
461
|
return this.emitQ4Unclosed(
|
|
@@ -458,6 +472,73 @@ var StopRouter = class {
|
|
|
458
472
|
clearDevAction();
|
|
459
473
|
return this.emitQ4Pass();
|
|
460
474
|
}
|
|
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
|
+
}
|
|
461
542
|
/** Q0: DB 不可用(core: stderr + 2) */
|
|
462
543
|
emitQ0(reason) {
|
|
463
544
|
process.stderr.write(
|