kld-sdd 2.4.19 → 2.5.1

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.
Files changed (47) hide show
  1. package/kld-sdd-guide.html +1109 -0
  2. package/lib/command-bridge.js +156 -0
  3. package/lib/deploy-codebuddy-hooks.js +99 -0
  4. package/lib/hook-gate-core.js +333 -0
  5. package/lib/init.js +136 -93
  6. package/lib/settings-merge.js +85 -0
  7. package/lib/skills-bundle.js +142 -5
  8. package/lib/tool-profiles.js +270 -0
  9. package/package.json +3 -2
  10. package/skywalk-sdd/index.cjs +1808 -129
  11. package/templates/commands/kunlunzhima/skill-bridge.md +23 -0
  12. package/templates/hooks/claude/hooks/sdd-apply-test-gate.cjs +175 -28
  13. package/templates/hooks/claude/hooks/sdd-post-tool.cjs +42 -21
  14. package/templates/hooks/codebuddy/hooks/sdd-apply-gate.cjs +16 -0
  15. package/templates/hooks/codebuddy/hooks/sdd-apply-test-gate.cjs +395 -0
  16. package/templates/hooks/codebuddy/hooks/sdd-post-tool.cjs +123 -0
  17. package/templates/hooks/codebuddy/hooks/sdd-pre-tool.cjs +16 -0
  18. package/templates/hooks/codebuddy/hooks/sdd-prompt.cjs +48 -0
  19. package/templates/hooks/codebuddy/hooks/sdd-skill-apply-gate.cjs +16 -0
  20. package/templates/hooks/codebuddy/hooks/sdd-stop.cjs +70 -0
  21. package/templates/hooks/codebuddy/settings.json +72 -0
  22. package/templates/openspec/proposal.md +0 -1
  23. package/templates/openspec/spec.md +2 -2
  24. package/templates/skills/kld-sdd/opsx-apply/SKILL.md +65 -356
  25. package/templates/skills/kld-sdd/opsx-apply/checklist.md +94 -0
  26. package/templates/skills/kld-sdd/opsx-apply/reference.md +403 -0
  27. package/templates/skills/kld-sdd/opsx-archive/SKILL.md +21 -5
  28. package/templates/skills/kld-sdd/opsx-archive/checklist.md +33 -0
  29. package/templates/skills/kld-sdd/opsx-check/SKILL.md +29 -5
  30. package/templates/skills/kld-sdd/opsx-check/checklist.md +37 -0
  31. package/templates/skills/kld-sdd/opsx-design/SKILL.md +47 -51
  32. package/templates/skills/kld-sdd/opsx-design/checklist.md +46 -0
  33. package/templates/skills/kld-sdd/opsx-design/reference.md +44 -0
  34. package/templates/skills/kld-sdd/opsx-explore/SKILL.md +1 -1
  35. package/templates/skills/kld-sdd/opsx-propose/SKILL.md +52 -96
  36. package/templates/skills/kld-sdd/opsx-propose/checklist.md +44 -0
  37. package/templates/skills/kld-sdd/opsx-propose/reference.md +94 -0
  38. package/templates/skills/kld-sdd/opsx-rules/SKILL.md +131 -0
  39. package/templates/skills/kld-sdd/opsx-rules/checklist.md +27 -0
  40. package/templates/skills/kld-sdd/opsx-rules/reference.md +124 -0
  41. package/templates/skills/kld-sdd/opsx-spec/SKILL.md +47 -51
  42. package/templates/skills/kld-sdd/opsx-spec/checklist.md +46 -0
  43. package/templates/skills/kld-sdd/opsx-spec/reference.md +49 -0
  44. package/templates/skills/kld-sdd/opsx-task/SKILL.md +43 -46
  45. package/templates/skills/kld-sdd/opsx-task/checklist.md +46 -0
  46. package/templates/skills/kld-sdd/opsx-task/reference.md +40 -0
  47. package/templates/skills/kld-sdd/opsx-test/SKILL.md +13 -1
@@ -0,0 +1,23 @@
1
+ ---
2
+ name: "${BRIDGE_NAME}"
3
+ description: "${BRIDGE_DESCRIPTION}"
4
+ argument-hint: "${BRIDGE_ARGUMENT_HINT}"
5
+ ---
6
+
7
+ # SDD KunlunZhima Skill Bridge
8
+
9
+ 本命令是 KunlunZhima IDE 的 SDD Skill 桥接入口,不包含 Skill 的具体执行步骤。
10
+
11
+ ## Skill 路径
12
+
13
+ 请**完整读取并严格遵循**以下 Skill 文件:
14
+
15
+ `${SKILL_RELATIVE_PATH}`
16
+
17
+ ## 用户输入
18
+
19
+ 将本命令后的用户输入作为 Skill 的输入参数传递。
20
+
21
+ ## 相对引用
22
+
23
+ Skill 正文中的相对路径引用均以 Skill 所在目录(`${SKILL_DIR}`)解析。
@@ -215,10 +215,17 @@ function block(decision, reason, extra) {
215
215
  process.exit(2);
216
216
  }
217
217
 
218
- function recordWarning(projectRoot, applyEvent, code, message) {
218
+ function recordWarning(projectRoot, applyEvent, code, message, taskIds) {
219
219
  const logPath = path.join(projectRoot, 'skywalk-sdd', 'log.cjs');
220
- if (!fs.existsSync(logPath)) return;
220
+ if (!fs.existsSync(logPath)) {
221
+ // Y8 修复:log.cjs 缺失时 stderr 提示(本 repo 无 log.cjs 属预期——脚手架部署时 deployTelemetryDataDir 复制 index.cjs→log.cjs)
222
+ try { console.error(`[sdd-apply-test-gate] log.cjs 不存在(${logPath}),telemetry_warning 未记录;确认 deployTelemetryDataDir 已执行`); } catch {}
223
+ return;
224
+ }
221
225
  try {
226
+ // T1.5b(v3): task_update_reuse 写入复用 task_ids,供 E4(A3) 扣分识别
227
+ const details = { warning: code, test_strategy: applyEvent.test_strategy };
228
+ if (Array.isArray(taskIds) && taskIds.length > 0) details.task_ids = taskIds;
222
229
  const args = [
223
230
  logPath,
224
231
  'record',
@@ -230,7 +237,7 @@ function recordWarning(projectRoot, applyEvent, code, message) {
230
237
  '--source=claude-hook',
231
238
  '--result=partial',
232
239
  `--summary=${message}`,
233
- `--details-json=${JSON.stringify({ warning: code, test_strategy: applyEvent.test_strategy })}`,
240
+ `--details-json=${JSON.stringify(details)}`,
234
241
  ];
235
242
  if (applyEvent.capability) args.push(`--capability=${applyEvent.capability}`);
236
243
  if (applyEvent.session_id) args.push(`--session-id=${applyEvent.session_id}`);
@@ -280,36 +287,176 @@ function shouldGate(projectRoot, applyEvent) {
280
287
  return { strategy, changeName: applyEvent.change, capability: applyEvent.capability };
281
288
  }
282
289
 
283
- // ── 主逻辑 ─────────────────────────────────────────────
284
-
285
- const input = parseInput(readStdin());
286
- const projectRoot = getProjectRoot(input);
287
- const toolName = String(input.tool_name || input.toolName || '');
288
- const toolInput = input.tool_input || input.toolInput || {};
289
- const command = String(toolInput.command || '');
290
-
291
- const activeApply = findActiveApplyStage(projectRoot);
292
- if (!activeApply) {
293
- process.exit(0);
290
+ // ── T1.1: task_update 复用检测(不阻断,仅记 warning) ──
291
+ /**
292
+ * 检测同一 change 的 implementation task_update 是否复用同一次测试结果。
293
+ * 判定:同 session_id + 同 test_results 三元组(passed+failed+duration_ms) 的 group 内 >1 条(不再要求时间戳相邻)。
294
+ * 测试确实跑过,只是 task_update 记录不实 telemetry_warning(task_update_reuse),不阻断。
295
+ * 返回 { reused, groups: [{ duration_ms, fingerprint, session_id, task_ids }] }
296
+ */
297
+ function detectReusedTestResults(events) {
298
+ const taskUpdates = (events || []).filter((e) =>
299
+ e && e.type === 'task_update' &&
300
+ e.details && e.details.test_results &&
301
+ isRealTestDetails(e.details.test_results)
302
+ );
303
+ const bySession = new Map();
304
+ for (const e of taskUpdates) {
305
+ const sid = e.session_id || '_no_session_';
306
+ if (!bySession.has(sid)) bySession.set(sid, []);
307
+ bySession.get(sid).push(e);
308
+ }
309
+ const groups = [];
310
+ let reused = false;
311
+ for (const [, group] of bySession) {
312
+ const byFingerprint = new Map();
313
+ for (const e of group) {
314
+ const tr = e.details.test_results;
315
+ const passed = Number(tr.passed);
316
+ const failed = Number(tr.failed);
317
+ const dur = Number(tr.duration_ms);
318
+ if (!Number.isFinite(dur)) continue;
319
+ const fp = `${passed}|${failed}|${dur}`;
320
+ if (!byFingerprint.has(fp)) byFingerprint.set(fp, []);
321
+ byFingerprint.get(fp).push(e);
322
+ }
323
+ for (const [fp, evs] of byFingerprint) {
324
+ if (evs.length < 2) continue;
325
+ const sorted = evs.slice().sort((a, b) => new Date(a.timestamp || 0) - new Date(b.timestamp || 0));
326
+ // T1.5(v3): 去掉 <5s 相邻限制 — 同 session + 同三元组即复用,无论时间间隔
327
+ // todo-cli 真实场景:实现任务与验证任务间隔 15-17s,<5s 阈值漏检 3/4 组
328
+ reused = true;
329
+ const first = sorted[0];
330
+ groups.push({
331
+ duration_ms: Number(first.details.test_results.duration_ms),
332
+ fingerprint: fp,
333
+ session_id: first.session_id || null,
334
+ task_ids: sorted.map((e) => e.task_id || e.event_id).filter(Boolean),
335
+ });
336
+ }
337
+ }
338
+ return { reused, groups };
294
339
  }
295
340
 
296
- const bashGate = toolName === 'Bash' && isApplyCompletionBash(command);
297
- const stopGate = !toolName || toolName === 'Stop';
341
+ /** T1.6: 检测本阶段是否有失败/门禁拦截但无 process_note 过程记录。
342
+ * Y4 修复:when 参数过滤时间窗口(仅检测 sinceTimestamp 后的事件),避免 change 级误报。
343
+ * 返回 { gap, hasFailure, hasGateBlock, hasProcessNote } */
344
+ function detectProcessNoteGap(events, sinceTimestamp) {
345
+ const evs = events || [];
346
+ const filtered = sinceTimestamp ? evs.filter(e => new Date(e.timestamp || 0).getTime() >= new Date(sinceTimestamp).getTime()) : evs;
347
+ const stageEnds = filtered.filter((e) => e.type === 'stage_end');
348
+ const hasFailure = stageEnds.some((e) => e.result === 'failure' || e.result === 'partial');
349
+ const hasGateBlock = filtered.some((e) =>
350
+ e.type === 'telemetry_warning' && e.details && e.details.warning === 'apply_test_missing'
351
+ );
352
+ const hasProcessNote = filtered.some((e) => e.type === 'process_note');
353
+ const gap = (hasFailure || hasGateBlock) && !hasProcessNote;
354
+ return { gap, hasFailure, hasGateBlock, hasProcessNote };
355
+ }
298
356
 
299
- if (!bashGate && !stopGate) {
300
- process.exit(0);
357
+ /** B2(v3): 检测 tdd 策略下 apply 阶段是否缺失"红灯"(失败的测试结果)。
358
+ * TDD 节奏要求测试骨架先 RED(failed>0)再实现转 GREEN;若 apply 阶段有真实测试信号
359
+ * 但全部 failed===0,说明骨架可能未经 RED 验证即标记完成 → 记 telemetry_warning(tdd_red_missing),不阻断。
360
+ * 不复用 isRealTestDetails(其要求 command 非空,会漏掉无 command 的骨架 test_results)。
361
+ * 返回 { missing, hasRed, signalCount } */
362
+ function detectMissingTddRed(events, sinceTimestamp) {
363
+ const evs = events || [];
364
+ const filtered = sinceTimestamp ? evs.filter(e => new Date(e.timestamp || 0).getTime() >= new Date(sinceTimestamp).getTime()) : evs;
365
+ let signalCount = 0;
366
+ let hasRed = false;
367
+ for (const e of filtered) {
368
+ const tr = e && e.details && (e.details.test_results || e.details.test);
369
+ if (!tr || typeof tr !== 'object') continue;
370
+ const passed = Number(tr.passed);
371
+ const failed = Number(tr.failed);
372
+ const dur = Number(tr.duration_ms);
373
+ const isReal = (Number.isFinite(passed) && passed > 0)
374
+ || (Number.isFinite(failed) && failed > 0)
375
+ || (Number.isFinite(dur) && dur > 0);
376
+ if (!isReal) continue;
377
+ signalCount++;
378
+ if (Number.isFinite(failed) && failed > 0) hasRed = true;
379
+ }
380
+ if (signalCount === 0) return { missing: false, hasRed: false, signalCount: 0, reason: 'no-real-test-signals' };
381
+ return { missing: !hasRed, hasRed, signalCount };
301
382
  }
302
383
 
303
- const gate = shouldGate(projectRoot, activeApply);
304
- if (!gate) {
305
- process.exit(0);
384
+ /** 是否已存在某 code telemetry_warning(去重,避免每次 hook 触发都重复记录) */
385
+ function hasExistingWarning(events, code) {
386
+ return (events || []).some((e) =>
387
+ e.type === 'telemetry_warning' && e.details && e.details.warning === code
388
+ );
306
389
  }
307
390
 
308
- const reason = buildReason(gate.strategy, gate.changeName, gate.capability);
309
- recordWarning(projectRoot, { ...activeApply, test_strategy: gate.strategy }, 'apply_test_missing', 'Apply test gate blocked: no unit test evidence');
391
+ // ── 主逻辑(仅直接执行时运行;被 require 时跳过,便于单元测试) ──
392
+ if (require.main === module) {
393
+ const input = parseInput(readStdin());
394
+ const projectRoot = getProjectRoot(input);
395
+ const toolName = String(input.tool_name || input.toolName || '');
396
+ const toolInput = input.tool_input || input.toolInput || {};
397
+ const command = String(toolInput.command || '');
398
+
399
+ const activeApply = findActiveApplyStage(projectRoot);
400
+ if (!activeApply) {
401
+ process.exit(0);
402
+ }
403
+
404
+ const bashGate = toolName === 'Bash' && isApplyCompletionBash(command);
405
+ const stopGate = !toolName || toolName === 'Stop';
406
+
407
+ if (!bashGate && !stopGate) {
408
+ process.exit(0);
409
+ }
410
+
411
+ // 非阻断警告检测(T1.1 task_update 复用 / T1.6 process_note 缺失):放行但记录 warning
412
+ try {
413
+ const warningEvents = loadChangeEvents(projectRoot, activeApply.change);
414
+ const warningCtx = { ...activeApply, test_strategy: readTestStrategy(projectRoot, activeApply.change) };
415
+ const reuse = detectReusedTestResults(warningEvents);
416
+ if (reuse.reused && !hasExistingWarning(warningEvents, 'task_update_reuse')) {
417
+ const taskCount = reuse.groups.reduce((s, g) => s + g.task_ids.length, 0);
418
+ const reusedTaskIds = reuse.groups.flatMap((g) => g.task_ids).filter(Boolean);
419
+ recordWarning(projectRoot, warningCtx, 'task_update_reuse',
420
+ `检测到 ${reuse.groups.length} 组 task_update 复用同一次测试结果(共 ${taskCount} 条),测试确实跑过但记录不实`,
421
+ reusedTaskIds);
422
+ }
423
+ const noteGap = detectProcessNoteGap(warningEvents, activeApply.timestamp);
424
+ if (noteGap.gap && !hasExistingWarning(warningEvents, 'process_note_missing')) {
425
+ recordWarning(projectRoot, warningCtx, 'process_note_missing',
426
+ '本阶段存在失败/门禁拦截但无 process_note 过程记录,建议补记');
427
+ }
428
+ // B2(v3): tdd 策略下 apply 阶段无红灯(失败 test_result)→ 软警告,不阻断
429
+ if (warningCtx.test_strategy === 'tdd') {
430
+ const redGap = detectMissingTddRed(warningEvents, activeApply.timestamp);
431
+ if (redGap.missing && !hasExistingWarning(warningEvents, 'tdd_red_missing')) {
432
+ recordWarning(projectRoot, warningCtx, 'tdd_red_missing',
433
+ 'tdd 策略下 apply 阶段未检测到失败的测试结果(红灯),测试骨架可能未经 RED 验证即标记完成');
434
+ }
435
+ }
436
+ } catch {
437
+ // 警告检测失败不阻断主流程
438
+ }
439
+
440
+ const gate = shouldGate(projectRoot, activeApply);
441
+ if (!gate) {
442
+ process.exit(0);
443
+ }
444
+
445
+ const reason = buildReason(gate.strategy, gate.changeName, gate.capability);
446
+ recordWarning(projectRoot, { ...activeApply, test_strategy: gate.strategy }, 'apply_test_missing', 'Apply test gate blocked: no unit test evidence');
447
+
448
+ block('block', reason, {
449
+ test_strategy: gate.strategy,
450
+ change: gate.changeName,
451
+ capability: gate.capability,
452
+ });
453
+ }
310
454
 
311
- block('block', reason, {
312
- test_strategy: gate.strategy,
313
- change: gate.changeName,
314
- capability: gate.capability,
315
- });
455
+ module.exports = {
456
+ detectReusedTestResults,
457
+ detectProcessNoteGap,
458
+ detectMissingTddRed,
459
+ hasExistingWarning,
460
+ isRealTestDetails,
461
+ requiresUnitTests,
462
+ };
@@ -70,15 +70,23 @@ function latestActiveStage(projectRoot) {
70
70
  .sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime())[0] || null;
71
71
  }
72
72
 
73
+ // inferResult:解析 tool_response 判定构建/测试结果
74
+ // 返回 { result: 'success'|'failure'|'partial', exit_code_known: boolean }
75
+ // v2.5 契约变更:返回类型从 string 变为对象(含 exit_code_known 标注);旧调用方 `inferResult(input) === 'success'` 需改为 `.result === 'success'`
73
76
  function inferResult(input) {
74
77
  const response = input.tool_response || input.toolResponse || {};
75
- if (typeof response.exit_code === 'number') return response.exit_code === 0 ? 'success' : 'failure';
76
- if (typeof response.exitCode === 'number') return response.exitCode === 0 ? 'success' : 'failure';
77
- if (typeof response.success === 'boolean') return response.success ? 'success' : 'failure';
78
- return 'partial';
78
+ if (typeof response.exit_code === 'number') return { result: response.exit_code === 0 ? 'success' : 'failure', exit_code_known: true };
79
+ if (typeof response.exitCode === 'number') return { result: response.exitCode === 0 ? 'success' : 'failure', exit_code_known: true };
80
+ if (typeof response.success === 'boolean') return { result: response.success ? 'success' : 'failure', exit_code_known: true };
81
+ // fallback:从 stdout/stderr 识别构建失败信号;无法判定时 partial 且 exit_code_known=false
82
+ const out = String(response.stdout || response.stderr || '');
83
+ if (/BUILD FAILED|error TS\d+|ERR_/i.test(out)) return { result: 'failure', exit_code_known: false };
84
+ return { result: 'partial', exit_code_known: false };
79
85
  }
80
86
 
81
87
  function inferRecord(command) {
88
+ // 排除 telemetry 自身的 record 命令(防止 log.cjs/log.js record 被误判为 build/test 结果)
89
+ if (/\b(log\.cjs|log\.js)\s+record\b/.test(command)) return null;
82
90
  if (/\b(npm|pnpm|yarn)\s+(test|run\s+test)\b|\bpytest\b|\bmvn\b.*\btest\b|\bgo\s+test\b|\bcargo\s+test\b/i.test(command)) {
83
91
  return {
84
92
  type: 'test_result',
@@ -96,23 +104,29 @@ function inferRecord(command) {
96
104
  return null;
97
105
  }
98
106
 
99
- function recordTelemetry(projectRoot, activeStage, record, result) {
107
+ function recordTelemetry(projectRoot, activeStage, record, result, exitCodeKnown, input, runner) {
100
108
  const logPath = path.join(projectRoot, 'skywalk-sdd', 'log.cjs');
101
109
  const legacyLogPath = path.join(projectRoot, 'skywalk-sdd', 'log.js');
102
110
  const logCli = fs.existsSync(logPath) ? logPath : legacyLogPath;
103
111
  if (!fs.existsSync(logCli)) return;
104
112
 
113
+ const toolInput = (input && (input.tool_input || input.toolInput)) || {};
114
+ const toolResponse = (input && (input.tool_response || input.toolResponse)) || {};
105
115
  const details = { [record.detailsKey]: { ...record.details } };
106
116
  if (record.type === 'build_result') {
107
117
  details.build_results.success = result === 'success';
108
118
  details.build_results.error_count = result === 'success' ? 0 : null;
119
+ details.build_results.exit_code_known = Boolean(exitCodeKnown);
120
+ const durationMs = typeof toolResponse.duration_ms === 'number' ? toolResponse.duration_ms : null;
121
+ details.build_results.duration_ms = durationMs;
122
+ details.build_results.duration_known = durationMs !== null;
109
123
  }
110
124
 
111
125
  const args = [
112
126
  logCli,
113
127
  'record',
114
128
  `--type=${record.type}`,
115
- `--command=${activeStage.command || activeStage.stage || 'unknown'}`,
129
+ `--command=${toolInput.command || activeStage.command || activeStage.stage || 'unknown'}`,
116
130
  `--project=${projectRoot}`,
117
131
  `--change=${activeStage.change || 'general'}`,
118
132
  `--agent=${activeStage.agent_type || 'claude-code'}`,
@@ -124,23 +138,30 @@ function recordTelemetry(projectRoot, activeStage, record, result) {
124
138
  if (activeStage.capability) args.push(`--capability=${activeStage.capability}`);
125
139
  if (activeStage.task_id) args.push(`--task-id=${activeStage.task_id}`);
126
140
  if (activeStage.session_id) args.push(`--session-id=${activeStage.session_id}`);
127
- execFileSync('node', args, { cwd: projectRoot, stdio: 'ignore' });
141
+ const run = runner || ((a) => execFileSync('node', a, { cwd: projectRoot, stdio: 'ignore' }));
142
+ run(args);
128
143
  }
129
144
 
130
- const input = parseInput(readStdin());
131
- const toolInput = input.tool_input || input.toolInput || {};
132
- const command = String(toolInput.command || input.command || '');
133
- const record = inferRecord(command);
134
- if (!record) process.exit(0);
145
+ // 仅在直接执行时跑主流程;require 时不读 stdin(便于单元测试)
146
+ if (require.main === module) {
147
+ const input = parseInput(readStdin());
148
+ const toolInput = input.tool_input || input.toolInput || {};
149
+ const command = String(toolInput.command || input.command || '');
150
+ const record = inferRecord(command);
151
+ if (!record) process.exit(0);
135
152
 
136
- const projectRoot = findProjectRoot(input);
137
- const activeStage = latestActiveStage(projectRoot);
138
- if (!activeStage || !activeStage.change || activeStage.change === 'general') {
139
- process.exit(0);
140
- }
153
+ const projectRoot = findProjectRoot(input);
154
+ const activeStage = latestActiveStage(projectRoot);
155
+ if (!activeStage || !activeStage.change || activeStage.change === 'general') {
156
+ process.exit(0);
157
+ }
141
158
 
142
- try {
143
- recordTelemetry(projectRoot, activeStage, record, inferResult(input));
144
- } catch {
145
- process.exit(0);
159
+ try {
160
+ const inferred = inferResult(input);
161
+ recordTelemetry(projectRoot, activeStage, record, inferred.result, inferred.exit_code_known, input);
162
+ } catch {
163
+ process.exit(0);
164
+ }
146
165
  }
166
+
167
+ module.exports = { inferResult, inferRecord, recordTelemetry, findProjectRoot, latestActiveStage };
@@ -0,0 +1,16 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const core = require('./hook-gate-core.cjs');
5
+
6
+ const parsed = core.parseHookInput(core.readStdin(), { strict: true });
7
+ if (!parsed.ok) {
8
+ core.blockWithReason(parsed.error);
9
+ }
10
+
11
+ const input = core.normalizeHookInput(parsed.input);
12
+ const result = core.evaluateApplyWriteGate(input, 'codebuddy');
13
+ if (result.action === 'block') {
14
+ core.blockWithReason(result.reason);
15
+ }
16
+ core.allowExit();