kc-beta 0.1.2 → 0.3.0
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/bin/kc-beta.js +14 -2
- package/package.json +1 -1
- package/src/agent/context-window.js +151 -0
- package/src/agent/context.js +8 -4
- package/src/agent/engine.js +261 -8
- package/src/agent/event-log.js +111 -0
- package/src/agent/llm-client.js +352 -59
- package/src/agent/pipelines/base.js +6 -0
- package/src/agent/pipelines/distillation.js +18 -0
- package/src/agent/pipelines/extraction.js +21 -0
- package/src/agent/pipelines/initializer.js +75 -14
- package/src/agent/pipelines/production-qc.js +19 -0
- package/src/agent/pipelines/skill-authoring.js +14 -0
- package/src/agent/pipelines/skill-testing.js +20 -0
- package/src/agent/retry.js +83 -0
- package/src/agent/session-state.js +79 -0
- package/src/agent/skill-loader.js +13 -1
- package/src/agent/token-counter.js +62 -0
- package/src/agent/tools/document-parse.js +104 -21
- package/src/agent/tools/document-search.js +24 -8
- package/src/agent/tools/sandbox-exec.js +16 -5
- package/src/agent/tools/web-search.js +107 -0
- package/src/agent/tools/worker-llm-call.js +14 -5
- package/src/agent/tools/workspace-file.js +47 -20
- package/src/agent/workspace.js +24 -1
- package/src/cli/components.js +24 -5
- package/src/cli/config.js +340 -0
- package/src/cli/index.js +113 -11
- package/src/cli/onboard.js +216 -53
- package/src/config.js +63 -10
- package/src/model-tiers.json +153 -0
- package/src/providers.js +367 -0
- package/template/AGENT.md +20 -0
- package/template/skills/en/meta/compliance-judgment/SKILL.md +10 -42
- package/template/skills/en/meta/document-chunking/SKILL.md +32 -0
- package/template/skills/en/meta/document-parsing/SKILL.md +11 -18
- package/template/skills/en/meta/entity-extraction/SKILL.md +13 -28
- package/template/skills/en/meta/tree-processing/SKILL.md +19 -1
- package/template/skills/en/meta-meta/auto-model-selection/SKILL.md +53 -0
- package/template/skills/en/meta-meta/pdf-review-dashboard/SKILL.md +57 -0
- package/template/skills/en/meta-meta/pdf-review-dashboard/scripts/generate_review.js +262 -0
- package/template/skills/en/meta-meta/rule-extraction/SKILL.md +24 -1
- package/template/skills/en/meta-meta/skill-authoring/SKILL.md +6 -0
- package/template/skills/en/meta-meta/skill-to-workflow/SKILL.md +4 -0
- package/template/skills/zh/meta/compliance-judgment/SKILL.md +41 -262
- package/template/skills/zh/meta/document-chunking/SKILL.md +32 -0
- package/template/skills/zh/meta/document-parsing/SKILL.md +65 -132
- package/template/skills/zh/meta/entity-extraction/SKILL.md +68 -230
- package/template/skills/zh/meta/tree-processing/SKILL.md +82 -194
- package/template/skills/zh/meta-meta/auto-model-selection/SKILL.md +51 -0
- package/template/skills/zh/meta-meta/pdf-review-dashboard/SKILL.md +55 -0
- package/template/skills/zh/meta-meta/pdf-review-dashboard/scripts/generate_review.js +262 -0
- package/template/skills/zh/meta-meta/rule-extraction/SKILL.md +79 -164
- package/template/skills/zh/meta-meta/skill-authoring/SKILL.md +64 -185
- package/template/skills/zh/meta-meta/skill-to-workflow/SKILL.md +95 -216
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: auto-model-selection
|
|
3
|
+
description: >
|
|
4
|
+
使用 Context7 CLI 获取最新 LLM 模型信息。当需要了解可用模型、模型能力、价格、
|
|
5
|
+
上下文窗口大小、或哪个模型适合某项任务时使用——包括分层分配、Worker LLM 工作流设计、
|
|
6
|
+
模型对比、服务商 API 调用方式等。Context7 提供训练数据中可能没有的最新信息。
|
|
7
|
+
需要安装 context7 CLI (npm i -g context7)。可选插件。
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# 通过 Context7 自动选择模型
|
|
11
|
+
|
|
12
|
+
## Context7 是什么
|
|
13
|
+
|
|
14
|
+
Context7 (`c7`) 是一个轻量 CLI 工具,可获取最新的库和 API 文档。安装:`npm i -g context7`。两个命令:
|
|
15
|
+
- `c7 library <查询>` — 按名称搜索库/服务商
|
|
16
|
+
- `c7 docs <libraryId> <查询>` — 获取具体文档和代码示例
|
|
17
|
+
|
|
18
|
+
## 使用时机
|
|
19
|
+
|
|
20
|
+
- 用户的 `model-tiers.json` 过期(KC 长时间未更新)
|
|
21
|
+
- 用户切换到新服务商,需要模型发现
|
|
22
|
+
- 用户明确要求更新模型选择
|
|
23
|
+
- 配置向导的 `/models` 端点失败,且内置模型列表过期
|
|
24
|
+
|
|
25
|
+
## 工作流程
|
|
26
|
+
|
|
27
|
+
1. 用户选择服务商并提供 API 密钥
|
|
28
|
+
2. 用 `c7 library <服务商名>` 找到对应的 library ID
|
|
29
|
+
3. 用 `c7 docs <id> "available models"` 获取当前模型列表
|
|
30
|
+
4. 从文档中识别:模型名称、能力(推理、编码、视觉)、上下文窗口大小、价格
|
|
31
|
+
5. 按能力和成本分配到分层:
|
|
32
|
+
- LLM tier1:最强(复杂判断、抽取)
|
|
33
|
+
- LLM tier2-3:中等(常规抽取、简单判断)
|
|
34
|
+
- LLM tier4:最便宜(大量简单任务)
|
|
35
|
+
- VLM tier1-3:视觉模型(文档解析/OCR)
|
|
36
|
+
6. 更新 `model-tiers.json` 或工作区 `.env`
|
|
37
|
+
|
|
38
|
+
## 分层原则
|
|
39
|
+
|
|
40
|
+
- 满足准确率阈值的最便宜模型
|
|
41
|
+
- 正则是 tier0 — 比任何 LLM 都小
|
|
42
|
+
- 不需要填满所有分层 — 服务商没有合适模型时留空即可
|
|
43
|
+
- 在 AGENT.md 中记录哪些模型适合哪些任务
|
|
44
|
+
|
|
45
|
+
## 前置条件
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
npm i -g context7
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
验证:`c7 library openai` 应返回结果。
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pdf-review-dashboard
|
|
3
|
+
description: >
|
|
4
|
+
生成双栏 PDF 审核面板,用于人工核查验证结果。左侧显示原始 PDF 文档,右侧显示验证结果。
|
|
5
|
+
点击结果条目可跳转至 PDF 对应页面。当开发者用户需要对照源文件审核验证输出、
|
|
6
|
+
或为演进循环收集真实标注数据时使用。输出为单个自包含 HTML 文件。
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 功能
|
|
10
|
+
|
|
11
|
+
生成单个自包含 HTML 文件:
|
|
12
|
+
- 左侧:浏览器内渲染的原始 PDF
|
|
13
|
+
- 右侧:可交互的验证结果列表
|
|
14
|
+
- 点击跳转:选中结果后 PDF 滚动到对应页面
|
|
15
|
+
|
|
16
|
+
开发者用户在浏览器中打开此 HTML 即可人工审核验证质量。
|
|
17
|
+
|
|
18
|
+
## 技术栈
|
|
19
|
+
|
|
20
|
+
- 单 HTML 文件,无需服务器
|
|
21
|
+
- PDF 以 base64 内嵌(完全自包含,可分享)
|
|
22
|
+
- 通过 CDN 加载 pdf.js 实现浏览器内 PDF 渲染
|
|
23
|
+
- 纯 JS + 内联 CSS,无框架依赖
|
|
24
|
+
- 深色主题,与 KC 仪表板风格一致
|
|
25
|
+
|
|
26
|
+
## 布局
|
|
27
|
+
|
|
28
|
+
- 可拖拽分隔条调整左右面板比例
|
|
29
|
+
- 左侧:PDF 查看器,顶部工具栏含翻页(上一页/下一页/跳转)和缩放(+/-/适应宽度)
|
|
30
|
+
- 右侧:结果列表,带筛选按钮,点击展开详情并跳转 PDF 页面
|
|
31
|
+
- 跳转时页面高亮动画
|
|
32
|
+
|
|
33
|
+
## 数据格式
|
|
34
|
+
|
|
35
|
+
生成器脚本读取 PDF 文件和结果 JSON,输出 HTML。
|
|
36
|
+
|
|
37
|
+
输入:
|
|
38
|
+
- `pdf_path` — 源 PDF 文档路径
|
|
39
|
+
- `results_path` — 验证结果 JSON 文件路径
|
|
40
|
+
|
|
41
|
+
结果 JSON 为对象数组,每个对象至少包含:
|
|
42
|
+
- 页面引用(对应 PDF 的哪一页)
|
|
43
|
+
- 结果状态(pass/fail/warning 或等效值)
|
|
44
|
+
|
|
45
|
+
右侧面板的列和详情字段自动适配验证工作流的输出数据。`scripts/generate_review.js` 是参考实现——根据项目输出格式调整数据映射部分。
|
|
46
|
+
|
|
47
|
+
## 使用时机
|
|
48
|
+
|
|
49
|
+
- 验证工作流完成后,供开发者用户可视化审核结果
|
|
50
|
+
- 为演进循环收集真实标注修正
|
|
51
|
+
- 向需要查看源文件依据的相关方展示结果
|
|
52
|
+
|
|
53
|
+
## 生成器脚本
|
|
54
|
+
|
|
55
|
+
见 `scripts/generate_review.js` — Node.js 脚本,输入 PDF 路径,输出审核 HTML。根据项目验证输出格式调整结果数据映射部分。
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PDF Review Dashboard Generator
|
|
4
|
+
*
|
|
5
|
+
* Generates a single self-contained HTML file with:
|
|
6
|
+
* - Left: PDF viewer (pdf.js CDN, base64 embedded)
|
|
7
|
+
* - Right: interactive verification results list
|
|
8
|
+
* - Click result → jump to PDF page
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node generate_review.js <pdf_path> <results_json_path> [output_html_path]
|
|
12
|
+
*
|
|
13
|
+
* The results JSON should be an array of objects. Adapt the DATA MAPPING
|
|
14
|
+
* section below to match your project's verification output format.
|
|
15
|
+
*/
|
|
16
|
+
import fs from "node:fs";
|
|
17
|
+
import path from "node:path";
|
|
18
|
+
|
|
19
|
+
const pdfPath = process.argv[2];
|
|
20
|
+
const resultsPath = process.argv[3];
|
|
21
|
+
const outputPath = process.argv[4] || "review_dashboard.html";
|
|
22
|
+
|
|
23
|
+
if (!pdfPath || !resultsPath) {
|
|
24
|
+
console.error("Usage: node generate_review.js <pdf_path> <results_json_path> [output_html_path]");
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Read inputs
|
|
29
|
+
const pdfBuffer = fs.readFileSync(pdfPath);
|
|
30
|
+
const pdfBase64 = pdfBuffer.toString("base64");
|
|
31
|
+
const pdfFileName = path.basename(pdfPath);
|
|
32
|
+
const rawResults = JSON.parse(fs.readFileSync(resultsPath, "utf-8"));
|
|
33
|
+
|
|
34
|
+
// ============================================================
|
|
35
|
+
// DATA MAPPING — adapt this section to your verification output
|
|
36
|
+
// ============================================================
|
|
37
|
+
// Map your raw results into the format the dashboard expects.
|
|
38
|
+
// Each item needs at minimum: id, label, result, page.
|
|
39
|
+
// Add any extra fields you want shown in the detail panel.
|
|
40
|
+
const results = Array.isArray(rawResults) ? rawResults : rawResults.results || [];
|
|
41
|
+
const mappedResults = results.map((r, i) => ({
|
|
42
|
+
id: r.id || r.rule_id || `R${String(i + 1).padStart(3, "0")}`,
|
|
43
|
+
label: r.rule || r.label || r.name || r.description || `Item ${i + 1}`,
|
|
44
|
+
result: r.result || r.status || "unknown",
|
|
45
|
+
confidence: r.confidence ?? r.score ?? null,
|
|
46
|
+
page: r.page || r.page_ref || 1,
|
|
47
|
+
// Detail fields — include whatever your workflow outputs
|
|
48
|
+
detail: r.detail || Object.fromEntries(
|
|
49
|
+
Object.entries(r).filter(([k]) => !["id","rule_id","rule","label","name","result","status","confidence","score","page","page_ref"].includes(k))
|
|
50
|
+
),
|
|
51
|
+
}));
|
|
52
|
+
// ============================================================
|
|
53
|
+
|
|
54
|
+
console.log(`PDF: ${pdfFileName} (${(pdfBuffer.length / 1024 / 1024).toFixed(1)}MB)`);
|
|
55
|
+
console.log(`Results: ${mappedResults.length} items`);
|
|
56
|
+
|
|
57
|
+
// Generate HTML
|
|
58
|
+
const html = buildHTML(pdfBase64, pdfFileName, mappedResults);
|
|
59
|
+
fs.writeFileSync(outputPath, html, "utf-8");
|
|
60
|
+
console.log(`Output: ${outputPath} (${(Buffer.byteLength(html) / 1024 / 1024).toFixed(1)}MB)`);
|
|
61
|
+
|
|
62
|
+
function buildHTML(pdfB64, fileName, items) {
|
|
63
|
+
const resultsJSON = JSON.stringify(items);
|
|
64
|
+
return `<!DOCTYPE html>
|
|
65
|
+
<html lang="zh-CN">
|
|
66
|
+
<head>
|
|
67
|
+
<meta charset="UTF-8">
|
|
68
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
69
|
+
<title>KC Review — ${fileName}</title>
|
|
70
|
+
<style>
|
|
71
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
72
|
+
:root {
|
|
73
|
+
--bg: #0a0a0a; --bg2: #141414; --bg3: #1e1e1e;
|
|
74
|
+
--text: #e5e5e5; --dim: #888; --border: #2a2a2a;
|
|
75
|
+
--green: #22c55e; --yellow: #eab308; --red: #ef4444;
|
|
76
|
+
--blue: #3b82f6; --orange: #f97316;
|
|
77
|
+
}
|
|
78
|
+
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--text); height: 100vh; overflow: hidden; }
|
|
79
|
+
#app { display: flex; height: 100vh; }
|
|
80
|
+
#pdf-panel { flex: 1; display: flex; flex-direction: column; border-right: 1px solid var(--border); min-width: 300px; }
|
|
81
|
+
#pdf-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--bg2); border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
82
|
+
#pdf-toolbar button { background: var(--bg3); color: var(--text); border: 1px solid var(--border); border-radius: 4px; padding: 4px 10px; cursor: pointer; font-size: 13px; }
|
|
83
|
+
#pdf-toolbar button:hover { background: var(--border); }
|
|
84
|
+
#pdf-toolbar span { color: var(--dim); font-size: 13px; }
|
|
85
|
+
#pdf-toolbar input[type=number] { width: 50px; background: var(--bg3); color: var(--text); border: 1px solid var(--border); border-radius: 4px; padding: 4px; text-align: center; font-size: 13px; }
|
|
86
|
+
#pdf-container { flex: 1; overflow: auto; display: flex; flex-direction: column; align-items: center; padding: 16px; gap: 8px; }
|
|
87
|
+
.pdf-page-wrapper { position: relative; box-shadow: 0 2px 8px rgba(0,0,0,0.5); }
|
|
88
|
+
.pdf-page-wrapper canvas { display: block; }
|
|
89
|
+
.page-highlight { position: absolute; inset: 0; background: rgba(59,130,246,0.12); border: 2px solid var(--blue); pointer-events: none; opacity: 0; transition: opacity 0.3s; }
|
|
90
|
+
.page-highlight.active { opacity: 1; animation: pulse-border 1.5s ease-out; }
|
|
91
|
+
@keyframes pulse-border { 0% { border-color: var(--orange); box-shadow: 0 0 20px rgba(249,115,22,0.4); } 100% { border-color: var(--blue); box-shadow: none; } }
|
|
92
|
+
#drag-handle { width: 5px; background: var(--border); cursor: col-resize; flex-shrink: 0; transition: background 0.2s; }
|
|
93
|
+
#drag-handle:hover, #drag-handle.dragging { background: var(--blue); }
|
|
94
|
+
#results-panel { flex: 1; display: flex; flex-direction: column; min-width: 350px; }
|
|
95
|
+
#results-toolbar { display: flex; align-items: center; gap: 8px; padding: 8px 12px; background: var(--bg2); border-bottom: 1px solid var(--border); flex-shrink: 0; flex-wrap: wrap; }
|
|
96
|
+
#results-toolbar .filter-btn { background: var(--bg3); color: var(--dim); border: 1px solid var(--border); border-radius: 12px; padding: 3px 10px; cursor: pointer; font-size: 12px; transition: all 0.2s; }
|
|
97
|
+
#results-toolbar .filter-btn.active { color: var(--text); border-color: var(--blue); background: rgba(59,130,246,0.15); }
|
|
98
|
+
#results-toolbar .summary { margin-left: auto; font-size: 12px; color: var(--dim); }
|
|
99
|
+
#results-list { flex: 1; overflow: auto; }
|
|
100
|
+
.result-item { border-bottom: 1px solid var(--border); cursor: pointer; transition: background 0.15s; }
|
|
101
|
+
.result-item:hover { background: var(--bg3); }
|
|
102
|
+
.result-item.selected { background: rgba(59,130,246,0.1); border-left: 3px solid var(--blue); }
|
|
103
|
+
.result-row { display: flex; align-items: center; padding: 10px 12px; gap: 10px; }
|
|
104
|
+
.result-id { font-size: 11px; color: var(--dim); min-width: 40px; font-family: monospace; }
|
|
105
|
+
.result-label { flex: 1; font-size: 13px; }
|
|
106
|
+
.result-badge { font-size: 11px; font-weight: 600; padding: 2px 8px; border-radius: 10px; text-transform: uppercase; }
|
|
107
|
+
.badge-pass { background: rgba(34,197,94,0.15); color: var(--green); }
|
|
108
|
+
.badge-fail { background: rgba(239,68,68,0.15); color: var(--red); }
|
|
109
|
+
.badge-warning { background: rgba(234,179,8,0.15); color: var(--yellow); }
|
|
110
|
+
.badge-unknown { background: rgba(136,136,136,0.15); color: var(--dim); }
|
|
111
|
+
.result-confidence { font-size: 12px; color: var(--dim); min-width: 40px; text-align: right; }
|
|
112
|
+
.result-page { font-size: 11px; color: var(--dim); min-width: 30px; text-align: right; }
|
|
113
|
+
.result-detail { display: none; padding: 8px 12px 14px 62px; font-size: 12px; line-height: 1.6; color: var(--dim); border-top: 1px dashed var(--border); }
|
|
114
|
+
.result-item.expanded .result-detail { display: block; }
|
|
115
|
+
.detail-row { margin-bottom: 4px; }
|
|
116
|
+
.detail-key { color: var(--text); font-weight: 500; }
|
|
117
|
+
</style>
|
|
118
|
+
</head>
|
|
119
|
+
<body>
|
|
120
|
+
<div id="app">
|
|
121
|
+
<div id="pdf-panel">
|
|
122
|
+
<div id="pdf-toolbar">
|
|
123
|
+
<button onclick="prevPage()">◀</button>
|
|
124
|
+
<span>Page</span>
|
|
125
|
+
<input type="number" id="page-input" value="1" min="1" onchange="goToPage(this.value)">
|
|
126
|
+
<span id="page-count">/ ?</span>
|
|
127
|
+
<button onclick="nextPage()">▶</button>
|
|
128
|
+
<span style="margin-left:8px">|</span>
|
|
129
|
+
<button onclick="zoomOut()">−</button>
|
|
130
|
+
<span id="zoom-label">100%</span>
|
|
131
|
+
<button onclick="zoomIn()">+</button>
|
|
132
|
+
<button onclick="fitWidth()">Fit</button>
|
|
133
|
+
</div>
|
|
134
|
+
<div id="pdf-container"></div>
|
|
135
|
+
</div>
|
|
136
|
+
<div id="drag-handle"></div>
|
|
137
|
+
<div id="results-panel">
|
|
138
|
+
<div id="results-toolbar">
|
|
139
|
+
<span class="summary" id="results-summary"></span>
|
|
140
|
+
</div>
|
|
141
|
+
<div id="results-list"></div>
|
|
142
|
+
</div>
|
|
143
|
+
</div>
|
|
144
|
+
<script type="module">
|
|
145
|
+
const PDF_B64 = "${pdfB64}";
|
|
146
|
+
const RESULTS = ${resultsJSON};
|
|
147
|
+
|
|
148
|
+
// PDF setup
|
|
149
|
+
const pdfjsLib = await import("https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.10.38/pdf.min.mjs");
|
|
150
|
+
pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/4.10.38/pdf.worker.min.mjs";
|
|
151
|
+
const pdfData = Uint8Array.from(atob(PDF_B64), c => c.charCodeAt(0));
|
|
152
|
+
const pdf = await pdfjsLib.getDocument({ data: pdfData }).promise;
|
|
153
|
+
const totalPages = pdf.numPages;
|
|
154
|
+
document.getElementById("page-count").textContent = "/ " + totalPages;
|
|
155
|
+
document.getElementById("page-input").max = totalPages;
|
|
156
|
+
|
|
157
|
+
let scale = 1.2, currentPage = 1;
|
|
158
|
+
const container = document.getElementById("pdf-container");
|
|
159
|
+
const pageCanvases = new Map();
|
|
160
|
+
|
|
161
|
+
async function renderAllPages() {
|
|
162
|
+
container.innerHTML = ""; pageCanvases.clear();
|
|
163
|
+
for (let i = 1; i <= totalPages; i++) {
|
|
164
|
+
const page = await pdf.getPage(i);
|
|
165
|
+
const vp = page.getViewport({ scale });
|
|
166
|
+
const w = document.createElement("div");
|
|
167
|
+
w.className = "pdf-page-wrapper"; w.id = "page-" + i;
|
|
168
|
+
w.style.width = vp.width + "px"; w.style.height = vp.height + "px";
|
|
169
|
+
const c = document.createElement("canvas");
|
|
170
|
+
c.width = vp.width; c.height = vp.height;
|
|
171
|
+
await page.render({ canvasContext: c.getContext("2d"), viewport: vp }).promise;
|
|
172
|
+
const hl = document.createElement("div"); hl.className = "page-highlight";
|
|
173
|
+
w.appendChild(c); w.appendChild(hl); container.appendChild(w);
|
|
174
|
+
pageCanvases.set(i, w);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
await renderAllPages();
|
|
178
|
+
|
|
179
|
+
function goToPage(n) { n = Math.max(1, Math.min(parseInt(n)||1, totalPages)); currentPage = n; document.getElementById("page-input").value = n; const el = document.getElementById("page-"+n); if(el) el.scrollIntoView({behavior:"smooth",block:"start"}); }
|
|
180
|
+
function prevPage() { goToPage(currentPage-1); }
|
|
181
|
+
function nextPage() { goToPage(currentPage+1); }
|
|
182
|
+
function zoomIn() { scale = Math.min(scale+0.2, 3); updateZoom(); }
|
|
183
|
+
function zoomOut() { scale = Math.max(scale-0.2, 0.4); updateZoom(); }
|
|
184
|
+
function fitWidth() { pdf.getPage(1).then(p => { scale = (document.getElementById("pdf-panel").clientWidth-40)/p.getViewport({scale:1}).width; updateZoom(); }); }
|
|
185
|
+
function updateZoom() { document.getElementById("zoom-label").textContent = Math.round(scale*100)+"%"; renderAllPages(); }
|
|
186
|
+
window.goToPage=goToPage; window.prevPage=prevPage; window.nextPage=nextPage;
|
|
187
|
+
window.zoomIn=zoomIn; window.zoomOut=zoomOut; window.fitWidth=fitWidth;
|
|
188
|
+
|
|
189
|
+
// Detect unique result statuses for filter buttons
|
|
190
|
+
const statuses = [...new Set(RESULTS.map(r => r.result))];
|
|
191
|
+
const toolbar = document.getElementById("results-toolbar");
|
|
192
|
+
const filterHTML = '<button class="filter-btn active" data-filter="all">All</button>' +
|
|
193
|
+
statuses.map(s => '<button class="filter-btn" data-filter="'+s+'">'+s.charAt(0).toUpperCase()+s.slice(1)+'</button>').join("");
|
|
194
|
+
toolbar.insertAdjacentHTML("afterbegin", filterHTML);
|
|
195
|
+
let activeFilter = "all", selectedId = null;
|
|
196
|
+
toolbar.querySelectorAll(".filter-btn").forEach(b => b.addEventListener("click", () => {
|
|
197
|
+
activeFilter = b.dataset.filter;
|
|
198
|
+
toolbar.querySelectorAll(".filter-btn").forEach(x => x.classList.toggle("active", x.dataset.filter===activeFilter));
|
|
199
|
+
selectedId = null; renderResults();
|
|
200
|
+
}));
|
|
201
|
+
|
|
202
|
+
function renderResults() {
|
|
203
|
+
const list = document.getElementById("results-list");
|
|
204
|
+
const filtered = activeFilter === "all" ? RESULTS : RESULTS.filter(r => r.result === activeFilter);
|
|
205
|
+
const counts = statuses.map(s => RESULTS.filter(r=>r.result===s).length + " " + s).join(" · ");
|
|
206
|
+
document.getElementById("results-summary").textContent = counts;
|
|
207
|
+
|
|
208
|
+
list.innerHTML = filtered.map(r => {
|
|
209
|
+
const bc = ["pass","fail","warning"].includes(r.result) ? "badge-"+r.result : "badge-unknown";
|
|
210
|
+
const sel = r.id === selectedId ? " selected expanded" : "";
|
|
211
|
+
const conf = r.confidence != null ? Math.round(r.confidence*100)+"%" : "";
|
|
212
|
+
let detailHTML = "";
|
|
213
|
+
if (r.detail && typeof r.detail === "object") {
|
|
214
|
+
detailHTML = Object.entries(r.detail).map(([k,v]) =>
|
|
215
|
+
'<div class="detail-row"><span class="detail-key">'+k+': </span>'+String(v)+'</div>'
|
|
216
|
+
).join("");
|
|
217
|
+
}
|
|
218
|
+
return '<div class="result-item'+sel+'" data-id="'+r.id+'" data-page="'+r.page+'">' +
|
|
219
|
+
'<div class="result-row">' +
|
|
220
|
+
'<span class="result-id">'+r.id+'</span>' +
|
|
221
|
+
'<span class="result-label">'+r.label+'</span>' +
|
|
222
|
+
'<span class="result-badge '+bc+'">'+r.result+'</span>' +
|
|
223
|
+
(conf ? '<span class="result-confidence">'+conf+'</span>' : '') +
|
|
224
|
+
'<span class="result-page">p.'+r.page+'</span>' +
|
|
225
|
+
'</div>' +
|
|
226
|
+
(detailHTML ? '<div class="result-detail">'+detailHTML+'</div>' : '') +
|
|
227
|
+
'</div>';
|
|
228
|
+
}).join("");
|
|
229
|
+
|
|
230
|
+
list.querySelectorAll(".result-item").forEach(el => el.addEventListener("click", () => {
|
|
231
|
+
const id = el.dataset.id, page = parseInt(el.dataset.page);
|
|
232
|
+
if (selectedId === id) { selectedId = null; el.classList.remove("selected","expanded"); }
|
|
233
|
+
else { list.querySelectorAll(".result-item").forEach(e=>e.classList.remove("selected","expanded")); selectedId = id; el.classList.add("selected","expanded"); }
|
|
234
|
+
jumpToPage(page);
|
|
235
|
+
}));
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function jumpToPage(page) {
|
|
239
|
+
currentPage = page; document.getElementById("page-input").value = page;
|
|
240
|
+
const el = document.getElementById("page-"+page);
|
|
241
|
+
if(el) { el.scrollIntoView({behavior:"smooth",block:"center"});
|
|
242
|
+
const hl = el.querySelector(".page-highlight"); hl.classList.remove("active");
|
|
243
|
+
void hl.offsetWidth; hl.classList.add("active"); setTimeout(()=>hl.classList.remove("active"),2000); }
|
|
244
|
+
}
|
|
245
|
+
renderResults();
|
|
246
|
+
|
|
247
|
+
// Drag handle
|
|
248
|
+
const handle = document.getElementById("drag-handle");
|
|
249
|
+
let dragging = false;
|
|
250
|
+
handle.addEventListener("mousedown", e => { dragging=true; handle.classList.add("dragging"); e.preventDefault(); });
|
|
251
|
+
document.addEventListener("mousemove", e => { if(!dragging) return; const r=e.clientX/document.getElementById("app").clientWidth; const c=Math.max(0.2,Math.min(0.8,r)); document.getElementById("pdf-panel").style.flex="0 0 "+(c*100)+"%"; document.getElementById("results-panel").style.flex="1"; });
|
|
252
|
+
document.addEventListener("mouseup", () => { dragging=false; handle.classList.remove("dragging"); });
|
|
253
|
+
|
|
254
|
+
container.addEventListener("scroll", () => {
|
|
255
|
+
const cr = container.getBoundingClientRect(); let closest=1, cd=Infinity;
|
|
256
|
+
pageCanvases.forEach((w,n) => { const d=Math.abs(w.getBoundingClientRect().top-cr.top); if(d<cd){cd=d;closest=n;} });
|
|
257
|
+
if(closest!==currentPage){currentPage=closest;document.getElementById("page-input").value=closest;}
|
|
258
|
+
});
|
|
259
|
+
</script>
|
|
260
|
+
</body>
|
|
261
|
+
</html>`;
|
|
262
|
+
}
|
|
@@ -3,206 +3,121 @@ name: rule-extraction
|
|
|
3
3
|
description: Extract and organize business verification rules from regulation documents into discrete, testable units. Use when processing documents in Rules/ to identify individual verification rules, when decomposing a regulation into atomic checks, or when the developer user adds new regulation files. Covers reading regulation text, identifying rule boundaries, determining granularity, handling cross-references, and producing a rule catalog. Also use when rules are provided in structured formats like xlsx or csv.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
|
-
#
|
|
6
|
+
# Rule Extraction
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
Rules are the atoms of verification. Each rule you extract will become its own skill folder, its own workflow, and its own production pipeline.
|
|
9
9
|
|
|
10
|
-
|
|
10
|
+
## How This Differs from Data Extraction
|
|
11
11
|
|
|
12
|
-
|
|
12
|
+
Rule extraction is a **one-off task** at the start of a project. You read regulation documents and decompose them into discrete, testable rules. This is fuzzy, agile work — rules are read by you (a SOTA agent), so the schema can be messy and evolve freely.
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
Data/entity extraction (`entity-extraction`) is the **repeating task** that runs on every document being verified. It must fit a unified, stable schema because it feeds into automated workflows.
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Don't conflate the two. Rule extraction happens once; data extraction happens on every document.
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
## Rule Structure: Location → Extraction → Judgment
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
Every verification rule decomposes into three parts:
|
|
21
21
|
|
|
22
|
-
|
|
22
|
+
1. **Location**: Where in the document to look (which chapter, section, table, or full document).
|
|
23
|
+
2. **Extraction**: What data to pull from that location (a number, a date, a clause, a description).
|
|
24
|
+
3. **Judgment**: How to determine pass/fail (threshold comparison, semantic assessment, cross-field check).
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
When extracting a rule, explicitly note all three parts. This determines the downstream pipeline structure:
|
|
27
|
+
- Full-document rules need no location step.
|
|
28
|
+
- Single-section rules need one location step.
|
|
29
|
+
- Cross-section rules (comparing values across chapters) need multiple location steps.
|
|
25
30
|
|
|
26
|
-
|
|
31
|
+
Classify each rule's scope accordingly — it affects how the verification workflow is structured.
|
|
27
32
|
|
|
28
|
-
|
|
33
|
+
## Philosophy
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
A well-extracted rule is:
|
|
36
|
+
- **Atomic**: it checks one thing. "The borrower's debt-to-income ratio must not exceed 50%" is one rule. "The loan agreement must comply with Regulation X" is not — it is a container for many rules.
|
|
37
|
+
- **Testable**: given a document, you can definitively say whether the rule passes or fails (or is not applicable).
|
|
38
|
+
- **Self-contained**: the rule's meaning does not require reading ten other rules to understand. Cross-references should be resolved into the rule's description.
|
|
39
|
+
- **Scoped**: you know WHERE in the document to look. "Chapter 3, Section 2" or "the risk disclosure section" or "the signature page."
|
|
31
40
|
|
|
32
|
-
|
|
41
|
+
But perfection is the enemy of progress. Extract rules at the granularity that feels right for the regulation and the business scenario. You will iterate. The developer user will tell you if rules are too coarse or too fine.
|
|
33
42
|
|
|
34
|
-
|
|
43
|
+
## Rule Schema Design Principles
|
|
35
44
|
|
|
36
|
-
|
|
45
|
+
Individual rules should be atomic and testable (above). The rule catalog as a whole must also satisfy system-level properties:
|
|
37
46
|
|
|
38
|
-
|
|
47
|
+
### Coverage Target
|
|
48
|
+
Extracted rules should cover at least 95% of the regulation's checkable requirements. After initial extraction, perform a coverage audit: read the source regulation end-to-end and mark which paragraphs are covered by at least one rule. Uncovered paragraphs are either non-checkable (definitions, context) or gaps to close.
|
|
39
49
|
|
|
40
|
-
###
|
|
41
|
-
|
|
50
|
+
### Atomicity Test
|
|
51
|
+
One rule = one pass/fail outcome. If a rule can produce two independent pass/fail results, it should be two rules. Ask: "Can this rule partially pass?" If yes, decompose further.
|
|
42
52
|
|
|
43
|
-
###
|
|
44
|
-
|
|
53
|
+
### Ambiguity Minimization
|
|
54
|
+
No two rules should produce contradictory results on the same document. After extraction, review rule pairs that touch overlapping scope. If Rule A says pass and Rule B says fail for the same entity, their scope boundaries are unclear — fix them.
|
|
45
55
|
|
|
46
|
-
###
|
|
47
|
-
|
|
56
|
+
### Downstream Anticipation
|
|
57
|
+
Rules will be distilled into workflows (see `skill-to-workflow`). Design with distillation in mind: clear input/output boundaries, explicit judgment criteria, minimal reliance on implicit domain knowledge. If a rule requires reading between the lines, make the interpretation explicit. Use `task-decomposition` to identify natural boundaries between rules.
|
|
48
58
|
|
|
49
|
-
###
|
|
50
|
-
|
|
59
|
+
### Catalog Versioning
|
|
60
|
+
When rules change (additions, modifications, deprecations), version the entire rule catalog as a unit. Individual rule versions track specific rules; the catalog version tracks the coherent set. Record the catalog version in `versions.json` alongside individual rule versions.
|
|
51
61
|
|
|
52
|
-
|
|
53
|
-
当规则发生变更(新增、修改、废弃)时,将整个规则目录作为一个整体进行版本化。单条规则的版本跟踪的是具体规则;目录版本跟踪的是规则集的一致性状态。在 `versions.json` 中记录目录版本,与单条规则版本并列。
|
|
62
|
+
## Extraction Strategies
|
|
54
63
|
|
|
55
|
-
|
|
64
|
+
### Strategy 1: Structured Input (Developer User Provides Rules)
|
|
56
65
|
|
|
57
|
-
|
|
66
|
+
When the developer user provides rules in xlsx, csv, or a structured document where each row/entry is a distinct rule with clear scope:
|
|
67
|
+
- Follow their structure exactly. Do not re-decompose.
|
|
68
|
+
- Map each row to a rule, preserving the developer user's identifiers.
|
|
69
|
+
- Ask clarifying questions only if entries are ambiguous.
|
|
58
70
|
|
|
59
|
-
###
|
|
71
|
+
### Strategy 2: Hierarchical Extraction from Regulation Text
|
|
60
72
|
|
|
61
|
-
|
|
62
|
-
2. 尊重开发者用户的规则划分——他们比你更懂业务
|
|
63
|
-
3. 为每条规则生成标准编号(R001、R002...)
|
|
64
|
-
4. 检查是否存在隐含的复合规则需要进一步拆分
|
|
65
|
-
5. 补充表格中可能缺失的信息:适用范围、前提条件、判定标准
|
|
73
|
+
For raw regulation documents (PDF, DOCX, legal text):
|
|
66
74
|
|
|
67
|
-
|
|
75
|
+
1. **Survey the document structure.** Read the table of contents or scan headers. Understand the hierarchy: parts, chapters, sections, articles, clauses.
|
|
76
|
+
2. **Identify rule-bearing sections.** Not every section contains a verification rule. Some are definitions, some are procedural, some are context. Focus on sections that impose obligations, prohibitions, thresholds, or requirements.
|
|
77
|
+
3. **Peel the onion.** Start at the highest structural level and work downward:
|
|
78
|
+
- Level 1: What major areas does the regulation cover? (e.g., capital adequacy, risk disclosure, governance)
|
|
79
|
+
- Level 2: Within each area, what are the specific chapters or sections?
|
|
80
|
+
- Level 3: Within each section, what are the individual requirements?
|
|
81
|
+
- Stop peeling when you reach atomic rules.
|
|
82
|
+
4. **Handle cross-references.** Regulations love to say "as defined in Section X" or "subject to the conditions in Article Y." Resolve these by including the referenced content in the rule's description, not just the reference.
|
|
83
|
+
5. **Handle compound rules.** "The report must include (a) risk factors, (b) financial projections, and (c) management discussion" — this is three rules, not one. Decompose unless the developer user specifically wants them grouped.
|
|
68
84
|
|
|
69
|
-
-
|
|
70
|
-
- 如果表格中某条规则的描述过于笼统,标记为「待细化」,向开发者用户确认
|
|
71
|
-
- 保留原始表格的引用关系(如行号),便于回溯
|
|
85
|
+
For long documents (100+ pages), use the onion-peeler approach described in `references/chunking-strategies.md`. Do not try to read the entire document in one pass.
|
|
72
86
|
|
|
73
|
-
|
|
87
|
+
### Strategy 3: Expert Notes
|
|
74
88
|
|
|
75
|
-
|
|
89
|
+
Sometimes rules come from the developer user's domain expertise rather than formal regulations:
|
|
90
|
+
- "We always check that the guarantor's signature matches the name on page 1."
|
|
91
|
+
- "If the collateral value is below 120% of the loan amount, flag it."
|
|
76
92
|
|
|
77
|
-
|
|
93
|
+
Capture these with the same rigor as formal regulation rules. They are equally important in the verification app.
|
|
78
94
|
|
|
79
|
-
|
|
80
|
-
- 章节编号体系(第X条、第X款、第X项)
|
|
81
|
-
- 哪些章节是定义性条款(不含核查规则)
|
|
82
|
-
- 哪些章节是实质性要求(包含核查规则)
|
|
83
|
-
- 哪些章节是程序性条款(审批流程,可能包含时限类规则)
|
|
84
|
-
- 附则、附件中是否有补充规则
|
|
95
|
+
## Rule Catalog
|
|
85
96
|
|
|
86
|
-
|
|
97
|
+
Maintain a lightweight catalog of all extracted rules. This is your index, not the rules themselves (those live in skill folders). The catalog should track:
|
|
87
98
|
|
|
88
|
-
|
|
89
|
-
-
|
|
90
|
-
-
|
|
91
|
-
-
|
|
99
|
+
- Rule ID (simple sequential: R001, R002, ...)
|
|
100
|
+
- Rule title (one line)
|
|
101
|
+
- Source (which regulation document, which section)
|
|
102
|
+
- Status (extracted / skill-written / skill-tested / workflow-written / workflow-tested / production)
|
|
103
|
+
- Dependencies (rules that must be checked before this one)
|
|
92
104
|
|
|
93
|
-
|
|
105
|
+
Format: a simple markdown table or JSON file. Do not over-engineer this. The catalog exists to give you and the developer user an overview of progress.
|
|
94
106
|
|
|
95
|
-
|
|
107
|
+
## Handling Ambiguity
|
|
96
108
|
|
|
97
|
-
|
|
109
|
+
Regulations are often ambiguous. When you encounter ambiguity:
|
|
110
|
+
1. Extract the rule as you understand it.
|
|
111
|
+
2. Note the ambiguity explicitly in the rule description.
|
|
112
|
+
3. Ask the developer user for clarification.
|
|
113
|
+
4. Update the rule after receiving clarification.
|
|
98
114
|
|
|
99
|
-
|
|
100
|
-
2. 提取核查标准(什么条件、什么阈值)
|
|
101
|
-
3. 提取适用范围(什么业务场景、什么前提条件)
|
|
102
|
-
4. 提取例外情形(什么情况下不适用)
|
|
103
|
-
5. 标注原文位置(法规名称 + 条款编号)
|
|
115
|
+
Do not skip ambiguous rules. They are often the most important ones.
|
|
104
116
|
|
|
105
|
-
|
|
117
|
+
## When Rules Change
|
|
106
118
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
-
|
|
111
|
-
|
|
112
|
-
处理原则:将交叉引用解析为自包含的规则描述,在规则的 `references/` 中保留原始引用关系。
|
|
113
|
-
|
|
114
|
-
### 第五层:拆解复合规则
|
|
115
|
-
|
|
116
|
-
识别并拆分以下模式:
|
|
117
|
-
|
|
118
|
-
- 并列条件:「A且B」→ 拆为规则A、规则B
|
|
119
|
-
- 条件分支:「若X则A,若Y则B」→ 拆为规则A(前提X)、规则B(前提Y)
|
|
120
|
-
- 阶梯条件:「金额≤10万执行A流程,金额>10万执行B流程」→ 拆为规则A(阈值条件)、规则B(阈值条件)
|
|
121
|
-
|
|
122
|
-
不需要拆分的情形:
|
|
123
|
-
- 规则本身就是一个条件判断:「发票日期应在合同有效期内」——这就是一条原子规则
|
|
124
|
-
- 规则包含多个字段但逻辑统一:「收款单位名称、账号应与合同约定一致」——可以保留为一条规则,因为核查逻辑相同
|
|
125
|
-
|
|
126
|
-
## 策略三:开发者用户口述的专家经验
|
|
127
|
-
|
|
128
|
-
有时候开发者用户不会给你法规文件,而是直接告诉你业务经验和核查要点。这种输入同样有效。
|
|
129
|
-
|
|
130
|
-
### 处理方式
|
|
131
|
-
|
|
132
|
-
1. 完整记录开发者用户的口述内容
|
|
133
|
-
2. 将口述转化为结构化规则(编号、名称、核查逻辑、判定标准)
|
|
134
|
-
3. 回读给开发者用户确认,特别注意:
|
|
135
|
-
- 是否遗漏了隐含的前提条件
|
|
136
|
-
- 阈值和标准是否准确
|
|
137
|
-
- 是否有例外情况没提到
|
|
138
|
-
4. 在规则来源中标注「专家经验」而非法规条文
|
|
139
|
-
|
|
140
|
-
## 规则目录管理
|
|
141
|
-
|
|
142
|
-
所有提取的规则汇总为一份轻量级目录,存放在工作空间根目录的 `rule-catalog.json` 中:
|
|
143
|
-
|
|
144
|
-
```json
|
|
145
|
-
{
|
|
146
|
-
"rules": [
|
|
147
|
-
{
|
|
148
|
-
"id": "R001",
|
|
149
|
-
"name": "发票日期有效性",
|
|
150
|
-
"source": "《增值税发票管理办法》第十五条",
|
|
151
|
-
"status": "extracted",
|
|
152
|
-
"priority": "high",
|
|
153
|
-
"skill_folder": "rule-skills/R001-invoice-date-validity/",
|
|
154
|
-
"notes": ""
|
|
155
|
-
}
|
|
156
|
-
],
|
|
157
|
-
"total": 1,
|
|
158
|
-
"extracted": 1,
|
|
159
|
-
"skill_authored": 0,
|
|
160
|
-
"workflow_distilled": 0,
|
|
161
|
-
"last_updated": "<时间戳>"
|
|
162
|
-
}
|
|
163
|
-
```
|
|
164
|
-
|
|
165
|
-
### 状态流转
|
|
166
|
-
|
|
167
|
-
每条规则的生命周期状态:
|
|
168
|
-
|
|
169
|
-
```
|
|
170
|
-
extracted → skill_authored → skill_tested → workflow_distilled → workflow_tested → production
|
|
171
|
-
```
|
|
172
|
-
|
|
173
|
-
目录中实时跟踪每条规则所处的阶段。
|
|
174
|
-
|
|
175
|
-
## 处理模糊与歧义
|
|
176
|
-
|
|
177
|
-
法规条文中经常存在模糊表述,如「合理期限内」「必要时」「视情况而定」。处理原则:
|
|
178
|
-
|
|
179
|
-
1. **先提取,不要跳过**——模糊不等于不重要
|
|
180
|
-
2. **在规则中标注歧义**——明确指出哪个部分存在解读空间
|
|
181
|
-
3. **向开发者用户确认**——提供你的理解,请开发者用户裁定
|
|
182
|
-
4. **确认后更新规则**——将开发者用户的裁定写入规则描述
|
|
183
|
-
|
|
184
|
-
绝对不要自行决定模糊条款的含义。你对业务的理解不如开发者用户。
|
|
185
|
-
|
|
186
|
-
## 法规变更时的处理
|
|
187
|
-
|
|
188
|
-
当 `Rules/` 中新增或修改了法规文件时:
|
|
189
|
-
|
|
190
|
-
1. 对比新旧版本,识别变更点
|
|
191
|
-
2. 定位受影响的已有规则
|
|
192
|
-
3. 判断影响程度:
|
|
193
|
-
- 措辞调整但核查逻辑不变——更新引用文本,无需重新测试
|
|
194
|
-
- 阈值或标准变更——更新规则参数,需要重新测试
|
|
195
|
-
- 新增核查要求——提取新规则
|
|
196
|
-
- 废止原有要求——将规则标记为 `deprecated`,不删除
|
|
197
|
-
4. 更新规则目录
|
|
198
|
-
5. 通知开发者用户变更影响范围
|
|
199
|
-
|
|
200
|
-
## 输出交付物
|
|
201
|
-
|
|
202
|
-
规则提取阶段完成后,应产出:
|
|
203
|
-
|
|
204
|
-
1. `rule-catalog.json`——规则总目录
|
|
205
|
-
2. 每条规则的初始描述文档(存放在对应技能文件夹的草稿中)
|
|
206
|
-
3. 模糊与歧义清单(待开发者用户确认)
|
|
207
|
-
4. 交叉引用映射(规则之间、规则与法规之间的引用关系)
|
|
208
|
-
5. 向开发者用户汇报提取结果的摘要
|
|
119
|
+
Regulations evolve. When the developer user adds new or updated regulation documents:
|
|
120
|
+
1. Identify which existing rules are affected.
|
|
121
|
+
2. Extract new rules or update existing ones.
|
|
122
|
+
3. Mark affected workflows for re-testing.
|
|
123
|
+
4. Use `version-control` to track the change.
|